The source code for this blog is available on GitHub.

Blog.

Reactを学ぶ14

Cover Image for Reactを学ぶ14

「React Hooks 入門 - Hooksと Redux を組み合わせて最新のフロントエンド状態管理手法を習得しよう!」を参考に。

作業時間:8時間 経過時間:58時間

useState

const [count,setCount] = useState(0)
...
// const increment = () => setCount(count + 1) 現在のstateに対して
const increment = () => setCount(previousCount => previousCount + 1) //前回のstateに対して
return (
  <button onClick={increment}>+1</button>
)

フック API リファレンス

propsでuseStateに参照させることもできる。 onClickで関数を呼べば良いのに気づかなかった。 呼び出した関数先でstateの更新処理を行える。

const App = props => {
  // const initialStates = {
  //   name: '',
  //   price: 1000
  // }
  // const [name,setName] = useState(initialStates.name)
  // const [price,setPrice] = useState(initialStates.price)
  
  const [name,setName] = useState(props.name)
  const [price,setPrice] = useState(props.price)
  ...
  const reset = () => {
    setPrice(props.price)
    setName(props.name)
  }
  ...
  return(
    <button onClick={reset}>Reset</button>
  )
}

App.defaultProps = {
  name: '',
  price: 1000
}

useEffect

const App = props => {
  ...
  useEffect(()=>{
    console.log('This is like componentDidMount or componentDidupdate')
  })

  useEffect(()=>{
    console.log('This is like componentDidMount')
  },[])

  useEffect(()=>{
    console.log('This callback is for name only.')
  },[name])
  ...  
  return (
  ...)
 }

状態遷移

状態遷移表というのが存在する 例えば以下のように、状態が遷移する要素と、遷移させるトリガーを対にした関係図になる。

| | 追加 | 削除 | 更新 | | ---------- | ---------- | ---------- | ---------- | | リスト | リストの最後に追加 | リストの最後を削除 | リストの最後を更新 |

Reactでは、このカラムがAction,追加・削除・更新などの種別のフィールドがタイプ、内容のフィールドがReducerとなる。

状態遷移図というのもある。

useReducer

// src/components/App.js
import Event from '../Event'
// useRdecuerの第一引数には、reducer,第二引数にはstateの初期状態、第三引数には初期処理
const [state,dispatch] = useReducer(reducer,[])
const App = () => {
  ...
  return (
    ...
    <tbody>
     {state.map((event,index) => (<Event key={index} event={event} dispatch={dispatch} />))}
    </tbody>
    ...
  )
}
// // src/Event/index.js
const Event = ({event,dispatch}) => {
  ...
}

子コンポーネントでどうやってstateやdispatchにアクセスするか、忘れていた。propsだ。 dispatchのイメージが固まってきた。 handlerでオブジェクト(action)がdispatchに渡される。 dispatchに渡ると、reducerにオブジェクトが渡される。タイプが識別され、応じて処理される。 どこで、dispatchとreducerが紐付けられているか、ぼんやりしていたけど、 純粋に useReducerの引数にreducerを渡して、そこからstateとdispatchを定義しているから、紐付いているのか、hooksの場合は。

componentsの分割

分割した子コンポーネントの中で、state,dispatchを定義すると、親と別々のオブジェクトが生成されてしまう。なので、同期がとれない。参照するようにしないといけない。propsを使用して解決する。

参照できない例(別々のメモリが確保される?)

// src/components/App.js
const App = () => {
   const [state,dispatch] = useReducer(reducer,[])
   return (
    <div className="container-fluid">
      <EventFrom />
      <Events />
    </div>
  );
}

// src/components/Events.js
const Events = () => {
  const [state,dispatch] = useReducer(reducer,[])
  return (
    ...
  )
}

参照できる例

// src/components/App.js
const App = () => {
  const [state,dispatch] = useReducer(reducer,[])
  return (
    <div className="container-fluid">
      <EventFrom state={state} dispatch={dispatch} />
      <Events state={state} dispatch={dispatch} />
    </div>
  );
}

// src/components/Events.js
const Events = ({state,dispatch}) => {
  return (
    ...
  )
}

同期をしたいデータを参照させるのは、hooksであろうとなかろうと同じ考え方、propsで紐付ける。 props:インターフェース。

Action

actionのタイプは定数で定義するのが慣習らしい。

Prop Drilling 問題

親から子にデータを参照させていくには、propsで受け渡していく必要がある。 しかし、階層が深くなったり、広くなったりすると、propsを受け渡すためだけに記述をしないといけないケースが増える。それを解決する手段がRedux。 コンポーネントに対して、stateとdispatchをreact-reduxのconnectでデコーレーションさせる。 そうすると、子自身からアクセスできるようになる。

context

createContext API

// src/contexts/AppContext.js
import {createContext} from 'react'
const AppContext = createContext()
export default AppContext
// src/components/App.js
import AppContext from '../contexts/AppContext'
const App = () => {
  return (
    <AppContext.Provider value={'hello world'}>
    ...
    </AppContext.Provider>
  );
}

// src/components/Events.js
import AppContext from '../contexts/AppContext'
const Events = ({state,dispatch}) => {
  return (
    <AppContext.Consumer>
        {value=>{
          return <div>{value}</div>
        }}
    </AppContext.Consumer>
    ...
  )
}

useContext

// src/contexts/AppContext.js
import {createContext} from 'react'
const AppContext = createContext()
export default AppContext
// src/components/App.js
import AppContext from '../contexts/AppContext'
const App = () => {
  return (
    <AppContext.Provider value={'hello world'}>
    ...
    </AppContext.Provider>
  );
}

// src/components/Events.js
import React, {useContext} from 'react
...
import AppContext from '../contexts/AppContext'
const Events = ({state,dispatch}) => {
  const value = useContext(AppContext)
  return (
    <div>{value}</div>
    ...
  )
}

引数にcontextを渡せば、変数を受け取れる。 依存関係を減らせるのが良い。そのコンポーネントだけに集中できる。

Redux

複数の状態管理を行うには、Reactとは別のライブラリが必要となる。Redux。 Reduxでreducerを複数管理することによって、状態管理をそれぞれ保持できる。 combineReducers()を使用すると、stateの返り値がオブジェクトとして返される。

// src/reducers/index.js
import {combineReducers} from 'redux'
import events from './events'
export default combineReducers({events})
// state.eventsでアクセスできる

// src/components/App.js
// オブジェクトで初期化する
const initialState = {
  events: [],
}
const [state,dispatch] = useReducer(reducer,initialState)

LocalStorage

localstorageと併用してデータの永続化を行える。 stringfyメソッドで文字列化し、pargeで戻す。

const APP_KEY = 'appWithRedux'
const App = () => {
  const appState = localStorage.getItem(APP_KEY)
  const initialState = appState ? JSON.parse(appState) : {
    events: [],
    operationLogs: []
  }
  const [state,dispatch] = useReducer(reducer,initialState)
  useEffect(()=>{
    localStorage.setItem(APP_KEY,JSON.stringify(state))
  },[state])
...
}

そのほか

Git

git push origin HEADが分からないので調べた。 リモートリポジトリに対して、ローカルリポジトリ名と同じブランチ名にプッシュできる。 知らなかった。。とても楽。 git push -u origin HEAD のオプションuは追跡監視するとのことだがまだピンと来ていない。。


More Stories