用React-Native+Mobx做一个迷你水果商城APP(附源码)


Posted in Javascript onDecember 25, 2017

前言

最近一直在学习微信小程序,在学习过程中,看到了 wxapp-mall 这个微信小程序的项目,觉得很不错,UI挺小清新的,便clone下来研究研究,在看源码过程中,发现并不复杂,用不多的代码来实现丰富的功能确实令我十分惊喜,于是,我就想,如果用react-native来做一个类似这种小项目难不难呢,何况,写一套代码还能同时跑android和ios(小程序也是。。。),要不写一个来玩玩?有了这个想法,我便直接 react-native init 一个project来写一下吧(๑•̀ㅂ•́)و✧

先来张动图,dengdengdeng~~

用React-Native+Mobx做一个迷你水果商城APP(附源码) 

技术框架以及组件

  • 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是可扩展的状态管理工具,比react-redux要简单,上手也比较快。在这个小项目中,因为没有后台服务接口,用的都是本地的假数据,为了模拟实现 浏览商品 =>加入购物车=>结账=>清空购物车=>还原商品原始状态 这么一个流程,便用Mobx来管理所有的数据以及商品的状态(有没有选中,有没有加入购物车),这样,所有的页面都可以共享数据以及改变商品的状态,页面之间的数据和商品状态都是同步更新的。具体用Mobx怎么来实现这流程,在下面会分享使用感受和遇到的一些小坑。

开始

先react-native init一个project,然后用yarn或者npm装好所有的依赖和组件。因为使用Mobx会用到ES7中装饰器,所以还要安装 babel-plugin-transform-decorators-legacy 这个插件,然后在.babelrc文件下添加一下内容即可。

{ 
 "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 文件夹下的组件都写有注释的(°?°〃)

主要还是来聊聊Mobx吧

先来看看用Mobx实现的具体流程,看下面的动图(⊙?⊙)

ps: 可能图片太大,加载有点慢,请稍等......

用React-Native+Mobx做一个迷你水果商城APP(附源码) 

1.数据存储和获取

这些都是用假数据来模拟实现的,在最开始,先写好假数据的数据结构,例如:

"data":
 [{ 
 "name": '那么大西瓜',
 "price": '2.0', 
 "image": require('../img/a11.png'), 
 "count": 0, 
 "isSelected": true
 },...]

在 Mobx 文件夹下的 store.js, 在这里主要是存储和管理app用到的所有商品的数据,将 逻辑 和 状态 从组件中移至一个独立的,可测试的单元,这个单元在每个页面下都可以用到

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),

这样,可以通过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注入的
@observerexport default class HomeScreen extends Component {
 ......
}

加上了 @inject('rootStore') ,我们就可以愉快地使用 this.props.rootStore 来拿到我们想要的数据啦^_^ ,同样,在商品信息,分类页,购物车页面js下,也需要使用 @inject('rootStore') 来实现数据的获取,然后再一步步地把数据传到它们的子组件中。

2. 加入购物车的实现

在首页和分类页中,都可以点击跳转到商品信息页,然后再加入到购物车里

用React-Native+Mobx做一个迷你水果商城APP(附源码) 

实现方法 :

在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中增加数据,切换到购物车页面时,视图会同步刷新,看到已加入购物车的水果。

3.改变商品状态同步更新视图

当用户在购物车页面操作商品状态时,数据改变时,视图会跟着同步刷新。

例如,商品的增加数量,减少数据,选中状态,商品全选和商品删除,总价格都会随着商品的数量变化而变化。

用React-Native+Mobx做一个迷你水果商城APP(附源码) 

图又来了~~

实现上面的功能,主要用到了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的计算属性。

4.结算商品

商品结算和清空购物车的逻辑都写在 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和一些方法来模拟实现 支付中 => 支付完成 => 清空购物车 => 还原商品状态。

好了,这个流程就搞定了,哈哈。

5.遇到的小坑

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,因为我删除商品方法其实是根据name来删的,而不是index,所以用index来作为FlatList的Item的key时是会出现bug的。

_keyExtractor = (item,index)=> { 
 // 千万别用index,不然在删购物车数据时,如果从第一个item开始删会产生节点渲染错乱的bug 
 return item.name
}

附上github项目地址:  github.com/shooterRao/…

总结

以上所述是小编给大家介绍的用React-Native+Mobx做一个迷你水果商城APP(附源码),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
善用事件代理,警惕闭包的性能陷阱。
Jan 20 Javascript
浅谈javascript回调函数
Dec 07 Javascript
JavaScript操作HTML DOM节点的基础教程
Mar 11 Javascript
基于MVC5和Bootstrap的jQuery TreeView树形控件(二)之数据支持json字符串、list集合
Aug 11 Javascript
微信小程序 Image API实例详解
Sep 30 Javascript
js 用于检测类数组对象的函数方法
May 02 Javascript
Angular中$state.go页面跳转并传递参数的方法
May 09 Javascript
JavaScript适配器模式详解
Oct 19 Javascript
vue2中,根据list的id进入对应的详情页并修改title方法
Aug 24 Javascript
小程序实现五星点评效果
Nov 03 Javascript
js实现贪吃蛇小游戏
Oct 29 Javascript
js实现盒子拖拽动画效果
Aug 09 Javascript
jQuery简单实现向列表动态添加新元素的方法示例
Dec 25 #jQuery
Webpack优化配置缩小文件搜索范围
Dec 25 #Javascript
详解Webpack实战之构建 Electron 应用
Dec 25 #Javascript
Angular4实现图片上传预览路径不安全的问题解决
Dec 25 #Javascript
JS实现十字坐标跟随鼠标效果
Dec 25 #Javascript
jQuery EasyUI window窗口使用实例代码
Dec 25 #jQuery
JS中使用textPath实现线条上的文字
Dec 25 #Javascript
You might like
PHP中如何实现常用邮箱的基本判断
2014/01/07 PHP
PHP使用xmllint命令处理xml与html的方法
2014/12/15 PHP
php中输出json对象的值(实现方法)
2018/03/07 PHP
使用Jquery来实现可以输入值的下拉选单 雏型
2011/12/06 Javascript
详解JavaScript函数绑定
2013/08/18 Javascript
仿百度联盟对联广告实现代码
2014/08/30 Javascript
jQuery中:submit选择器用法实例
2015/01/03 Javascript
jquery简单图片切换显示效果实现方法
2015/01/14 Javascript
JavaScript获取当前运行脚本文件所在目录的方法
2016/02/03 Javascript
JS实现把鼠标放到链接上出现滚动文字的方法
2016/04/06 Javascript
微信小程序 wx.request(OBJECT)发起请求详解
2016/10/13 Javascript
微信小程序 缓存(本地缓存、异步缓存、同步缓存)详解
2017/01/17 Javascript
JavaScript仿微信(电话)联系人列表滑动字母索引实例讲解(推荐)
2017/08/16 Javascript
JS获取input[file]的值并显示在页面的实现方法
2018/03/09 Javascript
纯js封装的ajax功能函数与用法示例
2018/05/14 Javascript
用react-redux实现react组件之间数据共享的方法
2018/06/08 Javascript
举例讲解Python程序与系统shell交互的方式
2015/04/09 Python
django中SMTP发送邮件配置详解
2019/07/19 Python
python tkinter实现彩球碰撞屏保
2019/07/30 Python
python爬虫 批量下载zabbix文档代码实例
2019/08/21 Python
python字符串常用方法及文件简单读写的操作方法
2020/03/04 Python
CSS3中Transition动画属性用法详解
2016/07/04 HTML / CSS
详解webapp页面滚动卡顿的解决办法
2018/12/26 HTML / CSS
Napapijri西班牙在线商店:夹克、外套、运动衫等
2020/11/05 全球购物
职业技术学校毕业生推荐信
2013/12/03 职场文书
医院工作检讨书范文
2014/02/10 职场文书
八一建军节感言
2014/02/28 职场文书
售后服务承诺书
2014/03/26 职场文书
个人贷款承诺书
2014/03/28 职场文书
第二课堂活动总结
2014/05/07 职场文书
节水标语大全
2014/06/11 职场文书
工伤事故赔偿协议书(标准)
2014/09/29 职场文书
护理医院见习报告
2014/11/03 职场文书
矛盾论读书笔记
2015/06/29 职场文书
python保存图片的四个常用方法
2022/02/28 Python
详细聊聊Oracle表碎片对性能有多大的影响
2022/03/19 Oracle