useReducerの改善

アプリの状態の出現useReduceruseContext管理により、アプリの状態ははるかに便利になり、Reduxを使用する必要がなくなりました。



初めてReduxを捨てて標準のものを選んだときuseReducer、いくつかの便利な機能が不足していると感じました。



  • useSelectorあなたが使用されているコンポーネントのレンダリングを最適化することを可能にするuseContextにはmemo
  • 唯一のディスパッチごとに個別のディスパッチを使用する必要がないため、アプリの状態の更新が簡単になりますuseReducer
  • キャッシュ全員をキャッシュすることを心配する必要はありませんuseReducer


そこでuseReducer、これら3つの機能を追加して規格の改善を試みることにしましたこのアイデアは、私がFlexReducerと呼ぶ新しい小さなライブラリに進化しました



興味深い事実!



Flexの減速はで使用されていないuseReducer任意のuseContextその実装の。



例として、典型的なTodoアプリを使用して標準のuseReducer+useContextとFlexReducerを使用することの長所と短所を見てみましょう



まず、Reactツリーがレンダリングされるメインファイルを作成しましょう。



// index.js
import TodoApp from "./TodoApp";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <TodoApp />,
  rootElement
);


:レデューサーを組み合わせたり、ストアとプロバイダーを作成したりする必要はありません。やったー!:)



それでは、を使用してメインのTodoAppコンポーネントを記述しましょうuseReducer



// TodoApp.js
import { useReducer, createContext, useMemo } from 'react';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const AppContext = createContext(null);
const cache = {};

export default function TodoApp() {
  const [state, dispatch] = useReducer(reducer, cache.state || initialState);
  cache.state = state;
  const actions = useMemo(() => ({
    setInput: (value) => {
      dispatch({
        type: 'SET_INPUT', 
        payload: value
      })
    },
    addTodo: ({ id, content }) => {
      dispatch({
        type: 'ADD_TODO',
        payload: { id, content }
      })
    }
  }), []);
  return (
    <AppContext.Provider value=[state, actions]>
      <div className="todo-app">
        <h1>{state.title}</h1>
        <input value={state.input} onChange={e => actions.setInput(e.target.value)} />
        <AddTodo />
        <TodoList />
      </div>
    </AppContext>
  );
}


いいね。同じことですが、FlexReducerを使用しています。



// TodoApp.js
import { useFlexReducer, dispatch } from 'flex-reducer';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const setInput = (value) => dispatch({
  type: SET_INPUT,
  payload: value
});
export const addTodo = ({ id, content }) => dispatch({
  type: ADD_TODO,
  payload: { id, content }
});

export default function TodoApp() {
  const [state] = useFlexReducer('app', reducer, initialState);
  return (
    <div className="todo-app">
      <h1>{state.title}</h1>
      <input value={state.input} onChange={e => setInput(e.target.value)} />
      <AddTodo />
      <TodoList />
    </div>
  );
}


, .

:



  • React Context.
  • .
  • actions dispatch.


re-renders Add Todo button.

.



// AddTodo.js
import { useContext, memo } from 'react';
import { appContext } from './TodoApp';

const genId = () => Math.rand();

const AddTodo = memo(({ input, actions }) => {
  function handleAddTodo() {
    if (content) {
      actions.addTodo({ id: genId(), content: input });
      actions.setInput('');
    }
  }
  return (
    <button onClick={handleAddTodo}>
      Add Todo
    </button>
  );
})

export default const MemoizedAddTodo = () => {
  const [state, actions] = useContext(appContext);
  return (
    <AddTodo input={state.input} actions={actions} />
  );
}


useContext AddTodo render memo. -, useContext props.



Flex Reducer.



// AddTodo.js
import { useSelector } from 'flex-reducer';
import { addTodo, setInput } from "./TodoApp";

const genId = () => Math.rand();

export default const AddTodo = React.memo(() => {
  const content = useSelector(state => state.app.input);
  function handleAddTodo() {
    if (content) {
      addTodo({ id: genId(), content });
      setInput('');
    }
  }
  return (
    <button onClick={handleAddTodo}>
      Add Todo
    </button>
  );
})


. -. useSelector, re-render input.



, , Flex Reducer .

remote data, , react-query.



useReducer.



// TodoApp.js
import { useReducer, createContext, useMemo } from 'react';
import { useQuery } from 'react-query';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const AppContext = createContext(null);
const cache = {};

export default function TodoApp() {
  const [reducerState, dispatch] = useReducer(reducer, cache.state || initialState);
  cache.state = reducerState;
  const actions = useMemo(() => ({
    setInput: (value) => {
      dispatch({
        type: 'SET_INPUT', 
        payload: value
      })
    },
    addTodo: ({ id, content }) => {
      dispatch({
        type: 'ADD_TODO',
        payload: { id, content }
      })
    }
  }), []);

  const todos = useQuery('todos', fetchTodoList);
  const state = { ...reducerState, todos };

  return (
    <AppContext.Provider value=[state, actions]>
      <div className="todo-app">
        <h1>{state.title}</h1>
        <input value={state.input} onChange={e => actions.setInput(e.target.value)} />
        <AddTodo />
        <TodoList />
      </div>
    </AppContext>
  );
}


. .



Flex Reducer.



// TodoApp.js
import { useFlexReducer, dispatch } from 'flex-reducer';
import { useQuery } from 'react-query';
import AddTodo from './AddTodo';
import TodoList from './TodoList';

export const setInput = (value) => dispatch({
  type: SET_INPUT,
  payload: value
});
export const addTodo = ({ id, content }) => dispatch({
  type: ADD_TODO,
  payload: { id, content }
});
export const setTodos = (todos) => dispatch({
  type: SET_TODOS,
  payload: todos
});

export default function TodoApp() {
  const [state] = useFlexReducer('app', reducer, initialState);
  const todos = useQuery('todos', fetchTodoList);
  React.useEffect(() => {
    setTodos(todos);
  }, [todos]);

  return (
    <div className="todo-app">
      <h1>{state.title}</h1>
      <input value={state.input} onChange={e => setInput(e.target.value)} />
      <AddTodo />
      <TodoList />
    </div>
  );
}


todos query.





useReducer+useContext使用してアプリの状態を管理すると非常に便利です。ただし、これには、コンテキストとキャッシュを使用した慎重な「手動」作業が必要です。

Flex Reducerはこれを処理し、コードの読みやすさを向上させ、最適化の記述を容易にしmemo、whenの量を減らします。ただし、リモートデータを宣言的に操作すると(react-queryの場合のように)問題が発生します。



注意!



Flex Reducerは単なる実験であり、本番環境では使用されていません。



読んでくれてありがとう。どんな考えでも大歓迎です。



Todoアプリの実用的な例はここにあります

FlexReducerリポジトリへのリンク




All Articles