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)。
安装及使用
安装
- node 环境(>=8.0.0);
- Taro开发工具[@tarojs](/user/tarojs)/cli
# 使用 npm 安装 CLI
$ npm install -g [@tarojs](/user/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](/user/tarojs)/async-await
$ npm install --save [@tarojs](/user/tarojs)/async-await
随后在项目入口文件 app.jsx 中直接 import ,就可以开始使用 async functions 功能了
// src/app.jsx
import '[@tarojs](/user/tarojs)/async-await'
使用Redux
在 Taro 中可以自由地使用 React 生态中非常流行的数据流管理工具 Redux 来解决复杂项目的数据管理问题。而为了更方便地使用 Redux ,Taro 提供了与 react-redux API 几乎一致的包 [@tarojs](/user/tarojs)/redux 来让开发人员获得更加良好的开发体验。
首先请安装 redux 、 [@tarojs](/user/tarojs)/redux 和 [@tarojs](/user/tarojs)/redux-h5,以及一些需要用到的 redux 中间件
$ npm install --save redux [@tarojs](/user/tarojs)/redux [@tarojs](/user/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](/user/tarojs)/taro'
import { Provider } from '[@tarojs](/user/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](/user/tarojs)/taro'
import { View, Text } from '[@tarojs](/user/tarojs)/components'
import { connect } from '[@tarojs](/user/tarojs)/redux'
import './index.scss'
import { add, minus } from '../../actions/counter'
[@connect](/user/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