Yuya-Furusawa / react-nestjs-full-webapp

0 stars 0 forks source link

Reduxについて #2

Open Yuya-Furusawa opened 3 years ago

Yuya-Furusawa commented 3 years ago

Redux

Redux自体はstateを一括管理するライブラリ 必ずしもReactとセットなわけではない

大規模プロジェクトではメリット大(個人開発程度ならオーバーキル) でも最近ではHooksが出てきてuseReducer + useContextで賄うことが多い

参照

Yuya-Furusawa commented 3 years ago

Reduxの概念図

redux-diagram

参照

Yuya-Furusawa commented 3 years ago

Redux w/o React

Reduxは必ずしもReactとセットな訳ではない、ただのstoreを管理するためのライブラリ

①storeを作る

const store = redux.createStore(reducer);

storeを作るためにはreducerが必要

②reducerを作る

reducerは現在のstateをactionを引数に新しいstateを返すただの関数

//初期のstate
const initialState = { value: 0 };

//Action Creatorを定義
const increment = () => ({ type: INCREMENT });
const add = (number) => ({ type: ADD, payload: number });

//reducerを定義
const reducer = (state=initialState, action) => {
  //typeに応じた処理、switch文とか使う
  if (action.type === INCREMENT) {
    return { value: state.value + 1 };
  }
  if (action.type === ADD) {
    return { value: state.value + action.payload };
  }
  return state;
};

③actionをstoreにdispatchする

actionはユーザーの操作などをトリガーとして外部から与えられる その際、storeにactionを渡すときはdispatchしなくては行けない 多くの場合、Action Creatorを使ってdispatchする

store.dispatch(increment());

以上!基本的にはたったこれだけ! reducerを使うstoreを作り、そのstoreにactionをdispatchするだけ

Yuya-Furusawa commented 3 years ago

Redux w/ React

いよいよReactにReduxのロジックを埋め込んでいく

①まずはDOMにstoreを登録する

import { Provider } from 'react-redux';

ReactDOM.render(
  <Provider store={store}>
    <Application />
  </Provider>,
  document.getElementBtId('root')
);

②Hookを使ってstateを取得、storeにdispatch

useSelector: Redux Storeからstateを取得する

componentがレンダリングされる・actionがdispatchされるたびに呼び出される

import { useSelector } from 'react-redux';

const CounterComponent = () => {
  //引数は関数
  const counter = useSelector((state) => state.counter);
  return <div>{counter}</div>;
};

useDispatch: Redux Storeに渡すためのdispatch関数を呼び出す

useCallbackReact.memoで最適化して使うことが多いみたいです

import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch();
  return (
    <div>
      <span>{value}</span>
      //onClickとかでdispatchとかを使う
      <button onClick={() => dispatch({ type: 'increment-counter' })}>
        Increment counter
      </button>
    </div>
  )
}

Connect API

Hook登場以前の主流なやり方(?)

TODO

参照

Yuya-Furusawa commented 3 years ago

Reselectライブラリ

selectorはstoreからstateを持ってきてくれる関数

//selectorの例
//単純なselector
const select = state => state.a;
//useSelector
const todos = useSelector(state => state.todos);

これはシンプルな例だが、stateを持ってくる処理が重くなるほどコストが高く、パフォーマンスが低下する

⇒cacheを使おう!Reselectライブラリを使うとそれが簡単にできる

Reselectライブラリ

import { createSelector } from 'reselect';

const selectA = state => state.a;
const selectB = state => state.b;
//createSelectorで新しく作られたselector
const selectAB = createSelector(
  [selectA, selectB],  //selector
  (a, b) => { return a+b; };  //selectorを用いた関数
);
const ab = selectAB(state);

createSelectorを使って作られたselectorはcacheが有効

※Reselect以外にもcacheを有効にするライブラリはある e.g) proxy-maximize, re-reselect, reselect-tools, redux-views

参照

Yuya-Furusawa commented 3 years ago

Redux Toolkit

普通にreduxのロジックを書くと(特にプロジェクトの規模が大きくなった場合)コードが肥大化し、 可読性が下がる・メンテナンスがしにくくなるなどの弊害が起こる (特にmiddlewareを複数挟むとしんどくなる)

Redux Toolkitを使うと簡潔にReduxのロジックを書くことができる。

configStoreを使ってstoreを作る

import { configStore } from '@reduxjs/toolkit';

export const store = configStore({ reducer: {} });

②Reactにstoreを渡す

import { Provider } from 'react-redux';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

③Redux State Sliceを作る Sliceは「initial state, reducer関数, slice name」から、「reducer関数, slice name, action creators, action types」を返す action creators, action typesはreducer関数から自動的に作られる

import { createSlice } from '@reduxjs/toolkit'; 

const initialState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter', //sliceの名前
  initialState,
  reducers: {
    increment: (state) => { state.value += 1 },
    decrement: (state) => { state.value -= 1 },
    incrementByAmount: (state, action) => { state.value += action.payload }
  }
});

//action creatorがreducer関数に応じて作成される
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

④Sliceで作ったreducerをstoreに登録する

import counterReducer from '../features/counter/counterSlice'

export const store = configStore({
  //sliceごとにReducerを登録
  //slice nameを用いる
  reducer: { counter: countReducer }
});

⑤Sliceで作ったstateやactionをcomponentで使う

import { useSelector, useDispatch } from 'react-redux';
import { decrement, increment } from './counterSlice'; //これはあくまでもaction creator

export function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
};

参照

Yuya-Furusawa commented 3 years ago

useContext + useReducer

大規模プロジェクトではReduxを使うことが多いが、それほど規模が大きくない場合、Hooksが登場してからは、useReducerで済ましてしまうことが多い。 そのときに、Propsのバケツリレーを使うためにuseContextを併用することが多い。

①Initial Stateを定義する

// countがstate
const initialState = { count: 0 };

②Reducer関数を定義する

const reducerFunc = (state, action) => {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
  }
};

③useReducerを使う

// state: stateの参照に使う
// dispatch: stateの更新に使う
const [state, dispatch] = useReducer(reducerFunc, initialState);

④コンポーネントで用いる

const Counter = () => {
  const [state, dispatch] = useReducer(reducerFunc, initialState);
  return(
    <>
      <p>Count {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>INCREMENT</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>DECREMENT</button>
    </>
  );
};

参照