开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
你好,我是南一。这是我在准备面试八股文的笔记,如果有发现错误或者可完善的地方,还请指正,万分感谢🌹
数据可视化大作业,做了两天,用了React、Echart。过程中遇到很多关于React Hook的问题,本文将一一记录
一、useState的浅比较
useState 返回的更新 state 的 dispatch 函数,会浅比较两次的state,发现相同则不会开启更新调度任务demo 中两次 state 指向了相同的内存空间,所以默认为 state 相等,就不会发生视图更新了。举个🌰
import { useEffect, useState } from "react"
export default function Index() {
const [option, setOption] = useState({
name: 'Nanyi',
age: 18,
sex: 'man',
data: []
})
const onClick = function () {
new Promise((resolve) => {
resolve([1, 2, 3, 4, 5])
}).then((res) => {
option.data = res
setOption(option)
})
}
useEffect(() => {
console.log(option);
}, [option])
return <div>
<button onClick={onClick}>点击</button>
</div>
}
在此Demo中,首次渲染会触发useEffect,打印出一个对象,当你点击按钮之后,没有任何反应,这是因为要更新的option跟原来的option是指向同一个对象,在 useState 的 dispatchAction 处理逻辑中,会进行浅比较,发现一样,就不会开启更新调度任务,useEffect依赖于option,自然也就没有调用回调函数
解决办法:setOption({...option})将对象进行一次浅拷贝即可。
通过官网可知,此处浅比较用的是Object.is比较算法
useState 跟 setState 的异同
相同点:
setState 跟 useState 更新视图,底层都调用了scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。
不同点:
-
在非 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
-
setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
-
setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。
二、useState函数式更新
先看例子,我想要点击按钮之后,页面信息更新为Mike,并且发请求获取数据注入oprion中
export default function Index() {
const [key, setKey] = useState(0)
const [option, setOption] = useState({ name: 'Nanyi', age: 18, data: [] })
const onClick = function () {
setKey(key + 1)
// 模拟发请求
new Promise((resolve) => {
resolve([1, 2, 3, 4, 5])
}).then((res) => {
option.data = res
setOption({ ...option })
})
}
useEffect(() => {
// 首次渲染不要更新
key && setOption({ name: 'Mike', age: 21, data: [] })
}, [key])
useEffect(() => {
console.log(option);
}, [option])
return <div>
名字:{option.name}<br />
<button onClick={onClick}>点击</button>
</div>
}
实际效果,页面信息仍为Nanyi,且请求来的数据也注到Nanyi的对象中。
原因: 实际上setOption({ name: 'Mike', age: 21, data: [] })已经执行了,但是setOption({ ...option })获取到的状态不是最新的。
解决方法:setOption(preOption => { ...preOption }) 用函数式更新,可以取到最新一次更新的状态。
三、状态更新批处理
React可以将多个状态更新分组到单个重新渲染中,以提高性能。通常,这会提高性能,不会影响应用程序的行为。
在React 18之前,只对React事件处理程序内部的更新进行批处理。从React 18开始,默认情况下为所有更新启用批处理。 请注意,React确保来自多个不同用户发起的事件的更新(例如,单击两次按钮)始终是单独处理的,不会进行批处理。这可以防止逻辑错误。
在罕见的情况下,您需要强制同步应用DOM更新,您可以将其包装在flushSync中。然而,这可能会影响性能,所以只在需要时才这样做。
const handleClick = function () {
// 批量更新
setNum(1)
setNum(4)
// 高优先级更新
ReactDom.flushSync(() => {
setNum(2)
})
setNum(5)
setNum(6)
// 滞后更新 批量更新规则被打破
setTimeout(() => {
setNum(3)
setNum(7)
setNum(8)
}, 0)
}
useEffect(() => {
console.log(num);
})
结果输出 2,6,8
四、setState 是同步还是异步?
在React18之前,我们会说在在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。所谓的异步,就是setState执行后能不能立即获取到最新的数据。批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新。
但是React18就都是"异步"的了
class Index extends Component {
state = {
data: 'data'
}
componentDidMount() {
this.setState({ data: 'async' })
console.log(this.state.data); // data
setTimeout(() => {
this.setState({ data: 'setTimeOut' })
console.log(this.state.data); // React18:async,React17:setTimeOut
}, 0)
}
render() {
return (
<div></div>
)
}
}
五、附加知识点
1、Object.is 算法规则
以下情况判定为相等:
- 都是
undefined - 都是
null - 都是
true或false - 都是相同长度、相同字符、按相同顺序排列的字符串
- 都是相同对象(意味着都是同一个对象的值引用)
- 都是数字且
- 都是 +0
- 都是 -0
- 都是 NaN
- 都是同一个值,非零且都不是 NaN
Object.is 与 === 、 == 有什么不同:
- +0 === -0 为true
- NaN === NaN 为false
- Object.is 不会进行类型隐式转换
2、JS 隐式转换
"3" - 2 // 输出:1
"3" + 2 // 输出:"32"
3 + "2" // 输出:"32"
"3" * "2" // 输出:6
"10" / "2" // 输出:5
1 + true // 输出:2
1 + false // 输出:1
1 + undefined // 输出:NaN
3 + null // 输出:3
"3" + null // 输出:"3null"
true + null // 输出:1
true + undefined // 输出:NaN
举个🌰:"91" > "123" 输出 true,我想以数值进行比较,就得进行类型转换,可以这样 1 * "91" > "123" 输出false
原理: 将其中一个操作数用乘1转换成数字,数字与字符串进行比较运算,字符串会被转换成数字再进行比较
3、对象判空
Object.keys(obj).length