用React-Native+Mobx做一個(gè)迷你水果商城APP(附源碼)
前言
最近一直在學(xué)習(xí)微信小程序,在學(xué)習(xí)過(guò)程中,看到了 wxapp-mall 這個(gè)微信小程序的項(xiàng)目,覺(jué)得很不錯(cuò),UI挺小清新的,便clone下來(lái)研究研究,在看源碼過(guò)程中,發(fā)現(xiàn)并不復(fù)雜,用不多的代碼來(lái)實(shí)現(xiàn)豐富的功能確實(shí)令我十分驚喜,于是,我就想,如果用react-native來(lái)做一個(gè)類(lèi)似這種小項(xiàng)目難不難呢,何況,寫(xiě)一套代碼還能同時(shí)跑android和ios(小程序也是。。。),要不寫(xiě)一個(gè)來(lái)玩玩?有了這個(gè)想法,我便直接 react-native init 一個(gè)project來(lái)寫(xiě)一下吧(๑•̀ㅂ•́)و✧
先來(lái)張動(dòng)圖,dengdengdeng~~
技術(shù)框架以及組件
- react "16.0.0"
- react-native "0.51.0"
- mobx: "3.4.1"
- mobx-react: "4.3.5"
- react-navigation: "1.0.0-beta.21"
- react-native-scrollable-tab-view: "0.8.0"
- react-native-easy-toast: "1.0.9"
- react-native-loading-spinner-overlay: "0.5.2"
為什么要用Mobx?
Mobx是可擴(kuò)展的狀態(tài)管理工具,比react-redux要簡(jiǎn)單,上手也比較快。在這個(gè)小項(xiàng)目中,因?yàn)闆](méi)有后臺(tái)服務(wù)接口,用的都是本地的假數(shù)據(jù),為了模擬實(shí)現(xiàn) 瀏覽商品 =>加入購(gòu)物車(chē)=>結(jié)賬=>清空購(gòu)物車(chē)=>還原商品原始狀態(tài) 這么一個(gè)流程,便用Mobx來(lái)管理所有的數(shù)據(jù)以及商品的狀態(tài)(有沒(méi)有選中,有沒(méi)有加入購(gòu)物車(chē)),這樣,所有的頁(yè)面都可以共享數(shù)據(jù)以及改變商品的狀態(tài),頁(yè)面之間的數(shù)據(jù)和商品狀態(tài)都是同步更新的。具體用Mobx怎么來(lái)實(shí)現(xiàn)這流程,在下面會(huì)分享使用感受和遇到的一些小坑。
開(kāi)始
先react-native init一個(gè)project,然后用yarn或者npm裝好所有的依賴(lài)和組件。因?yàn)槭褂肕obx會(huì)用到ES7中裝飾器,所以還要安裝 babel-plugin-transform-decorators-legacy 這個(gè)插件,然后在.babelrc文件下添加一下內(nèi)容即可。
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}
項(xiàng)目結(jié)構(gòu)
|-- android |-- ios |-- node_modules |-- src |-- common // 公用組件 |-- img // 靜態(tài)圖片 |-- mobx // mobx store |-- newGoods.js // 首頁(yè)新品數(shù)據(jù) |-- cartGoods.js // 購(gòu)物車(chē)數(shù)據(jù) |-- categoryGoods.js // 分類(lèi)頁(yè)數(shù)據(jù) |-- store.js // store倉(cāng)庫(kù),管理數(shù)據(jù)狀態(tài) |-- scene |-- Cart // 購(gòu)物車(chē)頁(yè)面 |-- Category // 分類(lèi)頁(yè) |-- Home // 首頁(yè) |-- ItemDetail // 商品信息頁(yè) |-- Mine // 我的頁(yè)面 |-- Root.js // root.js主要內(nèi)容是配置react-navigation(導(dǎo)航器) |-- index.js // 主入口
在Root.js文件中,有關(guān)react-navigation的配置和使用方法可以參考下官方文檔和這篇博客,里面都寫(xiě)得十分詳細(xì),有關(guān)react-navigation的疑問(wèn)我都在這2篇文章中找到答案,在這里相關(guān)react-navigation配置,使用方法和項(xiàng)目里面頁(yè)面布局,組件寫(xiě)法,在這里不打算細(xì)說(shuō),因?yàn)槎急容^簡(jiǎn)單,更多的是討論Mobx實(shí)現(xiàn)功能的一些邏輯和方法, screen 文件夾下的組件都寫(xiě)有注釋的(°ー°〃)
主要還是來(lái)聊聊Mobx吧
先來(lái)看看用Mobx實(shí)現(xiàn)的具體流程,看下面的動(dòng)圖(⊙﹏⊙)
ps: 可能圖片太大,加載有點(diǎn)慢,請(qǐng)稍等......
1.數(shù)據(jù)存儲(chǔ)和獲取
這些都是用假數(shù)據(jù)來(lái)模擬實(shí)現(xiàn)的,在最開(kāi)始,先寫(xiě)好假數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),例如:
"data":
[{
"name": '那么大西瓜',
"price": '2.0',
"image": require('../img/a11.png'),
"count": 0,
"isSelected": true
},...]
在 Mobx 文件夾下的 store.js, 在這里主要是存儲(chǔ)和管理app用到的所有商品的數(shù)據(jù),將 邏輯 和 狀態(tài) 從組件中移至一個(gè)獨(dú)立的,可測(cè)試的單元,這個(gè)單元在每個(gè)頁(yè)面下都可以用到
import { observable, computed, action } from 'mobx'
import cartGoods from './cartGoods'
import newGoods from './newGoods'import categoryGoods from './catetgoryGoods'
/**
* 根store
* @class RootStore
* CartStore 為購(gòu)物車(chē)頁(yè)面的數(shù)據(jù)
* NewGoodsStore 為首頁(yè)的數(shù)據(jù)
* categoryGoodsStore 為分類(lèi)頁(yè)的數(shù)據(jù)
*/
class RootStore {
constructor() {
this.CartStore = new CartStore(cartGoods,this)
this.NewGoodsStore = new NewGoodsStore(newGoods,this)
this.categoryGoodsStore = new categoryGoodsStore(categoryGoods,this)
}}
Class CartStore{
@observable allDatas = {}
constructor(data,rootStore) {
this.allDatas = data
this.rootStore = rootStore
}
}
Class NewGoodsStore{
...跟上面一樣
}
Class categoryGoodsStore{
...跟上面一樣
}
// 返回RootStore實(shí)例
export default new RootStore()
這里用了 RootStore 來(lái)實(shí)例化所有了stores(購(gòu)物車(chē),首頁(yè),分類(lèi)頁(yè)分別擁有各自的store),
這樣,可以通過(guò)RootStore 來(lái)管理和操作stores,從而實(shí)現(xiàn)它們之間的相互通信,共享引用。
其次,存儲(chǔ)數(shù)據(jù)用了Mobx的@observable方法,就是把數(shù)據(jù)成為觀察者,當(dāng)用戶(hù)操作視圖,導(dǎo)致數(shù)據(jù)發(fā)生變化時(shí),配合react-mobx提供的@observer可以自動(dòng)更新視圖,非常方便。
此外,為了把Mobx 的Rootstore注入到react-native的組件中,要通過(guò) mobx-react 提供的 Provider 實(shí)現(xiàn),在 Root.js 下,我是這么寫(xiě)的:
// 全局注冊(cè)并注入mobx的Rootstore實(shí)例,首頁(yè)新品,分類(lèi)頁(yè),商品詳情頁(yè),購(gòu)物車(chē)頁(yè)面都要用到store
import {Provider} from 'mobx-react'
// 獲取store實(shí)例
import store from './mobx/store'
const Navigation = () => {
return (
<Provider rootStore={store}>
<Navigator/>
</Provider>
)}
把Rootstore實(shí)例注入到組件樹(shù)中后,那么,是不是在組件中直接使用 this.props.rootStore 就可以取到了呢?
‘'不是的”,我們還需要在要用到Rootstore的組件里,要加點(diǎn)小玩意,在 HomeScreen.js (首頁(yè))中這么寫(xiě):
import { inject, observer } from 'mobx-react'
@inject('rootStore') // 緩存rootStore,也就是在Root.js注入的
@observerexport default class HomeScreen extends Component {
......
}
加上了 @inject('rootStore') ,我們就可以愉快地使用 this.props.rootStore 來(lái)拿到我們想要的數(shù)據(jù)啦^_^ ,同樣,在商品信息,分類(lèi)頁(yè),購(gòu)物車(chē)頁(yè)面js下,也需要使用 @inject('rootStore') 來(lái)實(shí)現(xiàn)數(shù)據(jù)的獲取,然后再一步步地把數(shù)據(jù)傳到它們的子組件中。
2. 加入購(gòu)物車(chē)的實(shí)現(xiàn)
在首頁(yè)和分類(lèi)頁(yè)中,都可以點(diǎn)擊跳轉(zhuǎn)到商品信息頁(yè),然后再加入到購(gòu)物車(chē)?yán)?/p>
實(shí)現(xiàn)方法 :
在itemDetail.js下,也就是商品信息頁(yè)面下,加入購(gòu)物車(chē)的邏輯是這樣子的:
addCart(value) {
if(this.state.num == 0) {
this.refs.toast.show('添加數(shù)量不能為0哦~')
return;
}
// 加入購(gòu)物車(chē)頁(yè)面的列表上
// 點(diǎn)一次,購(gòu)物車(chē)數(shù)據(jù)同步刷新
this.updateCartScreen(value)
this.refs.toast.show('添加成功^_^請(qǐng)前往購(gòu)物車(chē)頁(yè)面查看')
}
// 同步更新購(gòu)物車(chē)頁(yè)面的數(shù)據(jù)
updateCartScreen (value) {
let name = this.props.navigation.state.params.value.name;
// 判斷購(gòu)物車(chē)頁(yè)面是否存在同樣名字的物品
let index;
if(this.props.rootStore.CartStore)
index = this.props.rootStore.CartStore.allDatas.data.findIndex(e => (e.name === name))
// 不存在
if(index == -1) {
this.props.rootStore.CartStore.allDatas.data.push(value)
// 加入CartStore里
// 并讓購(gòu)物車(chē)icon更新
let length = this.props.rootStore.CartStore.allDatas.data.length
this.props.rootStore.CartStore.allDatas.data[length - 1].count += this.state.num}
else {
// 增加對(duì)應(yīng)name的count
this.props.rootStore.CartStore.allDatas.data[index].count += this.state.num
}}
簡(jiǎn)單的說(shuō),先獲取水果的名稱(chēng)name,然后再去判斷Mobx的CartStore里面是否存在同樣的名稱(chēng)的水果,如果有就增加對(duì)應(yīng)name的數(shù)量count,如果沒(méi)有,就往CartStore中增加數(shù)據(jù),切換到購(gòu)物車(chē)頁(yè)面時(shí),視圖會(huì)同步刷新,看到已加入購(gòu)物車(chē)的水果。
3.改變商品狀態(tài)同步更新視圖
當(dāng)用戶(hù)在購(gòu)物車(chē)頁(yè)面操作商品狀態(tài)時(shí),數(shù)據(jù)改變時(shí),視圖會(huì)跟著同步刷新。
例如,商品的增加數(shù)量,減少數(shù)據(jù),選中狀態(tài),商品全選和商品刪除,總價(jià)格都會(huì)隨著商品的數(shù)量變化而變化。
圖又來(lái)了~~
實(shí)現(xiàn)上面的功能,主要用到了Mobx提供的action方法,action是用來(lái)修改狀態(tài)的,也就是用action來(lái)修改商品的各種狀態(tài)(數(shù)量,選中狀態(tài)...),這些action,我是寫(xiě)在 store.js 的 CartStore類(lèi) 中的,下面貼出代碼
// 購(gòu)物車(chē)store
class CartStore {
@observable allDatas = {}
constructor(data,rootStore) {
this.allDatas = data
this.rootStore = rootStore
}
//加
@action
add(money) {
this.allDatas.totalMoney += money
}
// 減
@action
reduce(money) {
this.allDatas.totalMoney -= money
}
// checkbox true
@action
checkTrue(money) {
this.allDatas.totalMoney += money
}
// checkbox false
@action
checkFalse(money) {
if(this.allDatas.totalMoney <=0 )
return
this.allDatas.totalMoney -= money
}
// 全選
@action
allSelect() {
if(this.allDatas.isAllSelected) {
// 重置totalMoney
this.allDatas.totalMoney = 0
this.allDatas.data.forEach(e=> {
this.allDatas.totalMoney += e.count * e.price})}
else {
this.allDatas.totalMoney = 0
}}
// check全選
@action
check() {
// 所有checkbox為true時(shí)全選才為true
let allTrue = this.allDatas.data.every(v => ( v.isSelected === true ))
if(allTrue) {
this.allDatas.isAllSelected = true
}else {
this.allDatas.isAllSelected = false
}}
// 刪
@action
delect(name) {
this.allDatas.data = this.allDatas.data.filter (e => (e.name !== name ))
}
// 總價(jià)格
@computed get totalMoney() {
let money = 0;
let arr = this.allDatas.data.filter(e => (e.isSelected === true))
arr.forEach(e=> (money += e.price * e.count))
return money
}}
所有修改商品狀態(tài)的邏輯都在上面代碼里面,其中,totalMoney是用了Mobx的@computed方法,totalMoney是依賴(lài)于CartStore的data數(shù)據(jù),也就是商品數(shù)據(jù),但data的值發(fā)生改變時(shí),它會(huì)重新計(jì)算返回。如果了解vue的話(huà),這個(gè)就相當(dāng)于vue的計(jì)算屬性。
4.結(jié)算商品
商品結(jié)算和清空購(gòu)物車(chē)的邏輯都寫(xiě)在 CartCheckOut.js 里面,實(shí)現(xiàn)過(guò)程很簡(jiǎn)單,貼上代碼吧:
// 付款
pay() {
Alert.alert('您好',`總計(jì):¥ ${this.props.mobx.CartStore.totalMoney}`,
{text: '確認(rèn)支付', onPress: () => this.clear()},
{text: '下次再買(mǎi)', onPress: () => null}],{ cancelable: false })}
// 清空購(gòu)物車(chē)
clear() {
this.setState({visible: !this.state.visible})
setTimeout(()=>{
this.setState({ loadText: '支付成功!歡迎下次光臨!' })
setTimeout(()=> { this.setState({ visible: false },
()=>{ this.props.mobx.CartStore.allDatas.data = []
// 把所有商品count都變?yōu)?
this.props.mobx.NewGoodsStore.allDatas.data.forEach(e=> e.count = 0)
this.props.mobx.categoryGoodsStore.allDatas.data.forEach( e => {
e.detail.forEach(value => { value.count = 0 })
})
})},1500)},2000)}
這里主要用了setTimeout和一些方法來(lái)模擬實(shí)現(xiàn) 支付中 => 支付完成 => 清空購(gòu)物車(chē) => 還原商品狀態(tài)。
好了,這個(gè)流程就搞定了,哈哈。
5.遇到的小坑
1.我寫(xiě)了一個(gè)數(shù)組的亂序方法,里面有用到 Array.isArray() 這個(gè)方法來(lái)判斷是否為數(shù)組,但是,我用這個(gè)亂序函數(shù)時(shí),想用來(lái)搞亂store里面的數(shù)組時(shí),發(fā)現(xiàn)一直沒(méi)有執(zhí)行,覺(jué)得很奇怪。然后我直接用 Array.isArray() 這個(gè)方法來(lái)判斷store里面的數(shù)組,返回的一直都是false。。。于是我就懵了。。。后來(lái),我去看了Mobx官方文檔,終于找到了答案。原來(lái),store里面存放的數(shù)組,并不是真正的數(shù)組,而是 obverableArray ,如果要讓 Array.isArray() 判斷為true,就要在取到store的數(shù)組時(shí),加個(gè). slice() 方法,或者 Array.from() 都可以。
2.同樣,也是obverableArray的問(wèn)題。在購(gòu)物車(chē)頁(yè)面時(shí),我用了FlatList來(lái)渲染購(gòu)物車(chē)的item,起初,當(dāng)我增加商品到購(gòu)物車(chē),發(fā)現(xiàn)購(gòu)物車(chē)頁(yè)面并沒(méi)有刷新。有了上面的踩坑經(jīng)驗(yàn),我認(rèn)為是obverableArray引起的,因?yàn)镕latList的data接收的是real Array,于是,我用這樣的方法:
@computed get dataSource() {
return this.props.rootStore.CartStore.allDatas.data.slice();
}
...
<FlatList data={this.dataSource} .../>
于是,購(gòu)物車(chē)視圖就可以自動(dòng)地刷新了,在官方文檔上也有寫(xiě)到。
3.還有一個(gè)就是自己粗心造成的。我寫(xiě)完這個(gè)項(xiàng)目后,和朋友出去玩時(shí),順便發(fā)給朋友看看,他在刪除商品時(shí)發(fā)現(xiàn),從上往下刪刪不了,從下往上刪就可以。后來(lái)我用模擬器測(cè)試也是如此,于是就去看看刪除商品的邏輯,發(fā)現(xiàn)沒(méi)有問(wèn)題,再去看store的數(shù)據(jù),發(fā)現(xiàn)也是可以同步更新的,只是視圖沒(méi)有更新,很神奇,于是我又在FlatList去找原因,終于,原因找到了,主要是在keyExtractor里面,用index是不可以的,要用name來(lái)作為key,因?yàn)槲覄h除商品方法其實(shí)是根據(jù)name來(lái)刪的,而不是index,所以用index來(lái)作為FlatList的Item的key時(shí)是會(huì)出現(xiàn)bug的。
_keyExtractor = (item,index)=> {
// 千萬(wàn)別用index,不然在刪購(gòu)物車(chē)數(shù)據(jù)時(shí),如果從第一個(gè)item開(kāi)始刪會(huì)產(chǎn)生節(jié)點(diǎn)渲染錯(cuò)亂的bug
return item.name
}
附上github項(xiàng)目地址: github.com/shooterRao/…
總結(jié)
以上所述是小編給大家介紹的用React-Native+Mobx做一個(gè)迷你水果商城APP(附源碼),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
如何使用Redux Toolkit簡(jiǎn)化Redux
這篇文章主要介紹了如何使用Redux Toolkit簡(jiǎn)化Redux,幫助大家更好的理解和學(xué)習(xí)使用React框架,感興趣的朋友可以了解下2021-04-04
淺談react-native熱更新react-native-pushy集成遇到的問(wèn)題
下面小編就為大家?guī)?lái)一篇淺談react-native熱更新react-native-pushy集成遇到的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
react-router-dom 嵌套路由的實(shí)現(xiàn)
這篇文章主要介紹了react-router-dom 嵌套路由的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
手把手教您實(shí)現(xiàn)react異步加載高階組件
這篇文章主要介紹了手把手教您實(shí)現(xiàn)react異步加載高階組件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04

