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 | Dectype state = {count: int}let reducer = (state, action) => {switch action {| Inc => {count: state.count + 1}| Dec => {count: state.count - 1}}}@react.componentlet 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.componentlet 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><inputtype_="checkbox"checked=todo.completedonChange={_ => 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.componentlet 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></>}