#React hooksとは
React 16.8 で追加された新機能です。
クラスを書かなくても、state
などのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。
- React hooksを基礎から理解する (useState編)
- React hooksを基礎から理解する (useEffect編)
- React hooksを基礎から理解する (useContext編)
- React hooksを基礎から理解する (useReducer編) 今ここ
- React hooksを基礎から理解する (useCallback編)
- React hooksを基礎から理解する (useMemo編)
- React hooksを基礎から理解する (useRef編)
useReducerとは
状態管理のためのフックで、useState
と似たような機能。useState
はuseReducer
に内部実装されています。
(state, action) => newState
という型のreducer
を受け取り、現在のstate
とdispatch
関数の両方を返します。
react.jsx
const [state, dispatch] = useReducer(reducer,'初期値')
-
reducer
はstate
を更新するための関数で、dispatch
は、reducer
を実行するための呼び出し関数です。
(変数を宣言するときに、stateの更新方法をあらかじめ設定しておくことが出来る。)
dispatch(action)で実行
-
action
は何をするのかを示すオブジェクト -
{type: increment, payload: 0}
のように、type
プロパティ(action
の識別子)と値のプロパティで構成されている。
useStateとuseReducerの比較
useState | useReducer | |
---|---|---|
扱えるstateのtype | 数値、文字列、オブジェクト、論理値 | オブジェクト、配列 |
関連するstateの取り扱い | ☓ | 複数を同時に取り扱うことが出来る |
ローカルorグローバル | ローカル | グローバル useContext()と一緒に取り扱う |
Reduxで実現していたstate管理が、useContext & useReducerで実現できるようになり、Reduxが不要になってきました。
useReducer()を使ってカウンターを作ってみる
スタイリングにはMaterial-UIを使用
Material-UI
をinstallしたら、使いたいコンポーネントをすぐ見つけられるし、勝手にスタイリングしてくれるのでテンションあがります😁
参考:MATERIAL-UI
sample1:stateが単数
counter.jsx
//useReducerをimport
import React, {useReducer} from 'react'
import Button from '@material-ui/core/Button';
import ButtonGroup from '@material-ui/core/ButtonGroup';
//counterの初期値を0に設定
const initialState = 0
//reducer関数を作成
//countStateとactionを渡して、新しいcountStateを返すように実装する
const reducerFunc = (countState, action)=> {
//reducer関数にincrement、increment、reset処理を書く
//どの処理を渡すかはactionを渡すことによって判断する
switch (action){
case 'increment':
return countState + 1
case 'decrement':
return countState - 1
case 'reset':
return initialState
default:
return countState
}
}
const Counter = () => {
//作成したreducerFunc関数とcountStateをuseReducerに渡す
//useReducerはcountStateとdispatchをペアで返すので、それぞれを分割代入
const [count, dispatch] = useReducer(reducerFunc, initialState)
//カウント数とそれぞれのactionを実行する<Button/>を設置する
return (
<>
<h2>カウント:{count}</h2>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button onClick={()=>dispatch('increment')}>increment</Button>
<Button onClick={()=>dispatch('decrement')}>decrement</Button>
<Button onClick={()=>dispatch('reset')}>reset</Button>
</ButtonGroup>
</>
)
}
export default Counter
sample2: stateが複数
counter2.jsx
//useReducerをimport
import React, {useReducer} from 'react'
import Button from '@material-ui/core/Button';
import ButtonGroup from '@material-ui/core/ButtonGroup';
//counterの初期値を0に設定
//2つのcountStateを扱う。それぞれのinitialStateを設定
const initialState ={
firstCounter: 0,
secondCounter: 100
}
//reducer関数を作成
//countStateとactionを渡して、新しいcountStateを返すように実装する
const reducerFunc = (countState, action)=> {
//reducer関数にincrement、increment、reset処理を書く
//どの処理を渡すかはactionを渡すことによって判断する
//switch文のactionをaction.typeに変更
//firstCounter、secondCounter用にcaseを設定
//複数のcounterStateを持っている場合は、更新前のcounterStateを展開し、オブジェクトのマージを行う
switch (action.type){
case 'increment1':
return {...countState, firstCounter: countState.firstCounter + action.value}
case 'decrement1':
return {...countState, firstCounter: countState.firstCounter - action.value}
case 'increment2':
return {...countState, secondCounter: countState.secondCounter + action.value}
case 'decrement2':
return {...countState, secondCounter: countState.secondCounter - action.value}
case 'reset1':
return {...countState, firstCounter: initialState.firstCounter}
case 'reset2':
return {...countState, secondCounter: initialState.secondCounter}
default:
return countState
}
}
const Counter2 = () => {
//作成したreducerFunc関数とcountStateをuseReducerに渡す
//useReducerはcountStateとdispatchをペアで返すので、それぞれを分割代入
const [count, dispatch] = useReducer(reducerFunc, initialState)
//カウント数とそれぞれのactionを実行する<Button/>を設置する
//dispatchで渡しているactionをオブジェクトに変更して、typeとvalueを設定
return (
<>
<h2>カウント:{count.firstCounter}</h2>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button onClick={()=>dispatch({type: 'increment1', value: 1})}>increment1</Button>
<Button onClick={()=>dispatch({type: 'decrement1', value: 1})}>decrement1</Button>
<Button onClick={()=>dispatch({type: 'reset1'})}>reset</Button>
</ButtonGroup>
<h2>カウント2:{count.secondCounter}</h2>
<ButtonGroup color="secondary" aria-label="outlined primary button group">
<Button onClick={()=>dispatch({type: 'increment2', value: 100})}>increment2</Button>
<Button onClick={()=>dispatch({type: 'decrement2', value: 100})}>decrement2</Button>
<Button onClick={()=>dispatch({type: 'reset2'})}>reset</Button>
</ButtonGroup>
</>
)
}
export default Counter2
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218656/b89205ba-f8a5-14a6-4021-8ea58f3f6e5d.gif" width=400 />
## useReducer()を使って外部APIを取得してみる
`useEffect()`と`useReducer()`を使って外部APIを取得してみます。
axiosをyarn add
```
$ yarn add axios
```
参考:[axios](https://github.com/axios/axios)
外部API:[JSONPlaceholder](https://jsonplaceholder.typicode.com/)を利用
```App.js
//useReducerとuseReducerをReactからimport
import React, {useReducer,useEffect} from 'react'
import './App.css'
//axiosをimport
import axios from 'axios'
//initialStateを作成
const initialState = {
isLoading: true,
isError: '',
post: {}
}
//reducerを作成、stateとactionを渡して、新しいstateを返すように実装
const dataFetchReducer = (dataState, action) =>{
switch(action.type) {
case 'FETCH_INIT':
return {
isLoading: true,
post: {},
isError: ''
}
//データの取得に成功した場合
//成功なので、isErrorは''
//postにはactionで渡されるpayloadを代入
case 'FETCH_SUCCESS':
return {
isLoading: false,
isError: '',
post: action.payload,
}
//データの取得に失敗した場合
//失敗なので、isErrorにエラーメッセージを設定
case 'FETCH_ERROR':
return {
isLoading: false,
post: {},
isError: '読み込みに失敗しました'
}
//defaultではそのまま渡ってきたstateを返しておく
default:
return dataState
}
}
const App = () => {
//initialStateとreducer関数をuseReducer()に読み、stateとdispatchの準備
const [dataState, dispatch] = useReducer(dataFetchReducer, initialState)
useEffect(()=>{
//http getリクエストをurlを書く
axios
.get('https://jsonplaceholder.typicode.com/posts/1')
//リクエストに成功した場合
.then(res =>{
//dispatch関数を呼び、type:には'FETCH_SUCCESS'、payloadには受け取ったデータを代入する
dispatch({type:'FETCH_SUCCESS', payload: res.data})
})
//リクエストに失敗した場合catchの中に入ってくる
.catch(err => {
dispatch({type: 'FETCH_ERROR'})
})
})
return (
<div className='App'>
//Loadingが終わったら記事のタイトルを表示
<h3>{dataState.isLoading ? 'Loading...': dataState.post.title}</h3>
//エラーがあった場合の処理
<p>{dataState.isError ? dataState.isError : null}</p>
</div>
)
}
export default App
```
なんとかブラウザ上に表示出来た〜嬉し:smiley: