用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 相关文章推荐
js 居中漂浮广告
Mar 21 Javascript
js实现广告漂浮效果的小例子
Jul 02 Javascript
js抽奖实现随机抽奖代码效果
Dec 02 Javascript
jQuery入门介绍之基础知识
Jan 13 Javascript
JavaScript中常用的六种互动方法示例
Mar 13 Javascript
简介JavaScript中substring()方法的使用
Jun 06 Javascript
轻松学习jQuery插件EasyUI EasyUI实现树形网络基本操作(2)
Nov 30 Javascript
浅谈键盘上回车按钮的js触发事件
Feb 13 Javascript
jQuery设计思想
Mar 07 Javascript
jQuery实现的老虎机跑动效果示例
Dec 29 jQuery
jQuery zTree插件使用简单教程
Aug 16 jQuery
js实现自定义右键菜单
May 18 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错误提示failed to open stream: HTTP request failed!的完美解决方法
2011/06/06 PHP
php setcookie函数的参数说明及其用法
2014/04/20 PHP
PHP快速排序quicksort实例详解
2016/09/28 PHP
PHP之认识(二)关于Traits的用法详解
2019/04/11 PHP
js 页面执行时间计算代码
2009/03/04 Javascript
javascript闭包的高级使用方法实例
2013/07/04 Javascript
js中事件的处理与浏览器对象示例介绍
2013/11/29 Javascript
浏览器图片选择预览、旋转、批量上传的JS代码实现
2013/12/04 Javascript
IE6下拉框图层问题探讨及解决
2014/01/03 Javascript
node.js中的http.response.addTrailers方法使用说明
2014/12/14 Javascript
JavaScript动态修改网页元素内容的方法
2015/03/21 Javascript
详解Backbone.js框架中的模型Model与其集合collection
2016/05/05 Javascript
js判断输入字符串是否为空、空格、null的方法总结
2016/06/14 Javascript
JS如何设置iOS中微信浏览器的title
2016/11/22 Javascript
原生JS实现垂直手风琴效果
2017/02/19 Javascript
nodejs多版本管理总结
2018/04/03 NodeJs
jQuery实现的页面详情展开收起功能示例
2018/06/11 jQuery
vue+mousemove实现鼠标拖动功能(拖动过快失效问题解决方法)
2018/08/24 Javascript
跨域请求两种方法 jsonp和cors的实现
2018/11/11 Javascript
Vue项目实现简单的权限控制管理功能
2019/07/17 Javascript
python k-近邻算法实例分享
2014/06/11 Python
Python解决两个整数相除只得到整数部分的实例
2018/11/10 Python
Python实现截取PDF文件中的几页代码实例
2019/03/11 Python
python变量命名的7条建议
2019/07/04 Python
python多项式拟合之np.polyfit 和 np.polyld详解
2020/02/18 Python
记录模型训练时loss值的变化情况
2020/06/16 Python
python如何使用腾讯云发送短信
2020/09/17 Python
HTML5制作表格样式
2016/11/15 HTML / CSS
Omio法国:全欧洲低价大巴、火车和航班搜索和比价
2017/11/13 全球购物
生产部厂长职位说明书
2014/03/03 职场文书
2014入党积极分子破除“四风”思想汇报
2014/09/14 职场文书
论群众路线学习笔记
2014/11/06 职场文书
2015年卫生局工作总结
2015/07/24 职场文书
《扇形统计图》教学反思
2016/02/17 职场文书
Vue+Element UI实现概要小弹窗的全过程
2021/05/30 Vue.js
TS 类型兼容教程示例详解
2022/09/23 Javascript