Taro官网文档总结

2,281 阅读10分钟

最近一直在学习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.jsutil_helper.js

Taro 组件文件命名遵循 Pascal 命名法,例如 ReservationCard.jsx

3.JavaScript 书写规范

taro的书写规范大概和Eslint的规范类似,具体可参考官网链接:taro-docs.jd.com/taro/docs/s…

4.书写顺序

在 Taro 组件中会包含类静态属性、类属性、生命周期等的类成员,其书写顺序最好遵循以下约定(顺序从上至下)

  1. static 静态方法
  2. constructor
  3. componentWillMount
  4. componentDidMount
  5. componentWillReceiveProps
  6. shouldComponentUpdate
  7. componentWillUpdate
  8. componentDidUpdate
  9. componentWillUnmount
  10. 点击回调或者事件回调 比如 onClickSubmit() 或者 onChangeDescription()
  11. 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 中所有默认事件如 onClickonTouchStart 等等,均以 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.stateprops 一定是异步更新的,所以你不能在 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>
    )
  }
}

renderHeaderrenderFooter 函数中,我们可以访问类的 this,也可以传入不限量的参数,这类型的函数也可以调用无限次数。但这样的写法也存在一些限制:

  1. 函数的命名必须以 render 开头,render 后的第一个字母需要大写
  2. 函数的参数不得传入 JSX 元素或 JSX 元素引用
  3. 函数不能递归地调用自身

17.在我们设计组件时,有些组件通常不知道自己的子组件会有什么内容,例如 SidebarDialog 这样的容器组件。

我们建议在这样的情况使用 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.childrenthis.props.children[0] 在 Taro 中都是非法的。

this.props.children 无法用 defaultProps 设置默认内容。由于小程序的限制,Taro 也无法知道组件的消费者是否传入内容,所以无法应用默认内容。

不能把 this.props.children 分解为变量再使用。由于普通的 props 有一个确切的值,所以当你把它们分解为变量运行时可以处理,this.props.children 则不能这样操作,你必须显性地把 this.props.children 全部都写完整才能实现它的功能。

组合

1.1.9 开始支持

有些情况你不仅仅需要只传递一个子组件,可能会需要很多个「占位符」。例如在这个 Dialog 组件中,你不仅需要自定义它的 body,你希望它的 headerfooter 都是给 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 组件时,headerfooter 部分我们分别增加了 this.props.renderHeaderthis.props.renderFooter 作为占位符。相应地,我们在使用 Dialog 组件时,就可以给 renderHeaderrenderFooter 传入 JSX 元素,这两个分别传入的 JSX 元素将会填充它们在 Dialog 组件中的位置——就像在 Dialog JSX 标签里写入的内容,会填充到 this.props.children 的位置一样。

注意事项

组件的组合需要遵守 this.props.children 的所有规则。组合这个功能和 this.props.children 一样是通过 slot 实现的,也就是说 this.props.children 的限制对于组件组合也都同样适用。

所有组合都必须用 render 开头,且遵守驼峰式命名法。和我们的事件规范以 on 开头一样,组件组合使用 render 开头。

组合只能传入单个 JSX 元素,不能传入其它任何类型。当你需要进行一些条件判断或复杂逻辑操作的时候,可以使用一个 Block 元素包裹住,然后在 Block 元素的里面填充其它复杂的逻辑。