React Native屏幕适配,Babel插件无侵入性解决方案

1,248 阅读3分钟

最终方案:无需写pxToDp,直接使用插件即可

npm i @hancleee/babel-plugin-react-native-pxtodp

正文开始>>>

前言

一般设计稿的单位是px,而RN则是dp,这就涉及到一个单位转化的事情。网上的解决方案一般是通过封装👇👇这种px2dp的函数,在每个需要使用到的地方去调用

import { Dimensions } from "react-native";

const uiWidthPx = 750;
export function px2dp(uiElementPx) {
  return (uiElementPx * Dimensions.get("window").width) / uiWidthPx;
}

使用方法:

import { px2dp } from 'xxx';

const styles = StyleSheet.create({
  section: {
    width: px2dp(200),
    height: px2dp(400),
    backgroundColor: "pink",
  }
});

但这种方法有个弊端,就是每个样式里都需要写一个px2dp,很原始朴实的做法。

疑问

是否有一种很优雅的方案能解决屏幕适配的问题呢?

答案有,其实React Native跟React很像,我去参考了一下React是怎么做屏幕适配的,发觉它们的解决方案都是通过插件去实现的,比如postcss-pxtorem、postcss-viewport-units,无需手动一个个去调用。

自动px转dp的Babel插件

动手去写了一个Babel插件去实现自动px转dp,主要实现了

  1. 针对StyleSheet.create里的样式做自动转化;
  2. 针对行内样式的自动转化;
  3. 可对某个文件进行整体豁免,豁免的文件不进行转化;
  4. 可对某个样式进行局部豁免,只对豁免的样式不进行转化。
  5. 可配置,可对设计稿宽度、样式keyName、需要豁免的文件目录等进行配置。

具体的使用方法如下👇👇👇

安装
npm install @hancleee/babel-plugin-react-native-pxtodp -D
配置
module.exports = {
  plugins: [
	// ... 其他配置
	['@hancleee/babel-plugin-react-native-pxtodp',
		{
			uiWidth: 750,
			includes: ['src/views/'],
			excludes: [],
			superIncludes: [],
			extraKeyNames: []
		},
	],
  ],
}
设置特定文件豁免

在文件顶部加上注释 // diable-react-native-px-to-dp-file 则不对该文件做单位转化

如下:

// diable-react-native-px-to-dp-file
const styles = StyleSheet.create({
	container: {
		width: 200,
		height: 400,
	}
});
设置特定代码豁免

在样式前面加上注释 // ignore_px_to_dp 则不对该样式做单位转化

如下:

const styles = StyleSheet.create({
	container: {
		// ignore_px_to_dp
		width: 200,
		// ignore_px_to_dp
		height: 400,
	}
});

相关配置

在babel.config.js 中进行配置

参数名类型说明默认值
uiWidthNumber设计稿宽度750
includesString[]插件生效的文件夹[]
excludesString[]插件不生效的文件夹,优先级高于includes['node_modules']
superIncludesString[]插件生效的文件夹,优先级高于excludes[]
extraKeyNamesString[]插件生效的文件夹[...defaultExtraKeyNames]
/* 默认的extraKeyNames值,检测到这些key会做自动转化 */
// defaultExtraKeyNames
["width",
  "minWidth",
  "maxWidth",
  "height",
  "minHeight",
  "maxHeight",
  "borderRadius",
  "borderBottomWidth",
  "borderBottomStartRadius",
  "borderBottomRightRadius",
  "borderBottomLeftRadius",
  "borderBottomEndRadius",
  "borderTopWidth",
  "borderTopStartRadius",
  "borderTopRightRadius",
  "borderTopLeftRadius",
  "borderTopEndRadius",
  "borderWidth",
  "borderTopWidth",
  "borderRightWidth",
  "borderBottomWidth",
  "borderLeftWidth",
  "margin",
  "marginTop",
  "marginBottom",
  "marginLeft",
  "marginRight",
  "marginHorizontal",
  "marginVertical",
  "padding",
  "paddingTop",
  "paddingRight",
  "paddingBottom",
  "paddingLeft",
  "paddingHorizontal",
  "paddingVertical",
  "top",
  "right",
  "bottom",
  "left",
  "fontSize",
  "lineHeight"]

使用示例 
import { StyleSheet, View } from 'react-native'

const pxToDp = (px) => {
  return px * 2
}

const MyPage = () => {
  const myMargin = 20
  const myPadding = 30
  return (
    <View>
      <View style={{
        width: pxToDp(400),
        height: 20,
        backgroundColor: 'red',
        margin: myMargin || 100,
        padding: `${myPadding}`,
        minHeight: `${10 + 10}`
      }}
      ></View>
      <View
        style={[styles.width, {
          width: 400, 
          // ignore_px_to_dp
          height: 20,
          fontSize: `${fontSize}`,
          backgroundColor: 'green',
          borderRadius: pxToDp(12)
        }, styles.bgBox]}
      />
      <View style={[styles.container, { fontSize: `${fontSize}` }]} />
    </View>
  )
}

const styles = StyleSheet.create({
  width: {
    width: 100,
  },
  bgBox: {
    height: 10,
  },
  container: {
    fontSize: "10rpx",
    // ignore_px_to_dp
    width: 200,
    height: 400,
	// ignore_px_to_dp
    borderRadius: 2,
    // this is bg color
    backgroundColor: "pink",
    minHeight: `${10 + 10}`,
    minWidth: pxToDp(293)
  }
});

babel转化后的代码

import { Dimensions } from "react-native";
import { StyleSheet, View } from 'react-native';
const pxToDp = px => {
  return px * 2;
};
const MyPage = () => {
  const myMargin = 20;
  const myPadding = 30;
  return <View>
      <View style={{
      width: pxToDp(400),
      height: Dimensions.get("window").width * 20 / 750,
      backgroundColor: 'red',
      margin: Dimensions.get("window").width * (myMargin || 100) / 750,
      padding: Dimensions.get("window").width * (`${myPadding}`) / 750,
      minHeight: Dimensions.get("window").width * (`${10 + 10}`) / 750
    }}></View>
      <View style={[styles.width, {
      width: Dimensions.get("window").width * 400 / 750,
      // ignore_px_to_dp
      height: 20,
      fontSize: Dimensions.get("window").width * (`${fontSize}`) / 750,
      backgroundColor: 'green',
      borderRadius: pxToDp(12)
    }, styles.bgBox]} />
      <View style={[styles.container, {
      fontSize: Dimensions.get("window").width * (`${fontSize}`) / 750
    }]} />
    </View>;
};
const styles = StyleSheet.create({
  width: {
    width: Dimensions.get("window").width * 100 / 750
  },
  bgBox: {
    height: Dimensions.get("window").width * 10 / 750
  },
  container: {
    fontSize: "10rpx",
    // ignore_px_to_dp
    width: 200,
    height: Dimensions.get("window").width * 400 / 750,
    // ignore_px_to_dp
    borderRadius: 2,
    // this is bg color
    backgroundColor: "pink",
    minHeight: Dimensions.get("window").width * (`${10 + 10}`) / 750,
    minWidth: pxToDp(293)
  }
});