時間過得好快,不知不覺,加入脈脈已經快一個月了。以前的工做中主要作一些 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 ,我能夠幫忙內推~