自定义组件库扩展
除了 React Native 官方提供的组件,团队一般都会有自己的UI组件库。
此外,有很多优秀的第三方组件比如:ScrollableTabView, ViewPager等,如何把使用了这些组件的 React Native 应用转化为微信小程序呢?
下面我们提供2种方式,来处理这些组件,我们以实际的例子说明。
以下所涉及的 @areslabs/hi-rn
, @areslabs/hi-wx
, @areslabs/hello-rn
, @areslabs/hello-wx
。代码均可以在 packages 目录下找到。
手动对齐UI组件库
首先,我们需要预先在微信小程序端对齐一套对应的组件。为了方便说明,我们以第三方组件 @areslabs/hi-rn
为例,这个组件很简单,希望通过这个例子,能够让大家理解扩展第三方组件库的方式。这个组件的RN、微信小程序源代码都在 packages 目录下。
我们先来看一下RN组件代码:
export default class Hi extends React.Component {
render() {
return (
<View style={this.props.style}>
<Text
style={this.props.textStyle}
onPress={() => {
console.log('Hi ', this.props.name, ' !')
this.props.textPress && this.props.textPress()
}}
>Hi {this.props.name}!</Text>
</View>
)
}
}
这个组件接受一个属性 name
,并展示。
再看下对齐的微信小程序的代码,看下目录结构:
首先这个包需要符合微信小程序npm包的约定。代码需要在 miniprogram_dist 目录下。
分别来看下必要的几个文件 index.comp.js,index.js, index.json, index.wxml, index.wxss。
index.comp.js
代码如下:
import {RNBaseComponent, tackleWithStyleObj, styleType} from '@areslabs/wx-react'
const {VIEW} = styleType
export default class Hi extends RNBaseComponent{
getStyle(props) {
return {
style: tackleWithStyleObj(props.style, VIEW),
textStyle: tackleWithStyleObj(props.textStyle),
}
}
}
这个文件是由转化引擎内部的 mini-react 运行时用的,有两点需要注意:
- 继承自 RNBaseComponent
- 提供
getStyle
方法
继承自 RNBaseComponent 是为了方便转化引擎内部的识别。
重点在于这里的 getStyle
方法,在说 getStyle
之前,先说两点:
tackleWithStyleObj
方法,tackleWithStyleObj
就是把 RN 的样式对象,转化为微信小程序的等效样式的方法,它接受2个参数,第一个参数是 RN 合法的样式对象(可以是数组),第二个参数决定了需要添加的样式默认值。- 微信小程序的自定义组件和 RN 有一个很大的不同,RN 的自定义组件实际上只存在于 React 运行阶段,比如 Hi 组件,在 React 真正渲染之后是不存在的,而微信小程序会实实在在渲染出一个 Hi 节点。
所以,在小程序上我们需要把组件 Hi 的外层 View 的样式提取出来,放到 Hi 节点上,也就是 style: tackleWithStyleObj(props.style, VIEW)
。而内部文字的样式,也需要通过 tackleWithStyleObj
转化一次即:textStyle:tackleWithStyleObj(props.textStyle)
。注意这里 style 的时候使用了 tackleWithStyleObj
的第二个参数,原因是我们无法给 Hi 节点,添加样式默认值,而对应内部对齐使用的微信原生节点,是可以通过如下方式添加默认值的。
page, view, image, scroll-view {
display: flex;
flex-direction: column;
position: relative;
...
}
index.js
代码如下:
import {getPropsMethod, instanceManager} from '@areslabs/wx-react'
Component({
properties: {
name: null,
textStyle: null,
diuu: null,
},
attached() {
instanceManager.setWxCompInst(this.data.diuu, this)
},
detached() {
instanceManager.removeUUID(this.data.diuu)
},
methods: {
handlePress: function () {
console.log('Hi ', this.data.name, ' !')
const textPress = getPropsMethod(this, 'textPress')
textPress && textPress()
}
}
});
properties
字段定义了组件需要的属性,这里需要额外定义一个属性: diuu
, 同时在微信小程序的生命周期里面需要使用这个这个 diuu
属性, 即 attached 生命周期执行instanceManager.setWxCompInst(this.data.diuu, this)
, detached 生命周期instanceManager.removeUUID(this.data.diuu)
。
另外在 methods 字段里面, 提供了 handlePress 方法,对应 Text 标签的onPress,由于微信小程序不可以直接传递方法, 对于 this.props.textPress
需要调用getPropsMethod
获取对应方法。
style 属性不需要定义在 properties
?首先没必要,因为样式需要设置在微信小程序自定义组件生成的外部节点上,第二设置了也没有用,微信小程序传递属性的时候,自动过滤了style。
index.wxml
代码如下:
<view class="fillout">
<view style="{{textStyle}}" catchtap="handlePress">Hi {{name}}!</view>
</view>
这里有几点需要注意:
- React Native 的 Text 组件行为表现,更像微信小程序的 view 标签,这里用view 标签替换 Text 标签。
- React Native 的事件在嵌套的时候是不冒泡的,也就是Touchable组件嵌套 Touchable 组件在响应的时候只有内部响应,所以这里用catchtap,这里的 handlePress 和上面 index.js 文件里的 handlePress 是对应的
- React Native 的数据绑定
{this.props.name}
对应微信小程序的{ { name } }
。 - 正如之前所说,style 会被设置到自定义组件生成的节点之上,所以里面包裹的 view 需要撑满这个自定义节点,需要设置 fillout 类。
微信小程序自定义组件生成的节点,本身也是一个 view 节点,它即被设置了 style 的值,也就和 React Native 的包裹的 View 节点等效了,所以这里的包裹 view 节点可以省略。
实际上的 index.wxml 的代码如下:
<view style="{{textStyle}}" catchtap="handlePress">Hi {{name}}!</view>
index.json
代码如下:
{
"component": true
}
index.wxss
这里放置你在对齐阶段需要用到的 wxss 文件,一般情况由于 React Native 的样式都是设置在 style 属性,这个文件通常为空。
另外为了方便处理样式,微信小程序基本节点 view, image, scroll-view 都被默认设置为了 flex 布局,且方向为 column ,与 React Native 保持了一致。
配置文件
对齐之后,发布为@areslabs/hi-wxnpm包。然后在配置文件配置如下:
module.exports = {
dependenciesMap: {
...
"@areslabs/hi-rn": "@areslabs/hi-wx",
...
},
extCompLibs: [
{
name: '@areslabs/hi-rn',
compLists: [
'Hi'
]
}
]
}
和其他npm包一样,首先需要在 dependenciesMap
字段里面指明对应关系。额外的,组件库对齐的包还需要在 extCompLibs
字段,指明提供的所有组件, 对于 @areslabs/hi-rn
这个包,只有一个组件 Hi
使用方式
以上,我们对一个 React Native 组件在微信小程序上的适配工作就结束了,对于以后所有使用这个 Hi 组件的 React Native 应用来说,就是可以直接转化为微信小程序版本了。
import Hi from '@areslabs/hi-rn'
class HelloWorld extends React.Component {
render() {
return <View>
<Hi name="y5g"/>
</View>
}
}
React Native 的所有组件包括 Image, View, FlatList 都是通过以上的方式在微信小程序端实现了对齐,大家可以通过查看wx-react-native,来查看基本组件的对齐。
引擎直接转化组件
是不是自己的 UI 组件库,都需要用以上的对齐方式呢?还有一种可选方式。既然我们可以让 React Native 应用直接运行在微信小程序端,那么直接把 UI 组件库用转化引擎处理一次呢?答案是可行的, 我们以 @areslabs/hello-rn
为例,其 React Native 代码如下:
export default class Hello extends React.Component {
render() {
return (
<View style={this.props.style}>
<Text
style={this.props.textStyle}
onPress={() => {
console.log('Hi ', this.props.name, ' !')
this.props.textPress && this.props.textPress()
}}
>Hi {this.props.name}!</Text>
</View>
)
}
}
然后我们进入代码目录执行
alita -i ./ -o ../hello-wx/miniprogram_dist
这样在 hello-wx
目录就生成了对应的微信小程序 Hello 组件,然后我们把 miniproram_dist 目录下的package.json 文件移动到上层 hello-wx
目录,修改其 name
字段为 @areslabs/hello-wx
, 发布就可以了。
目录结构如下:
配置文件
module.exports = {
dependenciesMap: {
"@areslabs/hello-rn": "@areslabs/hello-wx"
}
}
这种方式只需要在 dependenciesMap
字段配置就可以了,不需要在 extCompLibs
字段指定了。
使用方式
这种方式对齐的组件在使用的时候,需要引入全路径如 @areslabs/hello-rn/index
(即使是index文件,也不能只引入目录)
import Hello from '@areslabs/hello-rn/index'
class HelloWorld extends React.Component {
render() {
return <View>
<Hello name="y5g"/>
</View>
}
}
这里一定要注意使用 全路径
最后
对于基本 UI 组件库,我们更加推荐手动对齐的方式,因为在手动对齐的时候,你可以使用所有微信小程序的原生 API /组件, 包括使用动画 API 等,能力更加的强大,另外手动对齐更加的自由, 你可以针对不同平台做不同的响应。而且,手动对齐的组件有更出色的性能,当一个组件被频繁使用的时候,更应该采用这种方式。
转化引擎直接转化组件的方式, 更加直接简单,可以用来处理基于基本 UI 组件库封装的高级组件。