🌊

[React] 初心者講座02 <コンポーネントの書き方>

2021/11/26に公開

目標

  • React Componentの定義方法が分かる
  • styledを使用したComponentが書ける

基本的なComponentの書き方

React Componentは.tsxファイルで記述する。
JSXという記法は変わらなくとも定義の仕方がいくつかある。
いくつかあるが僕の中のスタンダードはアロー関数React.VFC(React.FC)を使って定義する。
(React v18以降はReact.VFCは使わずReact.FCを使うことになると思う。)

SampleComponent.tsx
import React from 'react';

{/* props(Componentの引数)の名前はComponent名 + Propsとする */}
{/* 理由はprops自体をexportすることがよくあるから名前が被らないようにしておくため */}
type SampleComponentProps = {
  {/* childrenが必要な場合はちゃんと定義する */}
  children: React.ReactNode;
  foo: string;
};

{/* Componentの型はReact.VFCで定義しておく */}
const SampleComponent: React.VFC<SampleComponentProps> = (props) => {
  return (
    <div>
      <div>
        {props.foo}
      </div>
      <div>
        {props.children}
      </div>
    </div>
  );
};

{/* default exportは別にしなくてもよい */}
{/* つまり export const SampleComponent って書いてもよい */}
export default AppHeader;

JSX.Elementを型定義として使用してはダメか? → 別によい。
ただ、どちらかに統一しといてほうが良さそうだなとは思う。
以下、JSX.Elementで型定義した例。

SampleComponent.tsx
type SampleComponentProps = {
  children: React.ReactNode;
};

const SampleComponent = (props: SampleComponentProps): JSX.Element => {
  return (
    <div>
      {props.children}
    </div>
  );
};

export default SampleComponent;

Propsの書き方

別Componentのpropsを必要とする場合がよくある。
例として、メインタイトルとユーザ名を表示するヘッダComponentの実装を想定する。

Pickで必要なpropsを取り出す

まず、メインタイトルの実装が以下。
(見やすくするためにstyle記述はなしで、ただのspanにした。)

TitleLabel.tsx
import React from 'react';

export type TitleLabelProps = {
  label: string;
};

const TitleLabel: React.VFC<TitleLabelProps> => {
  return <span>{props.label}</span>;
};

export default TitleLabel;

この<TitleLabel />に加えてユーザ名を並べるヘッダComponentを以下のように定義する。
(styleは指定せず、とりあえず並べるだけにする。)

このときのHeaderPropsは以下のように定義する。
<TitleLabel />を見に行ってlabel: stringHeaderPropsにコピペはダメ。

Header.tsx
import React from 'react';
import TitleLabel, { TitleLabelProps } from './TitleLabel';

export type HeaderProps = Pick<TitleLabelProps, 'label'> & {
  userName: string;
  {/* label: string; ← NG */}
};

const Header: React.VFC<HeaderProps> => {
  return (
    <div>
      <TitleLabel label={props.label}>
      <span>{props.userName}</span>
    </div>
  );
};

export default Header;

Pickで取りたいprops名が被っているとき

この<Header />にサブタイトルが必要になったとする。
サブタイトルのComponentを以下のように定義した。

SubTitleLabel.tsx
import React from 'react';

export type SubTitleLabelProps = {
  label: string;
};

const SubTitleLabel: React.VFC<SubTitleLabelProps> => {
  return <span>{props.label}</span>;
};

export default SubTitleLabel;

Pick<TitleLabelProps, 'label'> & Pick<SubTitleLabel, 'label'>とは出来ない。
labelが重複してしまうので。
そんなときは以下のようにそれぞれ別名をつけて型を取得してくる。

Header.tsx
import React from 'react';
import TitleLabel, { TitleLabelProps } from './TitleLabel';
import SubTitleLabel, { SubTitleLabelProps } from './SubTitleLabel';

export type HeaderProps = {
  mainLabel: TitleLabelProps['label']; {/* stringになる */}
  subLabel: SubTitleLabelProps['label']; {/* stringになる */}
  userName: string;
};

const Header: React.VFC<HeaderProps> => {
  return (
    <div>
      <TitleLabel label={props.mainLabel}>
      <SubTitleLabel label={props.subLabel}>
      <span>{props.userName}</span>
    </div>
  );
};

export default Header;

MUI(Material-UI)を元にしたComponentの書き方

書き方、というよりはルール。

MUIの<Button />とかは基本的にPickでpropsを絞ったものを定義しておく。
そのまま使用するには汎用的すぎるので。
BaseButtonProps = ButtonProps & {...}みたいに丸ごと使うことはしない。

でも自分は<Box />はそのまま使ってたりする。
SwiftUIのVStack HStackのようなものを定義しといたほうが良さそうと思うこともある。
MUIのStack利用でもいいかも。

とりあえず以下、一例。

BaseButton.tsx
import React from 'react';
import Button, { ButtonProps } from '@mui/material/Button';

export type BaseButtonProps = Pick<ButtonProps, 'onClick'> & {
  label: string;
};

const BaseButton: React.VFC<BaseButtonProps> => {
  return (
    <Button onClick={props.onClick}>
      {props.label}
    </Button>
  );
};

export default BaseButton;

styled()を使用したComponent

公式にほとんど書いてある。

https://mui.com/system/styled/#main-content

@emotion/styledについても書いてあった

emotionstyled-componensの両方を使ったことがあるけど、@mui/systemstyledは使ったことがないので、本実装ではこちらを使ってみる。
多分、大差はない。

どのstyledであれpropsの値を適用することはできる。
以下のような感じ。

import styled from '@emotion/styled';

type SampleProps = {
  color?: string;
}

export const Sample = styled.div`
  color: ${({color}: SampleProps): string => color ?? '#333333'};
`

styled()かsxか

MUI v5よりsxというstyleを調整する仕組みが導入されている。公式はこちら

どっち使えばいいの?ってなるけど公式には以下のように書いてあった

When to use it?

  • styled-components: the API is great to build components that need to support a wide variety of contexts. These components are used in many different parts of the application and support different combinations of props.
  • sx prop: the API is great to apply one-off styles. It's called "utility" for this reason.

sxは例外的なデザイン調整に使用すればいい感じなのかな?
見出しは基本10pxで、見出しComponentも作ったけど、ここだけ20pxだ。的な。

パフォーマンスも公式に書かれていてsxよりstyledの方が優れていそう。

Benchmark case Code snippet Time normalized
Render 1,000 components <Div> 120ms
Render 1,000 styled components <StyledDiv> 160ms
Render 1,000 Box <Box sx={…}> 370ms

まあ、便利なので使っていく。

続き

https://zenn.dev/kosukek/articles/97f2fbc6269af3

https://zenn.dev/kosukek/articles/840a71762fabde

Discussion