引子
時間過得好快,不知不覺,加入脈脈已經快一個月了。之前的工作中主要做一些 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地址:codesandbox.io/s/wizardly-…
原理淺析
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,會找到類似的問題:github.com/facebook/re…,就是用的這種解決思路。
react-native-global-props 的實現
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 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:對脈脈感興趣的小夥伴,歡迎傳送簡歷到 496691544@qq.com ,我可以幫忙內推~
參考
- React-Native Text文件 reactnative.cn/docs/text
- Two-way to change default font family in React Native ospfolio.com/two-way-to-…
- 一文解決RN0.58部分安卓手機text顯示不全問題 segmentfault.com/a/119000002…
- react-native-global-props github.com/Ajackster/r…
- styled-components 原始碼閱讀:github.com/wangpin34/b…
- ReactNative原始碼篇:原始碼初識:github.com/sucese/reac…