最近一直在学习微信小程序,在学习过程当中,看到了wxapp-mall这个微信小程序的项目,以为很不错,UI挺小清新的,便clone下来研究研究,在看源码过程当中,发现并不复杂,用很少的代码来实现丰富的功能确实令我十分惊喜,因而,我就想,若是用react-native来作一个相似这种小项目难不难呢,况且,写一套代码还能同时跑android和ios(小程序也是。。。),要不写一个来玩玩?有了这个想法,我便直接react-native init 一个project来写一下吧(๑•̀ㅂ•́)و✧html
先来张动图,dengdengdeng~~vue
Mobx是可扩展的状态管理工具,比react-redux要简单,上手也比较快。在这个小项目中,由于没有后台服务接口,用的都是本地的假数据,为了模拟实现 浏览商品 =>加入购物车=>结帐=>清空购物车=>还原商品原始状态 这么一个流程,便用Mobx来管理全部的数据以及商品的状态(有没有选中,有没有加入购物车),这样,全部的页面均可以共享数据以及改变商品的状态,页面之间的数据和商品状态都是同步更新的。具体用Mobx怎么来实现这流程,在下面会分享使用感觉和遇到的一些小坑。node
先react-native init一个project,而后用yarn或者npm装好全部的依赖和组件。由于使用Mobx会用到ES7中装饰器,因此还要安装babel-plugin-transform-decorators-legacy这个插件,而后在.babelrc文件下添加一下内容便可。
react
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}复制代码
|-- android
|-- ios
|-- node_modules
|-- src
|-- common // 公用组件
|-- img // 静态图片
|-- mobx // mobx store
|-- newGoods.js // 首页新品数据
|-- cartGoods.js // 购物车数据
|-- categoryGoods.js // 分类页数据
|-- store.js // store仓库,管理数据状态
|-- scene
|-- Cart // 购物车页面
|-- Category // 分类页
|-- Home // 首页
|-- ItemDetail // 商品信息页
|-- Mine // 个人页面
|-- Root.js // root.js主要内容是配置react-navigation(导航器)
|-- index.js // 主入口
复制代码
在Root.js文件中,有关react-navigation的配置和使用方法能够参考下 官方文档 和 这篇博客 ,里面都写得十分详细,有关react-navigation的疑问我都在这2篇文章中找到答案,在这里相关react-navigation配置,使用方法和项目里面页面布局,组件写法,在这里不打算细说,由于都比较简单,更多的是讨论Mobx实现功能的一些逻辑和方法,screen文件夹下的组件都写有注释的(°ー°〃)android
先来看看用Mobx实现的具体流程,看下面的动图(⊙﹏⊙)ios
ps: 可能图片太大,加载有点慢,请稍等......git
这些都是用假数据来模拟实现的,在最开始,先写好假数据的数据结构,例如:es6
"data":
[{
"name": "那么大西瓜",
"price": "2.0",
"image": require('../img/a11.png'),
"count": 0,
"isSelected": true
},...]复制代码
在Mobx文件夹下的store.js,在这里主要是存储和管理app用到的全部商品的数据,将逻辑和状态从组件中移至一个独立的,可测试的单元,这个单元在每一个页面下均可以用到github
import { observable, computed, action } from 'mobx'
import cartGoods from './cartGoods'
import newGoods from './newGoods'
import categoryGoods from './catetgoryGoods'
/**
* 根store
* @class RootStore
* CartStore 为购物车页面的数据
* NewGoodsStore 为首页的数据
* categoryGoodsStore 为分类页的数据
*/
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实例
export default new RootStore()复制代码
这里用了RootStore
来实例化全部了stores(购物车,首页,分类页分别拥有各自的store),npm
这样,能够经过RootStore 来管理和操做stores,从而实现它们之间的相互通讯,共享引用。
其次,存储数据用了Mobx的@observable方法,就是把数据成为观察者,当用户操做视图,致使数据发生变化时,注意,配合react-mobx提供的@observer能够自动更新视图,很是方便。
此外,为了把Mobx 的Rootstore注入到react-native的组件中,要经过mobx-react提供的Provider实现,在Root.js下,我是这么写的:
// 全局注册并注入mobx的Rootstore实例,首页新品,分类页,商品详情页,购物车页面都要用到store
import {Provider} from 'mobx-react'
// 获取store实例
import store from './mobx/store'
const Navigation = () => {
return (
<Provider rootStore={store}>
<Navigator/>
</Provider>
)}
复制代码
把Rootstore实例注入到组件树中后,那么,是否是在组件中直接使用this.props.rootStore就能够取到了呢?
‘’不是的”,咱们还须要在要用到Rootstore的组件里,要加点小玩意,在HomeScreen.js
(首页)中这么写:
import { inject, observer } from 'mobx-react'
@inject('rootStore') // 缓存rootStore,也就是在Root.js注入的
@observer // 将react组件转变为响应式组件, 数据改变自动触发render函数
export default class HomeScreen extends Component {
......
}
复制代码
加上了@inject('rootStore')
,咱们就能够愉快地使用 this.props.rootStore
来拿到咱们想要的数据啦^_^ ,一样,在商品信息,分类页,购物车页面js下,也须要使用@inject('rootStore')
来实现数据的获取,而后再一步步地把数据传到它们的子组件中。
在首页和分类页中,均可以点击跳转到商品信息页,而后再加入到购物车里
实现方法:
在itemDetail.js下,也就是商品信息页面下,加入购物车的逻辑是这样子的:
addCart(value) {
if(this.state.num == 0) {
this.refs.toast.show('添加数量不能为0哦~')
return;
}
// 加入购物车页面的列表上
// 点一次,购物车数据同步刷新
this.updateCartScreen(value)
this.refs.toast.show('添加成功^_^请前往购物车页面查看')
}
// 同步更新购物车页面的数据
updateCartScreen (value) {
let name = this.props.navigation.state.params.value.name;
// 判断购物车页面是否存在一样名字的物品
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里
// 并让购物车icon更新
let length = this.props.rootStore.CartStore.allDatas.data.length
this.props.rootStore.CartStore.allDatas.data[length - 1].count += this.state.num}
else {
// 增长对应name的count
this.props.rootStore.CartStore.allDatas.data[index].count += this.state.num
}
}复制代码
简单的说,先获取水果的名称name,而后再去判断Mobx的CartStore里面是否存在一样的名称的水果,若是有就增长对应name的数量count,若是没有,就往CartStore中增长数据,切换到购物车页面时,视图会同步刷新,看到已加入购物车的水果。
当用户在购物车页面操做商品状态时,数据改变时,视图会跟着同步刷新。
例如,商品的增长数量,减小数据,选中状态,商品全选和商品删除,总价格都会随着商品的数量变化而变化。
图又来了~~
实现上面的功能,主要用到了Mobx提供的action方法,action是用来修改状态的,也就是用action来修改商品的各类状态(数量,选中状态...),这些action,我是写在store.js
的CartStore类
中的,下面贴出代码
// 购物车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时全选才为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 ))
}
// 总价格
@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
}}复制代码
全部修改商品状态的逻辑都在上面代码里面,其中,totalMoney是用了Mobx的@computed方法,totalMoney是依赖于CartStore的data数据,也就是商品数据,但data的值发生改变时,它会从新计算返回。若是了解vue的话,这个就至关于vue的计算属性。
商品结算和清空购物车的逻辑都写在CartCheckOut.js
里面,实现过程很简单,贴上代码吧:
// 付款
pay() {
Alert.alert('您好',`总计:¥ ${this.props.mobx.CartStore.totalMoney}`,
{text: '确认支付', onPress: () => this.clear()},
{text: '下次再买', onPress: () => null}],{ cancelable: false })}
// 清空购物车
clear() {
this.setState({visible: !this.state.visible})
setTimeout(()=>{
this.setState({ loadText: '支付成功!欢迎下次光临!' })
setTimeout(()=> { this.setState({ visible: false },
()=>{ this.props.mobx.CartStore.allDatas.data = []
// 把全部商品count都变为0
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和一些方法来模拟实现 支付中 => 支付完成 => 清空购物车 => 还原商品状态。
好了,这个流程就搞定了,哈哈。
1.我写了一个数组的乱序方法,里面有用到Array.isArray()
这个方法来判断是否为数组,可是,我用这个乱序函数时,想用来搞乱store里面的数组时,发现一直没有执行,以为很奇怪。而后我直接用Array.isArray()
这个方法来判断store里面的数组,返回的一直都是false。。。因而我就懵了。。。后来,我去看了Mobx官方文档,终于找到了答案。原来,store里面存放的数组,并非真正的数组,而是obverableArray
,若是要让Array.isArray()
判断为true,就要在取到store的数组时,加个.slice()
方法,或者Array.from()
均可以。
2.一样,也是obverableArray的问题。在购物车页面时,我用了FlatList来渲染购物车的item,起初,当我增长商品到购物车,发现购物车页面并无刷新。有了上面的踩坑经验,我认为是obverableArray引发的,由于FlatList的data接收的是real Array,因而,我用这样的方法:
@computed get dataSource() {
return this.props.rootStore.CartStore.allDatas.data.slice();
}
...
<FlatList data={this.dataSource} .../>复制代码
因而,购物车视图就能够自动地刷新了,在官方文档上也有写到。
3.还有一个就是本身粗心形成的。我写完这个项目后,和朋友出去玩时,顺便发给朋友看看,他在删除商品时发现,从上往下删删不了,从下往上删就能够。后来我用模拟器测试也是如此,因而就去看看删除商品的逻辑,发现没有问题,再去看store的数据,发现也是能够同步更新的,只是视图没有更新,因而我又在FlatList去找缘由,终于,缘由找到了,主要是在keyExtractor里面,用数组的index是不能够的,要用name来做为key,也就是说这里的key值,要足够稳定的,不能用index(索引)去绑定key,这也是react的语法之一。由于我删除商品方法实际上是根据name来删的,而不是index,因此用index来做为FlatList的Item的key时是会出现bug的。
_keyExtractor = (item,index)=> {
// 千万别用index,否则在删购物车数据时,若是从第一个item开始删会产生节点渲染错乱的bug
return item.name
}复制代码
断断续续花了差很少一个星期才写好,总得来讲,我感受用react-native来写这么一个商城项目要比小程序实现要复杂点,主要是在写组件上花的时间要多一点,和这里用Mobx来模拟实现购物流程也花了我些时间。Android打包成apk能够在个人模拟器上和我朋友的android手机上完美运行,还没发现什么bug,IOS的由于我没MAC,因此暂时还没打包测试T.T,但愿有条件的小伙伴能够clone下来,帮我测测,有Issue的话能够提下,多谢多谢ヽ(✿゚▽゚)ノ
附上github项目地址: github.com/shooterRao/… (若是感兴趣,但愿能点下Star,给予点鼓励,谢谢!)
这个小项目的灵感出于wxapp-mall,在此款小程序的基础上,优化了购物逻辑和一些交互上的修改。有些UI和Icon也沿用了此款小程序,我也获得了原做者的容许,很是感谢。此外,我还要特别感谢肖JerryShaw帮我做的水果图和App的logo,还有也要感谢Keson帮忙测试和提供建议。
此次是我第一次在掘金上发博客,也算是我第一次开源项目吧,有不足的地方,但愿你们能多多包涵,给点建议,谢谢!
还有,今天是平安夜,Happy Christmas Eve~