來自團隊支持 TOPLIFE小程序 業務的小夥伴,關於 Taro 的一篇使用感覺,但願對你們有所幫助。javascript
前陣子,來自咱們凹凸實驗室的遵循 React 語法規範的多端開發方案 - Taro 終於對外開源了,歡迎圍觀star(先打波廣告)。做爲第一批使用了 Taro 開發的TOPLIFE小程序的開發人員之一,天然是走了很多彎路,躺了很多坑,也幫忙找過很多bug。如今項目總算是上線了,那麼,也是時候給你們總結分享下了。css
當初開發TOPLIFE第一期的時候,用的實際上是WePY(那時Taro尚未開發完成),而後在第二期才全面轉換爲用 Taro 開發。做爲兩個小程序開發框架都使用過,並應用在生產環境裏的人,天然是要比較一下二者的異同點。html
相同的地方也不用多說什麼,都2018年了,這些特性的支持都是爲了讓小程序開發變得更現代,更工程化,重點是區別之處。java
開發風格react
最大的不一樣之處,天然就是開發風格上的差別,WePY使用的是類Vue開發風格, Taro 使用的是類 React 開發風格,能夠說開發體驗上仍是會有較大的區別。貼一下官方的demo簡單闡述下。git
WePY demogithub
<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 demonpm
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
都放在一個wpy文件裏,template
還支持多種模板引擎語法,而後支持computed
、watcher
等屬性,這些都是典型的Vue風格。json
而在 Taro 裏,就是徹頭徹尾的 React 風格,包括constructor
,componentWillMount
、componentDidMount
等各類 React 的生命週期函數,還有return
裏返回的jsx
,熟悉 React 的人上手起來能夠說是很是快了。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
複製代碼
TOPLIFE小程序整個項目大概3萬行代碼,數十個頁面,就是按上述目錄的方式組織代碼的。比較重要的文件夾主要是pages
、components
和actions
。
pages裏面是各個頁面的入口文件,簡單的頁面就直接一個入口文件能夠了,假若頁面比較複雜那麼入口文件就會做爲組件的聚合文件,redux
的綁定通常也是在這裏進行。
組件都放在components裏面。裏面的目錄是這樣的,假若有個coupon
優惠券頁面,在pages
天然先有個coupon
,做爲頁面入口,而後它的組件就會存放在components/coupon
裏面,就是components裏面也會按照頁面分模塊,公共的組件能夠建一個components/public
文件夾,進行復用。
這樣的好處是頁面之間互相獨立,互不影響。因此咱們幾個開發人員,也是按照頁面的維度來進行分工,互不干擾,大大提升了咱們的開發效率。
actions這個文件夾也是比較重要,這裏處理的是拉取數據,數據再處理的邏輯。能夠說,數據處理得好,流動清晰,整個項目就成功了一半,具體能夠看下面***更好地使用redux***的部分。如上,假如是coupon
頁面的actions
,那麼就會放在actions/coupon
裏面,能夠再一次見到,全部的模塊都是以頁面的維度來區分的。
除此以外,asset文件用來存放的靜態資源,如一些icon類的圖片,但建議不要存放太多,畢竟程序包有限制。而constants則是一些存放常量的地方,例如api
域名,配置等等。
只要按照上述或相似的代碼組織方式,遵循規範和約定,開發大型項目時不說能提升多少效率,至少順手了不少。
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
,還能夠實現其它的功能,具體就由各位看官自由發揮了。
要說最大的感覺,就是在開發的過程當中,有時會忘記了本身在寫小程序,還覺得是在寫React頁面。是的,有次我想給頁面綁定一個滾動事件,才醒悟根本就沒有doucment.body.addEventListener
這種東西。在使用WePY
過程當中,那些奇奇怪怪的語法仍是時常提醒着我這是小程序,不是h5頁面,而在用Taro
的時候,這個差別化已經被消磨得不多了。儘管仍是有必定的限制,但我基本上就是用開發React的習慣來使用Taro
,能夠說極大地提升了個人開發體驗。
那Taro
,或者是小程序開發,有沒有什麼要注意的地方?固然有,走過的彎路能夠說是很是多了。
navigateTo
函數作了一層封裝,防止溢出。componentWillUnmount
生命週期中清理一下當前頁面的數據就行了。小程序說到底不是h5,不會說每次進入頁面就會刷新,也不會離開就銷燬,刷新,清理數據的動做都須要本身再生命週期函數裏主動觸發。onPageScroll
來監聽,因此當我想在組件裏進監聽操做時,要將該部分的邏輯提早到onPageScroll
函數,提升了抽象成本。例如我須要開發一個滾動到某個位置就吸頂的tab
,原本能夠在tab
內部處理的邏輯被提早了,減小了其可複用性。總的來講,用 Taro 來開發小程序體驗仍是很不錯的,最重要的是,可使用jsx寫小程序了!!!做爲React粉的一員,能夠說是至關的興奮了~
最後,歡迎關注 github.com/nervjs/taro