title: 最佳实践

关于 JSX 支持程度补充说明

由于 JSX 中的写法千变万化,我们不能支持到所有的 JSX 写法,同时由于微信小程序端的限制,也有部分 JSX 的优秀用法暂时不能得到很好地支持,特在此补充说明一下对于 JSX 的支持程度

以上的规则在 Taro 默认生成的模板都有 ESLint 检测,无需做任何配置。如果你的编辑器没有安装 ESLint 插件可以参考以下教程在你的编辑器安装:

默认情况下 Taro 的编译器也会对无法运行的代码进行警告,当没有调用栈信息时代码是可以生成的。如果你需要在编译时禁用掉 ESLint 检查,可以在命令前加入 ESLINT=false 参数,例如:

  1. $ ESLINT=false taro build --type weapp --watch

最佳编码方式

经过较长时间的探索与验证,目前 Taro 在微信小程序端是采用依托于小程序原生自定义组件系统来设计实现 Taro 组件化的,所以目前小程序端的组件化会受到小程序原生组件系统的限制,而同时为了实现以 React 方式编写代码的目标,Taro 本身做了一些编译时以及运行时的处理,这样也带来了一些值得注意的约束,所以有必要阐述一下 Taro 编码上的最佳实践。

组件样式说明

微信小程序的自定义组件样式默认是不能受外部样式影响的,例如在页面中引用了一个自定义组件,在页面样式中直接写自定义组件元素的样式是无法生效的。这一点,在 Taro 中也是一样,而这也是与大家认知的传统 web 开发不太一样。

给组件设置 defaultProps

在微信小程序端的自定义组件中,只有在 properties 中指定的属性,才能从父组件传入并接收

  1. Component({
  2. properties: {
  3. myProperty: { // 属性名
  4. type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
  5. value: '', // 属性初始值(可选),如果未指定则会根据类型选择一个
  6. observer: function (newVal, oldVal, changedPath) {
  7. // 属性被改变时执行的函数(可选),也可以写成在 methods 段中定义的方法名字符串, 如:'_propertyChange'
  8. // 通常 newVal 就是新设置的数据, oldVal 是旧数据
  9. }
  10. },
  11. myProperty2: String // 简化的定义方式
  12. }
  13. ...
  14. })

而在 Taro 中,对于在组件代码中使用到的来自 props 的属性,会在编译时被识别并加入到编译后的 properties 中,暂时支持到了以下写法

  1. this.props.property
  2. const { property } = this.props
  3. const property = this.props.property

但是一千个人心中有一千个哈姆雷特,不同人的代码写法肯定也不尽相同,所以 Taro 的编译肯定不能覆盖到所有的写法,而同时可能会有某一属性没有使用而是直接传递给子组件的情况,这种情况是编译时无论如何也处理不到的,这时候就需要大家在编码时给组件设置 defaultProps 来解决了。

组件设置的 defaultProps 会在运行时用来弥补编译时处理不到的情况,里面所有的属性都会被设置到 properties 中初始化组件,正确设置 defaultProps 可以避免很多异常的情况的出现。

组件传递函数属性名以 on 开头

在 Taro 中,父组件要往子组件传递函数,属性名必须以 on 开头

  1. // 调用 Custom 组件,传入 handleEvent 函数,属性名为 `onTrigger`
  2. class Parent extends Component {
  3. handleEvent () {
  4. }
  5. render () {
  6. return (
  7. <Custom onTrigger={this.handleEvent}></Custom>
  8. )
  9. }
  10. }

这是因为,微信小程序端组件化是不能直接传递函数类型给子组件的,在 Taro 中是借助组件的事件机制来实现这一特性,而小程序中传入事件的时候属性名写法为 bindmyevent 或者 bind:myevent

  1. <!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 -->
  2. <component-tag-name bindmyevent="onMyEvent" />
  3. <!-- 或者可以写成 -->
  4. <component-tag-name bind:myevent="onMyEvent" />

所以 Taro 中约定组件传递函数属性名以 on 开头,同时这也和内置组件的事件绑定写法保持一致了。

小程序端不要在组件中打印传入的函数

前面已经提到小程序端的组件传入函数的原理,所以在小程序端不要在组件中打印传入的函数,因为拿不到结果,但是 this.props.onXxx && this.props.onXxx() 这种判断函数是否传入来进行调用的写法是完全支持的。

小程序端不要将在模板中用到的数据设置为 undefined

由于小程序不支持将 data 中任何一项的 value 设为 undefined ,在 setState 的时候也请避免这么用。你可以使用 null 来替代。

小程序端不要在组件中打印 this.props.children

在微信小程序端是通过 <slot /> 来实现往自定义组件中传入元素的,而 Taro 利用 this.props.children 在编译时实现了这一功能, this.props.children 会直接被编译成 <slot /> 标签,所以它在小程序端属于语法糖的存在,请不要在组件中打印它。

支持 props 传入 JSX

1.1.9 开始支持

支持 props 传入 JSX,但是元素传入 JSX 的属性名必须以 render 开头

例如,子组件写法

  1. class Dialog extends Component {
  2. render () {
  3. return (
  4. <View className='dialog'>
  5. <View className='header'>
  6. {this.props.renderHeader}
  7. </View>
  8. <View className='body'>
  9. {this.props.children}
  10. </View>
  11. <View className='footer'>
  12. {this.props.renderFooter}
  13. </View>
  14. </View>
  15. )
  16. }
  17. }

父组件调用子组件是传入 JSX

  1. class App extends Component {
  2. render () {
  3. return (
  4. <View className='container'>
  5. <Dialog
  6. renderHeader={
  7. <View className='welcome-message'>Welcome!</View>
  8. }
  9. renderFooter={
  10. <Button className='close'>Close</Button>
  11. }
  12. >
  13. <View className="dialog-message">
  14. Thank you for using Taro.
  15. </View>
  16. </Dialog>
  17. </View>
  18. )
  19. }
  20. }

组件属性传递注意

不要以 idclassstyle 作为自定义组件的属性与内部 state 的名称,因为这些属性名在微信小程序小程序中会丢失。

组件 stateprops 里字段重名的问题

不要在 stateprops 上用同名的字段,因为这些被字段在微信小程序中都会挂在 data 上。

小程序中页面生命周期 componentWillMount 不一致问题

由于微信小程序里页面在 onLoad 时才能拿到页面的路由参数,而页面 onLoad 前组件都已经 attached 了。因此页面的 componentWillMount 可能会与预期不太一致。例如:

  1. // 错误写法
  2. render () {
  3. // 在 willMount 之前无法拿到路由参数
  4. const abc = this.$router.params.abc
  5. return <Custom adc={abc} />
  6. }
  7. // 正确写法
  8. componentWillMount () {
  9. const abc = this.$router.params.abc
  10. this.setState({
  11. abc
  12. })
  13. }
  14. render () {
  15. // 增加一个兼容判断
  16. return this.state.abc && <Custom adc={abc} />
  17. }

对于不需要等到页面 willMount 之后取路由参数的页面则没有任何影响。

组件的 constructorrender 提前调用

很多细心的开发者应该已经注意到了,在 Taro 编译到小程序端后,组件的 constructorrender 默认会多调用一次,表现得与 React 不太一致。

这是因为,Taro 的组件编译后就是小程序的自定义组件,而小程序的自定义组件的初始化时是可以指定 data 来让组件拥有初始化数据的。开发者一般会在组件的 constructor 中设置一些初始化的 state,同时也可能会在 render 中处理 stateprops 产生新的数据,在 Taro 中多出的这一次提前调用,就是为了收集组件的初始化数据,给自定义组件提前生成 data ,以保证组件初始化时能带有数据,让组件初次渲染正常。

所以,在编码时,需要在处理数据的时候做一些容错处理,这样可以避免在 constructorrender 提前调用时出现由于没有数据导致出错的情况。

JS 编码必须用单引号

在 Taro 中,JS 代码里必须书写单引号,特别是 JSX 中,如果出现双引号,可能会导致编译错误。

环境变量 process.env 的使用

不要以解构的方式来获取通过 env 配置的 process.env 环境变量,请直接以完整书写的方式 process.env.NODE_ENV 来进行使用

  1. // 错误写法,不支持
  2. const { NODE_ENV = 'development' } = process.env
  3. if (NODE_ENV === 'development') {
  4. ...
  5. }
  6. // 正确写法
  7. if (process.env.NODE_ENV === 'development') {
  8. }

使用 this.$componentType 来判断当前 Taro.Component 是页面还是组件

this.$componentType 可能取值分别为 PAGECOMPONENT,开发者可以根据此变量的取值分别采取不同逻辑。

预加载

微信小程序中,从调用 Taro.navigateToTaro.redirectToTaro.switchTab 后,到页面触发 componentWillMount 会有一定延时。因此一些网络请求可以提前到发起跳转前一刻去请求。

Taro 提供了 componentWillPreload 钩子,它接收页面跳转的参数作为参数。可以把需要预加载的内容通过 return 返回,然后在页面触发 componentWillMount 后即可通过 this.$preloadData 获取到预加载的内容。

注意:调用跳转方法时需要使用绝对路径,相对路径不会触发此钩子。

  1. class Index extends Component {
  2. componentWillMount () {
  3. console.log('isFetching: ', this.isFetching)
  4. this.$preloadData
  5. .then(res => {
  6. console.log('res: ', res)
  7. this.isFetching = false
  8. })
  9. }
  10. componentWillPreload (params) {
  11. return this.fetchData(params.url)
  12. }
  13. fetchData () {
  14. this.isFetching = true
  15. ...
  16. }
  17. }

在小程序中,可以使用 this.$preload 函数进行页面跳转传参

用法:this.$preload(key:String|Object, [value: Any])

之所以命名为 $preload,因为它也有一点预加载数据的意味。

如果觉得每次页面跳转传参时,需要先把参数 stringify 后加到 url 的查询字符串中很繁琐,可以利用 this.$preload 进行传参。

另外如果传入的是下一个页面的数据请求 promise,也有上一点提到的“预加载”功能,也能够绕过 componentWillMount 延时。不同点主要在于代码管理,开发者可酌情使用。

例子:

  1. // 传入单个参数
  2. // A 页面
  3. // 调用跳转方法前使用 this.$preload
  4. this.$preload('key', 'val')
  5. Taro.navigateTo({ url: '/pages/B/B' })
  6. // B 页面
  7. // 可以于 this.$router.preload 中访问到 this.$preload 传入的参数
  8. componentWillMount () {
  9. console.log('preload: ', this.$router.preload.key)
  10. }
  1. // 传入多个参数
  2. // A 页面
  3. this.$preload({
  4. x: 1,
  5. y: 2
  6. })
  7. Taro.navigateTo({ url: '/pages/B/B' })
  8. // B 页面
  9. componentWillMount () {
  10. console.log('preload: ', this.$router.preload)
  11. }

全局变量

在 Taro 中推荐使用 Redux 来进行全局变量的管理,但是对于一些小型的应用, Redux 就可能显得比较重了,这时候如果想使用全局变量,推荐如下使用。

新增一个自行命名的 JS 文件,例如 global_data.js,示例代码如下

  1. const globalData = {}
  2. export function set (key, val) {
  3. globalData[key] = val
  4. }
  5. export function get (key) {
  6. return globalData[key]
  7. }

随后就可以在任意位置进行使用啦

  1. import { set as setGlobalData, get as getGlobalData } from './path/name/global_data'
  2. setGlobalData('test', 1)
  3. getGlobalData('test')