【RN】Omitを使ってTextStyleを独自仕様にするTips
はじめに
今回はReact Native + Typescript
でText
コンポーネントのProps
を改造した際の内容を備忘録として残します。
Text
に限らず、デフォルトのコンポーネントをラップした独自コンポーネントを作成することを考えている場合は役立つ内容かと思います。
前知識「Textの基本的な使い方」
React Native
未経験の方のために簡単に説明させていただきます。
React Native
にはデフォルトでいくつかのコンポーネントが用意されており、Text
もそのひとつです。
以下のように使います。
<Text style={{fontSize: 20, color: "red"}} >ほげほげ</Text>
react
でいうところの<p>
に近い使い勝手です。
Textをラップした独自コンポーネントを作りたい
ある日、TextをラップしたHogeText
というコンポーネントを作ることになりました。
事の経緯としては、アプリの規模が大きくなってきて「あちこちで異なるfontSize
のText
が乱立」してきたことが原因です。
※この辺りの取り決めがチーム内でできておらず、「大きい文字」を人によっては24
にしたり28
にしたりと随分とちぐはぐなデザインになってきたための対策です。
HogeText
はプロパティとしてfontSize
を持ち、fontSize
は"default"
か"large"
のどちらかの値しか受け取れません。
fontSize
は省略可能で、その場合は"default"
として扱われます。
使用イメージは以下の通りです。
<HogeText fontSize="large">大きな文字</HogeText>
<HogeText fontSize="default" >普通の文字</HogeText>
<HogeText>普通の文字2</HogeText>
単にText
をラップするだけなので簡単だと思っていましたが、Text
にはstyle
の他にもnumberOfLines
などの有用なプロパティがあります。
HogeText
はfontSize
以外はText
と同じ使い勝手にしたいので、Props
としてはTextProps
型を持たせたいです。
import { TextProps } from 'react-native';
// HogeTextのプロパティ型
type HogeTextProps = {
fontSize: "default" | "large"
} & TextProps
しかしTextProps
にはstyle
が含まれており、style
(型としてはTextStyle
型)には当然fontSize
が含まれています。
従って、以下のような指定ができてしまいます。
// ↓これでは本末転倒
<HogeText fontSize="default" style={{fontSize: 100}} numberOfLines={1} >普通の文字</HogeText>
やりたいこととしては「基本はText
と同じ振る舞いなんだけど、style
にfontSize
を指定できないText
を作りたい」というものです。
TypescriptのOmitを使う
解決策としてはTypescript
のOmit
を使いました。
Omit
は 「指定した型から、特定のプロパティを除いた型を作る」 時に使います。
以下のようなイメージです。
// Hoge型
type Hoge = {
a: string;
b: number;
c: boolean;
}
// Hoge型からaというプロパティを除外した型
type HogeRemovedA = Omit<Hoge, "a">;
const hoge: HogeRemovedA = {
// ↓これは怒られる
// a: "aaa"
b: 10,
c: false,
}
上記を踏まえて実際に実装したHogeText
とそのプロパティが以下の通りになります。
import React from 'react';
import {StyleProp, TextProps, TextStyle, Text} from 'react-native';
/** TextStyleからfontSizeを除いたType */
type NoFontSizeTextStyle = Omit<TextStyle, 'fontSize'>;
/** TextPropsからstyleを除いたType */
type NoStyleTextProps = Omit<TextProps, 'style'>;
/** TextPropsの中にfontSizeを含まないstyleを持つType */
type NoFontSizeTextProps = NoStyleTextProps & {style?: StyleProp<NoFontSizeTextStyle>};
/** ワンライナーで書くとこんな感じ */
type NoFontSizeTextPropsOneLiner = Omit<TextProps, 'style'> & { style?: StyleProp<Omit<TextStyle,'fontSize'>>};
/** HogeTextの独自Propsを定義 */
type HogeTextProps = {
fontSize: "default" | "large";
} & NoFontSizeTextProps;
const HogeText: React.FC<HogeTextProps> = (props) => {
const fontSize: "default" | "large" = props?.fontSize || "default";
// largeなら22,defaultなら18
const fontSizeValue: number = fontSize === "large"? 22 : 18;
return(
<Text props={...props} style={{fontSize: fontSizeValue}}>
{props.children}
</Text>
)
}
ポイントは以下の通りです。
-
TextStyle
からfontSize
を除外した型を生成(NoFontSizeTextStyle
) -
TextProps
からstyle
を除外した型を生成(NoStyleTextProps
) -
NoStyleTextProps
とstyle
というNoFontSizeTextStyle
型のプロパティを持つ型を合成(NoFontSizeTextProps
) -
NoFontSizeTextProps
は基本はTextProps
だが、style.fontSize
だけがない
このHogeText
はText
と同じように呼び出すことができますが、style.fontSize
を指定することはできず、文字サイズを変えたい場合はプロパティとして用意してあるfontSize
を指定("default"
か"large"
のみ受け付ける)する必要があります。
// ×これは怒られる
<HogeText style={{fontSize: 30}}>30</HogeText>
// ◯fontSize以外のスタイルや通常のTextPropsは指定可能
<HogeText style={{fontWeight: "bold"}} numberOfLines={1} >bold</HogeText>
あとは今までText
を使っていた箇所をHogeText
に置き換えて、それぞれ正しいfontSize
を指定すればOKです。
まとめ
今回はText
の型をいじって、独自に定義したfontSize
のみに対応するコンポーネントを作成しました。
アプリが巨大化するとデフォルトのコンポーネントをラップしたコンポーネントを作る機会も多いかと思います。
その際にデフォルトで用意されたプロパティの型を加工して、独自使用にすることができるため、意図しない挙動を防ぐことができます。
今回の内容が役立ちましたら幸いです。
Discussion