useCallbackuseMemo使用优化总结


useCallback 与 useMemo优化总结

是否能每一个方法都包裹一下useCallback?

答案是不要把所有的方法都包上 useCallback

// useCallback 用与缓存函数,但不可滥用!

const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);

const handleClickButton1 = () => {
  setCount1(count1 + 1)
};
const handleClickButton2 = useCallback(() => {
  setCount2(count2 + 1)
}, [count2]);

return (
  <>
    <button onClick={handleClickButton1}>button1</button>
    <button onClick={handleClickButton2}>button2</button>
  </>
)

/* 组件重新渲染时会重新创建 handleClickButton1 函数,
&
*  handleClickButton2 里的 useCallback里也会重新创建一个函数,
&
*  尽管这个函数可能因为依赖值一样,不会被赋予handleClickButton2变量上。
&  
&
*  这种情况下,handleClickButton2会取之前的函数,
&
*  但因为下面的button会被重新渲染,到handleClickButton2这里,
&
*  useCallback 每次都需要对比 **依赖值是否变化,在加上还要缓存之前的值,
&
*  性能开销反而更大。
&  
*  这种情况建议不要再套一层 useCallback
*/

useCallback 传空数组(不更新带来的问题)

// 传空数组意味着当前方法没有依赖值,将不会被更新,

// 另外因为js **静态作用域 将导致此函数内部count2永远都是0       不太懂,什么是静态作用域
import React, { useState, useCallback } from 'react';
import Button from './Button';

export default function App() {
  const [count2, setCount2] = useState(0);

  const handleClickButton2 = useCallback(() => {
    setCount2(count2 + 1);
  }, []);

  return (
    <Button 
      count={count2}
      onClickButton={handleClickButton2}
    >Button2</Button>
  );
}

// 由点击button随机数发生的变化判断,Button只被重渲了一次
// Button.jsx
import React from 'react';

const Button = ({ onClickButton, children }) => {
  return (
    <>
      <button onClick={onClickButton}>{children}</button>
      <span>{Math.random()}</span> 
    </>
  );
};

export default React.memo(Button);

useCallback 频繁更新带来的问题(使用useRef优化)

// 这里 handleSubmit 的缓存与否取决于 text, 

// 作为input 的value, text更新必然是相当频繁的,

// 带来的问题就是,handleSubmit所对应的函数的频繁更新,

// 导致了 OtherForm的频繁渲染
const [text, setText] = useState('');

const handleSubmit = useCallback(() => {
  // ...
}, [text]);

return (
  <form>
    <input value={text} onChange={(e) => setText(e.target.value)} />
    <OtherForm onSubmit={handleSubmit} />
  </form>
);
/**
* useRef生成的对象在整个组件保持不变(引用不变),
&
* 且在组件的任何地方都可以访问到最新的值,
& 
* 这里 textRef 由于引用不变, handleSubmit只会被创建一次,
&
* 因为这个原因,OtherForm 也不会频繁渲染,节省了性能。
&
* 虽然函数只被创建了一次,但useRef对象保存的值在此函数是可以实时拿到的?    ****待定
*/

const textRef = useRef('');
const [text, setText] = useState('');

const handleSubmit = useCallback(() => {
  console.log(textRef.current);
  // ...
}, [textRef]);

return (
  <form>
    <input value={text} onChange={(e) => {
      const { value } = e.target;
      setText(value)
      textRef.current = value;
    }} />
    <OtherForm onSubmit={handleSubmit} />
  </form>
);

useCallback 一般使用最多场景

useCallback 是要配合子组件的 shouldComponentUpdate 或者 React.memo 一起来使用的,否则就是反向优化。

useMemo (使用场景广泛)

优化子组件

const [count, setCount] = useState(0);

 const userInfo = {
   // ...
  age: count,
  name: 'Jace'
}

return <UserCard userInfo={userInfo}>
  
 /**
 * 这里的 userInfo 每次都是一个新对象,无论count发生改变没有,
 * 都会导致子组件 UserCard 重新渲染
 */
const userInfo = useMemo(() => {
  return {
    // ...
    name: "Jace",
    age: count
  };
}, [count]);

/**
* 这里的 userInfo 会在count 发生变化才返回新对象
*/

优化自身

在组件内部有一些复杂的计算时,可以使用useMemo缓存这个值。

useMemo 更多的使用场景

// 可将useMemo 的返回值 定义为一个数组,这样就可以通过结构赋值的方式取值。
// 若返回值数组的其中一项包含数组,那么也就变相的实现了useCallback,
// 同时还实现了对多个数据的缓存。

const [age, followUser] = u
    new Date().getFullYear() - userInfo.birth, // 根据生日计算年龄
    async () => { // 关注用户
      await request('/follow', { uid: userInfo.id });
      // ...
    }
  ];
}, [userInfo]);

return (
  <div>
    <span>name: {userInfo.name}</span>
    <span>age: {age}</span>
    <Card followUser={followUser}/>
      // **更加新奇的用法!
    {
      useMemo(() => (
        // 如果 Card1 组件内部没有使用 React.memo 函数,那还可以通过这种方式在父组件减少子组件的渲染
        <Card1 followUser={followUser}/>
      ), [followUser])
    }
  </div>
)

文章作者: KarlFranz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source KarlFranz !
评论
  目录