时间过得好快,不知不觉,加入脉脉已经快一个月了。以前的工做中主要作一些 PC、H五、Node 相关的开发,开发RN仍是头一次接触,感受还挺好玩的。😂css
前两天Fix了一个RN端的BUG,同事的小米10Pro手机上发现文字被遮挡。以下图所示:react
不只是小米,一些 Android 的其余机型也会遇到相似的问题。由于 Android 手机厂商不少不少,不像 iPhone 只有一家公司,默认字体是不统一的。这时候若是组件没有设置字体,就会使用手机的默认字体。而有些字体,好比 “OnePlus Slate”、“小米兰亭pro” 在使用 Text 组件渲染的时候,就会出现被遮挡的问题。android
那么,如何解决这个问题呢?git
第一种思路比较简单,能够封装 Text 组件,针对 Android 系统设置默认字体。github
首先,建立一个新文件,命名为: CustomText.js。web
// 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 中使用这个组件。segmentfault
// 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 属性便可。react-native
可是这种处理方式须要将全部引入、使用组件的都改成 ,仍是比较麻烦的,有没有什么更加方便的方法呢?babel
首先,增长一个函数,来覆盖 Text 的 render 方法:app
// 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地址:codesandbox.io/s/wizardly-…
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,会找到相似的问题:github.com/facebook/re…,就是用的这种解决思路。
Ajackster/react-native-global-props 是一个能够添加默认组件属性的库。
下面摘自 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
是一个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:对脉脉感兴趣的小伙伴,欢迎发送简历到 496691544@qq.com ,我能够帮忙内推~