318
189

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React hooksを基礎から理解する (useReducer編)

Last updated at Posted at 2020-06-16

#React hooksとは

React 16.8 で追加された新機能です。
クラスを書かなくても、stateなどのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。

useReducerとは

状態管理のためのフックで、useStateと似たような機能。useStateuseReducerに内部実装されています。

(state, action) => newState という型のreducer を受け取り、現在のstatedispatch関数の両方を返します。

react.jsx
const [state, dispatch] = useReducer(reducer,'初期値')
  • reducerstateを更新するための関数で、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:












318
189
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
318
189

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?