クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【React】コンテクスト(Context)でネストされたコンポーネントに値を渡す

Reactの続きです。前回はステートで条件分岐やループをやってみました。今回は、コンポーネントの階層(ネスト)を超えて共通の値を渡すのに便利な「コンテクスト(Context)」という機能を体験したく。それではいきましょう!

【目次】

※参考:前回記事
https://www.i-ryo.com/entry/2020/06/02/201503

※参考:Reactを習得するためにやったことまとめ
qiita.com

コンテクスト(Context)とは

今回も掌田さんの「React.js & Next.js入門」を参考に進める。

React.js & Next.js超入門

React.js & Next.js超入門

書籍ではコンポーネント編の次にRedux編に入るが、コンポーネント編の最後に「コンテクスト」という機能の解説があった。

React公式サイトではこちらにあたる

コンテクストは各階層で手動でプロパティを下に渡すことなく、コンポーネントツリー内でデータを渡す方法を提供します。

※参考:コンテクスト – React

コンテクストの書き方

コンポーネントの外でコンテクストを生成する。

const コンテクスト = React.createContext(値);

React.createContext
コンテクストオブジェクトを作成します。

※参考:コンテクスト – React

コンポーネント内でコンテクストを読み込む。

static contextType = コンテクスト;

Class.contextType
クラスの contextType プロパティには React.createContext() により作成されたコンテクストオブジェクトを指定することができます。これにより、this.context を使って、そのコンテクストタイプの最も近い現在値を利用できます。

※参考:コンテクスト – React

そうするとJSXのタグの中でコンテクストの値を呼び出せる

<hoge>{this.context.値}</hoge>

コンポーネントのネストが深いと値を「親→子→孫→ひ孫…」と「バケツリレー」で渡す必要がある。コンテクストを使うと「ひ孫」にも値を直接渡すことができる。

※参考:React Context / Hooks 入門

コンテクストでネストされたコンポーネントに値を渡す

実際にやってみた。

App.js変更内容

まず、変数dataにオブジェクト(連想配列)でデータを用意

let data = {
  title: 'はじめてのこんてきすと',
  message: 'これはコンテキストを使って読み込んでみたテキストなんです。はい。'
}

変数dataはコンポーネントの外にある。

変数HajimeteContextcreateContext()dataを読み込む。これもコンポーネントの外。

const HajimeteContext = React.createContext(data);

「App」コンポーネントの中で「Title」「Message」コンポーネントがネストしてる

class App extends React.Component {

  render() {
    return (
    <div className="App">
      <header className="App-header">
        <div className="App-images">
          <Img align="left" />
          <Img align="center" />
          <Img align="rignt" />
        </div>
        <h1>コンテキストテスト</h1>
        <Title />
        <Message />
      </header>
    </div>
    );
  }
}

「Title」コンポーネントでHajimeteContextコンテクストを読み込みtitleの値を表示

class Title extends React.Component {
  static contextType = HajimeteContext;

  render() {
    return (
    <div>
      <h2>{ this.context.title }</h2>
    </div>
    );
  }
}

「Message」コンポーネントでHajimeteContextコンテクストを読み込みmessageの値を表示

class Message extends React.Component {
  static contextType = HajimeteContext;

  render() {
    return (
    <div>
      <p>{ this.context.message }</p>
    </div>
    );
  }
}

挙動確認

Reactを起動!

npm start

ブラウザが立ち上がる。

f:id:idr_zz:20200609064018j:plain おお!コンテクストの値がコンポーネントに表示されとる!

App.jsコード全体

import React, { Component } from 'react';
import Img from './Img';
import './App.css';

let data = {
  title: 'はじめてのこんてきすと',
  message: 'これはコンテキストを使って読み込んでみたテキストなんです。はい。'
}

const HajimeteContext = React.createContext(data);

class App extends React.Component {

  render() {
    return (
    <div className="App">
      <header className="App-header">
        <div className="App-images">
          <Img align="left" />
          <Img align="center" />
          <Img align="rignt" />
        </div>
        <h1>コンテキストテスト</h1>
        <Title />
        <Message />
      </header>
    </div>
    );
  }
}

class Title extends React.Component {
  static contextType = HajimeteContext;

  render() {
    return (
    <div>
      <h2>{ this.context.title }</h2>
    </div>
    );
  }
}

class Message extends React.Component {
  static contextType = HajimeteContext;

  render() {
    return (
    <div>
      <p>{ this.context.message }</p>
    </div>
    );
  }
}

export default App;

プロバイダ(Provider)とは

先ほどのコンテクストは値を読み込むのみだったが、コンテクストの構造は保ったままま値だけを変えたい時がある。そういうときにはプロバイダ(Provider)という機能を使う。

全てのコンテクストオブジェクトにはプロバイダ (Provider) コンポーネントが付属しており、これによりコンシューマコンポーネントはコンテクストの変更を購読できます。

※参考:コンテクスト – React

プロバイダの書き方

コンポーネントをコンテキスト.Providerタグで囲う。

<コンテキスト.Provider value="値">
    // コンポーネント
</ コンテキスト.Provider>

囲われた中のコンポーネントの値だけが変更される。

プロバイダ(Provider)でコンテクストの値を変更する

実際にやってみる。

App.js変更内容

「App」コンポーネントにプロバイダーの値newdataを設定

class App extends React.Component {
  newdata = {
    title: 'はじめてのぷろばいだ',
    message: 'ここだけプロバイダを使って書き換えてみたテキストなんです。はい。'  
  }

  // 中略
}

「Title」「Message」コンポーネントを3つずつ読み込んでいるが2つ目のみHajimeteContext.Providerで囲いnewdataの値を読み込む。

class App extends React.Component {
  // 中略

  render() {
    return (
    <div className="App">
      <header className="App-header">
        // 中略
        <Title />
        <Message />
        <HajimeteContext.Provider value={this.newdata}>
          <Title />
          <Message />
        </HajimeteContext.Provider>
        <Title />
        <Message />
      </header>
    </div>
    );
  }
}

挙動確認

ブラウザを確認すると… f:id:idr_zz:20200609064046j:plain やた!2番目だけプロバイダの値になっている!

App.jsコード全体

import React, { Component } from 'react';
import Img from './Img';
import './App.css';

let data = {
  title: 'はじめてのこんてきすと',
  message: 'これはコンテキストを使って読み込んでみたテキストなんです。はい。'
}

const HajimeteContext = React.createContext(data);

class App extends React.Component {
  newdata = {
    title: 'はじめてのぷろばいだ',
    message: 'ここだけプロバイダを使って書き換えてみたテキストなんです。はい。'  
  }

  render() {
    return (
    <div className="App">
      <header className="App-header">
        <div className="App-images">
          <Img align="left" />
          <Img align="center" />
          <Img align="rignt" />
        </div>
        <h1>コンテキストテスト</h1>
        <Title />
        <Message />
        <HajimeteContext.Provider value={this.newdata}>
          <Title />
          <Message />
        </HajimeteContext.Provider>
        <Title />
        <Message />
      </header>
    </div>
    );
  }
}

class Title extends React.Component {
  static contextType = HajimeteContext;

  render() {
    return (
    <div>
      <h2>{ this.context.title }</h2>
    </div>
    );
  }
}

class Message extends React.Component {
  static contextType = HajimeteContext;

  render() {
    return (
    <div>
      <p>{ this.context.message }</p>
    </div>
    );
  }
}

export default App;

最後に

ということでコンテクストからの値の読み込みをやってみました。自分はまだReactで大規模な物を作っていないため、コンポーネント間の「バケツリレー」の辛さはあまり感じていないんですが、値と直接関係ないコンポーネントが汚染されないのはいい感じですね。

次回からは「Redux」による値の状態管理を体験してみたく思います。それではまた!


※参考:Reactを習得するためにやったことまとめ
qiita.com