评论

从微信小程序到Taro,不完全指南

从微信小程序到Taro,不完全指南

Taro介绍

简介

Taro 是一套遵循React语法规范的多端统一开发 框架。使用Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信/百度/支付宝/字节跳动/QQ小程序、快应用、H5、React-Native 等)运行的代码。

特性

React 语法风格

Taro 遵循 React 语法规范,它采用与 React 一致的组件化思想,组件生命周期与 React 保持一致,同时支持使用 JSX 语法,让代码具有更丰富的表现力,使用 Taro 进行开发可以获得和 React 一致的开发体验。

快速开发微信小程序

Taro 立足于微信小程序开发,众所周知小程序的开发体验并不是非常友好,比如小程序中无法使用 npm 来进行第三方库的管理,无法使用一些比较新的 ES 规范等等,针对小程序端的开发弊端,Taro 具有以下的优秀特性

  • 支持使用 npm/yarn 安装管理第三方依赖
  • 支持使用 ES7/ES8 甚至更新的 ES 规范,一切都可自行配置
  • 支持使用 CSS 预编译器,例如 Sass 等
  • 支持使用 Redux 进行状态管理
  • 小程序 API 优化,异步 API Promise 化

支持多端开发转化

Taro 方案的初心就是为了打造一个多端开发的解决方案。目前 Taro 代码可以支持转换到 微信/百度/支付宝/字节跳动/QQ小程序 、快应用、 H5 端 以及 移动端(React Native)。

安装及使用

安装

  1. node 环境(>=8.0.0);
  2. Taro开发工具@tarojs/cli
# 使用 npm 安装 CLI
$ npm install -g @tarojs/cli

使用

使用命令创建模板项目

$ taro init myApp

项目结构

├── config                 配置目录
|   ├── dev.js             开发时配置
|   ├── index.js           默认配置
|   └── prod.js            打包时配置
├── src                    源码目录
|   ├── components         公共组件目录
|   ├── pages              页面文件目录
|   |   ├── index          index 页面目录
|   |   |   ├── banner     页面 index 私有组件
|   |   |   ├── index.js   index 页面逻辑
|   |   |   └── index.css  index 页面样式
|   ├── utils              公共方法库
|   ├── app.css            项目总通用样式
|   └── app.js             项目入口文件
└── package.json

各端dev环境启动

$ npm run dev:h5      web
$ npm run dev:weapp   微信小程序
$ npm run dev:swan    百度小程序
$ npm run dev:alipay  支付宝小程序
$ npm run dev:tt      字节跳动小程序
$ npm run dev:qq      QQ小程序
$ npm run dev:rn

生命周期

React框架中的生命周期函数被保留,新增支持小程序的几个生命周期

componentWillMount()

页面加载时触发,一个页面只会调用一次,此时页面 DOM 尚未准备好,还不能和视图层进行交互

componentDidMount()

页面初次渲染完成时触发,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互

shouldComponentUpdate(nextProps, nextState)

页面是否需要更新,返回 false 不继续更新,否则继续走更新流程

componentWillUpdate(nextProps, nextState)

页面即将更新

componentDidUpdate(prevProps, prevState)

页面更新完毕

componentWillUnmount()

页面卸载时触发,如 redirectTo 或 navigateBack 到其他页面时

componentDidShow()

页面显示/切入前台时触发

componentDidHide()

页面隐藏/切入后台时触发, 如 navigateTo 或底部 tab 切换到其他页面,小程序切入后台等

页面事件处理函数

增加在小程序中的专属事件处理函数,如下:

  • onPullDownRefresh(): 监听用户下拉刷新事件
  • onReachBottom():监听用户上拉触底事件
  • onPageScroll(Object):监听用户滑动页面事件
  • onShareAppMessage(Object): 监听用户点击页面内转发按钮(Button 组件 openType='share')或右上角菜单“转发”按钮的行为,并自定义转发内容。
  • onTabItemTap(Object):点击 tab 时触发

设计稿及尺寸单位

在 Taro 中尺寸单位建议使用 px、 百分比 %,Taro 默认会对所有单位进行转换。在 Taro 中书写尺寸按照 1:1 的关系来进行书写,即从设计稿上量的长度 100px,那么尺寸书写就是 100px,当转成微信小程序的时候,尺寸将默认转换为 100rpx,当转成 H5 时将默认转换为以 rem 为单位的值。

如果你希望部分 px 单位不被转换成 rpx 或者 rem ,最简单的做法就是在 px 单位中增加一个大写字母,例如 Px 或者 PX 这样,则会被转换插件忽略。

Taro 默认以 750px作为换算尺寸标准,如果设计稿不是以 750px 为标准,则需要在项目配置 config/index.js 中进行设置,例如设计稿尺寸是 640px,则需要修改项目配置 config/index.js 中的 designWidth 配置为 640:

const config = {
  projectName: 'myProject',
  date: '2018-4-18',
  designWidth: 640,
  ....
}

目前 Taro 支持 750、 640 、 828 三种尺寸设计稿,他们的换算规则如下:

const DEVICE_RATIO = {
  '640': 2.34 / 2,
  '750': 1,
  '828': 1.81 / 2
}

路由功能

在 Taro 中,路由功能是默认自带的,不需要开发者进行额外的路由配置。

我们只需要在入口文件的 config 配置中指定好 pages,然后就可以在代码中通过 Taro 提供的 API 来跳转到目的页面,例如:

// 跳转到目的页面,打开新页面
Taro.navigateTo({
  url: '/pages/page/path/name'
})

路由传参

我们可以通过在所有跳转的 url 后面添加查询字符串参数进行跳转传参,例如

// 传入参数 id=2&type=test
Taro.navigateTo({
  url: '/pages/page/path/name?id=2&type=test'
})

这样的话,在跳转成功的目标页的生命周期方法里就能通过 this.$router.params 获取到传入的参数,例如上述跳转,在目标页的 componentWillMount 生命周期里获取入参

class C extends Taro.Component {
  componentWillMount () {
    console.log(this.$router.params) // 输出 { id: 2, type: 'test' }// 输出 { id: 2, type: 'test' }
  }
}

state状态管理&Props

state状态更新一定是异步的,同步更新需要传入callBack

这是 Taro 和 React 另一个不同的地方:React 的 setState 不一定总是异步的,而对于 Taro 而言,setState 之后,你提供的对象会被加入一个数组,然后在执行下一个 eventloop 的时候合并它们。 例如:

// 假设我们之前设置了 this.state.counter = 0
updateCounter () {
  this.setState({
    counter: 1
  })
  console.log(this.state.counter) // 这里 counter 还是 0
}

正确的做法是这样,在 setState 的第二个参数传入一个 callback:

// 假设我们之前设置了 this.state.counter = 0
updateCounter () {
  this.setState({
    counter: 1
  }, () => {
    // 在这个函数内你可以拿到 setState 之后的值
  })
}Ï

任何组件的事件传递都要以 on 开头

但在 Taro 中,只要当 JSX 组件传入的参数(props)是函数,参数名就必须以 on 开头:

const element = <View onClick={this.onTag} />
const element2 = <Input onFocus={this.onFocus} />
const element3 = <CustomElement onAnimationEnd={this.props.onAnimationEnd} />

环境变量 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 在编译时提供了一些内置的环境变量来帮助用户做一些特殊处理。用于判断当前编译类型,可以通过这个变量来书写对应一些不同环境下的代码,在编译时会将不属于当前编译类型的代码去掉,只保留当前编译类型下的代码。

Taro.ENV_TYPE

ENV_TYPE.WEAPP 微信小程序环境

ENV_TYPE.SWAN 百度小程序环境

ENV_TYPE.ALIPAY 支付宝小程序环境

ENV_TYPE.TT 字节跳动小程序环境

ENV_TYPE.WEB WEB(H5)环境

ENV_TYPE.RN ReactNative 环境

关于 JSX 支持程度补充说明

由于 JSX 中的写法千变万化,我们不能支持到所有的 JSX 写法,同时由于微信小程序端的限制,也有部分 JSX 的优秀用法暂时不能得到很好地支持,特在此补充说明一下对于 JSX 的支持程度:

  • 不能使用 Array#map 之外的方法操作 JSX 数组

Taro 在小程序端实际上把 JSX 转换成了字符串模板,而一个原生 JSX 表达式实际上是一个 React/Nerv 元素(react-element)的构造器,因此在原生 JSX 中你可以随意地一组 React 元素进行操作。但在 Taro 中你只能使用 map 方法,Taro 转换成小程序中 wx:for。

规则详情

以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:

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)

以下代码不会被警告,也应当在 Taro 任意端中能够运行:

numbers.filter(Boolean).map((number) => {
  const element = <View />
  return <View />
})
  • 暂不支持在 render() 之外的方法定义 JSX

由于微信小程序的 template 不能动态传值和传入函数,Taro 暂时也没办法支持在类方法中定义 JSX。

规则详情

以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:

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 参数中使用对象展开符

微信小程序组件要求每一个传入组件的参数都必须预先设定好,而对象展开符则是动态传入不固定数量的参数。所以 Taro 没有办法支持该功能。

规则详情

以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:

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

<View {...props} />

<Custom {...props} />

以下代码不会被警告,也应当在 Taro 任意端中能够运行:

const { id, ...rest } = obj

const [ head, ...tail]  = array

const obj = { id, ...rest }

异步编程

Taro 支持使用 async functions 来让开发者获得不错的异步编程体验,开启 async functions 支持需要安装包 @tarojs/async-await

$ npm install --save @tarojs/async-await

随后在项目入口文件 app.jsx 中直接 import ,就可以开始使用 async functions 功能了

// src/app.jsx
import '@tarojs/async-await'

使用Redux

在 Taro 中可以自由地使用 React 生态中非常流行的数据流管理工具 Redux 来解决复杂项目的数据管理问题。而为了更方便地使用 Redux ,Taro 提供了与 react-redux API 几乎一致的包 @tarojs/redux 来让开发人员获得更加良好的开发体验。

首先请安装 redux 、 @tarojs/redux 和 @tarojs/redux-h5,以及一些需要用到的 redux 中间件

$ npm install --save redux @tarojs/redux @tarojs/redux-h5 redux-thunk redux-logger

随后可以在项目 src 目录下新增一个 store 目录,在目录下增加 index.js 文件用来配置 store,按自己喜好设置 redux 的中间件,例如下面例子中使用 redux-thunk 和 redux-logger 这两个中间件

// src/store/index.js
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import rootReducer from '../reducers'

const middlewares = [
  thunkMiddleware,
  createLogger()
]

export default function configStore () {
  const store = createStore(rootReducer, applyMiddleware(...middlewares))
  return store
}

接下来在项目入口文件 app.js 中使用 @tarojs/redux 中提供的 Provider 组件将前面写好的 store 接入应用中

// src/app.js
import Taro, { Component } from '@tarojs/taro'
import { Provider } from '@tarojs/redux'

import configStore from './store'
import Index from './pages/index'

import './app.scss'

const store = configStore()

class App extends Component {
  config = {
    pages: [
      'pages/index/index'
    ],
    window: {
      navigationBarTitleText: 'Test'
    }
  }

  render() {
    return (
      <Provider store={store}>
        <Index />
      </Provider>
    )
  }
}

Taro.render(<App />, document.getElementById('app'))

然后就可以开始使用了。如 redux 推荐的那样,可以增加

  • constants 目录,用来放置所有的 action type 常量
  • actions 目录,用来放置所有的 actions
  • reducers 目录,用来放置所有的 reducers

例如我们要开发一个简单的加、减计数器功能

新增 action type

// src/constants/counter.js
export const ADD = 'ADD'
export const MINUS = 'MINUS'

新增 reducer 处理

// src/reducers/counter.js
import { ADD, MINUS } from '../constants/counter'

const INITIAL_STATE = {
  num: 0
}

export default function counter (state = INITIAL_STATE, action) {
  switch (action.type) {
    case ADD:
      return {
        ...state,
        num: state.num + 1
      }
    case MINUS:
      return {
        ...state,
        num: state.num - 1
      }
    default:
      return state
  }
}


// src/reducers/index.js
import { combineReducers } from 'redux'
import counter from './counter'

export default combineReducers({
  counter
})

新增 action 处理

// src/actions/counter.js
import {
  ADD,
  MINUS
} from '../constants/counter'

export const add = () => {
  return {
    type: ADD
  }
}
export const minus = () => {
  return {
    type: MINUS
  }
}

最后,我们可以在页面(或者组件)中进行使用,我们将通过 tarojs/redux 提供的 connect 方法将 redux 与我们的页面进行连接

// src/pages/index/index.js
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { connect } from '@tarojs/redux'
import './index.scss'

import { add, minus } from '../../actions/counter'

@connect(({ counter }) => ({
  counter
}), (dispatch) => ({
  add () {
    dispatch(add())
  },
  dec () {
    dispatch(minus())
  }
}))
class Index extends Component {
  config = {
    navigationBarTitleText: '首页'
  }

  render () {
    return (
      <View className='todo'>
        <Button className='add_btn' onClick={this.props.add}>+</Button>
        <Button className='dec_btn' onClick={this.props.dec}>-</Button>
        <View>{this.props.counter.num}</View>
      </View>
    )
  }
}

export default Index

connect 方法接受两个参数 mapStateToProps 与 mapDispatchToProps

  • mapStateToProps,函数类型,接受最新的 state 作为参数,用于将 state 映射到组件的 props
  • mapDispatchToProps,函数类型,接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法

Taro 代码与小程序代码混写

Taro 项目 支持 Taro 的代码与小程序(微信/百度/支付宝/字节跳动)原生的页面、组件代码混合存在,只需要将原生的页面、组件代码放入 src 目录下,随后在 入口文件 app.js 中定义好 pages 配置指向对应的原生的页面即可,在原生页面的配置中,你可以通过 usingComponents 来定义需要引入的组件,这里可以指定 Taro 组件同时也可以指定小程序原生的组件。

usingComponents 指定的小程序原生组件名字需要以小写开头。

组件库说明

http://taro-docs.jd.com/taro/docs/components-desc.html

API 说明

http://taro-docs.jd.com/taro/docs/apis/about/desc.html


最后一次编辑于  08-28  
点赞 4
收藏
评论

4 个评论

  • 张恬
    张恬
    08-31

    期待大神下一篇佳作~

    08-31
    赞同
    回复
  • 谋谋谋
    谋谋谋
    08-29

    珍爱生命 远离Taro


    08-29
    赞同
    回复
  • Stephen
    Stephen
    08-28

    就这?

    08-28
    赞同
    回复
  • 天空为谁蓝
    天空为谁蓝
    08-28
    哇哦,好棒哦
    08-28
    赞同
    回复
登录 后发表内容