useReducer 개선.
useReducer
및 useContext
후크를 정말 좋아합니다. 그들은 앱 상태 관리를 편리하게 만들었고 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>
);
}
보기에도 좋고 가독성도 좋아졌습니다.
다음 개선 사항이 있습니다.
이제 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
Reference
이 문제에 관하여(useReducer 개선.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ipshot/enhancing-usereducer-38ba텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)