跳至主内容区

React 与 Immer

[非官方测试版翻译]

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

egghead.io lesson 8: Using Immer with _useState_. Or: _useImmer_

useState + Immer

useState 钩子要求其内部存储的任何状态都被视为不可变的。通过使用 Immer,可以极大地简化 React 组件状态的深层更新。以下示例展示了如何将 produceuseState 结合使用,可在 CodeSandbox 上体验。

import React, { useCallback, useState } from "react";
import {produce} from "immer";

const TodoList = () => {
const [todos, setTodos] = useState([
{
id: "React",
title: "Learn React",
done: true
},
{
id: "Immer",
title: "Try Immer",
done: false
}
]);

const handleToggle = useCallback((id) => {
setTodos(
produce((draft) => {
const todo = draft.find((todo) => todo.id === id);
todo.done = !todo.done;
})
);
}, []);

const handleAdd = useCallback(() => {
setTodos(
produce((draft) => {
draft.push({
id: "todo_" + Math.random(),
title: "A new todo",
done: false
});
})
);
}, []);

return (<div>{*/ See CodeSandbox */}</div>)
}

useImmer

由于所有状态更新器都遵循相同的模式——将更新函数包裹在 produce 中,因此可以利用 use-immer 包自动将更新函数包裹在 produce 中:

import React, { useCallback } from "react";
import { useImmer } from "use-immer";

const TodoList = () => {
const [todos, setTodos] = useImmer([
{
id: "React",
title: "Learn React",
done: true
},
{
id: "Immer",
title: "Try Immer",
done: false
}
]);

const handleToggle = useCallback((id) => {
setTodos((draft) => {
const todo = draft.find((todo) => todo.id === id);
todo.done = !todo.done;
});
}, []);

const handleAdd = useCallback(() => {
setTodos((draft) => {
draft.push({
id: "todo_" + Math.random(),
title: "A new todo",
done: false
});
});
}, []);

// etc

完整示例请参见 CodeSandbox

useReducer + Immer

useState 类似,useReducer 也能与 Immer 无缝协作,如该 CodeSandbox 所示:

import React, {useCallback, useReducer} from "react"
import {produce} from "immer"

const TodoList = () => {
const [todos, dispatch] = useReducer(
produce((draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find(todo => todo.id === action.id)
todo.done = !todo.done
break
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
})
break
default:
break
}
}),
[
/* initial todos */
]
)

const handleToggle = useCallback(id => {
dispatch({
type: "toggle",
id
})
}, [])

const handleAdd = useCallback(() => {
dispatch({
type: "add",
id: "todo_" + Math.random()
})
}, [])

// etc
}

useImmerReducer

...这同样可以通过 use-immer 包中的 useImmerReducer 稍加简化(示例):

import React, { useCallback } from "react";
import { useImmerReducer } from "use-immer";

const TodoList = () => {
const [todos, dispatch] = useImmerReducer(
(draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find((todo) => todo.id === action.id);
todo.done = !todo.done;
break;
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
});
break;
default:
break;
}
},
[ /* initial todos */ ]
);

//etc

Redux + Immer

Redux Toolkit 文档中对 Redux + Immer 有详尽介绍。对于不使用 Redux Toolkit 的场景,可沿用上述处理 useReducer 的技巧:用 produce 包裹 reducer 函数,即可安全修改草稿状态!

例如:

import {produce} from "immer"

// Reducer with initial state
const INITIAL_STATE = [
/* bunch of todos */
]

const todosReducer = produce((draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find(todo => todo.id === action.id)
todo.done = !todo.done
break
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
})
break
default:
break
}
})