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

count: {count}

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

{counter.num}

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

parenat: {store.state.num}

\n", "

num

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

{state.num}

\n", " \n", " actions(s => {\n", " // console.log(s);\n", " return {\n", " ...s,\n", " num: s.num + 1\n", " };\n", " })\n", " }\n", " >\n", " increment\n", " \n", " \n", " );\n", " }\n", "}\n", "\n", "// Consumer使う版\n", "// const Grandson = () => (\n", "// \n", "// {({ state, actions }) => (\n", "//
\n", "//

{state.num}

\n", "// \n", "// actions(s => {\n", "// console.log(s);\n", "// return {\n", "// ...s,\n", "// num: s.num += 1\n", "// };\n", "// })\n", "// }\n", "// >\n", "// increment\n", "// \n", "//
\n", "// )}\n", "//
\n", "// );\n", "```\n" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" }, "toc": { "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": false, "toc_cell": false, "toc_position": {}, "toc_section_display": "block", "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }