├── .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 | 
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
--------------------------------------------------------------------------------