如何修改 React Native 的默認字體

引子

時間過得好快,不知不覺,加入脈脈已經快一個月了。以前的工做中主要作一些 PC、H五、Node 相關的開發,開發RN仍是頭一次接觸,感受還挺好玩的。😂css

爲何要設置默認字體?

前兩天Fix了一個RN端的BUG,同事的小米10Pro手機上發現文字被遮擋。以下圖所示:react

image.png

不只是小米,一些 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

image.png

可是這種處理方式須要將全部引入、使用組件的都改成 ,仍是比較麻煩的,有沒有什麼更加方便的方法呢?babel

覆蓋Text組件的render方法

首先,增長一個函數,來覆蓋 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'
  }
});
複製代碼

以下圖所示,咱們新增了一個 組件,沒有傳遞任何屬性,可是能夠看到,它是綠色的~

image.png

僅僅須要執行一遍這個函數,就能夠影響到全部組件的 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 ,我能夠幫忙內推~

參考

相關文章
相關標籤/搜索