React + TypeScript: 使用Immer的useImmerReducer钩子编写reducer函数

Immer是一个用于保持数据结构不可变的库。它经常在React官方文档的示例中被使用(如“使用Immer保持对象的不可变”等)。

Immer可以在各种场景中使用不可变的数据结构。例如,React的状态管理就是其中之一。如果对对象的引用没有改变,那么对象本身也不会被修改。此外,复制的成本相对较低。在数据树中未被修改的部分不会被复制,而是与先前的状态共享在内存中。有关Immer的基本用法,请阅读”React + TypeScript: 通过Immer保持状态不可变”。

在本文中我们要介绍的是一个叫做`useImmerReducer`的钩子函数(GitHub上有关`useImmerReducer`的相关内容。请注意,该`README.md`文件的描述可能稍有过时)。使用可变数组方法`push()`和通过数组索引进行赋值,同时可以保持状态的不可变性。

根据React公式文档中的示例进行编写。

这次的话题是将React官方网站上使用useReducer的示例(在“将状态逻辑提取到reducer中”的“步骤3:从组件中使用reducer”中)改写为使用useImmerReducer。最好的例子是下面的示例001,已经进行了修正以适用于TypeScript。

React + TypeScript: 将状态逻辑提取到 Reducer 中的示例001 02

关于这个示例,我在“React + TypeScript: 将状态逻辑分离到Reducer中”里进行了解释,请感兴趣的人参考一下。

使用 ImmerReducer 钩子

useImmerReducer钩子的语法与useReducer相同。

const [state, dispatch] = useImmerReducer(reducer, initialState);
    1. 第1个参数:reducer函数。

 

    1. 第2个参数:初始状态值。

 

    1. 返回值:数组。

第1个元素:当前状态。
第2个元素:dispatch函数(将动作发送给reducer)。

然后,将示例应用程序模块src/App.tsx中的导入和调用钩子useReducer替换为useImmerReducer。从事件处理程序中调用dispatch的方式可以保持不变。这是因为传递动作作为dispatch的参数的方式没有改变。

// import { useReducer } from 'react';
import { useImmerReducer } from 'use-immer';

export default function TaskApp() {
	// const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
	const [tasks, dispatch] = useImmerReducer(tasksReducer, initialTasks);

}

重写 reducer 函数

在使用TypeScript时,使用ImmerReducer替代Reducer时,对于reducer函数tasksReducer的类型标注会发生变化。函数的第一个参数是封装了状态的draft对象。第二个参数保持不变,仍然是action。第一个参数的名称也被设为draft。

// import type { Reducer } from 'react';
import type { ImmerReducer } from 'use-immer';

// export const tasksReducer: Reducer<TaskType[], ActionType> = (
export const tasksReducer: ImmerReducer<TaskType[], ActionType> = (
	// tasks,
	draft,
	action
) => {

};

可变的draft对象可以通过可变的语法进行修改。由于Immer会保持状态不可变,所以draft状态不会更改。当直接修改draft时,不需要返回值。但是,请不要忘记使用break。

export const tasksReducer: ImmerReducer<TaskType[], ActionType> = (

) => {
	switch (action.type) {
		case 'added': {
			/* return [
				...tasks,
				{
					id: action.id,
					text: action.text,
					done: false
				}
			]; */
			draft.push({
				id: action.id,
				text: action.text,
				done: false
			});
			break;
		}
		case 'changed': {
			/* return tasks.map((_task) => {
				if (_task.id === action.task.id) {
					return action.task;
				} else {
					return _task;
				}
			}); */
			const index = draft.findIndex((_task) => _task.id === action.task.id);
			draft[index] = action.task;
			break;
		}
		case 'deleted': {
			// return tasks.filter((_task) => _task.id !== action.id);
			return draft.filter((_task) => _task.id !== action.id);
		}

};

将useReducer替换为useImmerReducer的结果就是下面的示例002。在reducer函数(tasksReducer)的编写上变得更加简洁。

サンプル002■React + TypeScript:状態のロジックをリデューサに抽出する03

解释:这是一个React + TypeScript的示例,其中介绍了如何将状态逻辑提取到一个reducer中。代码示例可以在上面的链接中找到。

Immer所能做的事情

由于reducer必须是纯粹的,因此不能修改状态。然而,Immer提供的是一种特殊的draft对象,可以安全地进行修改。在内部,Immer会将对draft的更改应用于创建的状态副本上。因此,由useImmerReducer管理的reducer可以直接修改第一个参数(draft),而无需返回状态。

使用TypeScript重新编写和类型化Immer公式的示例。

额外的,我使用TypeScript重新编写了GitHub和Immer官方网站上的两个示例,并对其进行了类型标注。前一个官方示例的语法陈旧,而后一个示例的代码有错误,因此我都进行了修正。

GitHub「useImmerReducer」示例:

GitHub上的「useImmerReducer」示例:

React + TypeScript的サンプル003 useImmerReducer 01的代码可以在codesandbox上找到(链接为https://codesandbox.io/s/react-typescript-useimmerreducer-01-40nmsi)。

Immer公式网站上的「useImmerReducer」演示:

样例004■React + TypeScript:useImmerReducer 02
链接:https://codesandbox.io/s/react-typescript-useimmerreducer-02-qmx5nh

广告
将在 10 秒后关闭
bannerAds