Taro 简介

Taro 是一个基于 React 语法规范的多端统一开发框架,大家可以通过 taro.aotu.io 进一步了解。而前段时间 Taro 发布后,京东购物小程序就开始了部分页面基于 Taro 的重构工作,本文便是对商品分类页使用 Taro 进行代码重构的一些实践分享。

混合开发模式

过去的京东购物小程序未使用任何第三方框架,而是在原生小程序模式的基础上,进行了页面/组件基类、网络请求、本地存储、页面跳转等模块的封装。由于项目庞大(涉及 100 多个页面),把整个项目直接改造成 Taro 的开发方式肯定是不可行的,于是采用这么一种原生小程序与 Taro 相混合的开发模式,将部分旧页面使用 Taro 重构,部分新的页面则直接使用 Taro 进行开发。这里以商品分类页为例,先来看下原京东购物小程序项目的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
├── dist
│ ├── app.js
│ ├── app.json
│ ├── app.wxss
│ ├── assets/
│ ├── common/
│ ├── libs/
│ └── pages
│ ├── cate
│ │ ├── components/
│ │ ├── index.js
│ │ ├── index.json
│ │ ├── index.wxml
│ │ └── index.wxss
│ └── index/
├── src/
├── README.md
├── gulpfile.js
├── package.json
└── node_modules/

1. 初始化 Taro

在项目根目录处运行命令 taro init jdwxa-taro 进行初始化,完成后会新增一个名为 jdwxa-taro 的目录,Taro 相关的源代码就写在该目录中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├── dist/
├── src/
└── jdwxa-taro
├── config
│ ├── dev.js
│ ├── index.js
│ └── prod.js
├── node_modules/
├── package.json
├── project.config.json
└── src
├── app.js
├── app.scss
├── index.html
└── pages
└── cate
├── index.js
└── index.scss

2. Taro 配置

独立的 Taro 项目会将包括 app.jsapp.jsonapp.wxss 及页面文件均生成在 dist/ 目录中,而混合开发模式下只需要生成单个页面,这里需要对 Taro 进行一些配置,打开并编辑 config/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
const config = {
outputRoot: '../dist',
weapp: {
appOutput: false,
npm: {
dir: '../../dist/common',
name: 'taro'
}
},
// ...
}

如代码所示,outputRoot 字段为生成目标页面的存放路径,这里把它指向顶层(即原项目)的 dist/ 目录;weapp 部分,我们把 appOutput 设置成 false, 这样就不会生成 app.js、app.json、app.wxss 三个文件了,npm 字段则表示 Taro 运行时框架文件的存放目录,这里遵循原项目的规范,把它指定为 common/ 目录。这样 Taro 编译生成的目标文件就完美地融入进了原小程序项目。

3. 页面开发

页面开发过程中,跟原生小程序最大的不同就是 React + JSX 的编码方式了,习惯了原生小程序的同学可能要一些适应过程,具体的编码就不细说了,这里提几点注意事项:

  • 与小程序的 setData 方法不同,Taro 用于更新页面数据的 setState 是异步的,相关代码的执行时序需要特别注意;
  • 为了方便 JSX 模板的书写,原先很长的 WXML 内容建议拆分成一些小的组件;
  • 关于旧组件的复用,无论是小程序原生组件、普通 JS 模块、样式文件或是第三方组件库,都能很好的进行引入调用,这点无需担心;
  • 目前对于 Taro 编译生成的目标代码,调试起来会有些困难,但对 SourceMap 的支持正在积极开发中。

4. 最终效果

如今重构后的商品分类页已经在线上稳定运行有一段时间了,可以扫描下面的小程序码进行体验:

小程序码 of 京东购物小程序 - 商品分类页

Taro 带来的收益

多端运行

最大的收益便是可以生成多端版本,避免重复工作、节省开发成本。以分类页为例,只需运行 npm run build:h5 便可生成 H5 版本的分类页,运行效果和小程序一致,大家可以扫描下面的二维码进行体验:

Taro 生成的 H5 版商品分类页

注:以上仅为 Taro 生成的示例页面,由于一些业务组件尚未完全适配两端,所以 H5 版本暂时没有正式投入使用。

性能提升

小程序项目中遇到的性能问题,大多是频繁地调用 setData 造成的,这是由于每调用一次 setData,小程序内部都会将该部分数据在逻辑层(运行环境 JSCore)进行类似序列化的操作,将数据转换成字符串形式传递给视图层(运行环境 WebView),视图层通过反序列化拿到数据后再进行页面渲染,这个过程下来有一定性能开销。

所以开发过程中,我们建议尽量对 setData 进行合并,减少调用次数,例如:

1
2
3
this.setData({ foo: 'Strawberry' })
this.setData({ foo: 'Strawberry', bar: 'Fields' })
this.setData({ baz: 'Forever' })

以上代码调用了 3 次 setData,造成不必要的性能开销,应对其进行合并:

1
2
3
4
5
this.setData({
foo: 'Strawberry',
bar: 'Fields',
baz: 'Forever',
})

而使用 Taro 之后,更新数据时调用的 setState 为异步方法,它会自动地对同一事件循环里的多次 setState 调用进行合并处理,此外还会进行数据 diff 优化,自动剔除那些未变更的数据,从而有效避免了此类性能问题。例如:

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
// 初始时
this.state = {
foo: '1967',
bar: {
foo: 'Strawberry',
bar: 'Fields',
baz: 'Forever',
}
}
// 第一次更新
this.setState({
bar: {
foo: 'Norwegian',
bar: 'Fields',
baz: 'Forever',
}
})
// 紧接着进行第二次更新
this.setState({
foo: '1967',
bar: {
foo: 'Norwegian',
bar: 'Wood',
baz: 'Forever',
}
})

以上代码虽然经过两次 setState,但只有 bar.foo 和 bar.bar 的数据更新了,此时 Taro 内部会自动对数据进行合并、并剔除重复数据,最终执行代码为:

1
2
3
4
5
// this.$scope 在小程序环境中为 page 实例
this.$scope.setData({
'bar.foo': 'Norwegian',
'bar.bar': 'Wood',
})

其他收益

比起原生小程序开发,Taro 带来了许多激动人心的特性(如支持 TypeScript、NPM、丰富的 JSX 语法、更高级的 ES 特性等等),不仅提升了开发体验,对自动化测试、持续构建等也会有不小的帮助。

举个例子,京东购物小程序里封装了一个 getImg 方法,该方法接受一个图片 url 及可选的宽高作为参数,然后根据设备类型决定是否使用 webp 格式、根据当前网络环境应用适当的图片压缩率、自动处理协议头和域名转换,最后生成符合目标大小的图片 url。我们要求所有的图片都必须经过 getImg 方法处理后才能进行展示,但由于 JS 方法只能在逻辑层进行调用,处理好后再传递给 WXML 进行展示,使得很难在自动化工具中进行检测,及时发现未调用 getImg 输出图片的情况。

而使用 Taro 之后,可以直接在 JSX 模板的 Image 标签输出时对 src 调用 getImg 方法进行处理,将此种写法作为规范明确后,就很容易通过自动化工具进行检测了:

1
2
3
render () {
return <Image src={getImg(url, 750)} />
}

So 对于现有项目来说,不需要进行整体重构,也能很好的将 Taro 集成进去。还等什么,赶紧试试吧~

感谢您的阅读,本文由 凹凸实验室 版权所有。如若转载,请注明出处:凹凸实验室(https://aotu.io/notes/2018/09/11/taro-in-jd/