Taro 與 React 的差異(Taro的約束(keng))

Taro 與 React 的差異(轉自小冊)

由於微信小程序的限制,React 中某些寫法和特性在 Taro 中還未能實現,後續將會逐漸完善。 截止到本小冊發佈前,Taro 的最新版本爲 1.1,因此以下講解默認版本爲 1.1。

暫不支持在 render() 之外的方法定義 JSX

由於微信小程序的 template 不能動態傳值和傳入函數,Taro 暫時也沒辦法支持在類方法中定義 JSX。

無效情況

class App extends Component {
  _render() {
    return <View />
  }
}

class App extends Component {
  renderHeader(showHeader) {
    return showHeader && <Header />
  }
}

class App extends Component {
  renderHeader = (showHeader) => {
    return showHeader& & <Header />
  }
}

解決方案
在 render 方法中定義。

class App extends Component {

  render () {
    const { showHeader, showMain } = this.state
    const header = showHeader && <Header />
    const main = showMain && <Main />
    return (
      <View>
        {header}
        {main}
      </View>
    )
  }
}

不能在包含 JSX 元素的 map 循環中使用 if 表達式

無效情況

numbers.map((number) => {
  let element = null
  const isOdd = number % 2
  if (isOdd) {
    element = <Custom />
  }
  return element
})

numbers.map((number) => {
  let isOdd = false
  if (number % 2) {
    isOdd = true
  }
  return isOdd && <Custom />
})

解決方案
儘量在 map 循環中使用條件表達式或邏輯表達式。

numbers.map((number) => {
  const isOdd = number % 2
  return isOdd ? <Custom /> : null
})

numbers.map((number) => {
  const isOdd = number % 2
  return isOdd && <Custom />
})

不能使用 Array.map 之外的方法操作 JSX 數組

Taro 在小程序端實際上把 JSX 轉換成了字符串模板,而一個原生 JSX 表達式實際上是一個 React/Nerv 元素(react - element)的構造器,因此在原生 JSX 中你可以對任何一組 React 元素進行操作。但在 Taro 中你只能使用 map 方法,Taro 轉換成小程序中 wx:for。

無效情況

test.push(<View />)

numbers.forEach(numbers => {
  if (someCase) {
    a = <View />
  }
})

test.shift(<View />)

components.find(component => {
  return component === <View />
})

components.some(component => component.constructor.__proto__ === <View />.constructor)

numbers.filter(Boolean).map((number) => {
  const element = <View />
  return <View />
})

解決方案
先處理好需要遍歷的數組,然後再用處理好的數組調用 map 方法。

numbers.filter(isOdd).map((number) => <View />)

for (let index = 0; index < array.length; index++) {
  // do you thing with array
}

const element = array.map(item => {
  return <View />
})

不能在 JSX 參數中使用匿名函數

無效情況

<View onClick={() => this.handleClick()} />

<View onClick={(e) => this.handleClick(e)} />

<View onClick={() => ({})} />

<View onClick={function () {}} />

<View onClick={function (e) {this.handleClick(e)}} />

解決方案
使用 bind 或 類參數綁定函數。

<View onClick={this.props.hanldeClick.bind(this)} />

不能在 JSX 參數中使用對象展開符

微信小程序組件要求每一個傳入組件的參數都必須預先設定好,而對象展開符則是動態傳入不固定數量的參數。所以 Taro 沒有辦法支持該功能。

無效情況

<View {...this.props} />

<View {...props} />

<Custom {...props} />

解決方案
開發者自行賦值:

render () {
    const { id, title } = obj
    return <View id={id} title={title} />
}

不允許在 JSX 參數(props)中傳入 JSX 元素

由於微信小程序內置的組件化的系統不能通過屬性(props) 傳函數,而 props 傳遞函數可以說是 React 體系的根基之一,我們只能自己實現一套組件化系統。而自制的組件化系統不能使用內置組件化的 slot 功能。兩權相害取其輕,我們暫時只能不支持該功能。

無效情況

<Custom child={<View />} />

<Custom child={() => <View />} />

<Custom child={function () { <View /> }} />

<Custom child={ary.map(a => <View />)} />

解決方案
通過 props 傳值在 JSX 模板中預先判定顯示內容,或通過 props.children 來嵌套子組件。

不支持無狀態組件(Stateless Component)

由於微信的 template 能力有限,不支持動態傳值和函數,Taro 暫時只支持一個文件只定義一個組件。爲了避免開發者疑惑,暫時不支持定義 Stateless Component。

無效情況

function Test () {
  return <View />
}

function Test (ary) {
  return ary.map(() => <View />)
}

const Test = () => {
  return <View />
}

const Test = function () {
  return <View />
}

解決方案
使用 class 定義組件。

class App extends Component {
  render () {
    return (
      <View />
    )
  }
}

命名規範
Taro 函數命名使用駝峯命名法,如onClick,由於微信小程序的 WXML 不支持傳遞函數,函數名編譯後會以字符串的形式綁定在 WXML 上,囿於 WXML 的限制,函數名有三項限制:

方法名不能含有數字
方法名不能以下劃線開頭或結尾
方法名的長度不能大於 20
請遵守以上規則,否則編譯後的代碼在微信小程序中會報以下錯誤:

image
推薦安裝 ESLint 編輯器插件
Taro 有些寫法跟 React 有些差異,可以通過安裝 ESLint 相關的編輯器插件來獲得人性化的提示。由於不同編輯器安裝的插件有所不同,具體安裝方法請自行搜索,這裏不再贅述。 如下圖,就是安裝插件後獲得的提示:

image
image
最佳編碼方式
經過較長時間的探索與驗證,目前 Taro 在微信小程序端是採用依託於小程序原生自定義組件系統來設計實現 Taro 組件化的,所以目前小程序端的組件化會受到小程序原生組件系統的限制,而同時爲了實現以 React 方式編寫代碼的目標,Taro 本身做了一些編譯時以及運行時的處理,這樣也帶來了一些值得注意的約束,所以有必要闡述一下 Taro 編碼上的最佳實踐。

組件樣式說明
微信小程序的自定義組件樣式默認是不能受外部樣式影響的,例如在頁面中引用了一個自定義組件,在頁面樣式中直接寫自定義組件元素的樣式是無法生效的。這一點,在 Taro 中也是一樣,而這也是與大家認知的傳統 Web 開發不太一樣。

給組件設置 defaultProps
在微信小程序端的自定義組件中,只有在 properties 中指定的屬性,才能從父組件傳入並接收

Component({
  properties: {
    myProperty: { // 屬性名
      type: String, // 類型(必填),目前接受的類型包括:String, Number, Boolean, Object, Array, null(表示任意類型)
      value: '', // 屬性初始值(可選),如果未指定則會根據類型選擇一個
      observer: function (newVal, oldVal, changedPath) {
         // 屬性被改變時執行的函數(可選),也可以寫成在 methods 段中定義的方法名字符串, 如:'_propertyChange'
         // 通常 newVal 就是新設置的數據, oldVal 是舊數據
      }
    },
    myProperty2: String // 簡化的定義方式
  }
  ...
})

而在 Taro 中,對於在組件代碼中使用到的來自 props 的屬性,會在編譯時被識別並加入到編譯後的 properties 中,暫時支持到了以下寫法

this.props.property

const { property } = this.props

const property = this.props.property

但是一千個人心中有一千個哈姆雷特,不同人的代碼寫法肯定也不盡相同,所以 Taro 的編譯肯定不能覆蓋到所有的寫法,而同時可能會有某一屬性沒有使用而是直接傳遞給子組件的情況,這種情況是編譯時無論如何也處理不到的,這時候就需要大家在編碼時給組件設置 defaultProps 來解決了。

組件設置的 defaultProps 會在運行時用來彌補編譯時處理不到的情況,裏面所有的屬性都會被設置到 properties 中初始化組件,正確設置 defaultProps 可以避免很多異常的情況的出現。

組件傳遞函數屬性名以 on 開頭
在 Taro 中,父組件要往子組件傳遞函數,屬性名必須以 on 開頭

// 調用 Custom 組件,傳入 handleEvent 函數,屬性名爲 `onTrigger`
class Parent extends Component {

  handleEvent () {

  }

  render () {
    return (
      <Custom onTrigger={this.handleEvent}></Custom>
    )
  }
}

這是因爲,微信小程序端組件化是不能直接傳遞函數類型給子組件的,在 Taro 中是藉助組件的事件機制來實現這一特性,而小程序中傳入事件的時候屬性名寫法爲 bindmyevent 或者 bind:myevent

<!-- 當自定義組件觸發“myevent”事件時,調用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以寫成 -->
<component-tag-name bind:myevent="onMyEvent" />

所以 Taro 中約定組件傳遞函數屬性名以 on 開頭,同時這也和內置組件的事件綁定寫法保持一致了。

小程序端不要在組件中打印傳入的函數
前面已經提到小程序端的組件傳入函數的原理,所以在小程序端不要在組件中打印傳入的函數,因爲拿不到結果,但是 this.props.onXxx && this.props.onXxx() 這種判斷函數是否傳入來進行調用的寫法是完全支持的。

小程序端不要將在模板中用到的數據設置爲 undefined
由於小程序不支持將 data 中任何一項的 value 設爲 undefined ,在 setState 的時候也請避免這麼用。你可以使用 null 來替代。

小程序端不要在組件中打印 this.props.children
在微信小程序端是通過 來實現往自定義組件中傳入元素的,而 Taro 利用 this.props.children 在編譯時實現了這一功能, this.props.children 會直接被編譯成 標籤,所以它在小程序端屬於語法糖的存在,請不要在組件中打印它。

組件屬性傳遞注意
不要以 id、class、style 作爲自定義組件的屬性與內部 state 的名稱,因爲這些屬性名在微信小程序中會丟失。

組件 state 與 props 裏字段重名的問題
不要在 state 與 props 上用同名的字段,因爲這些被字段在微信小程序中都會掛在 data 上。

小程序中頁面生命週期 componentWillMount 不一致問題
由於微信小程序裏頁面在 onLoad 時才能拿到頁面的路由參數,而頁面 onLoad 前組件都已經 attached 了。因此頁面的 componentWillMount 可能會與預期不太一致。例如:

// 錯誤寫法
render () {
  // 在 willMount 之前無法拿到路由參數
  const abc = this.$router.params.abc
  return <Custom adc={abc} />
}

// 正確寫法
componentWillMount () {
  const abc = this.$router.params.abc
  this.setState({
    abc
  })
}
render () {
  // 增加一個兼容判斷
  return this.state.abc && <Custom adc={abc} />
}

對於不需要等到頁面 willMount 之後取路由參數的頁面則沒有任何影響。

組件的 constructor 與 render 提前調用
很多細心的開發者應該已經注意到了,在 Taro 編譯到小程序端後,組件的 constructor 與 render 默認會多調用一次,表現得與 React 不太一致。

這是因爲,Taro 的組件編譯後就是小程序的自定義組件,而小程序的自定義組件的初始化時是可以指定 data 來讓組件擁有初始化數據的。開發者一般會在組件的 constructor 中設置一些初始化的 state,同時也可能會在 render 中處理 state 與 props 產生新的數據,在 Taro 中多出的這一次提前調用,就是爲了收集組件的初始化數據,給自定義組件提前生成 data ,以保證組件初始化時能帶有數據,讓組件初次渲染正常。

所以,在編碼時,需要在處理數據的時候做一些容錯處理,這樣可以避免在 constructor 與 render 提前調用時出現由於沒有數據導致出錯的情況。

JS 編碼必須用單引號
在 Taro 中,JS 代碼裏必須書寫單引號,特別是 JSX 中,如果出現雙引號,可能會導致編譯錯誤。

環境變量 process.env 的使用
不要以解構的方式來獲取通過 env 配置的 process.env 環境變量,請直接以完整書寫的方式 process.env.NODE_ENV 來進行使用

// 錯誤寫法,不支持
const { NODE_ENV = 'development' } = process.env
if (NODE_ENV === 'development') {
  ...
}

// 正確寫法
if (process.env.NODE_ENV === 'development') {

}

預加載
在微信小程序中,從調用 Taro.navigateTo、Taro.redirectTo 或 Taro.switchTab 後,到頁面觸發 componentWillMount 會有一定延時。因此一些網絡請求可以提前到發起跳轉前一刻去請求。

Taro 提供了 componentWillPreload 鉤子,它接收頁面跳轉的參數作爲參數。可以把需要預加載的內容通過 return 返回,然後在頁面觸發 componentWillMount 後即可通過 this.$preloadData 獲取到預加載的內容。

class Index extends Component {
  componentWillMount () {
    console.log('isFetching: ', this.isFetching)
    this.$preloadData
      .then(res => {
        console.log('res: ', res)
        this.isFetching = false
      })
  }

  componentWillPreload (params) {
    return this.fetchData(params.url)
  }

  fetchData () {
    this.isFetching = true
    ...
  }
}

小結
由於 JSX 中的寫法千變萬化,我們不能支持到所有的 JSX 寫法,同時由於微信小程序端的限制,也有部分 JSX 的優秀用法暫時不能得到很好地支持。這些不支持的寫法都可以通過其他寫法來規避,同時 ESLint 相關插件都能很好地提醒用戶避免踩坑。瞭解 Taro 這些注意事項後,接下來我們就來動手實現一個簡單的 Todo 項目。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章