引子
时间过得好快, 不知不觉, 加入脉脉已经快一个月了. 之前的工作中主要做一些 PC,H5,Node 相关的开发, 开发 RN 还是头一次接触, 感觉还挺好玩的.
为什么要设置默认字体?
前两天 Fix 了一个 RN 端的 BUG, 同事的小米 10Pro 手机上发现文字被遮挡. 如下图所示:
不仅是小米, 一些 Android 的其他机型也会遇到类似的问题. 因为 Android 手机厂商很多很多, 不像 iPhone 只有一家公司, 默认字体是不统一的. 这时候如果组件没有设置字体, 就会使用手机的默认字体. 而有些字体, 比如 "OnePlus Slate","小米兰亭 pro" 在使用 Text 组件渲染的时候, 就会出现被遮挡的问题.
那么, 如何解决这个问题呢?
如何实现全局字体的修改
自定义组件
第一种思路比较简单, 可以封装 Text 组件, 针对 Android 系统设置默认字体.
首先, 创建一个新文件, 命名为: CustomText.JS.
- // CustomText.JS
- import React from "react";
- import { StyleSheet, Text, Platform } from "react-native";
- // Fix Android 机型文字被遮挡的问题
- const defaultAndroidStyles = StyleSheet.create({
- text: {
- fontFamily: ""
- }
- });
- // 这里针对 web 加一个简单的样式, 方便测试
- const defaultWebStyles = StyleSheet.create({
- text: {
- color: "#165EE9"
- }
- });
- const CustomText = (props) => {
- let customProps = {
- ...props
- };
- if (Platform.OS === "android") {
- customProps.style = [defaultAndroidStyles.text, props.style];
- }
- if (Platform.OS === "web") {
- customProps.style = [defaultWebStyles.text, props.style];
- }
- delete customProps.children;
- const kids = props.children || props.children === 0 ? props.children : null;
- return <Text {...customProps}>{kids}</Text>;
- };
- export default CustomText;
接下来, 在 App.JS 中使用这个组件.
- // App.JS
- import React from 'react';
- import { StyleSheet, View, Text } from 'react-native';
- import { CustomText } from './CustomText';
- const App = () => {
- return (
- <View style={styles.container}>
- <Text style={styles.text}> 使用 Props 传递 style 样式 </Text>
- <CustomText > 使用 CustomText 自带 style 样式 </CustomText>
- </View>
- );
- };
- const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: '#070825'
- },
- text: {
- color: '#ff0000'
- }
- });
如下图所示, 会自带默认的蓝色, 而 则需要手动传递颜色等样式. 对于 如果我们需要修改颜色, 正常传递 style 属性即可.
但是这种处理方式需要将所有引入, 使用组件的都改为 , 还是比较麻烦的, 有没有什么更加方便的方法呢?
覆盖 Text 组件的 render 方法
首先, 增加一个函数, 来覆盖 Text 的 render 方法:
- // util.JS
- import { Text, Platform } from "react-native";
- export const setCustomText = () => {
- const TextRender = Text.render;
- let customStyle = {};
- // 重点, Fix Android 样式问题
- if (Platform.OS === "android") {
- customStyle = {
- fontFamily: ""
- };
- }
- // 为了方便演示, 增加绿色字体
- if (Platform.OS === "web") {
- customStyle = {
- lineHeight: "1.5em",
- fontSize: "1.125rem",
- marginVertical: "1em",
- textAlign: "center",
- color: "#00ca20"
- };
- }
- Text.render = function render(props) {
- let oldProps = props;
- props = { ...props, style: [customStyle, props.style] };
- try {
- return TextRender.apply(this, arguments);
- } finally {
- props = oldProps;
- }
- };
- };
这里参考了 Ajackster/react-native-global-props 中 setCustomText 的实现.
然后在 App.JS 中, 调用 setCustomText 函数即可.
- // App.JS
- import React from 'react';
- import { StyleSheet, View, Text } from 'react-native';
- import { CustomText } from './CustomText';
- import { setCustomText } from "./util";
- setCustomText();
- const App = () => {
- return (
- <View style={styles.container}>
- <Text > 通过调用 utils.setCustomText() 修改 </Text>
- <Text style={styles.text}> 使用 Props 传递 style 样式 </Text>
- <CustomText > 使用 CustomText 自带 style 样式 </CustomText>
- </View>
- );
- };
- const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: '#070825'
- },
- text: {
- color: '#ff0000'
- }
- });
如下图所示, 我们新增了一个 组件, 没有传递任何属性, 但是可以看到, 它是绿色的~
仅仅需要执行一遍这个函数, 就可以影响到所有组件的 render 方法, 我们不需要再导入 组件了. 这种方式真的可以帮助我们一劳永逸的解决这个问题!
demo 地址:
原理浅析
React Native Text 组件
Text 组件是一个类组件, 在它的 render 方法中, 首先解构了 props 属性, 然后根据是否存在祖先节点, 以及内部的状态合并 props 属性.
render 方法经过 babel 的编译之后, 会转换成 React.createElement 所包裹的语法树, 从而转换成虚拟的 Dom 树. 这就可以联想到另一个 API :
- React.cloneElement(
- element,
- [props],
- [...children]
- )
我们在覆盖 Text.render 方法时, 只需要使用 Text.prototype.render.call 得到之前的节点, 更新 props , 并调用 React.cloneElement 得到一个新的节点返回即可.
- import React from 'react';
- import { StyleSheet, Text } from 'react-native';
- const styles = StyleSheet.create({
- defaultFontFamily: {
- fontFamily: 'lucida grande',
- },
- });
- export default function fixOppoTextCutOff() {
- const oldRender = Text.prototype.render;
- Text.prototype.render = function render(...args) {
- const origin = oldRender.call(this, ...args);
- return React.cloneElement(origin, {
- style: [styles.defaultFontFamily, origin.props.style],
- });
- };
- }
搜索官方 issue, 会找到类似的问题:, 就是用的这种解决思路.
react-native-global-props 的实现
https://github.com/Ajackster / 是一个可以添加默认组件属性的库.
下面摘自 setCustomText.JS
- import { Text } from 'react-native'
- export const setCustomText = customProps => {
- const TextRender = Text.render
- const initialDefaultProps = Text.defaultProps
- Text.defaultProps = {
- ...initialDefaultProps,
- ...customProps
- }
- Text.render = function render(props) {
- let oldProps = props
- props = { ...props, style: [customProps.style, props.style] }
- try {
- return TextRender.apply(this, arguments)
- } finally {
- props = oldProps
- }
- }
- }
它覆盖了 Text 组件的静态属性: defaultProps 和 render 方法. 这里不一样的是, 它没有借助 React.cloneElement 返回一个新的节点, 而是在返回结果的前后, 修改 props 中的 style 属性, 这里等同于修改
arguments[0] 的值, 因为他们的引用相同. 并在最后重置 props, 避免 props 被污染. 可以看出, 这种方式实现的更加巧妙.
styled-components CSS.Text 为什么会受影响
styled-components 是一个 React 的第三方库, 是 CSS in JS 的优秀实践. 它对于 React Native 也有着不错的支持. 因为 React Native 修改样式只能通过修改 style 属性来完成, 所以 CSS in JS 的方案对于 React Native 项目来说有着天然的优势.
对于 React 项目, styled-components 会修改 className 属性来达到修改样式的目的; 而对于 React Native, 则是使用下面的方法, 修改组件 props 中的 style 属性来达到目的.
- propsForElement.style = [generatedStyles].concat(props.style || []);
- propsForElement.ref = refToForward;
- return createElement(elementToBeCreated, propsForElement);
但是, 这个修改的过程一定是在我们重写 render 函数之前完成的. 所以, 上面那个方法修改 style 对于 styled-components 创建的 React Native 组件同样适用.
结语
关于如何修改 React Native 的全局样式的讨论暂时告一段落了. 第一次发现还可以以这样的方式修改 React 的 render 函数的属性, 感觉还是比较神奇的. 也是第一次尝试写这种偏原理探究的文章, 如果有写的不对的地方, 或者想和我交流的话, 欢迎评论留言哈~
PS: 对脉脉感兴趣的小伙伴, 欢迎发送简历到 mailto:496691544@qq.com , 我可以帮忙内推~
参考
React-Native Text 文档 https://reactnative.cn/docs/text
Two-way to change default font family in React Native
一文解决 RN0.58 部分安卓手机 text 显示不全问题 segmentfault.com/a/119000002...
react-native-global-props
styled-components 源码阅读: https://github.com/wangpin34/blog/issues/49
ReactNative 源码篇: 源码初识:
来源: https://segmentfault.com/a/1190000039002723