如何将react-native的style样式转换成css样式
背景: 我们总是倾向于一套代码走天下,正所谓一招鲜,吃遍天。刚接触RN项目的时候,常常为RN style样式的写法而头痛,等到熟悉了RN样式写法时,一个web端项目从天而降,于是,你又不得不操练起日渐陌生的css写法。更过分的是,有时你还得在RN样式和css样式之间来回切换,时刻处于水深火热之中。抬首间,不禁叹息一声:人间不值得。
一、准备工作
本文中详细讲解sass样式的转换,其它诸如less、css、PostCss的转换请参考:(https://github.com/kristerkari/react-native-css-modules)
这里面有较为详细说明。
我们需要准备四个依赖:
react-native-sass-transformer 将 Sass 转换为与 React Native 兼容的样式对象并处理实时重新加载
babel-plugin-react-native-platform-specific-extensions 如果磁盘上存在特定于平台的文件,则将 ES6 导入语句转换为特定于平台的 require 语句
babel-plugin-react-native-classname-to-style 将 className 属性转换为 style 属性
二、 创建一个React-Native APP
参考官方文档创建即可。
三、安装依赖
yarn add babel-plugin-react-native-classname-to-style babel-plugin-react-native-platform-specific-extensions react-native-sass-transformer node-sass --dev
四、设置babel配置
对于React Native v0.57 或者更新版本
.babelrc
(or babel.config.js
)
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
"react-native-classname-to-style",
[
"react-native-platform-specific-extensions",
{
"extensions": ["scss", "sass"]
}
]
]
}
对于React Native v0.57以下版本
{
"presets": ["react-native"],
"plugins": [
"react-native-classname-to-style",
[
"react-native-platform-specific-extensions",
{
"extensions": ["scss", "sass"]
}
]
]
}
五、设置Metro配置
在项目根目录下新增一个metro.config.js的文件
const { getDefaultConfig } = require("metro-config");
module.exports = (async () => {
const {
resolver: { sourceExts }
} = await getDefaultConfig();
return {
transformer: {
babelTransformerPath: require.resolve("react-native-sass-transformer")
},
resolver: {
sourceExts: [...sourceExts, "scss", "sass"]
}
};
})();
对于React Native v0.57以下版本,在根目录下新增rn-cli.config.js文件
module.exports = {
getTransformModulePath() {
return require.resolve("react-native-sass-transformer");
},
getSourceExts() {
return ["js", "jsx", "scss", "sass"];
}
};
六、接下来你就可以愉快的使用sass来写样式
style.scss
.container {
flex: 1;
justify-content: center;
align-items: center;
background-color: #f5fcff;
}
.blue {
color: blue;
font-size: 30px;
}
你既可以使用className来写样式,也可以使用style
import React, { Component } from "react";
import { Text, View } from "react-native";
import styles from "./styles.scss";
const BlueText = () => {
return Blue Text;
};
export default class App extends Component<{}> {
render() {
return (
);
}
}
七、为sass配置TypeScript
在ts项目中,为sass配置类型提示很有必要。首先我们需要把在第三步和第五步中把react-native-sass-transformer依赖替换成react-native-typed-sass-transformer
为了让className 属性正常工作,我们还需要安装下面的依赖包:
对于React Native v0.57 或者更新版本
yarn add typescript --dev
老版本:
yarn add react-native-typescript-transformer typescript --dev
在package.json中添加下面依赖,然后运行yarn命令
"@types/react-native": "^0.57.55",
如果版本versions >=0.52.4
"@types/react-native": "kristerkari/react-native-types-for-css-modules#v0.57.55",
你也可以删掉版本号,但是不建议这样做
"@types/react-native": "kristerkari/react-native-types-for-css-modules",
如果你使用的rn版本>=0.57
,这样就OK了,如果不是,请参照文档:github.com/kristerkari…
八、原生提供的属性和方法如何添加到scss文件中,如何做不同机型的适配?
我们需要自定义一个transform用于sass文件的转换。
在metro.config.js
文件中,修改如下:
const { getDefaultConfig } = require("metro-config");
module.exports = (async () => {
const {
resolver: { sourceExts }
} = await getDefaultConfig();
return {
transformer: {
babelTransformerPath: require.resolve("./transformer.js")
},
resolver: {
sourceExts: [...sourceExts, "scss", "sass"]
}
};
})();
metro.config.js
const upstreamTransformer = require("metro-react-native-babel-transformer");
const sassTransformer = require("react-native-typed-sass-transformer");
const DtsCreator = require("typed-css-modules");
const css2rn = require("css-to-react-native-transform").default;
const creator = new DtsCreator();
/** 引入原生的属性和方法 */
const preImport = `
import { PixelRatio, Dimensions, StatusBar, Platform } from 'react-native';
let DEVICE_WIDTH = Dimensions.get('window').width;
let DEVICE_HEIGHT = Dimensions.get('window').height;
let S=(designPx) => {
return PixelRatio.roundToNearestPixel((designPx / 750) * DEVICE_WIDTH);
}
`
function renderCSSToReactNative(css) {
return css2rn(css, { parseMediaQueries: true });
}
/** px转换成pt,做一个标记 */
function pxToPtForMark(code){
let newCode=code;
try {
newCode=code.replace(/([0-9]+)px/g,(...arg)=>{
const px=Number(arg[1]);
return `${px}pt`;
})
} catch (error) {
throw Error('样式解析错误');
}
return newCode;
}
/** px 或者 pt单位的适配 需要注意正负值 */
function unitAdaption(code){
let newCode=code;
try {
newCode=code.replace(/"([-+]{0,1})([0-9]+)pt"/g,(...arg)=>{
const px=arg[1]+arg[2];
return `S(${px})`;
})
} catch (error) {
throw Error('样式解析错误');
}
return newCode;
}
/** vh和vw的适配 */
function vhAndVwAdaption(code){
let newCode=code;
try {
newCode=code.replace(/"([0-9]+)vw"/g,(...arg)=>{
const vw=Number(arg[1]);
return `${vw/100} * DEVICE_WIDTH`;
}).replace(/"([0-9]+)vh"/g,(...arg)=>{
const vh=Number(arg[1]);
return `${vh/100} * DEVICE_HEIGHT`;
});
} catch (error) {
throw Error('样式解析错误');
}
return newCode;
}
function isPlatformSpecific(filename) {
var platformSpecific = [".native.", ".ios.", ".android."];
return platformSpecific.some(name => filename.includes(name));
}
module.exports ={
transform:async function({ src, filename, options }) {
if (filename.endsWith(".scss") || filename.endsWith(".sass")) {
let newSrc=pxToPtForMark(src);
let css =await sassTransformer.renderToCSS({ src:newSrc, filename, options });
let cssObject = renderCSSToReactNative(css);
let cssObjectStr=JSON.stringify(cssObject);
cssObjectStr=unitAdaption(cssObjectStr);
cssObjectStr=vhAndVwAdaption(cssObjectStr);
//特殊文件直接return
if (isPlatformSpecific(filename)) {
return upstreamTransformer.transform({
src: preImport+";module.exports = " + cssObjectStr,
filename,
options
});
}
//一般文件创建types文件之后再return
return creator.create(filename, css).then(content => {
return content.writeFile().then(() => {
return upstreamTransformer.transform({
src: preImport+";module.exports = " + cssObjectStr,
filename,
options
});
});
});
} else {
return upstreamTransformer.transform({ src, filename, options });
}
}
}
在scss文件中,px单位转换成style对象时,会自动去掉,如下:
.unpaidRemind {
position: absolute;
bottom: 56px;
right: 28px;
background-color: #999;
padding: 20px;
border-radius: 16px;
}
.unpaidRemindText {
color: rgba(255, 255, 255, 0.9);
font-size: 28px;
}
转换之后变成
{
unpaidRemind: {
position: 'absolute',
bottom: 56,
right: 28,
backgroundColor: '#999',
padding: 20,
borderRadius: 16,
},
unpaidRemindText: {
color: 'rgba(255,255,255,0.9)',
fontSize: 28,
},
}
我们的目标是在在转后之后把所有px都换成我们的适配方法:
{
unpaidRemind: {
position: 'absolute',
bottom: S(55),
right: S(28),
backgroundColor: '#999',
padding: S(20),
borderRadius: S(16),
},
unpaidRemindText: {
color: 'rgba(255,255,255,0.9)',
fontSize: S(28),
},
}
最终拿到的代码类似于这样,它是可以直接执行的,同样的道理,我们可以注入更多的RN属性到我们的文件中,这取决于我们是否需要这些属性。
import { PixelRatio, Dimensions, StatusBar, Platform } from 'react-native';
let DEVICE_WIDTH = Dimensions.get('window').width;
let DEVICE_HEIGHT = Dimensions.get('window').height;
let S=(designPx) => { return PixelRatio.roundToNearestPixel((designPx / 750) * DEVICE_WIDTH); }
module.exports ={
unpaidRemind: {
position: 'absolute',
bottom: S(55),
right: S(28),
backgroundColor: '#999',
padding: S(20),
borderRadius: S(16),
},
unpaidRemindText: {
color: 'rgba(255,255,255,0.9)',
fontSize: S(28),
},
}
pxToPtForMark
方法将px
转换成pt
,这一步主要是方便我们后续把pt
转成S(28)
这种形式,unitAdaption
方法就是实现这一功能。为什么不是直接把px
转成S(28)
这种形式?renderCSSToReactNative
会把px
转没掉,我们无法区分flex:1
这种属性和fontSize:28
的区别,但是它不会吧pt
转没,而是变成fontSize:"28pt"
.
为了使vh
和vw
这两个单位能够生效,我们使用vhAndVwAdaption
方法做了处理,width:100vw
最后会变成width:100/100 * DEVICE_WIDTH
,其中DEVICE_WIDTH
就是我们前面注入的设备宽度这个变量。
九、referenceError:'xx' is not defined 报错
const Button=(props)=>{
const {style}=props;
return
}
const Page=()=>{
return
}
//以上写法会导致报referenceError:'xx' is not defined,而且是非必现,偶尔会报
//要这样写
const Button=(props)=>{
const {style}=props;
return
}
const Page=()=>{
return
}
链接:https://juejin.cn/post/6995883216695459870