在React应用中使用Immer
其实React中提供的Hook useState
已经提供了不可变数据的基本操作了,使用useState
构建的任何State,都必须通过成对返回的setXXX
方法修改其中的内容。
结合使用useState
和Immer,就可以大大简化组件中状态的深度更新操作。例如以下示例所示。
import produce from 'immer';
import { useCallback, useState } from 'react';
const TodoList = () => {
const [todos, setTodos] = useState([]);
const handleToggleAction = useCallback(id => {
// 这里利用produce创建了todo列表的新实例,而无需手动克隆原有的todo列表。
setTodos(
produce(draft => {
const todo = draft.find(t => t.id === id);
todo.completed = !todo.completed;
})
);
}, []);
return <div>{/* 这里输出Todo列表以及配置Callback的调用 */}</div>;
};
当示例中的这种用法可以形成一种模式的时候,那么就存在将这种逻辑提升一下的必要了。所以Immer库通过名为use-immer
的包提供了一个专用的Hook:useImmer
。
要使用useImmer
,只需要在应用项目中安装use-immer
即可。利用这个Hook重写上面的示例,就会让代码变得更加简单。
import { useCallback } from 'react';
import { useImmer } from 'use-immer';
const TodoList = () => {
const [todos, setTodos] = useImmer([]);
const handleToggleAction = useCallback(id => {
// 这里不再需要调用produce了,useImmer已经在其内部完成了这件事情。
// 此时的setTodos就不再是之前useState返回的直接接受一个新值的简单函数了,而是一个接受一个操作Draft的函数的函数。
setTodos(draft => {
const todo = draft.find(t => t.id === id);
todo.completed = !todo.completed;
});
}, []);
return <div>{/* 这里输出Todo列表以及配置Callback的调用 */}</div>;
};
与此同理,useReducer
也可以与Immer结合使用,例如以下示例。
import produce from 'immer';
import { useCallback, useReducer } from 'react';
const TodoList = () => {
// 这里将柯里化的produce函数作为reduce函数传给了useReducer。
const [todos, dispatch] = useReducer(
produce((draft, action) => {
switch (action.type) {
case 'add':
draft.push({ id: action.id, title: 'new todo', complete: false });
break;
case 'toggle':
const todo = draft.find(t => t.id === action.id);
todo.complete = !todo.complete;
break;
default:
break;
}
}),
[]
);
const handleToggleAction = useCallback(id => {
dispatch({ type: 'toggle', id });
});
return <div>{/* 这里输出Todo列表以及配置Callback的调用 */}</div>;
};
同样的,use-immer
包中也提供了一个Hook:useImmerReducer
来简化这种模式的代码。那么使用这个Hook重写上面的示例,也可以让括号减少一层。
import { useCallback } from 'react';
import { useImmerReducer } from 'use-immer';
const TodoList = () => {
// 采用useImmerReducer以后,就省去了调用produce的过程。
const [todos, dispatch] = useImmerReducer((draft, action) => {
switch (action.type) {
case 'add':
draft.push({ id: action.id, title: 'new todo', complete: false });
break;
case 'toggle':
const todo = draft.find(t => t.id === action.id);
todo.complete = !todo.complete;
break;
default:
break;
}
}, []);
const handleToggleAction = useCallback(id => {
dispatch({ type: 'toggle', id });
});
return <div>{/* 这里输出Todo列表以及配置Callback的调用 */}</div>;
};