useReducer
React.useReducer
帮助你用 action / reducer 模式表示 state。
用法
useState 的替代方案。它接收一个 (state, action) => newState
类型的 reducer,并返回当前 state
以及与其配套的 dispatch
函数 (action) => unit
。
在某些场景下,useReducer
会比 useState
更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer
还能给那些会触发深层更新的组件做性能优化,因为你可以向子组件传递 dispatch
而不是回调函数 。
注意: 你会发现,归功于 不可变记录,变体 和 模式匹配 特性,action / reducer 模式可以在 ReScript 中轻松地表达 action 和 state 变换。
示例
使用 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>
</>
}
React 会确保
dispatch
函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从useEffect
或useCallback
的依赖列表中省略dispatch
。
拥有更多复杂操作的基础 Todo List 应用
你可以充分利用变体的强大功能,通过数据 payload 来表达 action,从而参数化 state 变换:
// 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> </>
}
惰性初始化
你也可以惰性地创建 initialState
。为此,需要将 init
函数作为 React.useReducerWithMapState
的第三个参数传入,这样初始 state 将被设置为 init(initialState)
。
它让你将计算 state 的逻辑提取到 reducer 外部,这也为以后在响应某个 action 时重置 state 提供了便利:
// 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>
</>
}