Taro實踐 - TOPLIFE小程序 開發體驗

來自團隊支持 TOPLIFE小程序 業務的小夥伴,關於 Taro 的一篇使用感覺,但願對你們有所幫助。javascript

前言

前陣子,來自咱們凹凸實驗室的遵循 React 語法規範的多端開發方案 - Taro 終於對外開源了,歡迎圍觀star(先打波廣告)。做爲第一批使用了 Taro 開發的TOPLIFE小程序的開發人員之一,天然是走了很多彎路,躺了很多坑,也幫忙找過很多bug。如今項目總算是上線了,那麼,也是時候給你們總結分享下了。css

與WePY比較

當初開發TOPLIFE第一期的時候,用的實際上是WePY(那時Taro尚未開發完成),而後在第二期才全面轉換爲用 Taro 開發。做爲兩個小程序開發框架都使用過,並應用在生產環境裏的人,天然是要比較一下二者的異同點。html

相同點

  • 組件化開發
  • npm包支持
  • ES6+特性支持,PromiseAsync Functions
  • CSS預編譯器支持,Sass/Stylus/PostCSS等
  • 支持使用Redux進行狀態管理
  • …..

相同的地方也不用多說什麼,都2018年了,這些特性的支持都是爲了讓小程序開發變得更現代,更工程化,重點是區別之處。java

不一樣點

  • 開發風格
  • 實現原理
  • WePY支持slot,Taro暫不支持直接渲染children

開發風格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 裏,csstemplatescript都放在一個wpy文件裏,template還支持多種模板引擎語法,而後支持computedwatcher等屬性,這些都是典型的Vue風格。json

而在 Taro 裏,就是徹頭徹尾的 React 風格,包括constructorcomponentWillMountcomponentDidMount等各類 React 的生命週期函數,還有return裏返回的jsx,熟悉 React 的人上手起來能夠說是很是快了。redux

除此以外還有一些細微的差別之處:

  • WePY 裏的模板,或者說是wxml,用的都是小程序裏原生的組件,就是小程序文檔裏的各類組件;而Taro裏使用的每一個組件,都須要從@tarojs/components裏引入,包括ViewText等基礎組件(這種作實際上是爲了轉換多端作準備)
  • 事件處理上
    • Taro 中,是用click事件代替tap事件
    • WePY使用的是簡寫的寫法@+事件;而Taro則是on+事件名稱
    • 阻止冒泡上WePY用的是@+事件.stop;而Taro則是要顯式地使用e.stopPropagation()來阻止冒泡
    • 事件傳參WePY能夠直接在函數後面傳參,如@tap="click({{index}})";而Taro則是使用bind傳參,如onClick={this.handleClick.bind(null, params)}
  • WePY使用的是小程序原生的生命週期,而且組件有pagecomponent的區分;Taro 則是本身實現了相似React 的生命週期,並且沒有pagecomponent的區分,都是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萬行代碼,數十個頁面,就是按上述目錄的方式組織代碼的。比較重要的文件夾主要是pagescomponentsactions

  • pages裏面是各個頁面的入口文件,簡單的頁面就直接一個入口文件能夠了,假若頁面比較複雜那麼入口文件就會做爲組件的聚合文件,redux的綁定通常也是在這裏進行。

  • 組件都放在components裏面。裏面的目錄是這樣的,假若有個coupon優惠券頁面,在pages天然先有個coupon,做爲頁面入口,而後它的組件就會存放在components/coupon裏面,就是components裏面也會按照頁面分模塊,公共的組件能夠建一個components/public文件夾,進行復用。

    這樣的好處是頁面之間互相獨立互不影響。因此咱們幾個開發人員,也是按照頁面的維度來進行分工,互不干擾,大大提升了咱們的開發效率。

  • actions這個文件夾也是比較重要,這裏處理的是拉取數據,數據再處理的邏輯。能夠說,數據處理得好,流動清晰,整個項目就成功了一半,具體能夠看下面***更好地使用redux***的部分。如上,假如是coupon頁面的actions,那麼就會放在actions/coupon裏面,能夠再一次見到,全部的模塊都是以頁面的維度來區分的。

除此以外,asset文件用來存放的靜態資源,如一些icon類的圖片,但建議不要存放太多,畢竟程序包有限制。而constants則是一些存放常量的地方,例如api域名,配置等等。

只要按照上述或相似的代碼組織方式,遵循規範和約定,開發大型項目時不說能提升多少效率,至少順手了不少。

更好地使用redux

redux你們應該都不陌生,一種狀態管理的庫,一般會搭配一些中間件使用。咱們的項目主要是用了redux-thunkredux-logger中間件,一個用於處理異步請求,一個用於調試,追蹤actions

數據預處理

相信你們都遇到過這種時候,接口返回的數據和頁面顯示的數據並非徹底對應的,每每須要再作一層預處理。那麼這個業務邏輯應該在哪裏管理,是組件內部,仍是redux的流程裏?

舉個例子:

mage-20180612151609

例如上圖的購物車模塊,接口返回的數據是

{
	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能夠作更多的事情

connect你們都知道是用來鏈接storeactions和組件的,不少時候就只是根據樣板代碼複製一下,改改組件各自的storeactions。實際上,咱們還能夠作一些別的處理,例如:

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,或者是小程序開發,有沒有什麼要注意的地方?固然有,走過的彎路能夠說是很是多了。

頁面棧只有10層
  • 估計是每一個頁面的數據在小程序內部都有緩存,因此作了10層的限制。帶來的問題就是假如頁面存在循環跳轉,即A頁面能夠跳到B頁面,B頁面也能夠跳到A頁面,而後用戶從A進入了B,想返回A的時候,每每是直接在B頁面裏點擊跳轉到A,而不是點返回回到A,如此一來,10層很快就突破了。因此咱們本身對navigateTo函數作了一層封裝,防止溢出。
頁面內容有緩存
  • 上面說到,頁面內容有緩存。因此假如某個頁面是根據不一樣的數據渲染視圖,新渲染時會有上一次渲染的緩存,致使頁面看起來有個閃爍的變化,用戶體驗很是很差。其實解決的辦法也很簡單,每次在componentWillUnmount生命週期中清理一下當前頁面的數據就行了。小程序說到底不是h5,不會說每次進入頁面就會刷新,也不會離開就銷燬,刷新,清理數據的動做都須要本身再生命週期函數裏主動觸發。
不能隨時地監聽頁面滾動事件
  • 頁面的滾動事件只能經過onPageScroll來監聽,因此當我想在組件裏進監聽操做時,要將該部分的邏輯提早到onPageScroll函數,提升了抽象成本。例如我須要開發一個滾動到某個位置就吸頂的tab,原本能夠在tab內部處理的邏輯被提早了,減小了其可複用性。
Taro開發須要注意的地方
  • 原本也想詳細描述下的,不過在咱們幾位大佬的努力,加班加點下,已經開發出eslint插件,及補充完整了 Taro 文檔。你們只要遵循eslint插件規範,查看文檔,應該不會有太大問題,有問題歡迎提issue

總結

總的來講,用 Taro 來開發小程序體驗仍是很不錯的,最重要的是,可使用jsx寫小程序了!!!做爲React粉的一員,能夠說是至關的興奮了~

最後,歡迎關注 github.com/nervjs/taro

相關文章
相關標籤/搜索