[覚書] Reactを業務で使い始めて知ったこと
私は、これまでプライベートでしか React を使っていませんでした。
最近、業務で React を使う機会が増えたので、学んだことを残そうと思います。
React の歴史
なんで React って生まれたんだろうって気になりました。
簡単ですが、ちょこっとだけ調べて、次の記事にまとめました。
React は、次の問題を解決したかったんだと思います。
- DOM ツリーが大きくなるにつれて、下位の変更によるカスケード更新の負荷が大きくなる
そこで、React は、この問題を解決するために、仮想 DOM という仕組みを作ったんだと思います。
仮想 DOM、差分検出処理、そして Fiber
React は、直接 DOM を操作するのではなく、仮想 DOM に対して操作します。仮想 DOM は、名前の通り仮想的な DOM です。
仮想 DOM を DOM へ反映するために、差分検出処理(reconciliation)というアルゴリズムがあったり、Fiber と呼ばれる、レンダリングの最適化(優先順位)を目的としたアルゴリズムもあるようです。これらのおかげで、レンダリング負荷が軽減されるんだと思います。(しらんけど)
まだまだ理解が浅いので、これからもっと学んでいきたいと思います。
- 仮想 DOM と内部処理 – React
- 差分検出処理 – React
- acdlite/react-fiber-architecture: A description of React's new core algorithm, React Fiber
- React Fiber アーキテクチャについて | POSTD
レンダリングのタイミングは、いつなんでしょうか。
レンダリングタイミング
基本的に、React は、親コンポーネントをレンダリングすると、子コンポーネントもレンダリングされます。
再レンダリングをキューイングする関数、setState や forUpdate などを呼ぶと、コンポーネントはレンダリングされることになります。
コードベースが大きくなるにつれて、レンダリングのパフォーマンスが悪化していきます。
そこで、パフォーマンスの最適化が求められます。
パフォーマンス最適化
最初からパフォーマンス最適化をする必要はありませんが、要件によっては必要になることもあります。
最適化の手段として、React にある、次の 3 つの関数が使えます。
-
memo
- コンポーネントのレンダーをスキップできる
- 以前の props と現在の props で変更がなければ
- コンポーネントのレンダーをスキップできる
-
useMemo
- 値をメモ化できる
-
useCallback
- 関数をメモ化できる
- memo と併用して使う
- 関数をメモ化できる
パフォーマンス最適化 – Reactも参考になります。
比較アルゴリズム
React では、コンポーネントや状態が変更されたかどうかの判定に、Object.is() を使っているようです。
Object.is のサンプルコードは、次のとおりです。
Object.is("foo", "foo"); // true
Object.is("foo", "bar"); // false
Object.is([], []); // false
var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo); // true
Object.is(foo, bar); // false
string や integer のようなプリミティブな値は良いのですが、非プリミティブな値(Object)の場合の考慮が必要です。
例えば、memoの場合は、第二引数に比較関数を渡すことができます。
例えば、次のような感じです。
function MyComponent(props) {}
function areEqual(prevProps, nextProps) {
return JSON.stringify(prevProps.foo) === JSON.stringify(nextProps.foo);
}
export default React.memo(MyComponent, areEqual);
公式ページにも書いていますが、パフォーマンス最適化のみに使いましょう。
Tips
Object.is()
を使われている影響で、非プリミティブな値の状態更新に、工夫が必要です。
const [items, setItems] = useState(["a", "b"]);
// NG
items.push("c");
setItems(items); // 変更されない(Object.is()→true)
// OK
const newItems = [...items, "c"];
setItems(newItems); // 変更される(Object.is()→false)
NG の方は、同じオブジェクトを使いまわしているのに対し、OK の方は、新しくオブジェクトを生成しています。
パフォーマンス調査
トップダウンでパフォーマンス調査をするのが、ベターと思います。
- Chrome Developer Tools > Lighthouse を使い、performance score を確認
- Chrome Developer Tools > Performance を使い、処理に時間がかかっている箇所を見つける
- React Developer Tools > Profiler を使い、React コンポーネントのレンダリングで時間がかかっている箇所を調査
React コンポーネント デザインパターン
React でコンポーネントを実装していると、次の 3 つのパターンがあるようです。
- Container and presentation
- ロジックと UI を分離
- XxxContainer, Xxx という命名が多い
- Higher order component
- 高階コンポーネント
- withXxx という命名が多い
- Function as child
- コンポーネントではなく関数を child として渡す
ロジックを独自フックとして切り出す
テスタビリティや再利用性の観点より、ロジックを hooks として切り出すのが良さそうです。
命名は、use から始まることが多いです。
その他
- コンポーネントコードと同じフォルダ内に、次のファイルを置きたい
- テストコード (test)
- 仕様を知る
- カタログコード(storybook)
- UI を見る
- スタイルコード (scss)
- テストコード (test)
- input 要素などの onChange には、debounce を使う
- onChange の処理が重たいときに
Discussion