30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

React 要素を props で渡すことで不要な再レンダリングを回避する

この記事の内容は、React のv17.0.2で動作確認している。

React コンポーネントが入れ子になっているとき、親のコンポーネントが再レンダリングされると、子のコンポーネントも再レンダリングされる。stateの受け渡し等があるかどうかは関係ない。
そのため以下のコードの場合、ボタンを押下するとParentだけでなくChildも再レンダリングされ、両方のログが流れる。

import {useState} from 'react';
import {render} from 'react-dom';

function Child() {
  console.log('render Child');

  return <div>Child</div>;
}

function Parent() {
  const [state, setState] = useState(0);
  const onClick = () => {
    setState((s) => s + 1);
  };

  console.log('render Parent');

  return (
    <div>
      <button type="button" onClick={onClick}>
        count up
      </button>
      <br />
      {state}
      <Child />
    </div>
  );
}

function Container() {
  return <Parent />;
}

render(<Container />, document.querySelector('#container'));

だがChildParentstateの値がなんであれ表示する内容は変わらないため、Childの再レンダリングは不要である。
この不要な再レンダリングを回避する方法のひとつに、React.memoを使ったメモ化がある。

const Child = memo(() => {
  console.log('render Child');

  return <div>Child</div>;
});

メモ化については、以下の記事に詳しく書いた。

numb86-tech.hatenablog.com

これ以外の方法として、「Childコンポーネントが返す React 要素をParentpropsとして渡す」というものがある。

function Parent({children}) {
  const [state, setState] = useState(0);
  const onClick = () => {
    setState((s) => s + 1);
  };

  console.log('render Parent');

  return (
    <div>
      <button type="button" onClick={onClick}>
        count up
      </button>
      <br />
      {state}
      {children}
    </div>
  );
}

function Container() {
  return (
    <Parent>
      <Child />
    </Parent>
  );
}

こうすると、Parentが再レンダリングされてもChildは再レンダリングされない。

props.childrenではなく任意のpropsに React 要素を渡してもよい。

function Parent({originalProps}) {
  const [state, setState] = useState(0);
  const onClick = () => {
    setState((s) => s + 1);
  };

  console.log('render Parent');

  return (
    <div>
      <button type="button" onClick={onClick}>
        count up
      </button>
      <br />
      {state}
      {originalProps}
    </div>
  );
}

function Container() {
  return <Parent originalProps={<Child />} />;
}

これでも、同様の結果になる。

考えてみれば当然で、コンポーネント(関数)ではなくて要素(関数の返り値)を外から渡しているのだから、Parentが再レンダリングされたところで、渡されている要素はそのまま同じものが使われる。
以下のコードではParentのレンダリング毎のoriginalPropslistに入れているが、list内の全ての要素は常に同じものを参照している。

import {useState} from 'react';
import {render} from 'react-dom';

function Child() {
  console.log('render Child');

  return <div>Child</div>;
}

const list = [];

function Parent({originalProps}) {
  const [state, setState] = useState(0);
  const onClick = () => {
    setState((s) => s + 1);
  };

  console.log('render Parent');

  list.push(originalProps);
  if (list.length >= 2) {
    console.log(list.every((elem) => elem === originalProps)); // true
  }

  return (
    <div>
      <button type="button" onClick={onClick}>
        count up
      </button>
      <br />
      {state}
      {originalProps}
    </div>
  );
}

function Container() {
  return <Parent originalProps={<Child />} />;
}

render(<Container />, document.querySelector('#container'));

そのため、要素ではなくコンポーネントをpropsで渡してしまうと、再レンダリングは回避できない。

以下のコードでは、要素ではなくChildコンポーネントをpropsで渡し、Parentのなかで要素を作成している。
この場合、Parentが再レンダリングされる度にChildが呼び出され、新しく要素を作ることになる。
つまり、Parentが再レンダリングされるとChildも再レンダリングされるということである。

import {useState} from 'react';
import {render} from 'react-dom';

function Child() {
  console.log('render Child');

  return <div>Child</div>;
}

const componentList = [];
const elementList = [];

function Parent({OriginalProps}) {
  const [state, setState] = useState(0);
  const onClick = () => {
    setState((s) => s + 1);
  };

  console.log('render Parent');

  const element = <OriginalProps />; // 渡されたコンポーネントから要素を作成している

  componentList.push(OriginalProps);
  if (componentList.length >= 2) {
    console.log(componentList.every((elem) => elem === OriginalProps)); // true
  }

  // どの要素も`<div>Child</div>`ではあるが、別々の`<div>Child</div>`が都度作られている
  elementList.push(element);
  if (elementList.length >= 2) {
    console.log(elementList.every((elem) => elem === element)); // false
  }

  return (
    <div>
      <button type="button" onClick={onClick}>
        count up
      </button>
      <br />
      {state}
      {element}
    </div>
  );
}

function Container() {
  return <Parent OriginalProps={Child} />; // 要素ではなくてコンポーネントを渡している
}

render(<Container />, document.querySelector('#container'));

参考資料

プログラミングを独学する上で実際に役に立った本

この記事を書いた文脈

twitter.com

twitter.com

お二人が書かれたものは以下。

golden-lucky.hatenablog.com

voluntas.medium.com

前提

ブログのタイトルにあるように、30 歳からプログラミングを始めた。
最終学歴は大卒だが、定員割れしている文系私大である。高校は行っていないので、数学は中学レベル。つまり、何の素養もないところから独学を始めた。
いくつかの会社でプログラマとして働いたので、「プログラマになれた」と言っていいと思う。ブラック企業にぶち込まれたわけでもないし。

ここで紹介する技術書以外にも面白かった本はいくつもあるのだが、「何を読むことで、未経験の人間がプログラマとして働けるようになったのか」という文脈で選んだ。
これらの技術書がお勧め、という訳ではなく、自分はこうした、というだけ。

JavaScript

『JavaScript本格入門』

gihyo.jp

自分をプログラミング言語に入門させてくれたのは、間違いなくこの本。
難しすぎるわけでもなく、かといって敷居を下げすぎて単なるレシピ集のようになってしまっているわけでもなく、しっかりと「JavaScript という言語」について教えてくれる入門書。
この本のおかげで、JavaScript という門をくぐることができた。
改訂版が出ているので今読むならこっちだが、自分は未読。

『オブジェクト指向JavaScriptの原則』『開眼! JavaScript』

www.oreilly.co.jp

www.oreilly.co.jp

『JavaScript本格入門』を読み終えたあと、JavaScript の言語仕様をもっと学ぼうと思い、読んだ。
当時既に React や AngularJS が流行っていたが、フレームワークやライブラリの前に言語をちゃんと理解しようと思い、手にとった。
どちらもコンパクトで読みやすく、よかった。
どちらもかなり古い本なので、今更敢えて読むようなものではないと思う。

今から JavaScript をきちんと学ぶのなら、やっぱり JavaScript Primer だろうか。

GitHub

『Web制作者のためのGitHubの教科書』

book.impress.co.jp

Git や GitHub の習得が必須だということは理解していたのだが、ネット上の記事をいくつか読んでも、ピンと来なかった。
そこで、とにかく文字が少なそうなこの書籍を読むことにした。
かなり初歩的かつ具体的な内容で、何とか Git を使えるようになった最初のキッカケはこの本だった気がする。
CUI をろくに使えなかった当時の自分にとっては、Sourcetree の使い方を丁寧に説明してくれる本書はかなりありがたかった。

既にある程度理解した状態で読んだこの書籍も、入門書として質が高かった記憶がある。

numb86-tech.hatenablog.com

Web

『Webを支える技術』

gihyo.jp

HTTP や REST といった、ウェブにおける基本的な概念を分かりやすく説明してくれている名著。
自分はこの本で REST という言葉や HTTP の仕組みに初めて触れた。
近いテーマの本として『Real World HTTP』があるが、初学者には厳しいので、まずは本書を読んだほうがよいと思う。
著者によれば本書も初心者向けではないとのこと。確かに最初に読むような本ではない。とはいえウェブプログラミングをやるのなら、できるだけ早く HTTP の概念に触れたほうが、その後の学習効率が高まると思う。

twitter.com

設計

『オブジェクト指向設計実践ガイド』

gihyo.jp

これは、就職後に読んでよかった本。だが独学していく上で重要な本だったので、書いておく。
設計というものに対して関心が高まり、本書を手にとった。
「オブジェクト指向」というと Java や C++ で語られる印象があるのだが、本書は Ruby で説明してくれているので、ウェブプログラマでも取っ付き易い。
設計のテクニック自体もよかったが、それ以上に、「設計」や「オブジェクト指向」に対する考え方を変えることができたのが、個人的には大きかった。
詳しいことは以前書いたので、そちらを読んで欲しい。

numb86-tech.hatenablog.com

「設計」や「オブジェクト指向」といった言葉にビビる必要はないし、「オブジェクト指向とは何か」という論争に参加する必要もない。自分にとって必要なことを、必要になったときに学べばよいのだ。