前陣子,來自咱們凹凸實驗室的遵循 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粉的一員,能夠說是至關的興奮了~