开篇
列出渲染次数多的原因
-
回调中有异步操作(减少回调中的异步操作)。
-
传递给子组件的props在父组件渲染时被重新定义(使用useCallback、useMemo配合React.memo来减少子组件的渲染)。
使用useCallback时
-
当作为props传递给子组件时,配合React.memo用于减少子组件的渲染次数。
-
在当前组件中只是减少了重新定义的次数,重新定义的性能损耗其实可以忽略。
-
可通过setState回调函数,来减少依赖项,从而减少props被重新定义的次数。
使用useMemo时
- 其实只是提供了someRef.current的快捷使用方式,缓存了变量的索引。
- 在当前组件使用时,用于缓存通过较大计算量得到的值。
- 当作为props传递给子组件时,配合React.memo用于减少子组件的渲染次数。
实例
回调中有异步操作
javascript
const Child = ({handleClick})=>{
console.log('child: 我渲染了')
return (
<button onClick={handleClick}>点击我</button>
)
}
const App = ()=>{
const [num,setNum] = useState(0)
const handleClick = ()=>{
setNum(num + 1)
setNum(num + 1)
setNum(num + 1)
console.log('num',num)
setTimeout(()=>{
console.log(num)
setNum((a)=>{
console.log(a)
return a + 1
})
})
}
console.log('父:渲染了')
return (
<>
{num}
<br/>
<Child handleClick={handleClick}/>
</>
)
}
export default App;
对于setTimeout中console.log(num)输出0的理解:
setTimeout的回调产生了闭包,冻结了num的初始值。
javascript
const Child = ({handleClick})=>{
console.log('child: 我渲染了')
return (
<button onClick={handleClick}>点击我</button>
)
}
const App = ()=>{
const [num,setNum] = useState(0)
const handleClick = ()=>{
setNum(num + 1)
setNum(num + 1)
setNum(num + 1)
console.log('num',num)
// 排除setTimeout影响,研究闭包的形成
;(()=>{
console.log(num)
setNum((a)=>{
console.log(a)
return a + 1
})
})()
}
console.log('父:渲染了')
return (
<>
{num}
<br/>
<Child handleClick={handleClick}/>
</>
)
}
export default App;
消除影响,减少次数
如果回调中有异步操作,并且异步操作中也需要改变组件状态,就应该把异步操作外改变状态的语句移到异步操作内执行。
注:实践中发现,此种方式增加的渲染次数无法减少,因为异步中的set是同步执行的。从逻辑讲,确实两个setState的时机是不同的,异步中的setState需要等待,所以优先渲染已准备好的state,不让用户等待,是合理的。
思:一般遇到此种情况,为减少渲染次数,分拆成更小的组件去渲染可以解决问题,提升性能;或在不同时机去执行set操作。
传递给子组件的props在父组件渲染时被重新定义
javascript
const Child = ({handleClick})=>{
console.log('child: 我渲染了')
return (
<button onClick={handleClick}>点击我</button>
)
}
const ChildTwo = React.memo(({ handleClick2 }) => {
console.log('childtwo: 我渲染了')
return (
<button onClick={handleClick2}>点击我2</button>
)
})
const App = ()=>{
const [num,setNum] = useState(0)
const handleClick = ()=>{
setNum(num+1)
}
// const handleClick2 = useCallback(()=>{
// setNum(1)
// },[])
// const handleClick2 = useCallback(()=>{
// setNum(num+1)
// },[num])
const handleClick2 = useCallback(()=>{
setNum(num=>num+1)
},[])
const obj = useMemo(()=>{
return {}
},[])
console.log('父:渲染了')
return (
<>
{num}
<br/>
<Child handleClick={handleClick}/>
<ChildTwo handleClick2={handleClick2}/>
{/* <ChildTwo a={obj}/> */}
</>
)
}
export default App;
注:
useCallback和useMemo在使用时的依赖记得要加上,因为他们两个是根据依赖的变化来重新定义的,不然我们在其内部使用num时,是不能拿到num的最新值的,除非你使用setNum(num=>num+1),这也是减少依赖的好方法,不过不适用所有场景。
引申内容
-
自执行函数前方必须加上分号,否则js解释器会因为无法识别而报错。
-
react没有暴露主动控制组件重新渲染的接口,不过可以自己为组件传入一个props,通过改变这个props来实现组件的重新渲染。
-
纯展示列表不会出现数组变更的,可以用index作为key;数组可变更的列表就需要使用id等唯一且不变的标识作为key。
后端如何没有传递id,初始的时候自己可对数组格式化��入id。
可设置全局变量,做自增操作设置id,以保证id的唯一性。