1. Hook 的概念
Hook 本身单词意思是“钩子”,作用就是“勾住”某些生命周期函数或某些数据状态,并进行某些关联触发调用。
useState
的作用是“勾住”函数组件中自定义的变量。
2. useState 源码
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
需要注意:React 源码中使用的是 flow 语法,而不是 TypeScript 语法。虽然它们很相似,理解时可以当作 TS 语法来看待。
官方文档:React useState
3. useState 基本用法
useState(value)
函数返回一个数组,该数组包含两个元素:
- 第 1 个元素:我们定义的变量
- 第 2 个元素:修改该变量的函数
示例代码
const [variable, setVariable] = useState(value);
//....
setVariable(newValue); // 修改 variable 的值
4. 解决数据异步问题
const [count, setCount] = useState(0);
for (let i = 0; i < 3; i++) {
setCount(count + 1);
}
你认为 count
会增加 3 吗?
答案:不会。无论 for
循环执行多少次,最终 count
仅仅执行了一次 +1
。
原因:
- Hook 中的
setXxx
赋值是异步的 count
值的更新不会立即生效- 多次
setCount(count + 1)
使用的是同一个count
旧值
解决办法
for (let i = 0; i < 3; i++) {
setCount(prevData => {return prevData+1});
//可以简化为 setCount(prev => prev + 1);
}
5. 数据类型为 Object 的修改方法
const [person, setPerson] = useState({ name: '学习不止境', age: 23 });
想要修改 age
的值为 18
,正确做法如下:
let newData = {...person};
newData.age = 18;
setPerson(newData); //拷贝 `person`,然后修改 `age`,再赋值
setPerson({ ...person, age: 18 });//这种简写是解构赋值带来的,并不是React提供的
setState是执行的是 异步对比累加赋值,就是先对比之前数据属性中是否有age,如果有则修改age值,同时不会影响到其他属性的值。我猜测react是使用ES6中新增加的Object.assign()这个函数来实现这一步的。
React 不会自动合并对象,所以需要先拷贝 person
,然后修改 age
,再赋值。
6. 数据类型为 Array 的修改方法
和对象类似,数组也需要先拷贝一份再修改。
const [arr, setArr] = useState(['react', 'node']);
setArr([...arr, 'vue']); //添加'vue'
7. 性能优化
使用 setXxx
赋值时,React 会使用 Object.is()
进行比较,如果新值和旧值一致,则不会触发重新渲染。
Object.is()
的行为
let str = 'a';
Object.is(str, 'a'); // true
let num = '18';
Object.is(num, 18); // false (String 和 Number 类型不同)
let obj1 = { name: 'a' };
Object.is(obj1, { name: 'a' }); // false (不同对象,占用不同内存)
let obj2 = { name: 'a' };
let ref1 = obj2;
let ref2 = obj2;
Object.is(ref1, ref2); // true (指向同一对象)
结论
- 简单类型(String、Number):相同值不会触发重新渲染。
- 复杂类型(Object、Array):即使内容相同,但引用不同,仍会触发重新渲染。
- 同一对象引用:不会触发重新渲染。
最佳实践
为了优化性能,如果可以,尽量避免使用复杂类型的状态。