持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情
对象问题:
子组件中接收从父组件中传过来的count
function Child(props) {
console.log("child render");
return (
<div>
<p>child:{props.state.count}</p> //找到对象里的值
</div>
)
}
const MemoChild = memo(Child)
父组件,点击按钮改变count
function App() {
console.log("father render");
let initCount = {count:1} //初始对象
const [state, setState] = useState(initCount) //使用对象
return (
<div>
<p>{count} father</p>
<MemoChild state={state} /> //传给子组件
<button onClick={() => {
initCount.count=initCount.count+1 //先加1
setCount(initCount) //再更新
}} >click</button>
</div>
)
}
点击按钮,改变initCount的对象+1并且在setState更新initCount,打印结果,无论怎么点击使count+1都没有用,都一点都不会刷新。
但是当使用另一种方式+1,并且更新时,就可以更改状态:
function App() {
console.log("father render");
const [state, setState] = useState({count:1})
return (
<div>
<p>{count} father</p>
<MemoChild state={state} />
<button onClick={() => {setState({count:state.count+1})}}>click</button>
</div>
)
}
为什么呢?通过观察两组代码可以知道,第二种方法是直接改变前一个对象,每次都重新覆盖前一个对象,也相当于每次都开辟新的内存空间,拥有一个新的内存地址,使memo检测到每次都有新的状态可以更新,所以这种方法是可以改变声明的。但第一种方法未改变对象的地址只改变值是不可以的(第一个例子),只是在同一个地址上改变了变量的值,本质对象地址还是没有改变。
跟随渲染:
场景,同样是在父组件中传递值到子组件,当改变父组件中与子组件无关的状态时,按理说memo的机制就会使子组件不会重新声明,但,如果子组件引用的是一个对象,每次当父组件重新声明时,对象都会跟着声明,那每次都会拥有新的地址,然后更新代码实现如下:
function App() {
console.log("father render");
const [count, setCount] = useState(0)
let data = {count:0} //设置一个对象传给子组件
return (
<div>
<p>{count} father</p>
<MemoChild data={data} />
<button onClick={() => {
setCount(count+1) //更改父组件中显示的count
}} >click</button>
</div>
)
}
子组件中data状态并未改变:
function Child(props) {
console.log("child render");
return (
<div>
<p>child:{props.data.count}</p> //count始终是0
</div>
)
}
const MemoChild = memo(Child)
但由于每次父组件重新渲染,使data对象也重新开辟新的内存空间,导致子组件中count的值虽然不曾改变,却也跟随更新,由此可见,这种方式是有些浪费性能的。
函数计算
同样在子组件中显示一个经过计算的数据,且该数据也不会改变,只是不同的是,它是一个函数的返回值。
存在于父组件中,通过props把getDate的返回值传递给子组件
let getData =()=>{
let sum = 0;
for (let i = 0; i < 10; i++) {
console.log("for");
sum+=1;
}
return {count:sum}
}
而在父组件中,只是进行点击按钮修改父组件中的count数据,对for中循环数据没有更改,但是,每一次改变父组件中数据,子组件中for都会重新执行循环,也会浪费性能.
此时钩子函数useMemo就出场了。
使用useMemo将for循环函数包裹起来,并且将依赖数据设置为count,只有当count改变时,才会重新执行for循环
let getData =useMemo(()=>{
let sum = 0;
for (let i = 0; i < 10; i++) {
console.log("for");
sum+=1;
}
return {count:sum}
},[count])
注意,改成钩子函数之后的形式就要将之前的调用getData方法改为只放上getData
<MemoChild data={getData()} /> //useMemo之前
<MemoChild data={getData} /> //useMemo之后
再去改变父组件中状态,也不会影响到子组件了。因为一直引用的都是同一个依赖返回值。
因为useMemo就相当于是做了一个函数返回值缓存,包裹之后传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。
由此可知,被包裹后直接就是返回值,无需调用此函数,直接传递给子组件这个返回值即可。