前陣子,來(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)
相同的地方也不用多說(shuō)什么,都2018年了,這些特性的支持都是為了讓小程序開(kāi)發(fā)變得更現(xiàn)代,更工程化,重點(diǎn)是區(qū)別之處。 不同點(diǎn)
開(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 demoimport 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ì)微的差異之處:
總的來(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 。
除此之外, asset 文件用來(lái)存放的靜態(tài)資源,如一些icon類(lèi)的圖片,但建議不要存放太多,畢竟程序包有限制。而 constants 則是一些存放常量的地方,例如 api 域名,配置等等。 只要按照上述或類(lèi)似的代碼組織方式,遵循規(guī)范和約定,開(kāi)發(fā)大型項(xiàng)目時(shí)不說(shuō)能提高多少效率,至少順手了很多。 更好地使用reduxredux大家應(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è)好處
實(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層
頁(yè)面內(nèi)容有緩存
|
工作日 8:30-12:00 14:30-18:00
周六及部分節(jié)假日提供值班服務(wù)