前陣子,來自我們凹凸實驗室的遵循React 語法規范的 多端開發方案 - Taro 終于對外開源了,歡迎圍觀 star (先打波廣告)。作為第一批使用了 Taro 開發的TOPLIFE小程序的開發人員之一,自然是走了不少彎路,躺了不少坑,也幫忙找過不少bug。現在項目總算是上線了,那么,也是時候給大家總結分享下了。 與WePY比較當初開發TOPLIFE第一期的時候,用的其實是 WePY (那時Taro還沒有開發完成),然后在第二期才全面轉換為用 Taro 開發。作為兩個小程序開發框架都使用過,并應用在生產環境里的人,自然是要比較一下兩者的異同點。 相同點
相同的地方也不用多說什么,都2018年了,這些特性的支持都是為了讓小程序開發變得更現代,更工程化,重點是區別之處。 不同點
開發風格最大的不同之處,自然就是開發風格上的差異, WePY 使用的是類Vue開發風格, Taro 使用的是類React 開發風格,可以說開發體驗上還是會有較大的區別。貼一下官方的demo簡單闡述下。 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: '首頁', 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 都放在一個wpy文件里, template 還支持多種模板引擎語法,然后支持 computed 、 watcher 等屬性,這些都是典型的Vue風格。 而在 Taro 里,就是徹頭徹尾的 React 風格,包括 constructor , componentWillMount 、 componentDidMount 等各種 React 的生命周期函數,還有 return 里返回的 jsx ,熟悉 React 的人上手起來可以說是非常快了。 除此之外還有一些細微的差異之處:
總的來說,畢竟是兩種不同的開發風格,自然還是會有許多大大小小的差異。在這里與當前很流行的小程序開發框架之一 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萬行代碼,數十個頁面,就是按上述目錄的方式組織代碼的。比較重要的文件夾主要是 pages 、 components 和 actions 。
除此之外, asset 文件用來存放的靜態資源,如一些icon類的圖片,但建議不要存放太多,畢竟程序包有限制。而 constants 則是一些存放常量的地方,例如 api 域名,配置等等。 只要按照上述或類似的代碼組織方式,遵循規范和約定,開發大型項目時不說能提高多少效率,至少順手了很多。 更好地使用reduxredux大家應該都不陌生,一種狀態管理的庫,通常會搭配一些中間件使用。我們的項目主要是用了 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前被攔截處理了,將對應的商品店鋪和商品放在了一個對象了. 這樣做有幾個好處
實際上,不只是后臺數據返回的時候,其它數據結構需要變動的時候都可以做一層數據攔截,攔截的時機也可以根據業務邏輯調整,重點是要讓組件內部本身不關心 數據與視圖是否對應,只專注于內部交互的邏輯 ,這也很符合 React 本身的初衷,數據驅動視圖。 connect可以做更多的事情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 ,還可以實現其它的功能,具體就由各位看官自由發揮了。 項目感受要說最大的感受,就是在開發的過程中, 有時會忘記了自己在寫小程序,還以為是在寫React頁面 。是的,有次我想給頁面綁定一個滾動事件,才醒悟根本就沒有 doucment.body.addEventListener 這種東西。在使用 WePY 過程中,那些奇奇怪怪的語法還是時常提醒著我這是小程序,不是h5頁面,而在用 Taro 的時候,這個差異化已經被消磨得很少了。盡管還是有一定的限制,但我基本上就是用開發React的習慣來使用 Taro ,可以說極大地提高了我的開發體驗。 一些需要注意的地方那 Taro ,或者是小程序開發,有沒有什么要注意的地方?當然有,走過的彎路可以說是非常多了。 頁面棧只有10層
頁面內容有緩存
|