文章目录[隐藏]

useCallBack

我们先来看看react官方文档的解释

 const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
复制代码
  • useCallback钩子接收两个参数 内联回调函数依赖数组。它将返回该回调函数的memoized函数。该回调函数仅在某个依赖项改变时才会更新。
  • 当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

使用场景

可以先看看下代码

const Child = ({ count, fetchData }) => {
  console.log("child render");
  useEffect(() => {
    fetchData();
  }, [fetchData]);
  return <>{count}</>;
};

function App() {
  const [count, setCount] = useState(0);
  //模拟api请求
  const fetchData = () => {
    setTimeout(() => {
      setCount((old) => (old += 1));
    }, 2000);
  };

  return <Child count={count} fetchData={fetchData} />;
}
复制代码

就这么轻轻松松,一个死循环就诞生了…。

上面的Child组件作为一个纯ui组件,其业务逻辑都是通过props传递。这种场景在日常开发中很常见。

先分析下代码的执行过程

  1. App渲染Child组件,将count,fetchData传递到Child组件。
  2. Child在useEffect钩子执行fetchData方法,因为对fetchData存在依赖,所以将fetchData添加到依赖项。
  3. fetchData执行时,会调用setCount,导致App组件重新渲染。
  4. App重新渲染,会生成新的fetchData传给Child组件。
  5. Child组件发现fetchData是一个新的引入,又会再次执行fetchData
  • 这样 3 – 5这里就是一个死循环。

怎么办?

  • 如果我们清楚知道fetchData只会执行一次的话,可以在Child组件useEffect中移除依赖项,这样可能会是最简单的办法,但是我们一般项目都在安装hook 的lint的插件,就会报错提示:React Hook useEffect has a missing dependency。
useEffect(()=>{
    fetchData()
},[])
复制代码

此时useCallBack就可以派上用场

const fetchData = useCallback(() => {
    setTimeout(() => {
      setCount((old) => (old += 1));
    }, 2000);
  }, []);
复制代码

上面使用useCallback就可以保证fetchData函数的引用不会变化。

疑问

既然 useCallBack能够缓存函数,那岂不是将所有的react function组件里面使用到的函数都使用useCallBack来包装,就可以达到组件渲染优化的目的?

大家看了官方的解释说返回一个缓存后的函数,一般都会觉得使用了useCallBack性能会更好些。
我们看看下面例子

export default function App() {
  const [value, setValue] = useState("");
  const handelChange = (e) => {
    setValue(e.target.value);
  };
  return <input type={value} onChange={handelChange} />;
}
复制代码

假如我们将handelChange改写成

  const handelChange = useCallBack((e) => {
    setValue(e.target.value);
  },[]); 
复制代码

因为上面其实等同于

    const handelChange = (e)=>{
       setValue(e.target.value);         
    }
    const handelChangeMemoized = useCallBack(handelChange,[])
复制代码

在handelChange的基础开销下,还额外增加了useCallBack的开销,性能怎么可能会比单单handelChange还好呢。

那使用后性能还差了?先给出结论非也

继续看以下例子

const Input1 = ({ value, handelChange }) => {
  console.log("input 1 render");
  return <input type={value} onChange={handelChange} />;
};
const Input2 = ({ value, handelChange }) => {
  console.log("input 2 render");
  return <input type={value} onChange={handelChange} />;
};

export default function App() {
  const [value1, setValue1] = useState("");
  const [value2, setValue2] = useState("");
  const handelChange1 = (e) => {
    setValue1(e.target.value);
  };
  const handelChange2 = (e) => {
    setValue2(e.target.value);
  };
  console.log("app render");
  return (
    <>
      <Input1 value={value1} handelChange={handelChange1} />
      <br />
      <Input2 value={value2} handelChange={handelChange2} />
    </>
  );
}
复制代码

上面的例子中,app渲染Input1和Input2组件,但是运行之后我们可以发现,无论我们改变value1还是value2 都会导致 app-render,input1-render,input2-render。从性能的角度下,我们改变 value1,input2组件是不应该渲染的。反之改变value2,input1组件也不该渲染。我们分析一下,是因为value1改变后,app重新渲染,导致handelChange2生成了一个新的引用,所以才导致input2组件渲染的。

这种情况下useCallback就可以使用了。

我们来改写上面的例子。使用useCallBack缓存change函数

const handelChange1 = useCallback((e) => {
    setValue1(e.target.value);
  }, []);
  const handelChange2 = useCallback((e) => {
    setValue2(e.target.value);
  }, []);
复制代码

但是我们发现即使使用了useCallBack缓存change函数,修改value1的时候,input1和input2还是会render。这是为什么呢?

其实就是react渲染的基本逻辑,只要父组件更新必然会导致自组件的更新,即使子组件没有任何的props。

我们在app组件再添加一个child组件来验证下。

const Child = () => {
  console.log("child render");
  return <div />;
};
export default function App() {
  const [value1, setValue1] = useState("");
  const [value2, setValue2] = useState("");
  const handelChange1 = useCallback((e) => {
    setValue1(e.target.value);
  }, []);
  const handelChange2 = useCallback((e) => {
    setValue2(e.target.value);
  }, []);
  console.log("app render");
  return (
    <>
      <Input1 value={value1} handelChange={handelChange1} />
      <br />
      <Input2 value={value2} handelChange={handelChange2} />
      <Child />
    </>
  );
}
复制代码

验证得出每一次修改value1或者value2都是console如下

  • app render
  • input1 render
  • input2 render
  • child render

app组件其实等同于

  return React.createElement(React.Fragment, {}, [
    React.createElement(Input1, {value:value1,handelChange:handelChange1}, null),
    React.createElement(Input2, {value:value2,handelChange:handelChange2}, null),
    React.createElement(Child, {}, null),
  ]);

复制代码

可以看出即使Child没有任何的props,但是最终编译出来开始会传递一个空对象作为props。这就是为什么Child也会更新的原因了。

如何避免这种渲染性能上的浪费:答案就是:class组件可以继承PureComponent,后者使用shouldComponentUpdate生命周期。Fucniton组件就是React.memo().

const Input1 = memo(({ value, handelChange }) => {
  console.log("input 1 render");
  return <input type={value} onChange={handelChange} />;
});
const Input2 = memo(({ value, handelChange }) => {
  console.log("input 2 render");
  return <input type={value} onChange={handelChange} />;
});
const Child = memo(() => {
  console.log("child render");
  return <div />;
});
export default function App() {
  const [value1, setValue1] = useState("");
  const [value2, setValue2] = useState("");
  const handelChange1 = useCallback((e) => {
    setValue1(e.target.value);
  }, []);
  const handelChange2 = useCallback((e) => {
    setValue2(e.target.value);
  }, []);
  console.log("app render");
  return (
    <>
      <Input1 value={value1} handelChange={handelChange1} />
      <br />
      <Input2 value={value2} handelChange={handelChange2} />
      <Child />
    </>
  );
}
复制代码

这样就能达到渲染优化的效果。
useCallBack并不是万能,要结合实际情况,不然还是适得其反。

免责声明:务必仔细阅读

  • 本站为个人博客,博客所转载的一切破解、path、补丁、注册机和注册信息及软件等资源文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。

  • 本站为非盈利性站点,打赏作为用户喜欢本站捐赠打赏功能,本站不贩卖软件等资源,所有内容不作为商业行为。

  • 本博客的文章中涉及的任何解锁和解密分析脚本,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断.

  • 本博客的任何内容,未经许可禁止任何公众号、自媒体进行任何形式的转载、发布。

  • 博客对任何脚本资源教程问题概不负责,包括但不限于由任何脚本资源教程错误导致的任何损失或损害.

  • 间接使用相关资源或者参照文章的任何用户,包括但不限于建立VPS或在某些行为违反国家/地区法律或相关法规的情况下进行传播, 博客对于由此引起的任何隐私泄漏或其他后果概不负责.

  • 请勿将博客的任何内容用于商业或非法目的,否则后果自负.

  • 如果任何单位或个人认为该博客的任何内容可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明至admin@proyy.com.我们将在收到认证文件后删除相关内容.

  • 任何以任何方式查看此博客的任何内容的人或直接或间接使用该博客的任何内容的使用者都应仔细阅读此声明。博客保留随时更改或补充此免责声明的权利。一旦使用并复制了博客的任何内容,则视为您已接受此免责声明.

您必须在下载后的24小时内从计算机或手机中完全删除以上内容.

您使用或者复制了本博客的任何内容,则视为已接受此声明,请仔细阅读


更多福利请关注一一网络微信公众号或者小程序

一一网络微信公众号
打个小广告,宝塔服务器面板,我用的也是,很方便,重点是免费的也能用,没钱太难了,穷鬼一个,一键全能部署及管理,送你3188元礼包,点我领取https://www.bt.cn/?invite_code=MV9kY3ZwbXo=


一一网络 » useCallback

发表评论

发表评论

一一网络-提供最优质的文章集合

立即查看 了解详情