這是一篇針對(duì)舊版本用戶升級(jí)到 Taro Next 的遷移指南。因?yàn)楸菊聝?nèi)容包含了許多詳盡的闡述和遷移例子,所以看起來(lái)有一些長(zhǎng)。但請(qǐng)不要擔(dān)心,Taro Next 大部分用法還是和舊版本一樣的。本章沒有提到的內(nèi)容,你可以像舊版本的 Taro 一樣操作或使用。
事實(shí)上,你并不需要去更改任何業(yè)務(wù)的邏輯代碼,許多更改使用編輯器的「查找/替換」就可以完成。你甚至不需要完整地閱讀整章內(nèi)容(重點(diǎn)在 1,2 小節(jié), API 和 項(xiàng)目/頁(yè)面配置),只有當(dāng)出問(wèn)題時(shí)定位到具體的小節(jié)即可。
更新到 Taro Next 首先需要更新項(xiàng)目依賴:
# 更新 CLI
$ npm i -g @tarojs/cli@next
# 在項(xiàng)目目錄更新項(xiàng)目依賴
$ npm i @tarojs/runtime@next @tarojs/mini-runner@next @tarojs/components@next @tarojs/taro@next
$ npm i react @tarojs/react@next # 如果使用 React
$ npm i nervjs # 如果使用 Nerv
# CLI 命令和以前一模一樣
$ taro build --type weapp --watch
在舊版本 Taro 中,我們把所有面向應(yīng)用開發(fā)者的 API 都放在 @tarojs/taro
里,一個(gè)典型的 Taro 組件/頁(yè)面會(huì)像這樣:
// 類組件
import Taro, { Component } from '@tarojs/taro'
class Wallace extends Component {
componentDidMount () {
Taro.request().then(/* do something */)
}
render () {
return ...
}
}
// 函數(shù)式組件
import Taro, { useEffect } from '@tarojs/taro'
function Tall () {
useEffect(() => {
Taro.request().then(/* do something */)
}, [])
return ...
}
在 Taro Next 中,屬于框架本身的 API 從框架自己的包中引入,其它的 API 仍然從 @tarojs/taro
引入。使用哪個(gè)框架來(lái)進(jìn)行開發(fā)完全由開發(fā)者來(lái)決定。
import Taro from '@tarojs/taro'
import React, { Component } from 'react' // Component 是來(lái)自于 React 的 API
// 從 nervjs 中引入,那運(yùn)行的就是 Nerv
// import { Component } from 'nervjs'
class Reporter extends Component {
componentDidMount () {
Taro.request().then(/* do something */)
}
render () {
return ...
}
}
// 函數(shù)式組件
import Taro from '@tarojs/taro'
// useEffect 是來(lái)自于 React 的 API
import React, { useEffect } from 'react'
// 從 nervjs 中引入,那運(yùn)行的就是 Nerv
// import { useEffect } from 'nervjs'
function Fast () {
useEffect(() => {
Taro.request().then(/* do something */)
}, [])
return ...
}
Nerv 是凹凸實(shí)驗(yàn)室的一個(gè)開源類 React 框架,體積比 React 更小,多數(shù)情況性能表現(xiàn)也比 React 更好。但某些 React 生態(tài)的庫(kù)兼容性可能會(huì)出現(xiàn)問(wèn)題。
在舊版本 Taro 中,頁(yè)面/項(xiàng)目的配置掛載在類組件的類屬性或函數(shù)式的屬性上,通過(guò) AST 分析取出來(lái),然后生成 JSON 文件。但這樣做,項(xiàng)目頁(yè)面的配置就無(wú)法動(dòng)態(tài)地生成:
// app.js 項(xiàng)目配置
class App extends Component {
config = {
pages: [
'pages/index/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
}
}
render () {
return ...
}
}
// index.js 頁(yè)面配置
function Index () {
return ...
}
Index.config = {
navigationBarTitleText: '首頁(yè)'
}
在 Taro Next 中,會(huì)有一個(gè)新的文件:*.config.js
,*
代表你頁(yè)面/項(xiàng)目文件的文件名,config
文件必須和頁(yè)面/項(xiàng)目文件在同一文件夾。在這個(gè)文件里你可以使用任意合法的 JavaScript 語(yǔ)法,只要最終把配置作為對(duì)象通過(guò) export default
出去即可:
// app.js 項(xiàng)目文件
class App extends Component {
render () {
return ...
}
}
// app.config.js
export default {
pages: [
'pages/index/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
}
}
// index.js 頁(yè)面文件
function Index () {
return ...
}
// index.config.js 頁(yè)面配置
const title = '首頁(yè)'
export default {
navigationBarTitleText: title
}
一個(gè)完整的項(xiàng)目文件結(jié)構(gòu)示例會(huì)像這樣:
.
├── app.config.js // 入口文件項(xiàng)目配置
├── app.js
├── app.scss
├── components
│ └── result.js // 組件若不使用第三方組件則無(wú)需配置
└── pages
└── index
├── index.config.js // index 的頁(yè)面配置
└── index.js
如果你需要引入 React 相關(guān)生態(tài)的庫(kù),直接通過(guò) npm install
安裝然后引入使用即可,Taro 不會(huì)再維護(hù)類似于 taro-redux
、taro-mobx
之類的庫(kù)。
// 當(dāng)使用了 JSX 時(shí),babel 會(huì)隱式地調(diào)用 React.createElement
// 因此只要你使用了 JSX,就要把 React 或 Nerv 引入
import React from 'react'
import { useSelector } from 'react-redux'
// 如果是使用的是 Nerv
// import { useSelector } from 'nerv-redux'
function Excited () {
const counter = useSelector(state => state.counter)
return ...
}
在舊版本中可以通過(guò) this.$router
訪問(wèn)當(dāng)前組件/頁(yè)面路由的詳情。在 Taro Next 對(duì)應(yīng)的 API 是在 @tarojs/taro
中的 getCurrentInstance().router
,兩者的屬性一模一樣。
import { getCurrentInstance } from '@tarojs/taro'
class C extends Component {
current = getCurrentInstance()
componentWillMount () {
// getCurrentInstance().router 和 this.$router 和屬性一樣
console.log(this.current.router)
}
}
// 函數(shù)式組件
import { getCurrentInstance } from '@tarojs/taro'
function C () {
const { router } = getCurrentInstance()
// getCurrentInstance().router 和 useRouter 返回的內(nèi)容也一樣
// const router = useRouter()
}
而對(duì)于項(xiàng)目入口組件而言,路由信息我們推薦在 componentDidShow
生命周期的參數(shù)中直接讀取。
// app.js 項(xiàng)目入口文件
class App extends Component {
componentDidShow (options /* 這里有你想要的路由信息 */) {
}
render () {
...
}
}
聰明的讀者已經(jīng)猜到了,
getCurrentInstance().router
其實(shí)是訪問(wèn)小程序當(dāng)前頁(yè)面onLoad
生命周期參數(shù)的快捷方式。
在 Taro Next 中,沒有組件的外部樣式和全局樣式的概念,組件的配置(config.js
)是無(wú)效的,頁(yè)面和入口文件引入的 CSS 都會(huì)變成全局 CSS ,沒有了 externalClasses
和 addGlobalClass
這兩個(gè)概念。
如果你需要帶作用域的 CSS,可以考慮使用 CSS Modules。
jsxAttributeNameReplace 配置已被移除。因?yàn)槲覀儾恍枰渲?externalClasses
,這個(gè)屬性也失去了它存在的意義。
Webpack
升級(jí)到 Webpack@4
,Babel
升級(jí)到 babel@7
。Webpack 升級(jí)是在 taro@2
中完成的,如果你是從 taro@1
升級(jí)上來(lái)的話,或許需要去看看 Taro 2 更改 查看使用
Webpack 編譯后帶來(lái)的變化。
升級(jí)到 babel@7
意味著你的項(xiàng)目文件全部都會(huì)通過(guò)根目錄的 babel.config.js
的配置進(jìn)行編譯。
eslint-plugin-taro
已被廢棄,你不再需要遵循它所規(guī)定的種種限制。你可以發(fā)揮你的創(chuàng)造力使用任何合法的 JSX 語(yǔ)法:
import React from 'react'
import { View, Text } from '@tarojs/components'
function C () {
// 你可以選擇不使用 JSX,但元素還是必須從 `@tarojs/components` 引入
const title = React.createElement(View, null, 'Numbers:')
const numbers = []
for (let i = 0; i < 10; i++) {
numbers.push(<Text key={i}>{i}</Text>)
}
return <>
{title}
{numbers}
</>
}
舊版本文檔所提到的最佳實(shí)踐也不必再遵循。也就是說(shuō),即便你不給組件設(shè)置 defaultProps
,自定義事件名不以 on
開頭(還有其它的舊版本代碼風(fēng)格最佳實(shí)踐),你的代碼也能運(yùn)行。但值得注意的是,遵循這樣的 代碼風(fēng)格最佳實(shí)踐 可以讓你的代碼更健壯,你的應(yīng)用也會(huì)因此而收益。而對(duì)于另外的一些由于舊版本
Taro 執(zhí)行機(jī)制的 hack(例如 render 調(diào)用兩次,state 和 props 無(wú)法重名,不要打印組件),這類最佳實(shí)踐可以不必理會(huì)。
Taro Next 在底層會(huì)維護(hù)一個(gè)精簡(jiǎn)的 DOM 系統(tǒng),在框架中使用 ref
鏈接到的是一個(gè) Taro Element 實(shí)例,因此你直接可以使用 HTMLElement
的部分方法直接操作它。如果你需要獲取原生小程序 DOM 實(shí)例,那需要使用原生小程序的 SelectorQuery
來(lái)獲取。
大部分和渲染相關(guān)的 DOM 屬性你都可以通過(guò)像 Web 開發(fā)一樣獲取或設(shè)置(如果有必要的話你甚至可以通過(guò)
parentNode
和childNodes
訪問(wèn)元素的父元素和子元素?。?,但元素的位置你還是必須通過(guò)原生小程序 DOM 實(shí)例的boundingClientRect()
和scrollOffset()
方法獲取。
另外,如果你使用的是 React,就將無(wú)法使用字符串的形式來(lái)使用 ref
。(Nerv 不受此影響)
class C extends Components {
input = React.createRef()
componentDidMount () {
const node = this.input.current // node 是一個(gè) Taro Element 實(shí)例
node.focus() // ok, 在 Web 開發(fā)中常見做法
// 以下寫法也能更新視圖,但不推薦這么做,更推薦使用數(shù)據(jù)來(lái)驅(qū)動(dòng)視圖更新
node.setProerty('class', 'input-css-class')
node.className = 'input-css-class'
node.style.fontSize = '16px'
node.value = 'excited!'
// 如果你需要獲取原生小程序 DOM 的話
const miniNode = Taro.createSelectorQuery().select('#' + node.id)
}
render () {
return <Input ref={this.input} />
}
}
在未來(lái),我們可能會(huì)在 Taro Element 上提供一個(gè)可以快速訪問(wèn)小程序 DOM 實(shí)例的屬性。目前請(qǐng)按照上述例子使用。
當(dāng)你使用 React 時(shí)(使用 Nerv 不受此影響),以下生命周期被更名:
componentWillMount()
-> UNSAFE_componentWillMount()
componentWillReceiveProps
-> UNSAFE_componentWillReceiveProps()
componentWillUpdate
-> UNSAFE_componentWillUpdate()
新增一個(gè)生命周期: componentDidCatch(err, info)
,這是由框架本身(React 或 Nerv)提供的。componentDidCatch(err, info)
會(huì)在組件和它的子孫拋出錯(cuò)誤時(shí)觸發(fā),第一個(gè)參數(shù) err
指向拋出的錯(cuò)誤,第二個(gè)參數(shù) info
是組件的調(diào)用信息。
componentDidCatch
和原有的componentDidCatchError
共同存在,區(qū)別在于componentDidCatchError
只能在入口組件(App)中使用,對(duì)應(yīng)原生小程序的生命周期onError()
,componentDidCatch
可以在任何 React/Nerv 類組件中使用(包括入口組件)。
在 Taro Next,Taro 的專有 Hooks(例如 usePageScroll
, useReachBottom
)從 @tarojs/taro
中引入,框架自己的 Hooks (例如 useEffect
, useState
)從對(duì)應(yīng)的框架引入。
另外,舊版本的 Taro 可以在 Class Component 中使用 Hooks,但 React 是不允許這樣的行為的。
import { usePageScroll, useReachBottom } from '@tarojs/taro' // Taro 專有 Hooks
import { useState, useEffect } from 'react' // 框架 Hooks (基礎(chǔ) Hooks)
// 如果你使用 Nerv 的話
// import { useState, useEffect } from 'nervjs' // 框架 Hooks (基礎(chǔ) Hooks)
由于 Taro Next 沒有自定義組件,所以也沒有了 this.$scope
和 this.$componentType
的概念。getCurrentInstance().page
可以返回當(dāng)前小程序頁(yè)面的實(shí)例。
更多建議: