LoginSignup
1
2

More than 5 years have passed since last update.

React開発ノウハウメモ(随時更新)

Posted at

少し前までAngularを使って開発してきましたが、年明けごろからReactを使い始めたので、自分なりのノウハウをまとめておきます。随時更新予定です。

使っている言語・ライブラリはReact, TypeScript, RxJSです。
RxJSはAngularで使っており慣れているのでReactでも引き続き使っていこうと試行錯誤しています。型定義の無いライブラリが多く面倒なので、なるべく生のReactで解決しようとしていますが、今後は状態管理に別のライブラリを使うかもしれません。


型付け

  • statepropsのメンバーはComponent内では書き換えてはならないので、すべてのメンバーにreadonlyを付ける。全メンバーにreadonlyを付けるのは面倒なので以下のように定義している。
interface IProps extends Readonly<{
  member1: number,
  member2: string,
  member3: number[],
}>{}

これで

interface IProps {
  readonly member1: number;
  readonly member2: string;
  readonly member3: number[];
}

と同じ意味になる(はず)。

  • スタイリングはエラー発見しやすいCSSinJSを使っている。CSSオブジェクトの型定義を以下のように作成した。
import { CSSProperties } from 'react';

export interface CSSobj { readonly [key: string]: CSSProperties; }

使い方

const css: ICSSobj = {
  tableWrapper: {
    overflowX: 'auto',
  },
  spacer: {
    flexGrow: 1,
  },
  footer: {
    display: "flex",
    flexDirection: "row",
    alignItems: "center",
  },
  resetButtonWrapper: {
    padding: "7px",
  },
};

関数コンポーネント(FC)

ステートレスコンポーネントを関数で簡単に書けるのがReactを使い始めて一番良いと思ったところ。

自分は基本的に、state管理を行う親コンポーネント(class)と、ロジックをほぼ持たずCSSスタイリング等を行うview専用の子コンポーネント(FC)の親子組を作るようにした。

class InputWithReset extends React.Component<Readonly<{
  placeholder: string;
  valueChange: (v: string) => void;
}>, Readonly<{
  value: string
}>> {

  state = {
    value: '',
  };

  valueChange = (value: string) => {
    this.setState({ value: value });
    this.props.valueChange(value);
  }

  resetClick = () => {
    this.setState({ value: '' });
    this.props.valueChange('');
  }

  render = () => (
    <InputWithResetView
      value={this.state.value}
      placeholder={this.props.placeholder}
      valueChange={this.valueChange}
      resetClick={this.resetClick}
    />
  )
}

const css: ICSSobj = {
  input: {
    minWidth: "120px",
  },
};

const InputWithResetView = (props: Readonly<{
  placeholder: string;
  value: string;
  valueChange: (value: string) => void;
  resetClick: () => void;
}>) => {
  const onInput = (ev: React.ChangeEvent<HTMLInputElement>) =>
    props.valueChange( ev.target.value || '' );

  return (
    <FormControl>
      <InputLabel shrink htmlFor="input">{props.placeholder}</InputLabel>
      <Input
        style={css.input}
        id="input"
        type='text'
        value={props.value}
        onChange={onInput}
        endAdornment={
          <InputAdornment position="end">
            <IconButton
              aria-label="Reset Input"
              onClick={props.resetClick}
            >
              <ClearIcon />
            </IconButton>
          </InputAdornment>
        }
      />
    </FormControl>
  );
};

event.target.valueを取り出すようなhtml要素に近い部分の整形はviewの方で行うようにしている。
import文も整理されるので親子は別ファイルに書くことにした。

Props変更時の処理

class componentにおいてpropsの値が変わったとき、renderは呼ばれるがconstructor等に記述したpropsに依存する変数は再計算されない。

propsが変わったときにrender以外の処理を行いたい場合については、公式ページの getDerivedStateFromProps の説明に書かれている。stateをpropsに依存するようにするのはバグの元なのでgetDerivedStateFromPropsメソッド以外の方法を推奨しているようだ。
対応方法が箇条書きで3つ書かれているが、propsの値を使った変数の値を再計算したい場合、2つ目と3つ目が該当するように思われる。

どれが適しているかはケースによるが、自分はconstructor内のロジックも含めて全体的に再計算したかったため3つ目を採用した。親コンポーネントにおいてこのコンポーネントをkey={Date.now()}を付けて呼ぶことで、keyの変更によりコンポーネントが再生成されることを利用するという方法だ。

interface IProps extends Readonly<{ data: any }>{}

const ParentFC = (props: IProps) => (
  <div>
    {!!props.data &&
      <Child
        key={Date.now()}  // recreate this component every time when props change
        data={props.data}
      />
    }
  </div>
);


const eq = (prev: IProps, curr: IProps) => (
  (prev.data === curr.data)
);

export const Parent = React.memo(ParentFC, eq);
class Child extends React.Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    // propsの値を使った様々な計算
  }

...
}

また、コンポーネント全体を再生成するため高コストであることも考慮し、メモ化も行っている。

2つ目の use a memoization helper をやりたくなかった理由は、本来表示に関するメソッドであるはずのrender内にロジックを書くのが気持ち悪かったので。
props変更時にrenderは呼ばれるので、内部変数の再計算をここで行い、一部のみ変更したい場合に対応するためにメモ化するという方法のようで、必要な再計算が一部のみであることが分かっていればこちらを採用した方がパフォーマンスは向上するのかもしれない。

RxJS

複雑な状態管理にはRxJSを使っている。Angularのようにasyncパイプなどはなさそうなので、subscribeをちゃんと書く必要があるが、どこに書くのか分からなかったのでメモ。

Angularでやっていた時と同じく、takeWhilealive変数を使って自動unsubscribeさせるパターンを使っている。

export class A extends React.Component<IProps, IState> {
  state = {
    data: [],
  };

  private alive = true;

  componentWillUnmount() { this.alive = false; }

  componentDidMount() {
    this.data$.takeWhile( () => this.alive )
    .subscribe( v => {
      this.setState({ data: v });
    });
  }

...
}
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2