前阵子,来自咱们凹凸实验室的遵循 React 语法规范的多端开发方案 - Taro终于对外开源了,欢迎围观star(先打波广告)。做为第一批使用了Taro开发的TOPLIFE小程序的开发人员之一,天然是走了很多弯路,躺了很多坑,也帮忙找过很多bug。如今项目总算是上线了,那么,也是时候给你们总结分享下了。javascript
当初开发TOPLIFE第一期的时候,用的实际上是wepy(那时Taro尚未开发完成),而后在第二期才全面转换为用Taro开发。做为两个小程序开发框架都使用过,并应用在生产环境里的人,天然是要比较一下二者的异同点。css
相同的地方也不用多说什么,都2018年了,这些特性的支持都是为了让小程序开发变得更现代,更工程化,重点是区别之处vue
开发风格java
最大的不一样之处,天然就是开发风格上的差别,wepy使用的是类Vue开发风格, Taro使用的是类React开发风格,能够说开发体验上仍是会有较大的区别。贴一下官方的demo简单阐述下react
wepy demogit
<style lang="less"> @color: #4D926F; .userinfo { color: @color; } </style> <template lang="pug"> view(class='container') view(class='userinfo' @tap='tap') mycom(:prop.sync='myprop' @fn.user='myevent') text {{now}} </template> <script> import wepy from 'wepy'; import mycom from '../components/mycom'; export default class Index extends wepy.page { components = { mycom }; data = { myprop: {} }; computed = { now () { return new Date().getTime(); } }; async onLoad() { await sleep(3); console.log('Hello World'); } sleep(time) { return new Promise((resolve, reject) => setTimeout(resolve, time * 1000)); } } </script>
taro demogithub
import Taro, { Component } from '@tarojs/taro' import { View, Button } from '@tarojs/components' export default class Index extends Component { constructor () { super(...arguments) this.state = { title: '首页', list: [1, 2, 3] } } componentWillMount () {} componentDidMount () {} componentWillUpdate (nextProps, nextState) {} componentDidUpdate (prevProps, prevState) {} shouldComponentUpdate (nextProps, nextState) { return true } add = (e) => { // dosth } render () { return ( <View className='index'> <View className='title'>{this.state.title}</View> <View className='content'> {this.state.list.map(item => { return ( <View className='item'>{item}</View> ) })} <Button className='add' onClick={this.add}>添加</Button> </View> </View> ) } }
能够见到在wepy里,css
、template
、script
都放在一个wepy文件里,template
还支持多种模板引擎语法,而后支持computed
、watcher
等属性,这些都是典型的vue风格npm
而在taro里,就是彻头彻尾的react风格,包括constructor
,componentWillMount
、componentDidMount
等各类react的生命周期函数,还有return
里返回的jsx
,熟悉react的人上手起来能够说是很是快了json
除此以外还有一些细微的差别之处:redux
wxml
,用的都是小程序里原生的组件,就是小程序文档里的各类组件;而taro里使用的每一个组件,都须要从@tarojs/components
里引入,包括View
,Text
等基础组件(这种作实际上是为了转换多端作准备)事件处理上
click
事件代替tap
事件e.stopPropagation()
来阻止冒泡@tap="click({{index}})"
;而taro则是使用bind
传参,如onClick={this.handleClick.bind(null, params)}
page
和component
的区分;taro则是本身实现了相似react的生命周期,并且没有page
和component
的区分,都是component
总的来讲,毕竟是两种不一样的开发风格,天然仍是会有许多大大小小的差别。在这里与当前很流行的小程序开发框架之一wepy进行简单对比,主要仍是为了方便你们更快速地了解taro,从而选择更适合本身的开发方式。
taro官方提供的demo是很简单的,主要是为了让你们快速上手,入门。那么,当咱们要开发偏大型的项目时,应该如何使用taro使得开发体验更好,开发效率更高?做为深度参与TOPLIFE小程序开发的人员之一,谈一谈个人一些实践体验及心得
使用taro-cli生成模板是这样的
├── dist 编译结果目录 ├── config 配置目录 | ├── dev.js 开发时配置 | ├── index.js 默认配置 | └── prod.js 打包时配置 ├── src 源码目录 | ├── pages 页面文件目录 | | ├── index index页面目录 | | | ├── index.js index页面逻辑 | | | └── index.css index页面样式 | ├── app.css 项目总通用样式 | └── app.js 项目入口文件 └── package.json
假如引入了redux,例如咱们的项目,目录是这样的
├── dist 编译结果目录 ├── config 配置目录 | ├── dev.js 开发时配置 | ├── index.js 默认配置 | └── prod.js 打包时配置 ├── src 源码目录 | ├── actions redux里的actions | ├── asset 图片等静态资源 | ├── components 组件文件目录 | ├── constants 存放常量的地方,例如api、一些配置项 | ├── reducers redux里的reducers | ├── store redux里的store | ├── utils 存放工具类函数 | ├── pages 页面文件目录 | | ├── index index页面目录 | | | ├── index.js index页面逻辑 | | | └── index.css index页面样式 | ├── app.css 项目总通用样式 | └── app.js 项目入口文件 └── package.json
比较常见的一种项目目录组织方式,相比初始模板多了几个文件夹,用于存放redux相关的内容及其余的一些东西,整个项目结构相信仍是比较直观,简单明了的
redux你们应该都不陌生,一种状态管理的库,一般会搭配一些中间件使用。咱们的项目主要是用了redux-thunk
和redux-logger
中间件,一个用于处理异步请求,一个用于调试,追踪actions
相信你们都遇到过这种时候,接口返回的数据和页面显示的数据并非彻底对应的,每每须要再作一层预处理。那么这个业务逻辑应该在哪里管理,是组件内部,仍是redux
的流程里?
举个例子:
例如上图的购物车模块,接口返回的数据是
{ code: 0, data: { shopMap: {...}, // 存放购物车里商品的店铺信息的map goods: {...}, // 购物车里的商品信息 ... } ... }
对的,购车里的商品店铺和商品是放在两个对象里面的,但视图要求它们要显示在一块儿。这时候,若是直接将返回的数据存到store
,而后在组件内部render
的时候东拼西凑,将二者信息匹配,再作显示的话,会显得组件内部的逻辑十分的混乱,不够纯粹。
因此,我我的比较推荐的作法是,在接口返回数据以后,直接将其处理为与页面显示对应的数据,而后再dispatch
处理后的数据,至关于作了一层拦截,像下面这样:
const data = result.data // result为接口返回的数据 const cartData = handleCartData(data) // handleCartData为处理数据的函数 dispatch({type: 'RECEIVE_CART', payload: cartData}) // dispatch处理事后的函数 ... // handleCartData处理后的数据 { commoditys: [{ shop: {...}, // 商品店铺的信息 goods: {...}, // 对应商品信息 }, ...] }
能够见到,处理数据的流程在render前被拦截处理了,将对应的商品店铺和商品放在了一个对象了.
这样作有几个好处
handleCartData
函数里面的逻辑,不用改动组件内部的逻辑。后台数据——>拦截处理——>指望的数据结构——>组件
实际上,不仅是后台数据返回的时候,其它数据结构须要变更的时候均可以作一层数据拦截,拦截的时机也能够根据业务逻辑调整,重点是要让组件内部自己不关心数据与视图是否对应,只专一于内部交互的逻辑,这也很符合react
自己的初衷,数据驱动视图
connect
你们都知道是用来链接store
、actions
和组件的,不少时候就只是根据样板代码复制一下,改改组件各自的store
、actions
。实际上,咱们还能够作一些别的处理,例如:
export default connect(({ cart, }) => ({ couponData: cart.couponData, commoditys: cart.commoditys, editSkuData: cart.editSkuData }), (dispatch) => ({ // ...actions绑定 }))(Cart) // 组件里 render () { const isShowCoupon = this.props.couponData.length !== 0 return isShowCoupon && <Coupon /> }
上面是很普通的一种connect
写法,而后render
函数根据couponData
里是否数据来渲染。这时候,咱们能够把this.props.couponData.length !== 0
这个判断丢到connect
里,达成一种computed
的效果,以下:
export default connect(({ cart, }) => { const { couponData, commoditys, editSkuData } = cart const isShowCoupon = couponData.length !== 0 return { isShowCoupon, couponData, commoditys, editSkuData }}, (dispatch) => ({ // ...actions绑定 }))(Cart) // 组件里 render () { return this.props.isShowCoupon && <Coupon /> }
能够见到,在connect
里定义了isShowCoupon
变量,实现了根据couponData
来进行computed
的效果
实际上,这也是一种数据拦截处理。除了computed
,还能够实现其它的功能,具体就由各位看官自由发挥了
那taro,或者是小程序开发,有没有什么要注意的地方?固然有,走过的弯路能够说是很是多了
估计是每一个页面的数据在小程序内部都有缓存,因此作了10层的限制。带来的问题就是假如页面存在循环跳转,即A页面能够跳到B页面,B页面也能够跳到A页面,而后用户从A进入了B,想返回A的时候,每每是直接在B页面里点击跳转到A,而不是点返回回到A,如此一来,10层很快就突破了。因此咱们本身对navigateTo
函数作了一层封装,防止溢出
上面说到,页面内容有缓存。因此假如某个页面是根据不一样的数据渲染视图,新渲染时会有上一次渲染的缓存,致使页面看起来有个闪烁的变化,用户体验很是很差。其实解决的办法也很简单,每次在componentWillUnmount
生命周期中清理一下当前页面的数据就行了。小程序说到底不是h5,不会说每次进入页面就会刷新,也不会离开就销毁,刷新,清理数据的动做都须要本身再生命周期函数里主动触发
页面的滚动事件只能经过onPageScroll
来监听,因此当我想在组件里进监听操做时,要将该部分的逻辑提早到onPageScroll
函数,提升了抽象成本。例如我须要开发一个滚动到某个位置就吸顶的tab
,原本能够在tab
内部处理的逻辑被提早了,减小了其可复用性
原本也想详细描述下的,不过在咱们几位大佬的努力,加班加点下,已经开发出eslint插件,及补充完整了taro文档。你们只要遵循eslint插件规范,查看文档,应该不会有太大问题,有问题欢迎提issue
总的来讲,用taro来开发小程序体验仍是很不错的,最重要的是,可使用jsx写小程序了!!!做为react粉的一员,能够说是至关的兴奋了~