最近一直在学习taro,网上搜的重点知识总结很少,所以想着自己写一篇我觉得比较重要的知识点总结一下。
1.文件组织形式
以下文件组织规范为最佳实践的建议
所有项目源代码请放在项目根目录 src
目录下,项目所需最基本的文件包括 入口文件 以及 页面文件
- 入口文件为
app.js
- 页面文件建议放置在
src/pages
目录下
一个可靠的 Taro 项目可以按照如下方式进行组织
├── 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
2.文件命名
Taro 中普通 JS/TS 文件以小写字母命名,多个单词以下划线连接,例如 util.js
、util_helper.js
Taro 组件文件命名遵循 Pascal 命名法,例如 ReservationCard.jsx
3.JavaScript 书写规范
taro的书写规范大概和Eslint的规范类似,具体可参考官网链接:taro-docs.jd.com/taro/docs/s…
4.书写顺序
在 Taro 组件中会包含类静态属性、类属性、生命周期等的类成员,其书写顺序最好遵循以下约定(顺序从上至下)
- static 静态方法
- constructor
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
- 点击回调或者事件回调 比如
onClickSubmit()
或者onChangeDescription()
- render
5.推荐使用对象解构的方式来使用 state、props
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
render () {
const { isEnable } = this.props // ✓ 正确
const { myTime } = this.state // ✓ 正确
return (
<View className='test'>
{isEnable && <Text className='test_text'>{myTime}</Text>}
</View>
)
}
}
6.不要在调用 this.setState 时使用 this.state
由于 this.setState 异步的缘故,这样的做法会导致一些错误,可以通过给 this.setState 传入函数来避免
this.setState({
value: this.state.value + 1
}) // ✗ 错误
this.setState(prevState => ({ value: prevState.value + 1 })) // ✓ 正确
7.map 循环时请给元素加上 key 属性
list.map(item => {
return (
<View className='list_item' key={item.id}>{item.name}</View>
)
})
8.尽量避免在 componentDidMount 中调用 this.setState
因为在
componentDidMount
中调用this.setState
会导致触发更新
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
componentDidMount () {
this.setState({ // ✗ 尽量避免,可以在 componentWillMount 中处理
name: 1
})
}
render () {
const { isEnable } = this.props
const { myTime } = this.state
return (
<View className='test'>
{isEnable && <Text className='test_text'>{myTime}</Text>}
</View>
)
}
}
不要在 componentWillUpdate/componentDidUpdate/render 中调用 this.setState
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
componentWillUpdate () {
this.setState({ // ✗ 错误
name: 1
})
}
componentDidUpdate () {
this.setState({ // ✗ 错误
name: 1
})
}
render () {
const { isEnable } = this.props
const { myTime } = this.state
this.setState({ // ✗ 错误
name: 11
})
return (
<View className='test'>
{isEnable && <Text className='test_text'>{myTime}</Text>}
</View>
)
}
}
9.事件绑定均以 on 开头
在 Taro 中所有默认事件如
onClick
、onTouchStart
等等,均以on
开头
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
clickHandler (e) {
console.log(e)
}
render () {
const { myTime } = this.state
return (
<View className='test' onClick={this.clickHandler}> // ✓ 正确
<Text className='test_text'>{myTime}</Text>
</View>
)
}
}
10.不能使用 Array#map 之外的方法操作 JSX 数组
Taro 在小程序端实际上把 JSX 转换成了字符串模板,而一个原生 JSX 表达式实际上是一个 React/Nerv 元素(react-element)的构造器,因此在原生 JSX 中你可以随意地对一组 React 元素进行操作。但在 Taro 中你只能使用
map
方法,Taro 转换成小程序中wx:for
11.设计稿及尺寸单位
在 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
12.组件化 & Props
组件可以将 UI 切分成一些的独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件。
组件从概念上看就像是函数,它可以接收任意的输入值(称之为 props
),并返回一个需要在页面上展示的 Taro 元素。
Props 的只读性
一个声明的组件决不能修改它自己的 props
。
使用 PropTypes 检查类型
随着应用日渐庞大,你可以通过类型检查捕获大量错误。要检查组件的属性,你需要配置特殊的 propTypes
属性:
import PropTypes from 'prop-types';
class Greeting extends Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
13.正确地使用 State
关于 setState() 这里有三件事情需要知道:
1.不要直接更新状态
例如,此代码不会重新渲染组件:
// Wrong
this.state.comment = 'Hello'
应当使用 setState()
:
// Correct
this.setState({ comment: 'Hello' })
2.状态更新一定是异步的
Taro 可以将多个 setState()
调用合并成一个调用来提高性能。
因为 this.state
和 props
一定是异步更新的,所以你不能在 setState
马上拿到 state
的值,例如:
// 假设我们之前设置了 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 之后的值
})
}
3.state 更新会被合并
当你调用 setState()
,Taro 将合并你提供的对象到当前的状态中。
例如,你的状态可能包含几个独立的变量:
constructor(props) {
super(props)
this.state = {
posts: [],
comments: []
}
}
然后通过调用独立的 setState()
调用分别更新它们:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
})
})
}
合并是浅合并,所以 this.setState({comments})
不会改变 this.state.posts
的值,但会完全替换 this.state.comments
的值。
14.条件渲染
枚举条件渲染
有时渲染的条件非常多,不管是 if-else
还是 switch-case
来做条件渲染都会显得太麻烦。这时我们可以使用「表驱动法」:枚举渲染。
function Loading (props) {
const { loadingText, LOADING_STATUS, loadingStatus, onRetry } = props
return (
<View className='loading-status'>
{
{
'loading': loadingText,
'fail': <View onClick={onRetry}> 加载失败, 点击重试 </View>,
'no-more': '没有更多了'
}[loadingStatus] /** loadingStatus 是 `loading`、`fail`、`no-more` 其中一种状态 **/
}
</View>
)
}
15.渲染多个组件
下面,我们使用 JavaScript 中的 map()
方法遍历 numbers
数组。对数组中的每个元素返回 <Text>
标签,最后我们得到一个数组 listItems
:
const numbers = [...Array(100).keys()] // [0, 1, 2, ..., 98, 99]
const listItems = numbers.map((number) => {
return <Text className='li'> 我是第 {number + 1} 个数字</Text>
})
这段代码生成了一个 1 到 100 的数字列表。
Keys
但是在上面的代码,你会得到一个报错:提醒你当循环一个数组时应该提供 keys。Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 Nerv/小程序 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
taroKeys
taroKey
适用于循环渲染原生小程序组件,赋予每个元素唯一确定标识,转换为小程序的 wx:key
。
元素的 key 在他的兄弟元素之间应该唯一
数组元素中使用的 key 在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key
16.类函数式组件
自
v1.3.0-beta.0
起支持
由于一个文件不能定义两个组件,但有时候我们需要组件内部的抽象组件,这时类函数式组件就是你想要答案。假设我们有一个 Class 组件,它包括了一个 Header
一个 Footer
,我们可以这样定义:
class SomePage extends Taro.Component {
renderHeader () {
const { header } = this.state
return <View>{header}</View>
}
renderFooter (footer) {
return <View>{footer}</View>
}
render () {
return (
<View>
{this.renderHeader()}
{...}
{this.renderFooter('footer')}
</View>
)
}
}
在 renderHeader
或 renderFooter
函数中,我们可以访问类的 this
,也可以传入不限量的参数,这类型的函数也可以调用无限次数。但这样的写法也存在一些限制:
- 函数的命名必须以
render
开头,render
后的第一个字母需要大写 - 函数的参数不得传入 JSX 元素或 JSX 元素引用
- 函数不能递归地调用自身
17.在我们设计组件时,有些组件通常不知道自己的子组件会有什么内容,例如 Sidebar
和 Dialog
这样的容器组件。
我们建议在这样的情况使用 this.props.children
来传递子元素:
class Dialog extends Component {
render () {
return (
<View className='dialog'>
<View className='header'>Welcome!</View>
<View className='body'>
{this.props.children}
</View>
<View className='footer'>-- divider --</View>
</View>
)
}
}
这样就能允许其它组件在 JSX 中嵌套任意子组件传递给 Dialog
:
class App extends Component {
render () {
return (
<View className='container'>
<Dialog>
<View className="dialog-message">
Thank you for using Taro.
</View>
</Dialog>
</View>
)
}
}
在 <Dialog />
JSX 标签内的任何内容都会作为它的子元素(Children)都会传递到它的组件。
请不要对 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
全部都写完整才能实现它的功能。
组合
自
1.1.9
开始支持
有些情况你不仅仅需要只传递一个子组件,可能会需要很多个「占位符」。例如在这个 Dialog
组件中,你不仅需要自定义它的 body
,你希望它的 header
和 footer
都是给 Dialog
组件的使用者自由定制。这种情况可以这样做:
class Dialog extends Component {
render () {
return (
<View className='dialog'>
<View className='header'>
{this.props.renderHeader}
</View>
<View className='body'>
{this.props.children}
</View>
<View className='footer'>
{this.props.renderFooter}
</View>
</View>
)
}
}
class App extends Component {
render () {
return (
<View className='container'>
<Dialog
renderHeader={
<View className='welcome-message'>Welcome!</View>
}
renderFooter={
<Button className='close'>Close</Button>
}
>
<View className="dialog-message">
Thank you for using Taro.
</View>
</Dialog>
</View>
)
}
}
在我们声明 Dialog
组件时,header
和 footer
部分我们分别增加了 this.props.renderHeader
和 this.props.renderFooter
作为占位符。相应地,我们在使用 Dialog
组件时,就可以给 renderHeader
和 renderFooter
传入 JSX 元素,这两个分别传入的 JSX 元素将会填充它们在 Dialog
组件中的位置——就像在 Dialog
JSX 标签里写入的内容,会填充到 this.props.children
的位置一样。
注意事项
组件的组合需要遵守 this.props.children
的所有规则。组合这个功能和 this.props.children
一样是通过 slot
实现的,也就是说 this.props.children
的限制对于组件组合也都同样适用。
所有组合都必须用 render
开头,且遵守驼峰式命名法。和我们的事件规范以 on
开头一样,组件组合使用 render
开头。
组合只能传入单个 JSX 元素,不能传入其它任何类型。当你需要进行一些条件判断或复杂逻辑操作的时候,可以使用一个 Block
元素包裹住,然后在 Block
元素的里面填充其它复杂的逻辑。