- 每次状态更新组件都会重新渲染
- 每次组件更新就像是在给组件拍照,每次更新就是一张照片,记录了组件在特定时刻的状态
- 组件每次更新都有自己独立的state/props/事件处理程序等
- 每次拍照的照片在代码层面来讲都是通过函数的闭包机制实现的
导致的问题:
1.每次更新组件内处理程序获取到的是特定时刻的状态,如果是一个定时器修改了状态,那么我们拿到的不是最新的状态,而是那次特定时刻渲染的状态,虽然客观角度来说这是合理的(闭包机制),但是有时我们想拿到最新的状态,也就是定时器修改后的状态,那么我们就要使用useRef来解决了
待补充
2.每次更新组件内的事件处理程序等函数都会重新创建,那么会造成子组件每次都接收到不同的props导致不必要的渲染性能浪费,这时可以施用React.memo高阶组件和useMemo/useCallback解决(对于class组件,这个问题就可以通过PureComponent或者shouldComponentUpdate来解决)
原因:
父组件每次更新时,如果子组件props没有更新,那么就没必要进行重新渲染,但是react组件更新机制是父组件更新了子组件是无条件更新的,不管子组件有没有变化,所以我们要避免这个没必要的更新造成性能浪费
解决:
使用React.memo包裹的组件就可以被缓存下来,如果子组件props没有更新则还是用之前的缓存组件,memo在对比props的变化时使用的是浅层对比,所以想让其识别引用类型的props,我们更新状态的时候要更改props的栈引用,这点和PureComponent对比props的特性是类似的
import React, { useState } from 'react';
const Child = React.memo(() => {
console.log('子组件更新了...')
return <div>我是子组件</div>
})
const Father = () => {
const [count, setCount] = useState(0)
return <div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<Child />
</div>
}
export default Father;
React.memo还有第二个参数,就是给我们手动对比props来决定是否重新渲染子组件的(用得少了解即可一般不用)
const Child = React.memo(() => {
console.log('子组件更新了...')
return <div>我是子组件</div>
},(prevProps,nextProps)=>{ // 传入的参数就是变化前的props和变化后的props,用来给我们手动对比
// return true就是不更新,使用缓存组件,return false就是更新
})
tips: 我们并不是无脑的给组件套memo,如果该组件套了memo也是会更新props重新渲染,那么效率则比不用memo更低,所以我们要在合适的场景使用,对于那种组件状态不会频繁变化的场景
使用useCallback包裹函数,避免组件更新重新创建函数造成性能浪费:
import React, { useCallback, useState } from 'react';
const Child = React.memo(() => {
console.log('子组件更新了...')
return <div>我是子组件</div>
})
const Father = () => {
let [count, setCount] = useState(0)
// useCallback第二个参数和useEffect的差不多,也是决定是否触发的依赖项,填空数组就第一次渲染执行一次
const callback = useCallback(()=>{
console.log('按钮点击了',count)
},[count])
return <div>
<h1>{count}</h1>
<button onClick={callback}>点我</button>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<Child />
</div>
}
export default Father;
使用useCallback后就可以缓存函数了,指定依赖让组件更新时只在依赖项变化时重新创建函数的实例
使用空数组的结果就是不管count怎么变化输出的都是0,因为闭包函数没有重新创建过,里面的count仍然保留的是之前的count引用
指定依赖项为count的时候,只有当count更新时才会重新创建,才能拿到最新的count值,所以此时输出的count才是期望的值
tips:一般做缓存优化时都会将React.memo和useCallback成对使用来提高性能
useMemo:
useMemo用于缓存计算结果,他和useCallback不同之处就是useMemo用于缓存计算结果,他返回的是一个计算结果的值,而useCallback用于缓存函数,所以可以把useMemo理解成Vue中的计算属性computed,只有依赖项变化了才会更新缓存结果,提高效率,用法和useEffect差不多.
import React, { useCallback, useMemo, useState } from 'react';
const Child = React.memo(() => {
console.log('子组件更新了...')
return <div>我是子组件</div>
})
const Father = () => {
let [count, setCount] = useState(0)
const callback = useCallback(() => {
console.log('按钮点击了', count)
}, [count])
// 使用useMemo计算一个值返回
const name = useMemo(() => {
return `Suk-${count}`
}, [count])
return <div>
<h1>{count}</h1>
<button onClick={callback}>点我</button>
<h3>名字:{name}</h3>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<Child />
</div>
}
export default Father;