小程序模板網(wǎng)

Taro實(shí)踐 - TOPLIFE小程序 開(kāi)發(fā)體驗(yàn)

發(fā)布時(shí)間:2018-07-05 09:47 所屬欄目:小程序開(kāi)發(fā)教程

前陣子,來(lái)自我們凹凸實(shí)驗(yàn)室的遵循React 語(yǔ)法規(guī)范的 多端開(kāi)發(fā)方案 - Taro 終于對(duì)外開(kāi)源了,歡迎圍觀 star (先打波廣告)。作為第一批使用了 Taro 開(kāi)發(fā)的TOPLIFE小程序的開(kāi)發(fā)人員之一,自然是走了不少?gòu)澛?,躺了不少坑,也幫忙找過(guò)不少bug?,F(xiàn)在項(xiàng)目總算是上線了,那么,也是時(shí)候給大家總結(jié)分享下了。

與WePY比較

當(dāng)初開(kāi)發(fā)TOPLIFE第一期的時(shí)候,用的其實(shí)是 WePY (那時(shí)Taro還沒(méi)有開(kāi)發(fā)完成),然后在第二期才全面轉(zhuǎn)換為用 Taro 開(kāi)發(fā)。作為兩個(gè)小程序開(kāi)發(fā)框架都使用過(guò),并應(yīng)用在生產(chǎn)環(huán)境里的人,自然是要比較一下兩者的異同點(diǎn)。

相同點(diǎn)

  • 組件化開(kāi)發(fā)
  • npm包支持
  • ES6+特性支持,Promise, Async Functions 等
  • CSS預(yù)編譯器支持,Sass/Stylus/PostCSS等
  • 支持使用Redux進(jìn)行狀態(tài)管理
  • …..

相同的地方也不用多說(shuō)什么,都2018年了,這些特性的支持都是為了讓小程序開(kāi)發(fā)變得更現(xiàn)代,更工程化,重點(diǎn)是區(qū)別之處。

不同點(diǎn)

  • 開(kāi)發(fā)風(fēng)格
  • 實(shí)現(xiàn)原理
  • WePY支持slot,Taro暫不支持直接渲染children

開(kāi)發(fā)風(fēng)格

最大的不同之處,自然就是開(kāi)發(fā)風(fēng)格上的差異, WePY 使用的是類(lèi)Vue開(kāi)發(fā)風(fēng)格, Taro 使用的是類(lèi)React 開(kāi)發(fā)風(fēng)格,可以說(shuō)開(kāi)發(fā)體驗(yàn)上還是會(huì)有較大的區(qū)別。貼一下官方的demo簡(jiǎn)單闡述下。

WePY demo

<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 demo

import Taro, { Component } from '@tarojs/taro'
import { View, Button } from '@tarojs/components'

export default class Index extends Component {
  constructor () {
    super(...arguments)
    this.state = {
      title: '首頁(yè)',
      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>
    )
  }
}

可以見(jiàn)到在 WePY 里, css 、 template 、 script 都放在一個(gè)wpy文件里, template 還支持多種模板引擎語(yǔ)法,然后支持 computed 、 watcher 等屬性,這些都是典型的Vue風(fēng)格。

而在 Taro 里,就是徹頭徹尾的 React 風(fēng)格,包括 constructor , componentWillMount 、 componentDidMount 等各種 React 的生命周期函數(shù),還有 return 里返回的 jsx ,熟悉 React 的人上手起來(lái)可以說(shuō)是非??炝?。

除此之外還有一些細(xì)微的差異之處:

  • WePY 里的模板,或者說(shuō)是 wxml ,用的都是小程序里原生的組件,就是小程序文檔里的各種組件;而Taro里使用的每個(gè)組件,都需要從 @tarojs/components 里引入,包括 View , Text 等基礎(chǔ)組件(這種做其實(shí)是為了轉(zhuǎn)換多端做準(zhǔn)備)
  • 事件處理上
    • Taro 中,是用 click 事件代替 tap 事件
    • WePY使用的是簡(jiǎn)寫(xiě)的寫(xiě)法@+事件;而Taro則是on+事件名稱
    • 阻止冒泡上WePY用的是@+事件.stop;而Taro則是要顯式地使用 e.stopPropagation()來(lái)阻止冒泡
    • 事件傳參WePY可以直接在函數(shù)后面?zhèn)鲄?,?nbsp;@tap="click({{index}})" ;而Taro則是使用 bind 傳參,如 onClick={this.handleClick.bind(null, params)}
  • WePY使用的是小程序原生的生命周期,并且組件有 page 和 component 的區(qū)分;Taro 則是自己實(shí)現(xiàn)了類(lèi)似React 的生命周期,而且沒(méi)有 page 和 component 的區(qū)分,都是 component

總的來(lái)說(shuō),畢竟是兩種不同的開(kāi)發(fā)風(fēng)格,自然還是會(huì)有許多大大小小的差異。在這里與當(dāng)前很流行的小程序開(kāi)發(fā)框架之一 WePY 進(jìn)行簡(jiǎn)單對(duì)比,主要還是為了方便大家更快速地了解 Taro ,從而選擇更適合自己的開(kāi)發(fā)方式。

實(shí)踐體驗(yàn)

Taro 官方提供的demo 是很簡(jiǎn)單的,主要是為了讓大家快速上手,入門(mén)。那么,當(dāng)我們要開(kāi)發(fā)偏大型的項(xiàng)目時(shí),應(yīng)該如何使用 Taro 使得開(kāi)發(fā)體驗(yàn)更好,開(kāi)發(fā)效率更高?作為深度參與TOPLIFE小程序開(kāi)發(fā)的人員之一,談一談我的一些實(shí)踐體驗(yàn)及心得

如何組織代碼

使用taro-cli生成模板是這樣的

├── dist                   編譯結(jié)果目錄
├── config                 配置目錄
|   ├── dev.js             開(kāi)發(fā)時(shí)配置
|   ├── index.js           默認(rèn)配置
|   └── prod.js            打包時(shí)配置
├── src                    源碼目錄
|   ├── pages              頁(yè)面文件目錄
|   |   ├── index          index頁(yè)面目錄
|   |   |   ├── index.js   index頁(yè)面邏輯
|   |   |   └── index.css  index頁(yè)面樣式
|   ├── app.css            項(xiàng)目總通用樣式
|   └── app.js             項(xiàng)目入口文件
└── package.json

假如引入了redux,例如我們的項(xiàng)目,目錄是這樣的

├── dist                   編譯結(jié)果目錄
├── config                 配置目錄
|   ├── dev.js             開(kāi)發(fā)時(shí)配置
|   ├── index.js           默認(rèn)配置
|   └── prod.js            打包時(shí)配置
├── src                    源碼目錄
|   ├── actions            redux里的actions
|   ├── asset              圖片等靜態(tài)資源
|   ├── components         組件文件目錄
|   ├── constants          存放常量的地方,例如api、一些配置項(xiàng)
|   ├── reducers           redux里的reducers
|   ├── store              redux里的store
|   ├── utils              存放工具類(lèi)函數(shù)
|   ├── pages              頁(yè)面文件目錄
|   |   ├── index          index頁(yè)面目錄
|   |   |   ├── index.js   index頁(yè)面邏輯
|   |   |   └── index.css  index頁(yè)面樣式
|   ├── app.css            項(xiàng)目總通用樣式
|   └── app.js             項(xiàng)目入口文件
└── package.json

TOPLIFE小程序整個(gè)項(xiàng)目大概3萬(wàn)行代碼,數(shù)十個(gè)頁(yè)面,就是按上述目錄的方式組織代碼的。比較重要的文件夾主要是 pages 、 components 和 actions 。

  • pages里面是各個(gè)頁(yè)面的入口文件,簡(jiǎn)單的頁(yè)面就直接一個(gè)入口文件可以了,倘若頁(yè)面比較復(fù)雜那么入口文件就會(huì)作為組件的聚合文件, redux 的綁定一般也是在這里進(jìn)行。

  • 組件都放在 components 里面。里面的目錄是這樣的,假如有個(gè) coupon 優(yōu)惠券頁(yè)面,在 pages 自然先有個(gè) coupon ,作為頁(yè)面入口,然后它的組件就會(huì)存放在 components/coupon 里面,就是 components 里面也會(huì)按照頁(yè)面分模塊,公共的組件可以建一個(gè) components/public 文件夾,進(jìn)行復(fù)用。

    這樣的好處是頁(yè)面之間 互相獨(dú)立 , 互不影響 。所以我們幾個(gè)開(kāi)發(fā)人員,也是按照頁(yè)面的維度來(lái)進(jìn)行分工,互不干擾,大大提高了我們的開(kāi)發(fā)效率。

  • actions這個(gè)文件夾也是比較重要,這里處理的是拉取數(shù)據(jù),數(shù)據(jù)再處理的邏輯??梢哉f(shuō),數(shù)據(jù)處理得好,流動(dòng)清晰,整個(gè)項(xiàng)目就成功了一半,具體可以看下面***更好地使用redux***的部分。如上,假如是 coupon 頁(yè)面的 actions ,那么就會(huì)放在 actions/coupon 里面,可以再一次見(jiàn)到,所有的模塊都是以頁(yè)面的維度來(lái)區(qū)分的。

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

只要按照上述或類(lèi)似的代碼組織方式,遵循規(guī)范和約定,開(kāi)發(fā)大型項(xiàng)目時(shí)不說(shuō)能提高多少效率,至少順手了很多。

更好地使用redux

redux大家應(yīng)該都不陌生,一種狀態(tài)管理的庫(kù),通常會(huì)搭配一些中間件使用。我們的項(xiàng)目主要是用了 redux-thunk 和 redux-logger 中間件,一個(gè)用于處理異步請(qǐng)求,一個(gè)用于調(diào)試,追蹤 actions 。

數(shù)據(jù)預(yù)處理

相信大家都遇到過(guò)這種時(shí)候,接口返回的數(shù)據(jù)和頁(yè)面顯示的數(shù)據(jù)并不是完全對(duì)應(yīng)的,往往需要再做一層預(yù)處理。那么這個(gè)業(yè)務(wù)邏輯應(yīng)該在哪里管理,是組件內(nèi)部,還是 redux 的流程里?

舉個(gè)例子:

例如上圖的購(gòu)物車(chē)模塊,接口返回的數(shù)據(jù)是

{
	code: 0,
	data: {
        shopMap: {...}, // 存放購(gòu)物車(chē)?yán)锷唐返牡赇佇畔⒌膍ap
        goods: {...}, // 購(gòu)物車(chē)?yán)锏纳唐沸畔?
        ...
	}
	...
}

對(duì)的,購(gòu)車(chē)?yán)锏纳唐返赇伜蜕唐肥欠旁趦蓚€(gè)對(duì)象里面的,但視圖要求它們要顯示在一起。這時(shí)候,如果直接將返回的數(shù)據(jù)存到 store ,然后在組件內(nèi)部 render 的時(shí)候東拼西湊,將兩者信息匹配,再做顯示的話,會(huì)顯得組件內(nèi)部的邏輯十分的混亂,不夠純粹。

所以,我個(gè)人比較推薦的做法是,在接口返回?cái)?shù)據(jù)之后,直接將其處理為與頁(yè)面顯示對(duì)應(yīng)的數(shù)據(jù),然后再 dispatch 處理后的數(shù)據(jù),相當(dāng)于做了一層攔截,像下面這樣:

const data = result.data // result為接口返回的數(shù)據(jù)
const cartData = handleCartData(data) // handleCartData為處理數(shù)據(jù)的函數(shù)
dispatch({type: 'RECEIVE_CART', payload: cartData}) // dispatch處理過(guò)后的函數(shù)

...
// handleCartData處理后的數(shù)據(jù)
{
    commoditys: [{
        shop: {...}, // 商品店鋪的信息
        goods: {...}, // 對(duì)應(yīng)商品信息
    }, ...]
}

可以見(jiàn)到,處理數(shù)據(jù)的流程在render前被攔截處理了,將對(duì)應(yīng)的商品店鋪和商品放在了一個(gè)對(duì)象了.

這樣做有幾個(gè)好處

  • 一個(gè)是組件的渲染 更純粹 ,在組件內(nèi)部不用再關(guān)心如何將數(shù)據(jù)修修改改而滿足視圖要求, 只需關(guān)心組件本身的邏輯 ,例如點(diǎn)擊事件,用戶交互等

  • 二是數(shù)據(jù)的流動(dòng) 更可控 ,假如后續(xù)后臺(tái)返回的數(shù)據(jù)有變動(dòng),我們要做的只是改變 handleCartData 函數(shù)里面的邏輯,不用改動(dòng)組件內(nèi)部的邏輯。

    后臺(tái)數(shù)據(jù)——>攔截處理——>期望的數(shù)據(jù)結(jié)構(gòu)——>組件

實(shí)際上,不只是后臺(tái)數(shù)據(jù)返回的時(shí)候,其它數(shù)據(jù)結(jié)構(gòu)需要變動(dòng)的時(shí)候都可以做一層數(shù)據(jù)攔截,攔截的時(shí)機(jī)也可以根據(jù)業(yè)務(wù)邏輯調(diào)整,重點(diǎn)是要讓組件內(nèi)部本身不關(guān)心 數(shù)據(jù)與視圖是否對(duì)應(yīng),只專(zhuān)注于內(nèi)部交互的邏輯 ,這也很符合 React 本身的初衷,數(shù)據(jù)驅(qū)動(dòng)視圖。

connect可以做更多的事情

connect 大家都知道是用來(lái)連接 store 、 actions 和組件的,很多時(shí)候就只是根據(jù)樣板代碼復(fù)制一下,改改組件各自的 store 、 actions 。實(shí)際上,我們還可以做一些別的處理,例如:

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 寫(xiě)法,然后 render 函數(shù)根據(jù) couponData 里是否數(shù)據(jù)來(lái)渲染。這時(shí)候,我們可以把 this.props.couponData.length !== 0 這個(gè)判斷丟到 connect 里,達(dá)成一種 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 />
}

可以見(jiàn)到,在 connect 里定義了 isShowCoupon 變量,實(shí)現(xiàn)了根據(jù) couponData 來(lái)進(jìn)行 computed 的效果。

實(shí)際上,這也是一種數(shù)據(jù)攔截處理。除了 computed ,還可以實(shí)現(xiàn)其它的功能,具體就由各位看官自由發(fā)揮了。

項(xiàng)目感受

要說(shuō)最大的感受,就是在開(kāi)發(fā)的過(guò)程中, 有時(shí)會(huì)忘記了自己在寫(xiě)小程序,還以為是在寫(xiě)React頁(yè)面 。是的,有次我想給頁(yè)面綁定一個(gè)滾動(dòng)事件,才醒悟根本就沒(méi)有 doucment.body.addEventListener 這種東西。在使用 WePY 過(guò)程中,那些奇奇怪怪的語(yǔ)法還是時(shí)常提醒著我這是小程序,不是h5頁(yè)面,而在用 Taro 的時(shí)候,這個(gè)差異化已經(jīng)被消磨得很少了。盡管還是有一定的限制,但我基本上就是用開(kāi)發(fā)React的習(xí)慣來(lái)使用 Taro ,可以說(shuō)極大地提高了我的開(kāi)發(fā)體驗(yàn)。

一些需要注意的地方

那 Taro ,或者是小程序開(kāi)發(fā),有沒(méi)有什么要注意的地方?當(dāng)然有,走過(guò)的彎路可以說(shuō)是非常多了。

頁(yè)面棧只有10層

  • 估計(jì)是每個(gè)頁(yè)面的數(shù)據(jù)在小程序內(nèi)部都有緩存,所以做了10層的限制。帶來(lái)的問(wèn)題就是假如頁(yè)面存在循環(huán)跳轉(zhuǎn),即A頁(yè)面可以跳到B頁(yè)面,B頁(yè)面也可以跳到A頁(yè)面,然后用戶從A進(jìn)入了B,想返回A的時(shí)候,往往是直接在B頁(yè)面里點(diǎn)擊跳轉(zhuǎn)到A, 而不是點(diǎn)返回 回到A,如此一來(lái),10層很快就突破了。所以我們自己對(duì) navigateTo 函數(shù)做了一層封裝,防止溢出。

頁(yè)面內(nèi)容有緩存

  • 上面說(shuō)到,頁(yè)面內(nèi)容有緩存。所以假如某個(gè)頁(yè)面是根據(jù)不同的數(shù)據(jù)渲染視圖,新渲染時(shí)會(huì)有上一次渲染的緩存,導(dǎo)致頁(yè)面看起來(lái)有個(gè)閃爍的變化,用戶體驗(yàn)非常不好。其實(shí)解決的辦法也很簡(jiǎn)單,每次在 componentWillUnmount 生命周期中清理一下當(dāng)前頁(yè)面的數(shù)據(jù)就好了。小程序說(shuō)到底不是h5,不會(huì)說(shuō)每次進(jìn)入頁(yè)面就會(huì)刷新,也不會(huì)離開(kāi)就銷(xiāo)毀,刷新


易優(yōu)小程序(企業(yè)版)+靈活api+前后代碼開(kāi)源 碼云倉(cāng)庫(kù):starfork
本文地址:http://22321a.com/wxmini/doc/course/24621.html 復(fù)制鏈接 如需定制請(qǐng)聯(lián)系易優(yōu)客服咨詢:800182392 點(diǎn)擊咨詢
QQ在線咨詢