这是一篇针对旧版本用户升级到 Taro Next 的迁移指南。因为本章内容包含了许多详尽的阐述和迁移例子,所以看起来有一些长。但请不要担心,Taro Next 大部分用法还是和旧版本一样的。本章没有提到的内容,你可以像旧版本的 Taro 一样操作或使用

事实上,你并不需要去更改任何业务的逻辑代码,许多更改使用编辑器的「查找/替换」就可以完成。你甚至不需要完整地阅读整章内容(重点在 1,2 小节, API项目/页面配置),只有当出问题时定位到具体的小节即可。

更新到 Taro Next 首先需要更新项目依赖:

  1. # 更新 CLI
  2. $ npm i -g @tarojs/cli@next
  3. # 在项目目录更新项目依赖
  4. $ npm i @tarojs/runtime@next @tarojs/mini-runner@next @tarojs/components@next @tarojs/taro@next
  5. $ npm i react @tarojs/react@next # 如果使用 React
  6. $ npm i nervjs # 如果使用 Nerv
  7. # CLI 命令和以前一模一样
  8. $ taro build --type weapp --watch

API

在旧版本 Taro 中,我们把所有面向应用开发者的 API 都放在 @tarojs/taro 里,一个典型的 Taro 组件/页面会像这样:

  1. // 类组件
  2. import Taro, { Component } from '@tarojs/taro'
  3. class Wallace extends Component {
  4. componentDidMount () {
  5. Taro.request().then(/* do something */)
  6. }
  7. render () {
  8. return ...
  9. }
  10. }
  11. // 函数式组件
  12. import Taro, { useEffect } from '@tarojs/taro'
  13. function Tall () {
  14. useEffect(() => {
  15. Taro.request().then(/* do something */)
  16. }, [])
  17. return ...
  18. }

在 Taro Next 中,属于框架本身的 API 从框架自己的包中引入,其它的 API 仍然从 @tarojs/taro 引入。使用哪个框架来进行开发完全由开发者来决定。

  1. import Taro from '@tarojs/taro'
  2. import React, { Component } from 'react' // Component 是来自于 React 的 API
  3. // 从 nervjs 中引入,那运行的就是 Nerv
  4. // import { Component } from 'nervjs'
  5. class Reporter extends Component {
  6. componentDidMount () {
  7. Taro.request().then(/* do something */)
  8. }
  9. render () {
  10. return ...
  11. }
  12. }
  13. // 函数式组件
  14. import Taro from '@tarojs/taro'
  15. // useEffect 是来自于 React 的 API
  16. import React, { useEffect } from 'react'
  17. // 从 nervjs 中引入,那运行的就是 Nerv
  18. // import { useEffect } from 'nervjs'
  19. function Fast () {
  20. useEffect(() => {
  21. Taro.request().then(/* do something */)
  22. }, [])
  23. return ...
  24. }

Nerv 是凹凸实验室的一个开源类 React 框架,体积比 React 更小,多数情况性能表现也比 React 更好。但某些 React 生态的库兼容性可能会出现问题。

项目/页面配置

在旧版本 Taro 中,页面/项目的配置挂载在类组件的类属性或函数式的属性上,通过 AST 分析取出来,然后生成 JSON 文件。但这样做,项目页面的配置就无法动态地生成:

  1. // app.js 项目配置
  2. class App extends Component {
  3. config = {
  4. pages: [
  5. 'pages/index/index'
  6. ],
  7. window: {
  8. backgroundTextStyle: 'light',
  9. navigationBarBackgroundColor: '#fff',
  10. navigationBarTitleText: 'WeChat',
  11. navigationBarTextStyle: 'black'
  12. }
  13. }
  14. render () {
  15. return ...
  16. }
  17. }
  18. // index.js 页面配置
  19. function Index () {
  20. return ...
  21. }
  22. Index.config = {
  23. navigationBarTitleText: '首页'
  24. }

在 Taro Next 中,会有一个新的文件:*.config.js* 代表你页面/项目文件的文件名,config 文件必须和页面/项目文件在同一文件夹。在这个文件里你可以使用任意合法的 JavaScript 语法,只要最终把配置作为对象通过 export default 出去即可:

  1. // app.js 项目文件
  2. class App extends Component {
  3. render () {
  4. return ...
  5. }
  6. }
  7. // app.config.js
  8. export default {
  9. pages: [
  10. 'pages/index/index'
  11. ],
  12. window: {
  13. backgroundTextStyle: 'light',
  14. navigationBarBackgroundColor: '#fff',
  15. navigationBarTitleText: 'WeChat',
  16. navigationBarTextStyle: 'black'
  17. }
  18. }
  19. // index.js 页面文件
  20. function Index () {
  21. return ...
  22. }
  23. // index.config.js 页面配置
  24. const title = '首页'
  25. export default {
  26. navigationBarTitleText: title
  27. }

一个完整的项目文件结构示例会像这样:

  1. .
  2. ├── app.config.js // 入口文件项目配置
  3. ├── app.js
  4. ├── app.scss
  5. ├── components
  6. └── result.js // 组件若不使用第三方组件则无需配置
  7. └── pages
  8. └── index
  9. ├── index.config.js // index 的页面配置
  10. └── index.js

使用第三方 React 库

如果你需要引入 React 相关生态的库,直接通过 npm install 安装然后引入使用即可,Taro 不会再维护类似于 taro-reduxtaro-mobx 之类的库。

  1. // 当使用了 JSX 时,babel 会隐式地调用 React.createElement
  2. // 因此只要你使用了 JSX,就要把 React 或 Nerv 引入
  3. import React from 'react'
  4. import { useSelector } from 'react-redux'
  5. // 如果是使用的是 Nerv
  6. // import { useSelector } from 'nerv-redux'
  7. function Excited () {
  8. const counter = useSelector(state => state.counter)
  9. return ...
  10. }

路由

在旧版本中可以通过 this.$router 访问当前组件/页面路由的详情。在 Taro Next 对应的 API 是在 @tarojs/taro 中的 getCurrentInstance().router,两者的属性一模一样。

  1. import { getCurrentInstance } from '@tarojs/taro'
  2. class C extends Component {
  3. current = getCurrentInstance()
  4. componentWillMount () {
  5. // getCurrentInstance().router 和 this.$router 和属性一样
  6. console.log(this.current.router)
  7. }
  8. }
  9. // 函数式组件
  10. import { getCurrentInstance } from '@tarojs/taro'
  11. function C () {
  12. const { router } = getCurrentInstance()
  13. // getCurrentInstance().router 和 useRouter 返回的内容也一样
  14. // const router = useRouter()
  15. }

而对于项目入口组件而言,路由信息我们推荐在 componentDidShow 生命周期的参数中直接读取。

  1. // app.js 项目入口文件
  2. class App extends Component {
  3. componentDidShow (options /* 这里有你想要的路由信息 */) {
  4. }
  5. render () {
  6. ...
  7. }
  8. }

聪明的读者已经猜到了,getCurrentInstance().router 其实是访问小程序当前页面 onLoad 生命周期参数的快捷方式。

样式

在 Taro Next 中,没有 组件的外部样式和全局样式 的概念,组件的配置(config.js)是无效的,页面和入口文件引入的 CSS 都会变成全局 CSS ,没有了 externalClassesaddGlobalClass 这两个概念。

如果你需要带作用域的 CSS,可以考虑使用 CSS Modules

编译配置

jsxAttributeNameReplace 配置已被移除。因为我们不需要配置 externalClasses,这个属性也失去了它存在的意义。

编译依赖库

Webpack 升级到 Webpack@4Babel 升级到 babel@7。Webpack 升级是在 taro@2 中完成的,如果你是从 taro@1 升级上来的话,或许需要去看看 Taro 2 更改 查看使用 Webpack 编译后带来的变化。

升级到 babel@7 意味着你的项目文件全部都会通过根目录的 babel.config.js 的配置进行编译。

ESLint 和最佳实践

eslint-plugin-taro 已被废弃,你不再需要遵循它所规定的种种限制。你可以发挥你的创造力使用任何合法的 JSX 语法:

  1. import React from 'react'
  2. import { View, Text } from '@tarojs/components'
  3. function C () {
  4. // 你可以选择不使用 JSX,但元素还是必须从 `@tarojs/components` 引入
  5. const title = React.createElement(View, null, 'Numbers:')
  6. const numbers = []
  7. for (let i = 0; i < 10; i++) {
  8. numbers.push(<Text key={i}>{i}</Text>)
  9. }
  10. return <>
  11. {title}
  12. {numbers}
  13. </>
  14. }

旧版本文档所提到的最佳实践也不必再遵循。也就是说,即便你不给组件设置 defaultProps,自定义事件名不以 on 开头(还有其它的旧版本代码风格最佳实践),你的代码也能运行。但值得注意的是,遵循这样的 代码风格最佳实践 可以让你的代码更健壮,你的应用也会因此而收益。而对于另外的一些由于旧版本 Taro 执行机制的 hack(例如 render 调用两次,state 和 props 无法重名,不要打印组件),这类最佳实践可以不必理会。

Ref & DOM

Taro Next 在底层会维护一个精简的 DOM 系统,在框架中使用 ref 链接到的是一个 Taro Element 实例,因此你直接可以使用 HTMLElement 的部分方法直接操作它。如果你需要获取原生小程序 DOM 实例,那需要使用原生小程序的 SelectorQuery 来获取。

大部分和渲染相关的 DOM 属性你都可以通过像 Web 开发一样获取或设置(如果有必要的话你甚至可以通过 parentNodechildNodes 访问元素的父元素和子元素!),但元素的位置你还是必须通过原生小程序 DOM 实例的 boundingClientRect()scrollOffset() 方法获取。

另外,如果你使用的是 React,就将无法使用字符串的形式来使用 ref。(Nerv 不受此影响)

  1. class C extends Components {
  2. input = React.createRef()
  3. componentDidMount () {
  4. const node = this.input.current // node 是一个 Taro Element 实例
  5. node.focus() // ok, 在 Web 开发中常见做法
  6. // 以下写法也能更新视图,但不推荐这么做,更推荐使用数据来驱动视图更新
  7. node.setProerty('class', 'input-css-class')
  8. node.className = 'input-css-class'
  9. node.style.fontSize = '16px'
  10. node.value = 'excited!'
  11. // 如果你需要获取原生小程序 DOM 的话
  12. const miniNode = Taro.createSelectorQuery().select('#' + node.id)
  13. }
  14. render () {
  15. return <Input ref={this.input} />
  16. }
  17. }

在未来,我们可能会在 Taro Element 上提供一个可以快速访问小程序 DOM 实例的属性。目前请按照上述例子使用。

生命周期

当你使用 React 时(使用 Nerv 不受此影响),以下生命周期被更名:

  • componentWillMount() -> UNSAFE_componentWillMount()
  • componentWillReceiveProps -> UNSAFE_componentWillReceiveProps()
  • componentWillUpdate -> UNSAFE_componentWillUpdate()

新增一个生命周期: componentDidCatch(err, info) ,这是由框架本身(React 或 Nerv)提供的。componentDidCatch(err, info) 会在组件和它的子孙抛出错误时触发,第一个参数 err 指向抛出的错误,第二个参数 info 是组件的调用信息。

componentDidCatch 和原有的 componentDidCatchError 共同存在,区别在于 componentDidCatchError 只能在入口组件(App)中使用,对应原生小程序的生命周期 onError()componentDidCatch 可以在任何 React/Nerv 类组件中使用(包括入口组件)。

Hooks

在 Taro Next,Taro 的专有 Hooks(例如 usePageScroll, useReachBottom)从 @tarojs/taro 中引入,框架自己的 Hooks (例如 useEffect, useState)从对应的框架引入。

另外,旧版本的 Taro 可以在 Class Component 中使用 Hooks,但 React 是不允许这样的行为的。

  1. import { usePageScroll, useReachBottom } from '@tarojs/taro' // Taro 专有 Hooks
  2. import { useState, useEffect } from 'react' // 框架 Hooks (基础 Hooks)
  3. // 如果你使用 Nerv 的话
  4. // import { useState, useEffect } from 'nervjs' // 框架 Hooks (基础 Hooks)

$scope 和 $componentType

由于 Taro Next 没有自定义组件,所以也没有了 this.$scopethis.$componentType 的概念。getCurrentInstance().page 可以返回当前小程序页面的实例。