├── .gitignore ├── README.md └── react-context-api ├── README.md ├── counter ├── .gitignore ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── components │ │ └── counter.js │ ├── contexts │ │ └── counter.js │ └── index.js └── yarn.lock └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Recipes 2 | 3 | 本リポジトリはUdemyで公開中の以下のコースの補助資料です。ご興味のある方は以下のリンク先を参照してください。 4 | 5 | * 「[フロントエンドエンジニアのためのReact・Reduxアプリケーション開発入門](https://goo.gl/fLN4rJ)」 6 | 7 | -------------------------------------------------------------------------------- /react-context-api/README.md: -------------------------------------------------------------------------------- 1 | React Context APIについて 2 | 3 | # React Context API 4 | 5 | この記事では[`React Context API`](https://reactjs.org/docs/context.html)について見ていきたいと思います。 6 | 7 | まずは、`React Context API`の背景についてお話ししたいと思います。 8 | 9 | ## 背景 10 | 11 | * Reactでは、配下のコンポーネントにデータを渡すための手段として[`props`](https://reactjs.org/docs/components-and-props.html)という機能が提供されています。 12 | * ところが、この`props`を使用すると、親コンポーネントから子コンポーネントへ、さらに孫コンポーネントへ、...、といった具合で、渡したいコンポーネントまで渡したいデータをバケツリレーのように延々と渡していかなければならない弱点がありました。これを`prop drilling`問題と言います。 13 | * その問題を解消するべく、どのコンポーネントからでも特定のデータにアクセスできる仕組みが`react-redux`から提供されています。 14 | * Reduxを使用したことのある人なら誰もが知っている[`Provider`](https://github.com/reduxjs/react-redux/blob/master/docs/api.md#provider-store)コンポーネントです。 15 | * `Provider`コンポーネントは文字通り`Provider`コンポーネントでwrapした全てのコンポーネントに対して特定のデータを届けることを目的とするコンポーネントになります。 16 | * ところがその後、Reactモジュールは、バージョンv16.3で、Reduxに喧嘩を売っているのか?とまさに耳を疑うような機能を追加してきました。 17 | * `react-redux`の`Provider`とほぼ同様の機能で同名の`Provider`というコンポーネントをリリースしたんです。 18 | * この記事では、React側でリリースされたこの`Provier`を含む[Context](https://reactjs.org/docs/context.html) APIについての紹介と、`Context API`を使用したアプリケーションの実践的な実装例について紹介したいと思います。 19 | 20 | ## Reactアプリケーションの作成 21 | 22 | では、早速、Reactアプリケーションを作成しましょう。アプリケーションの名前は`counter`とします。 23 | 24 | ```bash 25 | $ npm init react-app counter 26 | ``` 27 | 28 | そして、`counter`ディレクトリに移動します。 29 | 30 | ```bash 31 | $ cd counter 32 | ``` 33 | 34 | 念のため、`package.json`の中身を確認しておきましょう。 35 | 36 | Reactのバージョンが`v16.3`以上でないと`React Context API`の動作確認ができないので確認します。 37 | 38 | `react`のバージョンが`^16.3.2` となっているので問題ないです。 39 | 40 | ```javascript 41 | { 42 | "name": "counter", 43 | "version": "0.1.0", 44 | "private": true, 45 | "dependencies": { 46 | "react": "^16.3.2", 47 | "react-dom": "^16.3.2", 48 | "react-scripts": "1.1.4" 49 | }, 50 | "scripts": { 51 | "start": "react-scripts start", 52 | "build": "react-scripts build", 53 | "test": "react-scripts test --env=jsdom", 54 | "eject": "react-scripts eject" 55 | } 56 | } 57 | ``` 58 | 59 | 続いてReactアプリケーションを起動します。ターミナルで`yarn start`と入力し、エンターキーを押します。 60 | 61 | ```bash 62 | $ yarn start 63 | ``` 64 | 65 | `yarn start`を実行するとブラウザが自動的に起動されます。そして、`Welcome to React`のおなじみの画面が表示されます。 66 | 67 | これが確認できたらエディターに戻って、ファイルを編集します。 68 | 69 | ## React Context APIを導入したカウンタアプリの例 70 | 71 | では、これから、React Context APIを導入したカウンタアプリの実装を始めます。 72 | 73 | まずはReactアプリケーションのトップレベルのファイルである`src/index.js`を編集していきます。 74 | 75 | ### 編集後の`src/index.js` 76 | 77 | そして、`src/index.js`を以下のように書き換えます。 78 | 79 | ```javascript 80 | import React from 'react'; 81 | import ReactDOM from 'react-dom'; 82 | import CounterContext from './contexts/counter' 83 | import Counter from './components/counter' 84 | 85 | class App extends React.Component { 86 | constructor(props) { 87 | super(props) 88 | 89 | this.increment = this.increment.bind(this) 90 | this.decrement = this.decrement.bind(this) 91 | 92 | this.state = { 93 | count: 0, 94 | increment: this.increment, 95 | decrement: this.decrement 96 | } 97 | } 98 | 99 | increment() { 100 | this.setState({count: this.state.count + 1}) 101 | } 102 | 103 | decrement() { 104 | this.setState({count: this.state.count - 1}) 105 | } 106 | 107 | render() { 108 | return ( 109 | 110 | 111 | 112 | ) 113 | } 114 | } 115 | 116 | ReactDOM.render(, document.getElementById('root')); 117 | ``` 118 | 119 | 以下の点がポイントになります。 120 | 121 | * `import CounterContext from './contexts/counter'` コンテキストの作成はこの`src/index.js`ではやらず別ファイルに分離します。 122 | * このアプリケーションではコンテキストのファイルは`contexts`ディレクトリに集めたいと思います。 123 | * そして`CounterContext`というコンテキストを定義します。 124 | * この`src/index.js`では、その`CounterContext`を`import`して使用します。 125 | * コンテキストは`Provier`と`Consumer`のAPIを提供します。 126 | * 今編集中の`src/index.js`で作成している`App`コンポーネントの役割は自身がもつカウンタの状態やカウンタの値をインクリメントしたりデクリメントする機能を有するstateを配下のコンポーネントに提供することです。 127 | * そのstateの提供時に`Provider`コンポーネントで提供すれば良いです。 128 | * また、今回のように小規模なアプリケーションだと問題にはならないですが、大規模なアプリケーションを想定するとおそらく今回のように別のファイルに分離しないと状態管理に関してカオスになってしまいます。 129 | * というのも、contextは実質無限個作成できるからです。 130 | * そういう理由からcontextは別のファイルに分離しました。 131 | * componentも別ファイルに分離しました。 132 | * これもcontextと同様の理由からですが、もはやこれはReactアプリケーションを書く人なら既に身についているであろう慣習です。 133 | * 状態はReactの基本機能であるstateで管理します。 134 | * 状態遷移についてもReactの`setState`で行います。 135 | * なのでReduxで書くときとは違い、stateの変更とそれを誘発するイベントハンドラがコンポーネントにべったりな点は、Reduxアプリを書き慣れている人で何でもかんでも分離したがる潔癖症な人にとってはかなりキモいコードと感じてしまうかもしれませんが仕方ないです。 136 | * importしたCounterContextに紐づく`CounterContext.Provider`というコンポーネントで渡したい状態を受け取るコンポーネントをwrapします。 137 | * `Provider`ではなく`CounterContext.Provider`と表記しているのは、今回のケースでは、`Provider`でも良かったんですがContextは無数存在し得る物なのでどのProviderなのかを識別できるよう`CounterContext.`というプレフィックスにより名前空間を識別する習慣を付けないと後々アプリケーションが大規模になってきて複数のContextが入り乱れるようになってときにリファクタリングを強いられると思うのでそうしています。こういう将来起こりうる問題を予測しながら安全なコードを早い段階から意識してかけるように準備しておくことはプログラマーとしては非常に大事な習慣になると思います。 138 | * ProvierでConsumerに渡したい状態を`value=`で渡しています。 139 | * 状態は`this.state`で取得できます。 140 | 141 | 以上が`App`コンポーネントの説明になります。 142 | 143 | ### Counterコンポーネント 144 | 145 | 続いてCounterコンポーネントを作成します。  146 | 147 | ```bash 148 | $ mkdir src/components 149 | $ touch src/components/counter.js 150 | ``` 151 | 152 | エディターで以下のように編集します。 153 | 154 | ```javascript 155 | import React from 'react'; 156 | import CounterContext from '../contexts/counter' 157 | 158 | const Counter = () => ( 159 | 160 | { 161 | ({ count, increment, decrement }) => { 162 | return ( 163 | 164 |
count: {count}
165 | 166 | 167 |
168 | ) 169 | } 170 | } 171 |
172 | ) 173 | 174 | export default Counter 175 | ``` 176 | 177 | では`Counter`コンポーネントについて説明していきます。 178 | 179 | * まず、今からコンポーネントを作成しますので、`import React`を書きます。 180 | * もちろん、`form 'react'`になります。 181 | * そして次に、`App`コンポーネントと同様に`CounterContext`を`import`します。 182 | * これは、`App`コンポーネント内の`CounterContext`の`Provider`が渡してくれる`value`を受けるためです。 183 | * そのvalueを受け取るには、CounterContextのConsumerコンポーネントが必要になります。 184 | * 今から作成するコンポーネントをCounterコンポーネントとします。 185 | * このコンポーネント独自の状態はないため関数コンポーネントにします。 186 | * そしてこのCounterコンポーネントの子コンポーネントに`CounterContext`の`Consumer`コンポーネントを設置します。 187 | * `CounterContext.Consumer`と表記しているのは、`CounterContext.Provider`と表記しているのと同じ理由からです。(上述) 188 | * Consumerの内部は関数であり必須です。 189 | * 関数を書いてください。 190 | * Consumer内部の関数の引数で、Providerが渡してくれたvalueを受け取ることができます。 191 | * 今回のアプリケーションでは、Appコンポーネント側から渡した、カウンタの値、インクリメントの関数、デクリメントの関数を受け取ることができます。 192 | * 後はこの様に、受け取った値を適当な場所に表示させたり、受け取った処理を適当なイベントハンドラに渡したりすれば良いです。 193 | 194 | 195 | 以上が、Counterコンポーネントの実装になります。 196 | 197 | ### CounterContextの作成 198 | 199 | 最後にカウンタ専用の`CounterContext`を作成します。 200 | 201 | ```bash 202 | $ mkdir src/contexts 203 | $ touch src/contexts/counter.js 204 | ``` 205 | 206 | `src/contexts/counter.js`を以下のように編集します。 207 | 208 | ```javascript 209 | import { createContext } from 'react'; 210 | 211 | const CounterContext = createContext() 212 | export default CounterContext 213 | ``` 214 | 215 | * コンテキストとは即ちProviderとConsumer間で共有したいオブジェクトです。 216 | * つまり、状態と処理です。 217 | * createContextの引数にはデフォルト値を渡すことができます。 218 | * ところが、今回のアプリケーションにおいてのデフォルト値の設定はAppコンポーネントで行っていますので、CounterContextでは何も設定しないというポリシーにしています。 219 | 220 | ## デモ画面 221 | 222 | * 以上のコーディングが終われば、ブラウザにカウンタアプリが表示されると思います。 223 | * +1ボタンを押したり、-1ボタンを押したりして動作確認をしてみてください。 224 | 225 | ![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/9001/aa76ae77-c1b3-0381-25f9-09b462abdfe9.gif) 226 | 227 | ## ソースコード 228 | 229 | * 今回の記事で扱った動作確認のとれているソースコードは[GitHub](https://github.com/ProgrammingSamurai/react-recipes)に公開しています。 230 | * 書くのが面倒という方は下記コマンドで`git clone`をしてコードの確認や挙動の確認をしてみてください。 231 | 232 | ```bash 233 | $ git clone git@github.com:ProgrammingSamurai/react-recipes.git 234 | ``` 235 | 236 | 以上で、React バージョンv16.3のContext APIの紹介は終わります。今後も目が離せないReactですね。それではまた! 237 | 238 | ## おわりに 239 | 240 | 先日、Udemy講師デビューを果たしました。「[フロントエンドエンジニアのためのReact・Reduxアプリケーション開発入門](https://goo.gl/M1V3sD)」というコースを公開中です。これからReactをやってみようとお考えの方は是非始めてみてください!こちらの[リンク](https://goo.gl/M1V3sD)から95%オフで購入できます。ご質問も大歓迎です! :) 241 | -------------------------------------------------------------------------------- /react-context-api/counter/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /react-context-api/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.3.2", 7 | "react-dom": "^16.3.2", 8 | "react-scripts": "1.1.4" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test --env=jsdom", 14 | "eject": "react-scripts eject" 15 | } 16 | } -------------------------------------------------------------------------------- /react-context-api/counter/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingSamurai/react-recipes/8cdec84d60071ee0d6e172417cf9e01d40009753/react-context-api/counter/public/favicon.ico -------------------------------------------------------------------------------- /react-context-api/counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /react-context-api/counter/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /react-context-api/counter/src/components/counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CounterContext from '../contexts/counter' 3 | 4 | const Counter = () => ( 5 | 6 | { 7 | ({ count, increment, decrement }) => { 8 | return ( 9 | 10 |
count: {count}
11 | 12 | 13 |
14 | ) 15 | } 16 | } 17 |
18 | ) 19 | 20 | export default Counter 21 | -------------------------------------------------------------------------------- /react-context-api/counter/src/contexts/counter.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | const CounterContext = createContext() 4 | export default CounterContext 5 | -------------------------------------------------------------------------------- /react-context-api/counter/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import CounterContext from './contexts/counter' 4 | import Counter from './components/counter' 5 | 6 | class App extends React.Component { 7 | constructor(props) { 8 | super(props) 9 | 10 | this.increment = this.increment.bind(this) 11 | this.decrement = this.decrement.bind(this) 12 | 13 | this.state = { 14 | count: 0, 15 | increment: this.increment, 16 | decrement: this.decrement 17 | } 18 | } 19 | 20 | increment() { 21 | this.setState({count: this.state.count + 1}) 22 | } 23 | 24 | decrement() { 25 | this.setState({count: this.state.count - 1}) 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 | 32 | 33 | ) 34 | } 35 | } 36 | 37 | ReactDOM.render(, document.getElementById('root')); 38 | -------------------------------------------------------------------------------- /react-context-api/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingSamurai/react-recipes/8cdec84d60071ee0d6e172417cf9e01d40009753/react-context-api/demo.gif --------------------------------------------------------------------------------