- Taro是一套遵循React语法规范的多端开发解决方案
- 使用Taro只书写一套代码(Nerv 类React代码),再通过Taro的编译工具,将源代码分别编译出可以在不同端(微信小程序、H5、RN等)运行的代码
- 安装Taro开发工具
@tarojs/cli
- 使用npm或者yarn全局安装,或者直接使用npx
npm install -g @tarojs/cli
yarn global add @tarojs/cli
- 使用命令创建模板项目
taro init myApp
- 开发期启动命令
npm run dev:h5
Webnpm run dev:weapp
微信小程序npm run dev:alipay
支付宝小程序npm run dev:swan
百度小程序npm run dev:rn
ReactNative
| —— dist 编译结果目录 - 不同格式打包后的文件都在此文件夹下
| —— config 编译目录
| | —— dev.js 开发时配置
| | —— index.js 默认配置
| | —— prod.js 打包时配置
| —— src 源码目录
| | —— pages 页面文件目录
| | | —— index index页面目录
| | | | —— index.js index页面逻辑
| | | | —— index.css index页面样式
| | —— app.css 项目总通用样式
| | —— app.js 项目入口文件[配置路由,Taro在编译成小程序时会将路由里的组件视为页面,不是路由中的组件编译成组件]
| —— package.json
- 实例代码
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import './index.less'
export default class Index extends Component {
config = {
navigationBarTitleText: '首页'
}
// 生命周期只能对state变量使用setState进行管理,对其他定义的变量无法管理
state = {
name: 'Hello Jack'
}
componentWillMount () {
// 在第一次渲染之前执行,只执行一次
console.log('第一次渲染之前执行')
}
componentDidMount () {
// 在第一次渲染之后执行,只执行一次
console.log('第一次渲染之后执行')
// 修改state中的值只可以使用setState进行,调用setState后还会再次调用render()渲染数据
this.setState({
// 此更改只会更新name属性值,不会更改其他state中的属性值
name: 'Hello Jack!'
})
}
componentWillUpdate () {
// state数据将要更新
console.log('state数据将要更新')
}
componentDidUpdate () {
// state数据更新过后
console.log('state数据更新过后')
}
/**
* @param {*} nextProps 下一个的props值
*/
componentWillReceiveProps (nextProps) {
// 此事件会在父组件传递给子组件的参数发生改变时触发
}
/**
* @param {*} nextProps 通过父组件传给子组件的对象Props
* @param {*} nextState 下一次的state状态值
*/
shouldComponentUpdate (nextProps, nextState) {
// 检查此次setState是否要进行render调用
return true; // 通过返回的true或false决定此次setState操作是否需要调用render渲染页面
// 此方法通常用于多次setState调用时,判断状态决定是否更新,提升render性能.比如if(nextState.text=='Jack')时才return true
// 如果其中使用this.state.value:此时获取的state是没改前的state;使用nextState则是判断修改后的
}
componentWillUnmount () {
// 卸载时执行, 只执行一次
console.log('卸载时执行')
// 什么时候卸载呢?
// 从A页面跳到B页面被销毁时
// 组件被页面刷新渲染时执行
}
componentDidShow () {
// reactjs是不存在的
// 但是在Taro是存在的
// 页面显示时触发
console.log('页面显示时触发')
}
componentDidHide () {
// 页面隐藏时触发
console.log('页面隐藏时触发')
}
getName() {
return '1111111'
}
render () {
// this表示当前index实例对象
// 渲染render中不论使用的是对象也好,方法也好,都可以执行获取结果返回
return (
<View className='index'>
<Text>{this.state.name}</Text>
</View>
)
}
}
- 调用结果
第一次渲染之前执行
页面显示时触发
第一次渲染之后执行
state数据将要更新
state数据更新过后
// setState设置state后取值不一定是更改后的
this.setState({name:'Jack'})
console.log(this.state.name) // 打印出来的不一定是Jack,可能是之前值,因为是异步的
// 采用setState的第二个参数回调函数的方式可以获取到更改后的state值
this.setState({name:'Jack'}, ()=> {
console.log(this.state.name) // 此时一定是Jack
})
- 状态更新一定是异步的
- React中的状态更新不一定是异步的
- 必须使用setState({})更新state中的数据,直接赋值是无效的
- 实例代码
- 父级jsx模板
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import './index.less'
import Child from './child'; // 注意: 引入的名称必须跟组件的名称保持一致
export default class Index extends Component {
config = {
navigationBarTitleText: '首页'
}
// 生命周期只能对state变量使用setState进行管理,对其他定义的变量无法管理
state = {
name: 'Hello Jack',
obj: undefined
}
componentWillMount () {
// 在第一次渲染之前执行,只执行一次
console.log('第一次渲染之前执行')
}
componentDidMount () {
// 在第一次渲染之后执行,只执行一次
console.log('第一次渲染之后执行')
// 修改state中的值只可以使用setState进行,调用setState后还会再次调用render()渲染数据
this.setState({
// 此更改只会更新name和obj属性值,不会更改其他state中的属性值
name: 'Hello Jack!',
obj: {key: [{name: 'Jack'}]}
})
}
componentWillUpdate () {
// state数据将要更新
console.log('state数据将要更新')
}
componentDidUpdate () {
// state数据更新过后
console.log('state数据更新过后')
}
/**
* @param {*} nextProps 下一个的props值
*/
componentWillReceiveProps (nextProps) {
// 此事件会在父组件传递给子组件的参数发生改变时触发
}
/**
* @param {*} nextProps 通过父组件传给子组件的对象Props
* @param {*} nextState 下一次的state状态值
*/
shouldComponentUpdate (nextProps, nextState) {
// 检查此次setState是否要进行render调用
return true; // 通过返回的true或false决定此次setState操作是否需要调用render渲染页面
// 此方法通常用于多次setState调用时,判断状态决定是否更新,提升render性能.比如if(nextState.text=='Jack')时才return true
// 如果其中使用this.state.value:此时获取的state是没改前的state;使用nextState则是判断修改后的
}
componentWillUnmount () {
// 卸载时执行, 只执行一次
console.log('卸载时执行')
// 什么时候卸载呢?
// 从A页面跳到B页面被销毁时
// 组件被页面刷新渲染时执行
}
componentDidShow () {
// reactjs是不存在的
// 但是在Taro是存在的
// 页面显示时触发
console.log('页面显示时触发')
}
componentDidHide () {
// 页面隐藏时触发
console.log('页面隐藏时触发')
}
getName() {
return '1111111'
}
fun() {
console.log('父组件传递函数到子组件')
}
render () {
let {name, obj} = this.state;
// this表示当前index实例对象
// 渲染render中不论使用的是对象也好,方法也好,都可以执行获取结果返回
// 注意: 小程序在传递函数时必须要加一个onXXX!!否则无法识别传递到子组件的函数
return (
<View className='index'>
<Text>{this.state.name}</Text>
<Child name={name} obj={obj} onFun={this.fun}></Child>
</View>
)
}
}
- 子类jsx模板
import Taro, {Component} from '@tarojs/taro';
import {View, Text} from '@tarojs/components';
export default class Child extends Component {
// 由于父组件在组件渲染完成后重新给state的name属性赋值,且该name属性传递给子组件作为props,则会触发componentWillReceiveProps方法
componentWillReceiveProps(nextProps) {
console.log(`props属性变化了,变化前是:${this.props.name}, 变化成: ${nextProps.name}`)
}
c1() {
// 调用父组件传递来的函数
this.props.onFun();
}
render() {
// 接受父组件传递的props值,传值可以使值类型也可以是引用类型或函数
let {name, obj} = this.props;
// 若调用包含this的函数,需要使用bind并传递参数this,否则在c1中的this指向的就是Click中的事件对象
return (
<View onClick={this.c1.bind(this)}>我是子节点 {name + '------' + obj.key[0].name}</View>
)
}
}
// 设置默认的props值
Child.defaultProps = {
// 如传递过来的props值为undefined时,则表明该props没有赋值,则会使用当前default值
// 但若传递来的props属性值是null,则不会读取defaultProps中的值,会视为已经赋值为null了
obj: {key: [{name: 'Susan'}]}
}
- 注意事项
- 在父组件中调用子组件,需要通过import方式将子组件引入,且注意引入的组件名需要跟子组件类名相同
- 在父组件中通过填写组件的属性值给子组件传递参数,在子组件中通过
this.props
的方式获取传递的属性值 - 如果在父组件传递给子组件 对象 并希望子组件调用时,可以在子组件使用defaultProps的方式设置默认属性值,如果父组件传递的是undefined,则会使用子组件的默认值,若父组件传递的是null,则不会使用子组件的默认值
- 父组件向子组件传递函数时,需要使用on开头的函数名,否则微信会报错
- 在Taro中,路由功能是默认自带的,不需要开发者进行额外的路由配置【在app.js中配置】
- 这里相当于通过小程序的配置配置了小程序和H5的路由问题
- Taro默认根据配置路径生成了Route
- 我们只需要在入口文件的config配置中指定好pages,然后就可以在代码中通过Taro提供的API来跳转到目的页面
- Taro路由API
Taro.navigateTo(OBJECT)
- 小程序中相当于打开一个新的WebView,WebView中放了一个新的页面(追加页面),小程序限制打开WebView的层数最多5层Taro.redirectTo
- 替换新页面Taro.switchTab
Taro.navigateBack
Taro.reLaunch
Taro.getCurrentPage
- 获取当前页面
- app.jsx
import Taro, { Component } from '@tarojs/taro'
import Index from './pages/index'
import './app.less'
class App extends Component {
config = {
pages: [
// 此处用来定义微信中的pages或h5路由
// Taro默认的首页就是pages里面数组的第一项
'pages/index/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
}
}
componentDidMount () {}
componentDidShow () {}
componentDidHide () {}
componentDidCatchError () {}
// 在 App 类中的 render() 函数没有实际作用
// 请勿修改此函数
render () {
return (
<Index />
)
}
}
Taro.render(<App />, document.getElementById('app'))
- test.jsx
import Taro, { Component } from '@tarojs/taro'
import { View, Text, Button } from '@tarojs/components'
export default class Test extends Component {
config = {
navigationBarTitleText: '测试页面'
}
navigateToIndex() {
// 导航到index页面
// 使用redirectTo,上方会出现小房子标志,点击会回到首页
// 使用navigateTo,上方会出现返回标志,点击会返回到上一个页面
Taro.redirectTo({url: '/pages/index/index'})
}
render () {
return (
<View className='index'>
<Button onClick={this.navigateToIndex}>跳转</Button>
</View>
)
}
}
- 我们可以通过在所有跳转url后面添加查询字符串参数进行跳转传参,例如传入参数id=2&type=test
Taro.navigateTo({
url: '/pages/page/path/name?id=2&type=test'
})
- 这样的话,在跳转成功的目标页的生命周期方法里就能通过
this.$route.params
获取到传入的参数,例如上述跳转,在目标页的componentWillMount()
生命周期里获取入参
- 在Taro中可以像使用webpack那样自由地引用静态资源,而且不需要安装任何loader(很棒吧~)
- 引用样式文件
- 使用import引入样式文件
import './test.less'
- 使用className声明使用的样式类
<Image className="img" src={background}/>
- 可以直接通过ES6的import语法来引用样式文件
- 引用js文件
- 可以直接通过ES6的import语法来引用JS文件
import {functionName} from './css/path/name.js'
import defaultExportName from './css/path/name.js'
可以去掉文件路径后面的后缀
- 可以直接通过ES6的import语法来引用JS文件
- 设置utils.js类
export function getData() {
console.log('get data')
}
export function setData() {
console.log('set data')
}
- 导入utils.js类并调用方法
import Taro, { Component } from '@tarojs/taro'
import { View, Text, Button } from '@tarojs/components'
import { setData, getData } from '../../utils'
export default class Test extends Component {
config = {
navigationBarTitleText: '测试页面'
}
navigateToIndex() {
// 导航到index页面
// 使用redirectTo,上方会出现小房子标志,点击会回到首页
// 使用navigateTo,上方会出现返回标志,点击会返回到上一个页面
Taro.redirectTo({url: '/pages/index/index?name=Jack'})
}
// 测试导入方法
clickHandle() {
setData()
}
render () {
return (
<View className='index'>
<Button onClick={this.navigateToIndex}>跳转</Button>
<Button onClick={this.clickHandle}>测试导入方法</Button>
</View>
)
}
}
- 可以直接通过ES6的import语法来引用此类文件,拿到文件引用后直接在JSX中进行使用
// 引用文件
import namedPng from '../../images/path/named.png'
// 使用
<View>
<Image src={namedPng} />
</View>
- 也可以用require来引用,值得注意的是只有本地文件才这样做,如果是线上图片直接赋值即可[使用require原因:直接写路径是无法找到的,需要将图片一起放置到dist下才能通过路径找到],示例如下:
<Image src={require('../../img/background.jpg')}/>
Taro由于需要适配各种客户端,故不能再渲染模板中使用if/else,只能实现处理好数组,之后遍历数组调用map进行显示
- 值得注意的是 小程序在短路表达式渲染时,会出现true或者false的短暂出现,所以如果是要适配小程序,最好采用三元表达式来进行判断
{
true||<Text>通过短路表达式决定无法显示</Text>
}
- 比较通用的一种方式,jsx语法是不支持if操作符的,所以只能用三元表达式或者短路表达式
render () {
let dom = null
dom = true?<Text>三元表达式条件正确</Text>:<Text>三元表达式条件不正确</Text>
return (
<View className='index'>
{dom}
</View>
)
}
- 可以通过遍历数组的形式进行列表渲染,但在渲染的每个组件中需要给一个key,因为React通过diff比较算法,进行最快的计算与比较渲染页面[在页面元素发生变化的时候可以快速比较及渲染变化的元素]
render () {
let {list} = this.state
return (
<View className='index'>
{
list.map((item, index)=> {
return(
<View key={item.id}>{item.name}</View>
)
})
}
</View>
)
}
- 通过
this.props.children
的方式可以在父组件中定义组件内容并传递给子组件,子组件可以获取父组件调用时包裹内部的内容 - 注意:
- 请不要对this.props.children进行任何操作
- this.props.children无法用defaultProps设置默认内容
- 不能把this.props.children分解为变量再使用
- 也可以通过传递属性值的方式传递组件内容,如
<DialogModal content={<View>传递给子组件用于接收</View>}></DialogModal>
// 父组件
import Taro, { Component } from '@tarojs/taro'
import { View, Text, Button } from '@tarojs/components'
import DialogModal from './dialogmodal'
export default class TestDialog extends Component {
config = {
navigationBarTitleText: '测试弹窗页面'
}
render () {
return (
<View className='index'>
<DialogModal>
<Text>我是传入的Text组件</Text>
</DialogModal>
<DialogModal>
<Button>我是传入的Button组件</Button>
</DialogModal>
<DialogModal>
<Image src={require('../../img/background.jpg')}></Image>
</DialogModal>
</View>
)
}
}
// 子组件
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
export default class DialogModal extends Component {
render () {
// 使用this.props.children可以获得父组件在引用当前组件时内部包含的内容
// 注意:
// 1. 请不要对this.props.children进行任何操作
// 2. this.props.children无法用defaultProps设置默认内容
// 3. 不能把this.props.children分解为变量再使用
return (
<View className='index'>
我是弹窗组件
{
this.props.children
}
</View>
)
}
}
- Taro事件采用驼峰命名
- 在Taro中阻止事件冒泡,必须明确的使用stopPropagation
- 向事件处理程序传递参数
- 任何组件的事件传递都要是on开头[为了小程序传递函数时生效],如果传递的事件不是on开头,则会视为字符串
- 只要当JSX组件传入的参数是函数,参数名就必须以on开头
- 以上是为了适配小程序,只做h5不需要关注
- 实例代码
import Taro, { Component } from '@tarojs/taro'
import { View, Text, Button } from '@tarojs/components'
export default class Event extends Component {
config = {
navigationBarTitleText: '测试事件'
}
state = {
name: 'Jack'
}
test(event) {
// this是指向当前page的,若this指向了事件,则需要在调用时给定this.test.bind(this)传递全局的this
console.log(this.state.name)
// 获取点击事件,如果是大元素包含小元素,希望点击小元素的时候不触发大元素的onClick事件,则需要使用stopPropagation的方式阻止事件冒泡
console.log(event);
// 打出来的内容是0-Jack 1-event,则说明当添加函数参数时,参数在前,event事件对象在后
console.log(arguments);
}
render () {
return (
<View className='index'>
<Button onClick={this.test.bind(this, this.state.name)}>测试事件</Button>
<Button onClick={()=> {console.log('Susan')}}>测试直接添加箭头函数</Button>
</View>
)
}
}
- 可以通过
process.env.TARO_ENV
获取当前环境名称,如果是微信则是weapp,如果是h5则是h5,使用环境可以用来渲染不同内容,比如不同的less文件
const currentEnvName = process.env.TARO_ENV;
if (currentEnvName === 'h5') {
require('./h5.less')
} else {
require('./weapp.less')
}
- Taro中书写样式常用类选择器【className】或自定义组件,样式只对当前组件有效。切记不要使用id选择器,标签选择器,属性选择器及层级选择器等,因为Taro会进行打包.
- Taro中推荐使用flex布局,在h5和小程序都可以达到理想的效果