useCallback别乱用! 性能优化也是有成本的.

前言

我们在得知useCallback能够优化性能后,恨不得每个函数都要拿useCallback包裹一下;不过我们需要明白: 错误或盲目的使用useCallback会导致性能不升反降;

useCallback这种memoized函数也是需要成本的,比如增加了额外的deps变化判断,再比如可能会获得更多的内存分配…; 因此性能优化带来的好处可能抵消不了它的成本; 打个比方就像你开车去10公里以外的小镇 和 你开车去隔壁的邻居家);

那么,什么情况下才可以使用useCallback去进行性能优化呢?
在你觉得这种性能的交换比较合理的时候.常用的场景比如:

函数 functionA 在 useEffect 内部被调用,为了避免 useEffect 的频繁触发,所以我用useCallback将函数 functionA 包起来;

函数 functionB 在另一个使用useCallback包裹的函数 functionC 中被调用,为了避免这个函数functionC频繁变更,所以我用useCallback将函数 functionB 包起来;

当函数 functionD 被传递给子组件,为了避免子组件进行不必要的频繁渲染,所以我用useCallback将函数functionD 包起来[※]

正文

场景一
在某些场景里需要在useEffect里调用一个函数,出于某些原因.你无法把这个函数移动到effect内部;

const Example = ({ someProp }) => {

 const doSomething = () => {
   console.log(someProp);
 }

 useEffect(() => {
   doSomething();
 }, [doSomething]); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}

因为useEffect中调用的 doSomething 函数使用了 someProp这个props,所以需要将doSomething放在useEffect的依赖列表里;

而为了确保这个函数不会随着组件渲染而重新创建,我们需要用useCallback把doSomething包裹起来,如下:

const Example = ({ someProp }) => {
  // ✅ 用 useCallback 包裹以避免随渲染发生改变
  const doSomething = useCallback(() => {
    // ... Does something with someProp ...
  }, [someProp]); // ✅ useCallback 的所有依赖都被指定了

 useEffect(() => {
    doSomething();
  }, [doSomething]); // ✅ useEffect 的所有依赖都被指定了
}

场景二
类似于场景一, 在某些情况下,你需要在useCallback包裹的函数中调用另一个函数,记得要给另一个函数也包上useCallback

const Example = ({ prop1, prop2 }) => {
 // ✅ 用 useCallback 包裹以避免随渲染发生改变
 const something = useCallback(() => {
 // ... Does something with prop2 ...
 }, [prop2]); // ✅ useCallback 的所有依赖都被指定了
 
 // ✅ 用 useCallback 包裹以避免随渲染发生改变
 const doSomething = useCallback(() => {
 	somethingOne();
   // ... Does something with prop1 ...
 }, [prop1, somethingOne]); // ✅ useCallback 的所有依赖都被指定了

useEffect(() => {
   doSomething();
 }, [doSomething]); // ✅ useEffect 的所有依赖都被指定了
}

场景三
当函数 functionD 被传递给子组件:

const ParentComp = () => {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)
  const [ name, setName ] = useState('hi~')
  const changeName = (newName) => setName(newName)

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

const ChildComp = ({ name, onClick }) => {
  console.log('render child-comp ...')
  return <>
    <div>Child Comp ... {name}</div>
    <button onClick={() => onClick('hello')}>改变 name 值</button>
  </>
}

我们先来看一下组件触发re render的时机

  • 组件的state的属性改变时;
  • 组件的props的任意属性值改变时;
  • 父组件重新rerender时;

根据组件的从新渲染机制,我们再来重新看一下代码:

const ParentComp = () => {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)
  const [ name, setName ] = useState('hi~')
  const changeName = (newName) => setName(newName)  // 父组件渲染时会创建一个新的函数

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

const ChildComp = ({ name, onClick }) => {
  console.log('render child-comp ...')
  return <>
    <div>Child Comp ... {name}</div>
    <button onClick={() => onClick('hello')}>改变 name 值</button>
  </>
}

会发现,当我点击父组件的button来增加次数时,纵使子组件的state和props没有任何变化,子组件依然会rerender,这显然不是我们所期待的,原因我们也清楚,因为父组件重新rerender (state变化,父组件rerender) 且 子组件的props的onClick属性发生了变化 (父组件rerender时,函数changeName重新生成);

我们可以通过React.memo结合useCallback来解决子组件不必要rerender的问题

  • 通过React.memo包裹子组件来解决因 父组件重新rerender 导致子组件rerender的问题;
  • 通过useCallback包裹函数changeName来解决因 子组件的props的onClick属性发生了变化 导致子组件rerender的问题;
const ParentComp = () => {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)
  const [ name, setName ] = useState('hi~')
   const changeName = useCallback((newName) => setName(newName), []) // ✅ 通过useCallback将函数changeName包裹

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

const ChildComp = React.memo(({ name, onClick }) => {  // ✅ 通过React.memo 高阶组件将子组件包裹
  console.log('render child-comp ...')
  return <>
    <div>Child Comp ... {name}</div>
    <button onClick={() => onClick('hello')}>改变 name 值</button>
  </>
})

[注:] React.memo和useCallback缺一不可;

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值