Help us understand the problem. What is going on with this article?

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

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

useReducer()を使って外部APIを取得してみる

useEffect()useReducer()を使って外部APIを取得してみます。

axiosをyarn add

$ yarn add axios

参考:axios

外部API:JSONPlaceholderを利用

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:

seira
エイチームライフスタイルのフロントエンドデザイナ。 最近はReact, Rails, php, jQuery Sass, Photoshop, Figmaなどを触っています。
life-a-tm
人生のイベントや日常生活に密着した比較サイト、情報サイト等様々なウェブサービスを企画・開発・運営
https://life.a-tm.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away