Taro 多端小程序学习笔记
快速搭建项目
项目初始化以及配置推荐看Taro官网更详细
安装
| # 使用 npm 安装 CLI npm install -g @tarojs/cli
# OR 使用 yarn 安装 CLI yarn global add @tarojs/cli
# OR 安装了 cnpm,使用 cnpm 安装 CLI cnpm install -g @tarojs/cli
|
查看 Taro 全部版本信息
项目初始化
编译运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| npm run dev:h5 npm run build:h5
npm run dev:weapp npm run build:weapp
npm run dev:swan npm run build:swan
npm run dev:alipay npm run build:alipay
npm run dev:tt npm run build:tt
npm run dev:qq npm run build:qq
npm run dev:jd npm run build:jd
......
|
项目结构
项目目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ├── dist 编译结果目录 | ├── config 项目编译配置目录 | ├── index.js 默认配置 | ├── dev.js 开发环境配置 | └── prod.js 生产环境配置 | ├── src 源码目录 | ├── pages 页面文件目录 | | └── index index 页面目录 | | ├── index.js index 页面逻辑 | | ├── index.css index 页面样式 | | └── index.config.js index 页面配置 | | | ├── app.js 项目入口文件 | ├── app.css 项目总通用样式 | └── app.config.js 项目入口配置 | ├── project.config.json 微信小程序项目配置 project.config.json ├── project.tt.json 字节跳动小程序项目配置 project.config.json ├── project.swan.json 百度小程序项目配置 project.swan.json ├── project.qq.json QQ 小程序项目配置 project.config.json | ├── babel.config.js Babel 配置 ├── tsconfig.json TypeScript 配置 ├── .eslintrc ESLint 配置 | └── package.json
|
编译配置
| └── config 项目编译配置目录 ├── index.js 默认配置 ├── dev.js 开发环境配置 └── prod.js 生产环境配置
|
源码组织
app
| └── src 源码目录 ├── app.js 项目入口文件 ├── app.css 项目总通用样式 └── app.config.js 项目入口配置
|
小程序的主体由下面三个文件组成:
文件 |
必须 |
作用 |
app.js |
是 |
小程序入口逻辑 |
app.css |
否 |
小程序全局样式 |
app.config.js |
是 |
小程序全局配置 |
page
| └── src 源码目录 └── pages 页面文件目录 └── index index 页面目录 ├── index.js index 页面逻辑 ├── index.css index 页面样式 └── index.config.js index 页面配置
|
一个小程序页面由三个文件组成,如下:
文件 |
必须 |
作用 |
page.js |
是 |
页面入口逻辑 |
page.css |
否 |
页面样式 |
page.config.js |
否 |
页面配置 |
项目配置
| └──project.config.json 微信小程序项目配置 project.config.json
|
Babel 配置
| └── babel.config.js Babel 配置
|
ESLint 配置
组件
组件的使用
taro 的组件使用支持 react 的组件方式,如果使用 taro 创建组件会报错。
child.jsx 子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { Component } from "@tarojs/taro"; import { View, Text } from "@tarojs/components"; class Child extends Component { render() { return <View>我是Child组件</View>; } } export default Child;
import Taro, { Component } from "@tarojs/taro"; import { View, Text } from "@tarojs/components"; class Child extends Taro.Component { render() { return <View>我是Child组件</View>; } } export default Child;
|
Error: TypeError: Super expression must either be null or a function
所以推荐使用 react 创建组件的方式
child.jsx 子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from "react"; import { View, Text } from "@tarojs/components"; class Child extends React.Component { render() { return <View>我是Child组件</View>; } } export default Child;
import { Component } from "react"; import { View, Text } from "@tarojs/components"; class Child extends Component { render() { return <View>我是Child组件</View>; } } export default Child;
|
父子组件传值
父子组件通过 props 传递数据,子组件不能修改,只能接收父组件的数据
父组件在组件内部设置一个属性传递给子组件
src\pages\index\index.jsx 父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less' import Child from './child'
export default class Index extends Component { state = { text: { username: '李四' }, } render() { console.log('render...') return ( <View className='index'> {/* <Child></Child> */} <Child text={this.state.text}></Child> </View> ) } }
|
src\pages\index\child.jsx 子组件
| import { Component } from 'react' import { View, Text } from '@tarojs/components'
class Child extends Component { render() { const { text } = this.props return <View>我是Child组件{text.username}</View> } } export default Child
|
设置默认属性
如果父组件没有传递任何属性,可以在子组件通过Child.defaultProps={}
设置默认属性
src\pages\index\index.jsx 父组件
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less' import Child from './child'
export default class Index extends Component { render() { return ( <View className='index'> <Child></Child> </View> ) } }
|
src\pages\index\child.jsx 子组件
| import { Component } from 'react' import { View, Text } from '@tarojs/components'
class Child extends Component { render() { const { text } = this.props return <View>我是Child组件{text.username}</View> } }
Child.defaultProps = { text: { username: '111111' }, } export default Child
|
常用生命周期
componentWillMount( )
componentWillMount( ) 组件挂载之前
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { componentWillMount() { console.log('组件挂载之前,componentWillMount') } render() { return ( <View className='index'> <Text>Hello world!</Text> </View> ) } }
|
componentDidMount( )
componentDidMount( ) 组件挂载之后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { state = { text: "张三", age: 10, }; componentDidMount () { componentDidMount() { console.log("组件挂载之后,componentDidMount"); this.setState( { text: "李四", }, () => { console.log(2, this.state.text); } ); console.log(1, this.state.text); } } render () { return ( <View className='index'> <Text>{this.state.text}</Text> <Text>{this.state.age}</Text> </View> ) } }
|
注意更改 state 只能用 setState,但是 setState 是异步更新。在 setState 之后获取的值是更改之前的值,想要得到最新的值只能在 setState 的第二个参数中的回调函数中拿到最新值
setState 更新最新数据
| this.setState( { text: '李四', }, () => { console.log(2, this.state.text) } ) console.log(1, this.state.text)
|
componentWillUnmount( )
componentWillUnmount( ) 组件销毁时
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { componentWillUnmount() { console.log('组件销毁时,componentWillUnmount') } render() { return ( <View className='index'> <Text>Hello world!</Text> </View> ) } }
|
componentDidShow( )
componentDidShow( ) 页面显示的时候,在 react 中不存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { componentDidShow() { console.log('页面显示的时候,componentDidShow') } render() { return ( <View className='index'> <Text>Hello world!</Text> </View> ) } }
|
componentWillHide( )
componentWillHide( ) 页面隐藏的时候,在 react 中不存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { componentWillHide() { console.log('页面隐藏的时候,componentDidHide') } render() { return ( <View className='index'> <Text>Hello world!</Text> </View> ) } }
|
componentWillUpdate( )
componentWillUpdate( ) state 数据将要更新之前
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { componentWillUpdate() { console.log('state数据将要更新之前,componentWillUpdate') } render() { return ( <View className='index'> <Text>Hello world!</Text> </View> ) } }
|
componentDidUpdate( )
componentDidUpdate( ) state 数据将要更新之后
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { componentDidUpdate() { console.log('state数据更新之后,componentDidUpdate') } render() { return ( <View className='index'> <Text>Hello world!</Text> </View> ) } }
|
shouldComponentUpdate(nextProps, nextState)
shouldComponentUpdate(nextProps, nextState) 检查此次 setState 是否进行 render 调用 默认返回 true,一般用来多次的 setState 调用时,提升 render 的性能。只需要判断最后一次是否修改 然后返回 true,再进行 render 渲染
两个参数 nextState 最新的状态,nextProps 最新的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less'
export default class Index extends Component { state = { text: "张三", age: 10, }; componentDidMount () { componentDidMount() { console.log("组件挂载之后,componentDidMount"); this.setState( { text: "李四", }, () => { console.log(2, this.state.text); } ); console.log(1, this.state.text); } } shouldComponentUpdate(nextProps, nextState) { if (nextState.text === "李四") return true; else return false; } render () { return ( <View className='index'> <Text>{this.state.text}</Text> <Text>{this.state.age}</Text> </View> ) } }
|
componentWillReceiveProps(nextProps, nextState)
componentWillReceiveProps(nextProps, nextState) 父组件传递给子组件的属性发送变化的时候,此钩子函数才会执行,初始化的时候不会执行。nextProps 才是最新的属性
src\pages\index\index.jsx 父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less' import Child from './child' export default class Index extends Component { state = { text: { username: '李四' }, } componentDidMount() { this.setState({ text: { username: '王五' }, }) } render() { return ( <View className='index'> <Child text={this.state.text}></Child> </View> ) } }
|
src\pages\index\child.jsx 子组件
| import { Component } from 'react' import { View, Text } from '@tarojs/components' class Child extends Component { componentWillReceiveProps(nextProps, nextState) { console.log(nextProps.text.username) } render() { const { text } = this.props return <View>我是Child组件{text.username}</View> } } export default Child
|
路由
基本路由配置
src\pages\test\test.jsx 新建一个文件以及配置文件 test.config.js
| import { Component } from 'react' import { View, Text } from '@tarojs/components'
export default class Index extends Component { render() { console.log('render...') return <View className='index'>test!!</View> } }
|
src\app.config.js 配置默认路径
| export default { pages: ['pages/test/test', 'pages/index/index'], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'WeChat', navigationBarTextStyle: 'black', }, }
|
将"pages/test/test"
写在数组前面就是默认路径,然后重启项目
基本页面跳转
navigateTo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Component } from 'react' import { View, Text, Button } from '@tarojs/components' import Taro from '@tarojs/taro' export default class Index extends Component { handleClick() { Taro.navigateTo({ url: '/pages/index/index', }) } render() { return ( <View className='index'> <Button onClick={this.handleClick}>test!!</Button> </View> ) } }
|
redirectTo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { Component } from 'react' import { View, Text, Button } from '@tarojs/components' import Taro from '@tarojs/taro' export default class Index extends Component { handleClick() { Taro.redirectTo({ url: '/pages/index/index', }) } render() { return ( <View className='index'> <Button onClick={this.handleClick}>test!!</Button> </View> ) } }
|
路由传参
src\pages\test\test.jsx 发送参数的页面,将参数拼接到 url 上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Component } from 'react' import { View, Text, Button } from '@tarojs/components' import Taro from '@tarojs/taro'
export default class Index extends Component { handleClick() { Taro.redirectTo({ url: '/pages/index/index?id=1', }) } render() { return ( <View className='index'> <Button onClick={this.handleClick}>test!!</Button> </View> ) } }
|
src\pages\index\index.jsx 接收参数的页面,const id = this.$router.params.id;
//已经失效 taro3 有更新用getCurrentInstance()
taro3 中更新开发中我们常常会调用 Taro.getCurrentInstance() 获取小程序的 app、page 对象、路由参数等数据。但频繁调用它可能会导致问题。把 Taro.getCurrentInstance() 的结果在组件中保存起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { Component } from 'react' import { View, Text } from '@tarojs/components' import './index.less' import Child from './child' import Taro from '@tarojs/taro'
export default class Index extends Component { state = { text: { username: '李四' }, } componentWillMount() { const id = Taro.getCurrentInstance().router.params.id console.log(id) } render() { return ( <View className='index'> {/* <Child></Child> */} <Child text={this.state.text}></Child> </View> ) } }
|
导出资源以及导入方式
导入模块
方法一
单独导出
src\util.js
| export function setData() { console.log("setData"); } export function getData() { console.log("getData"); }
|
解构赋值引入
src\pages\test\test.jsx
| import { setData , getData } from "../../util";
|
如果是 export 单独导出,则解构赋值方法引入
方法二
默认方式导出
| function setData() { console.log("setData"); } function getData() { console.log("getData"); } export default setData;
|
单独引入
| import getData from "../../util";
|
如果使用结构赋值方式引入会报错
| import { getData } from "../../util";
|
方法三
对象方式导出
| function setData() { console.log("setData"); } function getData() { console.log("getData"); } export default { setData, getData };
|
调用方式引入
| //引入 import util from "../../util";
//调用 util.getData();
|
导入图片资源
方法一
require 方式导入
| import { Component } from 'react' import { View, Image } from '@tarojs/components'
export default class Index extends Component { render() { return ( <View className='index'> <Image src={require('../../img/1.png')}></Image> </View> ) } }
|
方法二
import 方式导入
| import { Component } from 'react' import { View, Text, Button, Image } from '@tarojs/components' import img from '../../img/1.png'
export default class Index extends Component { render() { return ( <View className='index'> <Image src={img}></Image> </View> ) } }
|
导入样式资源
src\pages\test\test.less
| .index { .img { width: 100%; margin-top: 30px; } }
|
src\pages\test\test.jsx
| import { Component } from 'react' import { View, Text, Button, Image } from '@tarojs/components' import './test.less' export default class Index extends Component { render() { return ( <View className='index'> <Image className='img' src={img}></Image> </View> ) } }
|
三目运算符加上函数调用的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Component } from 'react' import { View, Text, Button, Image } from '@tarojs/components'
export default class Index extends Component { state = { isShow: false, } getDom() { let { isShow } = this.state return isShow ? ( <Button onClick={this.handleClick}>test!!</Button> ) : ( 'isShow:false' ) }
render() { return <View className='index'>{this.getDom()}</View> } }
|
使用方法
Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 Nerv/小程序 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Component } from 'react' import { View, Text, Button, Image } from '@tarojs/components'
export default class Index extends Component { state = { isShow: false, list: [ { id: 1, title: '张三' }, { id: 2, title: '李四' }, { id: 3, title: '王五' }, ], }
render() { const { list } = this.state return ( <View className='index'> {list.map((item, index) => { return <View key={item.id}>{item.title}</View> })} </View> ) } }
|
key 的取值
必须同时满足三个条件:
- 稳定
- 可预测
- 唯一(相对于其他兄弟元素)
最好的 key 就是数组里的 ID(通常由后端生成),他能同时满足以上三个条件,同时也不需要自己去生成。如果没有 ID,你能保证数组的元素某个键值字符串都是不同的(例如 item.title),那么使用那个字符串键值也可以。如果源数据没有提供很好的 key 值,或者需要遍历的数组生成的。那么你最好在数据创建或者修改之后给他添加一个好的 key 值:
与 React 的不同
在 React 中,JSX 是会编译成普通的 JS 的执行,每一个 JSX 元素,其实会通过 createElement 函数创建成一个 JavaScript 对象(React Element),因此实际上你可以这样写代码 React 也是完全能渲染的:
| const list = this.state.list .map((l) => { if (l.selected) { return <li>{l.text}</li> } }) .filter(React.isValidElement)
|
你甚至可以这样写:
| const list = this.state.list .map((l) => { if (l.selected) { return { $$typeof: Symbol(react.element), props: { children: l.text, }, type: 'li', } } }) .filter(React.isValidElement)
|
但是 Taro 中,JSX 会编译成微信小程序模板字符串,因此你不能把 map 函数生成的模板当做一个数组来处理。当你需要这么做时,应该先处理需要循环的数组,再用处理好的数组来调用 map 函数。例如上例应该写成:
| const list = this.state.list .filter((l) => l.selected) .map((l) => { return <li>{l.text}</li> })
|
Children 与组合
src\pages\dialog\testDialog.jsx 父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Component } from 'react' import { View, Text, Image } from '@tarojs/components' import Dialog from './dialog'
export default class TestDialog extends Component { render() { return ( <View className='index'> <Dialog> <Text>我是第一个Dialog组件</Text> </Dialog> {/* myTxt="你好!!!" //传递属性 */} <Dialog myTxt='你好!!!'> <Image src={require('../../img/1.png')}></Image> {/*类似插槽 */} </Dialog> </View> ) } }
|
src\pages\dialog\dialog.jsx 子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Component } from 'react' import { View } from '@tarojs/components'
export default class Dialog extends Component { config = { navigationBarTitleText: '弹出框', } render() { return ( <View className='index'> {/* {this.props.myTxt}传递的属性 */} {/* {this.props.children}类似插槽的功能 */} {this.props.myTxt}弹出框组件!!!{this.props.children} </View> ) } }
|
注意事项
请不要对 this.props.children 进行任何操作。Taro 在小程序中实现这个功能使用的是小程序的 slot 功能,也就是说你可以把 this.props.children 理解为 slot 的语法糖,this.props.children 在 Taro 中并不是 React 的 ReactElement 对象,因此形如 this.props.children && this.props.children、this.props.children[0] 在 Taro 中都是非法的。
this.props.children 无法用 defaultProps 设置默认内容。由于小程序的限制,Taro 也无法知道组件的消费者是否传入内容,所以无法应用默认内容。
不能把 this.props.children 分解为变量再使用。由于普通的 props 有一个确切的值,所以当你把它们分解为变量运行时可以处理,this.props.children 则不能这样操作,你必须显性地把 this.props.children 全部都写完整才能实现它的功能。
事件处理
src\pages\event\event.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { Component } from 'react' import { View, Button } from '@tarojs/components'
export default class Event extends Component { state = { name: '张山', } handleClick(param, event) { console.log(param, event) event.stopPropagation() } render() { return ( <View className='index'> {/* .bind第一个参数绑定this指向,第二个参数是传递的参数 */} <Button onClick={this.handleClick.bind(this, this.state.name)}> event </Button> </View> ) } }
|
.bind 第一个参数绑定 this 指向,第二个参数是传递的参数。
如果遇到事件冒泡需要阻止用event.stopPropagation()
内置环境变量
注意:环境变量在代码中的使用方式,参考
Taro 在编译时提供了一些内置的环境变量来帮助用户做一些特殊处理
process.env.TARO_ENV
用于判断当前编译类型,目前有 weapp / swan / alipay / h5 / rn / tt / qq / quickapp 八个取值,可以通过这个变量来书写对应一些不同环境下的代码,在编译时会将不属于当前编译类型的代码去掉,只保留当前编译类型下的代码,例如想在微信小程序和 H5 端分别引用不同资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { Component } from 'react' import { View, Button } from '@tarojs/components'
const isH5 = process.env.TARO_ENV export default class Event extends Component { componentWillUnmount() { if (isH5) { console.log('现在的环境是web') } else { console.log('现在的环境是小程序端') } } render() { return <View className='index'></View> } }
|
不同的运行环境可以引入不同的样式或者资源也能加载不同的组件
| if (process.env.TARO_ENV === 'weapp') { require('path/to/weapp/name') } else if (process.env.TARO_ENV === 'h5') { require('path/to/h5/name') }
|
同时也可以在 JSX 中使用,决定不同端要加载的组件
| render () { return ( <View> {process.env.TARO_ENV === 'weapp' && <ScrollViewWeapp />} {process.env.TARO_ENV === 'h5' && <ScrollViewH5 />} </View> ) }
|
Taro.request(option)
src\pages\blog\blog.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import Taro from '@tarojs/taro' import { useState } from 'react' import { View, Text, Button } from '@tarojs/components'
function Blog() { const [articleList, setArticleList] = useState([])
const testHandler = () => { Taro.request({ url: 'https://apiblog.jspang.com/default/getArticleList', }).then((res) => { console.log(res.data) setArticleList(res.data.list) }) } return ( <View> <Text>接口请求</Text> <Button onClick={testHandler}>获取数据</Button> {articleList.map((item, index) => { return <View key={index}>- {item.title} </View> })} </View> ) } export default Blog
|