本页内容多数整理自趣店 FED 开源的首个 Taro 多端统一实例 - 网易严选(小程序 + H5 + React Native)- https://github.com/js-newbee/taro-yanxuan。
样式是实现多端编译的核心挑战,因为 RN 端的样式只是实现了 CSS 的一个子集,具体可参考 React-Native 样式指南(这个指南的版本有点旧了,内容不一定准确)。
因此,要做到适配 RN,首先得遵循 React Native 的约束:
- 只用 flex 布局
- 只用 class 选择器,不用标签、子代、伪类等选择器
- 样式单位只使用 px,部分属性支持 %(相关说明)
- 图片统一用 Image 标签引入,不用 background 的方式
- 文本要用 Text 标签包裹,文本的样式要加在 Text 标签上
对于第 1 点只用 flex 布局,需要注意 RN 的 View 标签有如下默认样式:
view {
display: flex;
flex-direction: column;
position: relative;
box-sizing: border-box;
}
其中 flex 的主轴与 Web 默认值不一致,因此凡是用到 display: flex
的地方都需要声明主轴,且需要设置部分全局样式:
view, div {
box-sizing: border-box;
}
button {
/* 去掉微信小程序 button 的边框 */
&:after {
display: none;
}
}
对于第 2 点只用 class 选择器,建议使用 BEM 管理样式:
.comp {}
.comp__list {}
.comp__list-item {}
.comp__list-item--last {}
对于第 3 点,是由于 RN 不支持 background-image,需要使用 Image 标签配合 postion 实现
// 建议自行封装个 ImageBackground 组件解决此类需求
<View>
<Image style={{ position: 'absolute' }} />
</View>
最后,对于需要覆盖组件样式的情况,建议使用 style:
// 组件调用
<Comp textStyle={{ color: 'red' }} />
// Comp 组件
<View>
<Text style={this.props.textStyle}>Hello</Text>
</View>
之所以选用这样的方案,是基于微信小程序、RN 自身的限制及 Taro 目前支持的程度所作出的妥协,具体可参考 微信小程序的限制 中的详细说明,在此不展开。
由于 RN 不支持 fixed 定位,有 fixed 定位的场景就需要改用 ScrollView + absolute 实现:
<View style={{ position: 'relative' }}>
<ScrollView style={{ height: 'xxx' }}>内容区域</ScrollView>
<View style={{ position: 'absolute' }}>固定区域</View>
</View>
内容区域需要用到 ScrollView,而 ScrollView 又需要设置高度,就需要去计算页面可用高度,但该值在各端上不太一致,需要根据 systemInfo 进行二次计算
// 各端返回的 windowHeight 不一定是最终可用高度(例如可能没减去 statusBar 的高度),需二次计算
const NAVIGATOR_HEIGHT = 44
const TAB_BAR_HEIGHT = 50
function getWindowHeight(showTabBar = true) {
const info = Taro.getSystemInfoSync()
const { windowHeight, statusBarHeight, titleBarHeight } = info
const tabBarHeight = showTabBar ? TAB_BAR_HEIGHT : 0
if (process.env.TARO_ENV === 'rn') {
return windowHeight - statusBarHeight - NAVIGATOR_HEIGHT - tabBarHeight
}
if (process.env.TARO_ENV === 'h5') {
return `${windowHeight - tabBarHeight}px`
}
return `${windowHeight}px`
}
Taro 的差异处理一般是使用 process.env.TARO_ENV
进行判断,在编译时会自动去掉非当前编译环境的内容:
// 源代码
if (process.env.TARP_ENV === 'weapp') { // 微信小程序
console.log(1)
} else if (process.env.TARP_ENV === 'alipay') { // 支付宝小程序
console.log(2)
}
// 例如编译支付宝小程序时,编译之后的代码
{
console.log(2)
}
另外一种情况,则是要根据不同环境引入不同组件,Taro 有引入了 Tree skaing,配合上述环境判断方式,很方便能够实现:
import Comp from './comp'
import CompAlipay from './comp.alipay'
render() {
return (
process.env.TARO_ENV === 'alipay' ? <CompAlipay /> : <Comp />
)
}
当编译成支付宝小程序时,只会保留 CompAlipay 组件,不会将 Comp 引入,也就避免引入了其他端不支持的内容。
若要在 Sass 中使用别名,如 @styles
指向 src/styles
,需要设置 h5.sassLoaderOption,具体配置见 设置路径别名 alias。
跨域最好的解决方案是设置 CORS,如果没有条件,在开发时要解决跨域问题可以配置 devServer 的 proxy:
// config/dev.js
// 需要把 package.json 中 scripts 的 "dev:h5": "..." 改成:
// "dev:h5": "CLIENT_ENV=h5 npm run build:h5 -- --watch"
const isH5 = process.env.CLIENT_ENV === 'h5'
const HOST = '"http://xxx"'
module.exports = {
defineConstants: {
HOST: isH5 ? '"/api"' : HOST
},
h5: {
devServer: {
proxy: {
'/api/': {
target: JSON.parse(HOST),
pathRewrite: {
'^/api/': '/'
},
changeOrigin: true
}
}
}
}
}
Taro 编译 H5 时静态资源是固定的文件名:
<!-- css -->
<link href="/css/app.css" rel="stylesheet"></head>
<!-- js -->
<script type="text/javascript" src="/js/app.js"></script>
<!-- 图片 -->
background:url(/static/images/bg.png)
但这样不利于缓存、版本控制,建议配置 webpack 给静态资源带上 hash:
// config/index.js
h5: {
publicPath: '/',
staticDirectory: 'static',
output: {
filename: 'js/[name].[hash].js',
chunkFilename: 'js/[name].[chunkhash].js'
},
imageUrlLoaderOption: {
limit: 5000,
name: 'static/images/[name].[hash].[ext]'
},
miniCssExtractPluginOption: {
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[name].[chunkhash].css'
}
}
(有待验证新版本是否已解决了该问题)支付宝小程序的网络请求默认的 content-type 是 application/x-www-form-urlencoded,若将 content-type 设置为 application/json,还需要手动将 data 转为字符串,否则会出现开发者工具中网络请求正常,但体验版网络请求异常的情况
Taro.request({
url,
data: method === 'POST' ? JSON.stringify(data) : data,
method,
header: { 'Content-Type': 'application/json' }
})