最近又看了React官网,对于React视图更新的处理又有了一些理解,在此记录分享一下
Immutable 不可变数据
这是JavaScript中的一个概念,简单地说就是: 如果不创建一个全新的值替换它,它的内容是无法更改的。
在 JavaScript 中,原始值是不可变的——一旦创建了原始值,它就不能被改变,尽管持有它的变量可以被重新分配另一个值。相比之下,对象和数组默认是可变的——它们的属性和元素可以在不重新分配新值的情况下更改。 所以我们可以总结出来:不可变数据类型和可变数据类型的区别在于其内容是否可以被更改
代码解释:
let str = "hello"; // 这里我们声明了一个基础数据类型的值
str = "world"; // 当我们给这个变量赋予新的值的时候,实则是创建了一个新字符串,而不是改变原有字符串,原值就随即被释放了
let arr = [1, 2, 3]; // 声明一个引用数据类型的值
arr[0] = 4; // 当我们对这个值进行更新时,并没有改变它在堆内存中的引用地址即没有重新分配新值给到这个arr变量
React中应用 Immer.js
react的追求的是数据不可变(react内部比较的是数据的引用地址),操作数据太过繁琐,但JS更加习惯用可变的方式对数据进行操作
在React中,组件的渲染是基于其状态数据以及从父组件传递下来的属性进行的。当状态发生变化时,React会调用组件的 render() 方法重新渲染视图。因此,在开发过程中,我们需要考虑如何有效地更新组件状态。
React通过比较数据的引用地址来检查是否需要重新渲染。如果前后两次渲染、同一块数据的引用地址相同,则认为它们是相等的,不需要重新渲染;否则认为数据已经发生了变化,需要重新渲染。因此,React追求的是数据不可变,也即它们的值不能被直接更改,而是新建一个新的对象或者数组。
举个例子,假设一个组件有一个名为 data 的状态:
class MyComponent extends React.Component {
state = { data: { foo: 'bar' } };
render() {
...
}
}
当你想要更新状态中的 data 属性时,你必须创建一个全新的对象来代替旧的对象,而不能直接修改它的属性:
this.setState({ data: { foo: 'baz' } }); // 创建了一个新对象
如果你尝试直接修改原有对象的属性,则React可能无法察觉到状态的变化,造成渲染的错误。
Immer.js
React官网给我们推荐了这个库用于更新state,它可以让你用更符合js思维逻辑的方式在React应用中操作数据,下面我们来看示例。
在类组件中使用
class ClassDemo extends Component<any, IState> {
constructor(props: any) {
super(props);
this.state = {
obj: {
date: new Date(),
obj2: {
a: 'a',
b: 2,
},
},
};
this.upDataForImmer = this.upDataForImmer.bind(this);
}
upDatatoImmer = (data) => {
return produce(data, (draft) => {
draft.obj2.a = 'a-new';
});
}
upDataForImmer = () => {
const newState = produce(this.state.obj, (draft) => {
draft.obj2.a = 'a-new';
});
// 你也可以使用函数柯里化的思想进行优化处理
// const newState = this.upDatatoImmer(this.state.obj);
this.setState({
obj: newState,
});
};
render() {
return (
<div>
<button onClick={this.upDataForImmer}>更新数据</button>
</div>
);
}
}
在函数组件中,如果要使用复杂的state,可以使用
produce(currentState, recipe: (draftState) => void): nextState这个api进行更新,用法参考下方示例
function useReducerDemo() {
const [todos, setTodoList] = useReducer(
produce(
(
draft: Todo[],
action: { type: string; id: string }
): Todo[] | undefined => {
switch (action.type) {
case 'toggle':
// eslint-disable-next-line no-case-declarations
const todo = draft.find((todo) => todo.id === action.id);
(todo as unknown as Todo).done = !todo?.done;
break;
case 'add':
draft.push({
id: action.id,
title: 'A new todo',
done: false,
});
break;
default:
return [
{
id: 'JavaScript',
title: 'JavaScript',
done: false,
},
];
}
}
),
[
{
id: 'React',
title: 'React',
done: true,
},
{
id: 'Vue',
title: 'Vue',
done: false,
},
]
);
const handleToggle = (id:string) => {
setTodoList({
type: "toggle",
id
});
};
const handleAdd = () => {
setTodoList({
type: "add",
id: "todo_" + Math.random()
});
};
return (
<div>
<button onClick={handleAdd}>新增一条</button>
<ul>
{todos.map((todo, index: number) => (
<li key={index}>
<input
type="checkbox"
checked={todo.done}
onChange={() => {
handleToggle(todo.id)
}}
/>
{todo.title}
</li>
))}
</ul>
</div>
)
}
使用 use-immer
immer.js还提供了另一个简略的hooks库以更好的支持在函数式组件中的使用
通过npm install use-immer安装后使用,语法简单易学,可参考useState和useReducer这两个hooks
npm地址:www.npmjs.com/package/use…
使用案例:
function ImmerDom() {
const [imState, setImState] = useImmer<Option[][]>([[], [], [], []]);
useEffect(() => {
(function queryRegional() {
fetch('https://os.alipayobjects.com/rmsportal/ODDwqcDFTLAguOvWEolX.json')
.then((response) => response.json())
.then((data) => {
data.forEach((item: Option) => {
const firstSpellNum: number = pinyin(item.label)[0].codePointAt(0)!;
setRegionalSpell(firstSpellNum, item);
});
})
.catch((e) => console.log(e));
})();
}, []);
const setRegionalSpell = (spellNum: number, currentItem: Option) => {
const describeForSpellMap: DescribeForSpellMap[] = [
[
(spellNum: number) => spellNum >= 97 && spellNum <= 103,
(currentItem: Option) => {
setImState((draft: Option[][]) => {
draft[0].push(currentItem);
});
},
],
[
(spellNum: number) => spellNum >= 104 && spellNum <= 110,
(currentItem: Option) => {
setImState((draft: Option[][]) => {
draft[1].push(currentItem);
});
},
],
[
(spellNum: number) => spellNum >= 111 && spellNum <= 116,
(currentItem: Option) => {
setImState((draft: Option[][]) => {
draft[2].push(currentItem);
});
},
],
[
(spellNum: number) => spellNum >= 117 && spellNum <= 122,
(currentItem: Option) => {
setImState((draft: Option[][]) => {
draft[3].push(currentItem);
});
},
],
];
const getDescribe = describeForSpellMap.find((m) => m[0](spellNum));
getDescribe ? getDescribe[1](currentItem) : null;
};
return (
<div>
......
</div>
);
}
另外如果你需要使用Map或Set这种结构的数据,还需要在你项目的入口文件处显式的调用官方提供的api以启动支持 示例:App.jsx
import { enableMapSet } from 'immer';
enableMapSet();
function App() {
return (
<>
.......
</>
)
}
export default App
关于其他配置,感兴趣的可以参考Immer.js 的官网:immerjs.github.io/immer/zh-CN…