新的小程序開發框架?- Taro的深度實踐體驗

前言

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

與wepy比較

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

相同點

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

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

不一樣點

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

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

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

除此以外還有一些細微的差別之處: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

比較常見的一種項目目錄組織方式,相比初始模板多了幾個文件夾,用於存放redux相關的內容及其餘的一些東西,整個項目結構相信仍是比較直觀,簡單明瞭的

更好地使用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,還能夠實現其它的功能,具體就由各位看官自由發揮了

一些須要注意的地方

那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粉的一員,能夠說是至關的興奮了~

相關文章
相關標籤/搜索