指导
Taro 3.2正式版将于3月底发布,技术讲解文章也在紧锣密鼓地进行中。 Taro3.2 适配 React Native 系列文章的第一部分提到了运行时架构的解读。 本文将为您带来风格改编的内幕。
背景
从Taro 3开始,58同城成为Taro的战略合作伙伴,负责Taro 3的React Native部分的开发和推广。我们总结了京东Taro同学这些年的适配经验,以及技术内部使用的React Native的积累。 为了更好地提升开发体验,我们提出了一种新的架构[0]。
新的架构设计下,之前的样式处理方案需要重新设计和插入。 在接入的过程中,需要考虑React-Native风格管理和风格的差异,框架提供了人性化的兼容方案,以便在工程开发中更好的组织代码。
React-Native的样式支持基本上实现了CSS的子集,但属性名称并不完全相同。 最大的区别是不支持级联样式表,并且无法使用类读取静态样式webpack适配,因此很难适应Web。
适应问题
在开始接触样式系统之前,我们先来看一段代码,常见的React Native代码设置样式:
import React from "react";
import { StyleSheet, Text, View } from "react-native";
const App = () => (
React Native
);
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#eaeaea"
},
title: {
fontSize: 30,
fontWeight: "bold"
}
});
export default App;
常见Taro其他终端代码设置样式:
// app.js
import React from "react";
import { Text, View } from "@tarojs/components";
import "./app.css";
const App = () => (
React Native
);
export default App;
// app.css
.container {
flex: 1;
background-color: "#eaeaea";
}
.title {
font-size: 30;
font-weight: "bold";
}
通过分析里面的代码,Taro 适配 React Native 代码有如下和衍生的问题:
1、React Native代码(样式代码)与其他平台上样式管理的区别
React Native 风格仅支持声明式风格
其他端,比如小程序端webpack适配,可以通过类读取导出的样式,同时使用声明式样式和布局
2.如何将通用样式文件转换为对象
3.如何传递组件样式
React Native使用样式来传递,其他端使用类。 理论上来说,是受到书写风格的制约,这样三端才能更好的适配,CSS In JS 风格的方案更适合。
React Native支持的样式有限,如何处理不支持的样式
4、如何灵活处理平台类型的特殊逻辑和特殊风格
Taro处理过跨端文件,但仅限于脚本文件。 我们希望样式文件也可以跨端,希望ios和android的差异文件能够匹配。 例如Shadow样式只能在IOS上生效,Android则需要使用elevation来代替。 当然可以重画,但有时工程需要可以清晰区分两端。
总体设计
流程图
风格适配设计流程图:
下面是该过程的解释,并有相应的流程图表示。
核心流程:
样式代码作为对象处理
风格校准
标签属性className被处理成style
展开补充流程:
4.多种预编译语言的适配
5.更灵活的跨平台工程适配
设计实施
样式代码作为对象处理
如果我们需要样式文件在 React Native 中可用,我们首先需要将样式 CSS 处理为样式对象。 这里的第一步是将 CSS 解析为 AST 树 [1]:
body {
background: #eee;
color: #888;
}
遍历解析后的 AST 规则(选择器),然后遍历上面的声明(样式属性),使用 css-to-react-native[2] 将 CSS 属性转换为 React Native 样式属性,并在选择器中命名对象设置转换后的属性值和属性名称。 最后,使用一个大的样式对象来存储选择器命名的对象。
将样式处理为 JS 对象 [3]:
这一步在源码实现中将css-to-react-native和CSS解析器封装到taro-css-to-react-native NPM包中。
最后导入转换后的样式对象:
import { StyleSheet } from 'react-native'
const cssObject = {/**/}
export default StyleSheet.create(cssObject)
标签属性className被处理成style
上面提到的文件导出处理和多文件合并都是通过 Babel 插件实现的,而 className 转成 style 的处理也是通过 AST 修改 jsxElement 的属性。 以下是插件实现属性转换的核心逻辑:
上面就是如何将通常的CSS语法文件处理成可以被React Native标签的style属性接收的样式对象。 实际开发生产中,更推荐使用样式预处理器。 毕竟,优秀的句子糖可以提高劳动人民的生产力。
多种预编译语言的适配
格子风格
格子风格
设计过程中,所有样式文件都经过预处理语言PostCSS处理,从而可以集中处理一些常见的事务,如stylelint、条件编译、单元处理等。 stylelint检测用于校准样式书写,使用stylelint插件,通过stylelint-config-taro-rn[9]配置规则约束不支持的样式书写,例如校准组合选择器。
验证样式对象
React Native支持的样式是有限的,编写一些不支持的样式会导致应用程序报错或者崩溃,所以我们需要一个函数来校准代码并在控制台复制样式错误日志。
当编写不支持的样式属性时:
在控制台上提示用户错误信息:
更灵活的跨平台工程适配
总结
通过使用之前的一些技术手段,Taro 在开发 React Native 的过程中能够有比较好的体验。 但在React Native样式属性作为样式管理的限制下,对其他端样式的适配仍然存在限制。
1.未来优化的方向以及仍然存在的样式限制——选择器约束
在 JSX 层,只有 className 被转换为样式,这个限制导致选择器上只能使用类选择器。
在CSS转换为对象的层面,类选择器直接转换为对象,并且不支持组合选择器,这就限制了组合选择器的使用。
在预处理语言的嵌套写法中,不能使用可编译为组合选择器的写法,但可以使用可编译为BEM[11]的嵌套写法。
2.关于组合选择器的一点思考
我。 原子CSS
Atomic CSS[12]是Facebook今年5月提出的样式管理解决方案。 官方表示,使用 Atomic CSS 使首页 CSS 代码量减少了 80%。 感兴趣的朋友可以访问Facebook主页查看。
原子CSS意味着每个样式属性设置都对应一个类选择器来控制,避免重复的样式代码。 例如,如果你使用公共样式,只想使用它的部分样式,那么你会降低一级(后代选择器)来重绘你不想使用的样式,这在一定程度上减少了代码量。 如果项目的样式使用Atomic CSS,那么组合选择器的支持就变得不必要了。
二. JS 中的 CSS
JS[13]中的CSS使用语法糖来定义JS中的样式。 本质上,样式是内联编写的。 这与React Native的样式管理理念是一致的,也是JS爱好者中CSS强烈推荐的。 不过官方小程序中提到,style接收动态样式,运行时会解析,会影响渲染速率。
参考:
[0]
[1]
[2]
[3]
[4]%2Freact-native/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/tests/index.spec.js
[5]
[6]
[7]
[8]
[9]
[10]
[11]
[12]
[13]
关于作者:
王新建:房地产事业群--新房后端技术部
汇总的优点
在最新的Vue3版本中,也使用rollup作为打包工具。 与webpack相比,rollup要轻量很多。 不用摇树就能填满吞咽,是个不错的选择。 最大的用处就是打包制作库文件,比如sdk.js之类的。 虽然webpack也可以做到webpack打包优化,但是webpack比较重,而且打包后的文件有一些webpack的内部代码,比如__webpack__require这样的函数定义,给人一种不干净的感觉。 不过rollup创建的包非常干净,没有任何其他冗余代码。
如何使用
1.安装汇总
npm i rollup -g
二、几种使用方法
1.命令行操作
rollup src/main.js -o rel/bundle.js -f cjs //将main.js(es5)编译输出至bundle.js(commonjs)
2、配置文件方式
//新建一个rollup.config.js
export default {
input: 'src/main.js', // 入口文件
output: { // 输出 options
file: 'bundle.js', // 输出文件名
format: 'cjs' // 输出格式
}
}
//执行
rollup -c //当你的配置文件另有其名(dev),执行 rollup -c rollup.config.dev.js
3.模块模式(方便与gulp等其他工具配合)
//rollup.config.js
var rollup = require( 'rollup' );
var babel = require('rollup-plugin-babel');
rollup.rollup({
entry: 'src/main.js',
plugins: [ babel() ]
}).then( function ( bundle ) {
bundle.write({
format: 'umd',
moduleName: 'main', //umd或iife模式下,若入口文件含 export,必须加上该属性
dest: 'rel/bundle.js'
});
});
//命令行执行
node rollup.config.js
相关配置说明
1. 输入
入口文件地址
二、输出
output:{
file:'bundle.js', // 输出文件
format: 'cjs, // 五种输出格式:amd / es6 / iife / umd / cjs
name:'A', //当format为iife和umd时必须提供,将作为全局变量挂在window(浏览器环境)下:window.A=...
sourcemap:true //生成bundle.map.js文件,方便调试
}
三、插件
最常用的是babel插件。 比较烦人的是,与 webpack 不同,babel 的预设可以直接写在配置文件中,但你还是要独立写“src/.babelrc”(注意,我们可以写在 src 下,而且不一定要写在 src 下)放在项目根目录下),并确保安装插件:npm i rollup-plugin-babel
//rollup.config.js
import babel from 'rollup-plugin-babel';
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [ // 增加 plugins
babel({
exclude: 'node_modules/**' // 不对node_modules进行编译
})
]
}
//.babelrc
{
"presets": [
["@babel/env", {"modules": false}]
]
}
四、外部
external: ['react', 'redux'],// 告诉rollup,不打包react,redux;将其视为外部依赖
五、全球
globals: {
react: 'React', // 这跟external 是配套使用的,指明global.React即是外部依赖react
redux: 'Redux'
}
主要依赖
rollup 很难识别node_modules 中的包。 您需要安装插件 rollup-plugin-node-resolvewebpack打包优化,然后在插件中使用它。
node_modules 中的大部分包都是 commonjs 格式。 要在 rollup 中使用它们,必须首先将它们转换为 ES6 句型。 为此,需要安装插件 rollup-plugin-commonjs。
附上react-redux开源项目的rollup配置文件
import nodeResolve from 'rollup-plugin-node-resolve' // 帮助寻找node_modules里的包
import babel from 'rollup-plugin-babel' // rollup 的 babel 插件,ES6转ES5
import replace from 'rollup-plugin-replace' // 替换待打包文件里的一些变量,如 process在浏览器端是不存在的,需要被替换
import commonjs from 'rollup-plugin-commonjs' // 将非ES6语法的包转为ES6可用
import uglify from 'rollup-plugin-uglify' // 压缩包
const env = process.env.NODE_ENV
const config = {
input: 'src/index.js',
external: ['react', 'redux'], // 告诉rollup,不打包react,redux;将其视为外部依赖
output: {
format: 'umd', // 输出 UMD格式,各种模块规范通用
name: 'ReactRedux', // 打包后的全局变量,如浏览器端 window.ReactRedux
globals: {
react: 'React', // 这跟external 是配套使用的,指明global.React即是外部依赖react
redux: 'Redux'
}
},
plugins: [
nodeResolve(),
babel({
exclude: '**/node_modules/**'
}),
replace({
'process.env.NODE_ENV': JSON.stringify(env)
}),
commonjs()
]
}
if (env === 'production') {
config.plugins.push(
uglify({
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
warnings: false
}
})
)
}
export default config
发表评论