useReducer 개선.

32022 단어 reactreduxwebdev
저는 React 내장useReduceruseContext 후크를 정말 좋아합니다. 그들은 앱 상태 관리를 편리하게 만들었고 Redux를 사용하는 것은 나에게 의미를 잃었습니다.

처음 사용했을 때 꽤 유용한 Redux 기능이 부족하다는 것을 깨달았습니다.

  • 사용 선택기. 내부에서 memo를 사용하는 동안 useContext만으로는 다시 렌더링을 최적화할 수 없습니다.

  • 글로벌 파견. 모든useReducer에는 자체 디스패치가 있으므로 여러 디스패치를 ​​사용해야 합니다.

  • 은닉처. 리듀서 데이터를 캐싱하기 위한 특별한 장소가 있어야 합니다.

  • 그래서 이 후크 주변에 이 3가지 기능을 추가하기로 결정했습니다.
    그리고 이 아이디어는 내가 Flex Reducer라고 부르는 새로운 작은 라이브러리로 바뀌었습니다. 이 라이브러리는 (적어도 저에게는) 매우 편리해 보입니다.

    흥미로운 사실!
    Flex Reducer는 구현 시 useReducer 또는 useContext를 사용하지 않습니다.

    두 가지 버전의 일반적인 Todo 앱을 작성해 보겠습니다. 하나는 내장된 useReducer + useContext이고 다른 하나는 Flex Reducer가 있는 편리한 기능입니다.

    먼저 React 트리를 DOM에 렌더링할 루트 파일을 만듭니다. 두 버전 모두 동일합니다.

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

    참고: 더 이상 감속기를 결합하고 저장소를 만들고 공급자를 사용할 필요가 없습니다. 야! :)

    이제 내장된 useReducer를 사용하여 주요 Todo 앱 구성 요소를 생성해 보겠습니다.

    // 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>
      );
    }
    

    충분하다. Flex Reducer를 사용하면 어떻게 보이는지 봅시다.

    // 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를 사용할 필요가 없습니다.
  • 우리는 캐시에 대해 신경 쓸 필요가 없습니다.
  • 전역 디스패치가 있으므로 작업을 어디로든 이동할 수 있습니다.

  • 이제 Add Todo 버튼에 대한 재렌더링 최적화를 비교해 보겠습니다.
    React Hooks로.

    // 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에서 바로 memo를 사용할 수 없습니다. 그래서 우리는 그것을 감싸고 대신 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가 변경된 경우에만 다시 렌더링을 호출하는 input 덕분입니다.

    그러나 세상 모든 일에는 장단점이 있습니다.
    예를 들어 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>
      );
    }
    

    모든 todo 쿼리 업데이트에서 감속기 상태를 업데이트할 때 추가 렌더링에 문제가 있습니다.

    결론
    상태 관리를 위해 useReducer + useContext를 사용하는 것은 꽤 좋습니다. 그러나 컨텍스트와 캐시에 주의해야 합니다.
    Flex Reducer 이 작업을 수행하여 가독성, 메모 최적화를 개선하고 코드 기반을 줄입니다. 그러나 선언적 방식(예: 반응 쿼리)으로 원격 데이터로 작업할 때는 더 나쁩니다.

    경고!
    Flex Reducer는 실험이며 아직 프로덕션에 사용되지 않았습니다.

    읽어 주셔서 감사합니다. 어떤 생각이든 감사합니다.

    찾을 수 있는 Todo 앱의 전체 코드here .
    Link to Flex Reducer repository

    좋은 웹페이지 즐겨찾기