前言
由于React自身的原因,父组件的状态改变时,即使子组件并不依赖该状态,他仍然会得到渲染,这就导致了性能问题,关于性能优化一直是区分小白React开发者和进阶开发者的重要利器。
Memo
提到性能优化,那不得不提到Hooks出来前,针对函数式组件发布的Memo,旨在解决前言中提到的问题。看一段代码:
const Child = (props) => {
console.log(1)
return(
<div>1111111</div>
);
}
const Parent = (props) => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={(e) => { setCount(count+1) }}>增加</button>
<p>count:{count}</p>
<Child />
</>
)
}
很明显,在我们Parent组件中操作按钮,即使与子组件没有任何关系,它也会不断更新。这时很简单我们只需要用Memo把子组件包裹上,这样他就会返回一个新的组件,当父组件发生状态改变时,如果子组件接受的参数并没有变化,那么子组件是不会重新渲染的。
很明显我们上面的例子,子组件根本没有接收参数,自然也不会产生性能优化问题。
虽然看似Memo很好用,实则也确实很有用,但是在一些情况下是没啥用的,简单的来说,如果在Hooks写法中,子组件需要传值,那么基本是没用的,问题就在于useSate这个东西,他每次得到的值都是变化的,即使数值没有变化。
import React, { useState, memo } from "react";
const Child = props => {
console.log(props.fixed);
return <div>1111111</div>;
};
const ChildC = memo(Child);
const Parent = () => {
const [count, setCount] = useState(0);
const [fixed, setFixed] = useState(0);
return (
<>
<button
onClick={() => {
setCount(count + 1);
}}
>
增加
</button>
<p>count:{count}</p>
<ChildC fixed={{ fixed }} />
</>
);
};
export default Parent;
可以看到在这个例子中,由于父组件中点击事件的存在fixed每次都会被重新生成,而由于memo执行的浅比较,虽然只赋值fixed时安然无恙,但是当我们用引用类型包裹之后,memo即察觉出了些许问题,在没有数据更新的情况下强制刷新了子组件。
当然这里我也有个问题没有搞清楚,那就是如果我为fixed赋值引用类型,为什么直接传递fixed时子组件不刷新呢?
useCallback和useMemo
既然存在问题,Hooks就要解决嘛,那么useCallback和useMemo作为分别解决set函数和变量的两大方法,形成了新的优化运作方式。
import React, { useState, memo, useMemo, useCallback } from "react";
const Child = props => {
console.log(props.fixed);
return <div>1111111</div>;
};
const ChildC = memo(Child);
const Parent = () => {
const [count, setCount] = useState(0);
const [fixed, setFixed] = useState(0);
return (
<>
<button
onClick={() => {
setCount(count + 1);
}}
>
增加
</button>
<p>count:{count}</p>
<ChildC fixed={useMemo(() => ({ fixed }), [fixed])} />
</>
);
};
export default Parent;
关于useCallback就不举例了,类似。注意的是,如果后面不传依赖的话,即使与子组件相关的状态改变了,子组件也不会刷新。
同时我们也要注意滥用的问题,首先useMemo并不是无消耗的,反之它的性能消耗也不少,那么我们除非是进行高消耗的数据更新操作,不然反而是弄巧成拙。同样如上面所示,我们的传值如果仅仅是值类型,根本没必要用了。
总之工具虽好,也要分场合。