LoginSignup
2
5

More than 3 years have passed since last update.

Context / useContext を書きながら学ぶ を書きながら学ぶ(React Hooks 入門シリーズ 2/6)

Last updated at Posted at 2021-01-28

ロードマップ

React 16.8 で追加された機能であるReactのHooksについて書いてあります。

書きながら学ぶ React Hooks 入門シリーズとして書き下ろしました。

はじめに

Reactの組み込みフックであるContextuseContextの説明をします。

また、useContextは、ReactのContextと併用するので、Contextの解説もします。

Contextとは

useContextは、ContextというReactの仕組みを利用するために必要なフックです。

なので、まずはContextについて説明します。

Contextとは...と言っても様々な言い方ができるでしょう。

  • 「状態」と「状態を変更するメソッド」を、propsを用いず、アプリケーション全体で取り回すことができるやつ
  • Propsを利用せずに、様々な階層のコンポーネントに値を共有するReactのAPI

などなどですね。図で説明しましょう。

本をReactで表現するとします。

IMG_56E1E941470B-1.jpeg

ホントは、もっといろいろなコンポネントがありえますが、今回は、Bookのデータ(仮にbookDataとする)を<Page /><Title />で、使いたいとします。

IMG_1BF3BF95601E-1.jpeg

すると、このようにpropsをバケツリレーして渡す方法もあります。ですが、<Body /><Cover />はそんなデータを使用しない。とか、もっと多階層で中間のコンポネントはbookDataをまったく使わないので、バケツリレーで渡す意味がない時ありますよね?

もしくは、グローバルにどこでも取りまわせるstateを持ちたい時がありますよね?

そんな時は、Contextの出番です。

IMG_9AACCBB1127C-1.jpeg

Contextがデータストアの役割をはたし、propsを使わず直接bookDataを下層階層のコンポネントが使うことができるようにしたものです。

useContextは、Contextの機能をさらにシンプルに使うことができるやつです。

Contextを利用するために必要なもの

  • Contextオブジェクト: React.createContextというReactのAPIの戻り値
  • Provider: Contextオブジェクトが保持しているコンポネント
  • Consumer: Contextオブジェクトから値を取得しているコンポネント

サンプルコード

基本の状態は以下にします。(Bookを表すには雑すぎますが...ツリー構造さえわかればOKなので許してください)

スクリーンショット 2021-01-28 23.10.42.png

Contextを利用して、下層コンポネントに、データを受け渡します。
以下の順番で書いていきます。

  • createContext(BookContext)してexportしておく(Book コンポネントで実装)
  • Providerコンポネントを作成し、valueにオブジェクト(state)をセットします(Book コンポネントで実装)
  • 下層コンポネントで、先ほど作成したBookContextをimportします(Title コンポネントで実装)
  • importしたBookContextを使ってconsumerを作成(Title コンポネントで実装)
    • Consumerコンポネント内でbook state の中身にアクセスできます

赤枠が、変更点です👇

Context___useContext_を書きながら学ぶ_-_CodeSandbox.png

実行結果はこんな感じです。

スクリーンショット 2021-01-28 23.40.37.png

propsで上の階層から渡していないのに、下階層でcreateContextしたデータが引っ張れていることがわかります。

それでは次に、useContextを使っていきましょう。

useContextとは

useContextを使った場合でも、Providerを使って値を渡す点は同じです。

構文はこんな感じです。

const Contextオブジェクトの値 = useContext(Contextオブジェクト)

Contextオブジェクトから、取得できる値は、Contextオブジェクトが保持しているProviderのvalueプロパティに指定された値です。

サンプルコード

<Cover />とその子供の<Title />は、createContextされたBookContextAuthorContextコンテキストをPrivideされているので、それぞれのデータを引っ張ることができます。このように、複数のコンテキストを扱うことも可能です。

サンプルコード
/components/Book.js
import React, { createContext, useState } from "react";
import Cover from "./Cover";
import Body from "./Body";

const bookData = {
  author: "ryosuketter",
  title: "how to use React context",
  isbn: 12345,
  yearOfPublication: 2021
};

const authorData = {
  name: "ryosuketter",
  age: 35,
  gender: "male"
};

export const BookContext = createContext();
export const AuthorContext = createContext();

const Book = () => {
  const [book, setBook] = useState(bookData);
  const [author, setAuthor] = useState(authorData);
  return (
    <>
      <BookContext.Provider value={book}>
        <AuthorContext.Provider value={author}>
          <Cover />
        </AuthorContext.Provider>
        <Body />
      </BookContext.Provider>
    </>
  );
};

export default Book;
components/Cover/Title.js
import React, { useContext } from "react";
import { BookContext, AuthorContext } from "../../Book";

const Title = () => {
  const book = useContext(BookContext);
  const author = useContext(AuthorContext);

  return (
    <div>
      <p>this is {book.title}</p>
      <p>author is {author.name}</p>
      <p>age is {author.age}</p>
    </div>
  );
};

export default Title;

特定のコンポネントで、Contextの追加や上書きをしたデータを作成して使うことも可能です。

サンプルコード
/components/Book.js
// 上と同じ
components/Body/Page.js
import Reac, { useContext } from "react";
import { BookContext } from "../../Book";

const Page = () => {
  const book = useContext(BookContext);
  const customedBookContext = {
    ...book,
    author: "ryosuke",
    publisher: "Qiita publications"
  };

  return (
    <div>
      <p>this is page</p>
      <p>author is {customedBookContext.author}</p>
      <p>publisher is {customedBookContext.publisher}</p>
    </div>
  );
};

export default Page;

上2つのコードの実行結果

スクリーンショット 2021-01-29 1.04.52.png

Context利用時の注意点(Context更新時に不要な再レンダーを招く)

Contextは使い方によっては、パフォーマンスの問題を引き起こす可能性があります。

なぜから、Provider内のすべてのConsumerは、Providerのvalueプロパティが更新するたびに再レンダリングするからです。

特に、以下の場合は注意です

  • 再レンダリングされる Consumer の数が多い場合
  • Consumerの子コンポネントのレンダリングコストが高い場合

不要な再レンダリングを防ぐ方法は、次の3つです。

  • Contextオブジェクトの分割
  • React.memoの利用
  • useMemoの利用

ためにし、1つ実際にやってみましょう。

useMemoの利用をすることで不要な再レンダリングを防ぐ方法

仮に、本の著者の情報に対して、いいねボタン(+1カウントアップ)があるとします。

e7c7326683a1101eacaecdf0e275293c.gif

変更を行うのは/components/Book.js/components/Cover/Title.jsです(他のコードは、これまでと変わりません)。
Provider内のすべてのConsumerは、Providerのvalueプロパティが更新するたびに再レンダリングされるので、
下記のままでは、+1カウントアップ されるたびに、<Title />コンポネントも再レンダリングされます。

サンプルコード
/components/Book.js
import React, { createContext, useState } from "react";
import Cover from "./Cover";
import Body from "./Body";

const bookData = {
  author: "ryosuketter",
  title: "how to use React context",
  isbn: 12345,
  yearOfPublication: 2021
};

const authorData = {
  name: "ryosuketter",
  age: 35,
  gender: "male",
  like: 0
};

export const BookContext = createContext();
export const AuthorContext = createContext();

const Book = () => {
  const [book] = useState(bookData);
  const [author, setAuthor] = useState(authorData);
  return (
    <>
      <BookContext.Provider value={book}>
        <AuthorContext.Provider value={author}>
          <Cover />
        </AuthorContext.Provider>
        <Body />
      </BookContext.Provider>
      <p>Like: {author.like}</p>
      <button onClick={() => setAuthor({ ...author, like: author.like + 1 })}>
        +
      </button>
    </>
  );
};

export default Book;

components/Body/Title.js
import React, { useContext } from "react";
import { BookContext, AuthorContext } from "../../Book";

const Title = () => {
  const book = useContext(BookContext);
  const author = useContext(AuthorContext);

  console.log("render");
  return (
    <div>
      <p>this is {book.title}</p>
      <p>author is {author.name}</p>
      <p>age is {author.age}</p>
    </div>
  );
};

export default Title;

useMemoの利用をすることで不要な再レンダリングを防ぐ方法は、以下

サンプルコード
components/Body/Title.js
import React, { useContext, useMemo } from "react";
import { BookContext, AuthorContext } from "../../Book";

const Title = () => {
  const book = useContext(BookContext);
  const author = useContext(AuthorContext);

  return useMemo(() => {
    console.log("render");
    return (
      <div>
        <p>this is {book.title}</p>
        <p>author is {author.name}</p>
        <p>age is {author.age}</p>
      </div>
    );
  }, [book.title, author.name, author.age]);
};

export default Title;

useMemoの[deps]に、[book.title, author.name, author.age]を入れることで、こいつらが更新されない限り、メモ化されたデータが返されて、再レンダリングを防いでいます。

fd9af052eceedf1713eee933dad1b9ef.gif

ぜひ、試しながらやってみてください。

今回は以上です。

参考

2
5
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
2
5