useRef、useCallback、useMemo使用简述

本文来自Ant Design Pro课程中学习手册的一篇补充,本来放在语雀上,最近发现语雀对搜索引擎实在太不友好了,几乎不收录,所以移动到博客中。

useRef 不动时,引用它

比如一个表单获取文本框的值,可以用useState,可以用useRef。如果不涉及赋值,也就是不涉及量的变化操作,用useRef更好。

import { useState } from 'react';
import './App.css';

const App = () => {
  const [username, setUsername] = useState('');
  return (
    <>
      <form>
        <div>
          <input
            name='username'
            onChange={(event) => {
              setUsername(event.target.value);
            }}
          />
        </div>
        <button
          onClick={(event) => {
            event.preventDefault();
            console.log(username); //不建议的方式,因为每打入一个字母,就要setUserName一次
          }}
        >
          GET USERNAME
        </button>
      </form>
    </>
  );
};

export default App;
import { useRef } from 'react';
import './App.css';

const App = () => {
  const inputRef = useRef<HTMLInputElement>(null);
  return (
    <>
      <form>
        <div>
          <input name='username' ref={inputRef} />
        </div>
        <button
          onClick={(event) => {
            event.preventDefault();
            console.log(inputRef.current?.value); //更理想的获取方式,不管过程和变化,只要结果
          }}
        >
          GET USERNAME
        </button>
      </form>
    </>
  );
};

export default App;

2. React.Memo 组件别乱动

我有一个页面App.tsx,页面上就俩东西,一个是按钮自增,一个是子组件叫大列表BigList.tsx

根据react默认的渲染规则,我App渲染时,子组件也重新渲染。也就是,我自增按钮每次点击,大列表也渲染了,大浪费了,完全没必要。代码大概是这样:

import { useState } from 'react';
import BigList from './BigList';
import './App.css';

const App = () => {
  const [count, setCount] = useState(0);
  return (
    <>
      <div className='countBlock'>{count}</div>
      <button
        onClick={() => {
          setCount((count) => count + 1); //每次点击按钮,我庞大的列表也会重新渲染
        }}
      >
        自增按钮
      </button>
      <div>
        <BigList />
      </div>
    </>
  );
};

export default App;
const BigList = () => {
  const listData = ['庞大的列表', '庞大的列表'];
  console.log('庞大的列表重新渲染了');
  return (
    <>
      <ul style={{ border: '1px solid red' }}>
        {listData.map((value) => (
          <li>{value}</li>
        ))}
      </ul>
    </>
  );
};

export default BigList;

现在我要做的就是,点击自增按钮时,大列表不渲染。这事特别简单,把BigList用React.Memo括一下。

import React from 'react';

const BigList = () => {
  const listData = ['庞大的列表', '庞大的列表'];
  console.log('庞大的列表重新渲染了');
  return (
    <>
      <ul style={{ border: '1px solid red' }}>
        {listData.map((value) => (
          <li>{value}</li>
        ))}
      </ul>
    </>
  );
};

export default React.memo(BigList);  //如此简单就搞定了

React.memo告诉react,我这个组件如果没有内部变化,你不要重新渲染我。

但是,现实一般没有这么简单。上面这个BigList是个连props都没有的货,它里面的列表完全自给自足,这个情况在现实中不多。万一,万一它的列表数据是从props过来的,那就完犊子了。我们把listData移动到app里面,通过props传给它。

import { useState } from 'react';
import BigList from './BigList';
import './App.css';

const App = () => {
  const [count, setCount] = useState(0);
  const listData = ['庞大的列表', '庞大的列表']; //移动到这了
  
  return(
  // 其它没变省略了
      <div>
        <BigList listData={listData} /> //通过props传过去
      </div>
  );
  // 其它没变省略了
};

export default App;
import React from 'react';

type BigListProps = {
  listData: string[];
};

const BigList = (props: BigListProps) => {
  console.log('庞大的列表重新渲染了');
  return (
    <>
      <ul style={{ border: '1px solid red' }}>
        {props.listData.map((value) => (  //通过props传过来的值进行渲染
          <li>{value}</li>
        ))}
      </ul>
    </>
  );
};

export default React.memo(BigList);

这时,我们会发现,React.memo无效了。原因的话,因为props毕竟是母组件传过来的,app.tsx重新渲染时,那个listData变量也重新生成了(虽然值没有变化,但数组或对象在内存中存储的地址不同),所以导致我们大列表组件内部变了,也就再次重新渲染了。

3. useMemo 变量别乱动

useMemo这个hook专门处理变量乱动的问题,简单把app.tsx里的listData改造下

  const listData = useMemo(() => {
    return ['庞大的列表', '庞大的列表'];
  }, []);

成功!

但是还有一个闹心的场景,useMemo只能用在变量上,那如果母组件和子组件还有函数传递的话。。。

比如,我在它俩之间传递一个名叫ok的函数

const App = () => {
  .....
  const ok = () => {
    console.log('ok');
  };

  return (
    <>
      .....
      <div>
        <BigList listData={listData} ok={ok} />  // 多传了一个ok
      </div>
    </>
  );
};
import React from 'react';

type BigListProps = {
  listData: string[];
  ok: () => void;
};

const BigList = (props: BigListProps) => {
  console.log('庞大的列表重新渲染了');
  return (
    <>
      <ul style={{ border: '1px solid red' }}>
        {props.listData.map((value) => (
          <li>{value}</li>
        ))}
        <div>
          <button
            onClick={() => {
              props.ok();  //加了个按钮,专门执行传过来的ok
            }}
          >
            OK BUTTON
          </button>
        </div>
      </ul>
    </>
  );
};

export default React.memo(BigList);

再执行的话,又完了。

4. useCallback 函数别乱动

通过useCallback把app.tsx中ok函数改造下,就可以解决。

  const ok = useCallback(() => {
    console.log('ok');
  }, []);

5. useCallback vs useMemo

如果你突发其想,非要用useMemo试一下,返回一个函数,也成功

  const ok = useMemo(() => {
    return () => {
      console.log('ok');
    };
  }, []);

这时候,不懂事的你就要问了,它俩有啥区别呢?

经过一翻查询,我觉得这个问题,只能这么说:

useMemo更关注值,或者说结果。

useCallback更关注过程,或者说函数。

非要弄个例子的话,太难了,我也在迷茫中,我觉得不要过分追求,咱们还是保持平常心,爱咋咋地。

6. 这么好的东西,使劲用?

毕竟这些也是消耗资源的,肯定不是越多越好。

如果是,消耗资源的子列表,附属的弹窗、抽屉组件,真是没必要做多余渲染的,那用下还是好的,毕竟能够提高运行效率。

如果只是简单的一段文字,还要加代码改造,不用也罢。

点赞