#!/usr/bin/env python # coding: utf-8 # # ReactのContext周りについて # # - 2018/11/14 勉強会 # # アジェンダ # # 1. ContextAPI概要 # 1. APIの紹介 # 1. Reduxとの使い分け # 1. Hooksを使う # 1. 先日のreact-reduxのリリースについて # 1. 参考 # # ContextAPI概要 # # # ## 従来のReact # # - 親から子へデータを渡すときはpropsを使う # # ``` # // 親 # export default class Parent extends React.Component { # render() { # const count = 1; # // `count`を子孫にわたす # return ; # } # } # # // 子 # const Child = ({ count }) =>
count: {count}
; # ``` # ### もっと入れ子&複数propsになるとき # # - 親→子→孫→..と、渡すときにバケツリレーが続いてしまう # - これを`Prop Drilling`問題という # # ``` # // 親 データを子に渡す # export default class Parent extends React.Component { # render() { # const count = 1; # const word = 'hello'; # # return ; # } # } # # // 子 ここでは使わない # const Child = ({ count, word }) => ; # # // 孫 ここでも使わない # const Grandson = ({ count, word }) => ; # # // 曾孫 やっとここで使いたい # const GreatGrandchild = ({ count, word }) => ( # <> #
count: {count}
#
word: {word}
# # ); # ``` # ## 従来の解決の仕方 # # - Reduxを使う # - spread attributesを使って全渡しする # - {...props}みたいなやつ # - 本質的な解決法ではない # ## [ContextAPI](https://reactjs.org/docs/context.html)とは # # - React製 # - contextを使って子孫にデータを送る # # ## ContextAPIを用いた最小の例 # # ``` # import React, { createContext } from 'react'; # # const { Provider, Consumer } = createContext(); # # const SimpleContextAPIParent = () => ( # # # # ); # export default SimpleContextAPIParent; # # // 子 propsを伝搬させてない # const Child = () => ; # # // 孫 同じく伝搬させてない # const Grandson = () => ; # # const GreatGrandchild = () => ( # # {({ count, word }) => ( # <> #
values: {count}
#
word: {word}
# # )} #
# ); # # ``` # # # ### APIの解説 # # #### [React.createContext](https://reactjs.org/docs/context.html#reactcreatecontext) # # - 後述するProviderとConsumerをペアで返す # - 上記の例のように2値を一気に受け取る方法もあるが、 # - 以下のようにContextに名前をつける方法もある # - 使いたいContextが複数あるなら便利 # ``` # const Root = React.createContext; # ... # # ... # # ``` # - 引数にはdefault値を入れる # - 使い所がよくわからんので調べる # # #### [Provider](https://reactjs.org/docs/context.html#contextprovider) # # - Consumerにcontextを渡すコンポーネント # - `value`を使って渡す # - 値だけでなく関数も渡せる # - (ReduxのProviderと同じような機能で、全く同じ名前なのは..🤬🤬) # # #### [Consumer](https://reactjs.org/docs/context.html#contextconsumer) # # - Providerからcontextを受け取る # - 中には関数を書く # ``` # # {() => なんか} # # ``` # # ### ちなみに # # - 公式の例では # - 「テーマカラー」や「ログイン中ユーザー」など、「状態が頻繁に変わるわけではないが、いろんなコンポーネントからアクセスされるデータ」をContextに渡している # ## 少し拡張したコードを書いてみる # # - 次の例では # - まだ紹介していない`Class.contextType`も使う # - 関数も渡す # # # ``` # import React from 'react'; # # // 名前を付けてcontextを作る # const CounterContext = React.createContext(); # # // 親 # export default class ExtendedParent extends React.Component { # constructor(props) { # super(props); # this.state = { # count: 0 // stateを定義 # }; # } # # increment = () => { # this.setState({ # count: this.state.count + 1 # }); # }; # # render() { # return ( # # # # ); # } # } # # // 子 何もしてない # const Child = () => ; # # // 孫 これはclass # class Grandson extends React.Component { # static contextType = CounterContext; // contextTypeを使う # # componentDidMount() { # // lifecycleの中でもcontextにアクセスできる # console.log(this.context.count); # } # # render() { # const { count, increment } = { ...this.context }; // contextを受け取る # return ( # <> #

count: {count}

# # # ); # } # } # ``` # ### 解説 # # #### [Class.contextType](https://reactjs.org/docs/context.html#classcontexttype) # # - 前の例と同じように、Childコンポーネントではなにもしていない # - `value`で値と一緒に関数も渡している # ``` # # ... # ``` # - 孫コンポーネントの以下の部分でcontextを受け取っている # - `static contextTypes = CounterContext` # - 受け取ったcontextは`this.context.hogehoge`で利用できる # # - createContext()の引数=初期値がよくわからん # # # Consuming Multiple Contexts # # https://reactjs.org/docs/context.html#consuming-multiple-contexts # 複雑性が増しそう # ## Reduxとの使い分け # # - 今後もReduxを使っていくが # - 小規模だったり、ギリReduxを使いたくないぐらいのときに使えそう # - それ以上の規模のときに使うとぐちゃりそう # # - ReduxはReactの旧Context機能を使って実装されている # ## Hooksを使う # # - だいぶ簡素にかける # # # ``` # import React, { createContext, useState, useContext } from 'react'; # # // contextを作る # const CounterContext = createContext(); # # // 親 # const HooksParent = () => { # const [num, setNum] = useState(0); # return ( # setNum(num + 1) # }} # > # # # ); # }; # export default HooksParent; # # // 子 # const Child = () => ; # # // 孫 # const Grandson = () => { # const counter = useContext(CounterContext); // hooksを使う # return ( # // いつも通りの書き方できる # <> #

{counter.num}

# # # ); # }; # ``` # # # # ### 解説 # # - Consumerを使わずにcontextを受け取れる # - 今まではConsumerの中に関数を書いたりしないといけなかったが、それもなくなり通常通りの書き方できる # # ## ContextAPI + react-redux # # - 先日のリリース # - [Release v6.0.0-beta.1 · reduxjs/react-redux](https://github.com/reduxjs/react-redux/releases/tag/v6.0.0-beta.1) # - `yarn add react-redux@next`で入れる # - `yarn add redux` reduxも # - [テストコード](https://github.com/reduxjs/react-redux/blob/85fb553ba8e3f4b0efc158d2e48aafb4c18a04d4/test/components/Provider.spec.js)を参考に # - `ReactReduxContext`というコンポーネントが用意された # # # # # ## reduxっぽいやつ(未完成) # # - hooksも使いたい # - https://github.com/HriBB/react-repro/blob/e3177115ae655c7392fda182d4cdd39a70e6628a/pages/index.js # # ``` # import React from 'react'; # # // store的ななにか # const store = new class { # state = { # num: 0 # }; # actions = fn => { # console.log(this.state); # this.state = fn(this.state); # }; # // actions: fn => { # // this.state = fn(this.state); # // } # // actions: { # // increment: () => { # // this.state.num += 1; # // } # // } # }(); # # // contextを作る # const CounterContext = React.createContext({ num: 0 }); # # // 親 # export default class Parent extends React.Component { # render() { # return ( # #

parenat: {store.state.num}

#

num

# #
# ); # } # } # # // 子 # const Child = () => ; # # // 孫 # // // これはclass # class Grandson extends React.Component { # static contextType = CounterContext; # render() { # const { state, actions } = { ...this.context }; # console.log(state); # console.log(actions); # return ( # <> #

{state.num}

# # # ); # } # } # # // Consumer使う版 # // const Grandson = () => ( # // # // {({ state, actions }) => ( # //
# //

{state.num}

# // # //
# // )} # //
# // ); # ``` #