ReScript in Korean

useReducer

원문

React.useReducer는 액션/리듀서 패턴으로 상태를 표현하는데 도움을 줍니다.

사용법

let (state, dispatch) = React.useReducer(reducer, initialState)

useState의 대안입니다. (state, action) => newState 타입의 리듀서를 인자로 받고 (action) => unit 타입인 dispatch 함수와 쌍을 이루는 현재 state를 반환합니다.

React.useReducer는 여러 하위 값을 포함하는 복잡한 상태 관련 로직이 있거나 다음 상태가 이전 상태에 의존하는 경우 일반적으로 useState 훅보다 선호됩니다. useReducer를 사용하면 콜백 대신 디스패치를 전달할 수 있으므로 깊은 상태 구조 업데이트를 발생시키는 컴포넌트의 성능을 최적화 할 수 있습니다.

참고 액션 / 리듀서 패턴은 리스크립트의 불변 레코드, 배리언트 그리고 패턴 매칭 기능을 사용하여 작업 및 상태 변경을 쉽게 표현할 수 있습니다.

예제

React.useReducer를 사용한 카운터 예제

/* Counter.res */
type action = Inc | Dec
type state = {count: int}
let reducer = (state, action) => {
switch action {
| Inc => {count: state.count + 1}
| Dec => {count: state.count - 1}
}
}
@react.component
let make = () => {
let (state, dispatch) = React.useReducer(reducer, {count: 0})
<>
{React.string("Count:" ++ Belt.Int.toString(state.count))}
<button onClick={(_) => dispatch(Dec)}> {React.string("-")} </button>
<button onClick={(_) => dispatch(Inc)}> {React.string("+")} </button>
</>
}

리액트는 디스패치 함수 ID(identity)가 안정적이며 다시 렌더링할 때 변경되지 않음을 보장합니다. 이것이 디스패치(dispatch)를 useEffect 또는 useCallback 종속성 목록(Deps)에서 생략해도 안전한 이유입니다.

더 복잡한 액션이 포함 된 Todo List 앱

여러분은 배리언트의 모든 기능(full power)를 활용하여 액션과 데이터 페이로드를 표현해 상태 전환을 매개변수화 할 수 있습니다.

/* TodoApp.res */
type todo = {
id: int,
content: string,
completed: bool,
}
type action =
| AddTodo(string)
| RemoveTodo(int)
| ToggleTodo(int)
type state = {
todos: array<todo>,
nextId: int,
}
let reducer = (state, action) =>
switch action {
| AddTodo(content) =>
let todos = Js.Array2.concat(
state.todos,
[{id: state.nextId, content: content, completed: false}],
)
{todos: todos, nextId: state.nextId + 1}
| RemoveTodo(id) =>
let todos = Js.Array2.filter(state.todos, todo => todo.id !== id)
{...state, todos: todos}
| ToggleTodo(id) =>
let todos = Belt.Array.map(state.todos, todo =>
if todo.id === id {
{
...todo,
completed: !todo.completed,
}
} else {
todo
}
)
{...state, todos: todos}
}
let initialTodos = [{id: 1, content: "Try ReScript & React", completed: false}]
@react.component
let make = () => {
let (state, dispatch) = React.useReducer(
reducer,
{todos: initialTodos, nextId: 2},
)
let todos = Belt.Array.map(state.todos, todo =>
<li>
{React.string(todo.content)}
<button onClick={_ => dispatch(RemoveTodo(todo.id))}>
{React.string("Remove")}
</button>
<input
type_="checkbox"
checked=todo.completed
onChange={_ => dispatch(ToggleTodo(todo.id))}
/>
</li>
)
<> <h1> {React.string("Todo List:")} </h1> <ul> {React.array(todos)} </ul> </>
}

지연 초기화

let (state, dispatch) =
React.useReducerWithMapState(reducer, initialState, initial)

initialState 초기 상태를 늦게 생성할 수 있습니다. 이 방법을 사용하려면 React.useReducerWithMapState 훅을 사용하고 init 함수를 세 번째 인수르 전달할 수 있습니다. 초기 상태는 init(initialState)로 설정됩니다.

이 함수는 리듀서 외부의 초기 상태를 만들기 위한 로직을 추출할 수 있게 합니다. 또한 액션에 대한 응답을 가지고 나중에 상태를 재설정하는 데도 좋습니다.

/* Counter.res */
type action = Inc | Dec | Reset(int)
type state = {count: int}
let init = initialCount => {
{count: initialCount}
}
let reducer = (state, action) => {
switch action {
| Inc => {count: state.count + 1}
| Dec => {count: state.count - 1}
| Reset(count) => init(count)
}
}
@react.component
let make = (~initialCount: int) => {
let (state, dispatch) = React.useReducerWithMapState(
reducer,
initialCount,
init,
)
<>
{React.string("Count:" ++ Belt.Int.toString(state.count))}
<button onClick={_ => dispatch(Dec)}> {React.string("-")} </button>
<button onClick={_ => dispatch(Inc)}> {React.string("+")} </button>
</>
}