React Hooks(二)

  • useRef

  • 基本语法

    const ref = useRef(initialValue)
  • 返回值

        useRef返回一个只有一个属性current的对象,它被设置为你传递的 initialValue。之后你可以把它设置为其他值。如果你把 ref 对象作为一个 JSX 节点的 ref 属性传递给 React,React 将为它设置 current 属性

        注:ref.current 属性是可以修改的,当ref.current属性改变的时候,React 不会重新渲染组件,因为ref是 一个普通的js对象。除了初始化外不要在渲染期间写入或者读取 ref.current,可以在事件处理程序或者useEffect中读取和写入 ref

  • 用法一:用ref引用一个值

        因为改变 ref 不会触发重新渲染这意味着 ref 很适合用来存储一些不影响组件视图输出的信息

function handleStartClick() {
  const intervalId = setInterval(() => {
    // ...
  }, 1000);
  intervalRef.current = intervalId;
}

function handleStopClick() {
  const intervalId = intervalRef.current;
  clearInterval(intervalId);
}

        注:通过使用ref,哪怕重新渲染,也能用来存储信息(不像是普通对象,每次渲染都会重置) ;改变它 不会触发重新渲染(不像是 state 变量,会触发重新渲染)

  • 用法二:通过ref操作DOM

    import { useRef } from 'react';
    
    export default function Form() {
      const inputRef = useRef(null);
    
      function handleClick() {
        inputRef.current.focus();
      }
    
      return (
        <>
          <input ref={inputRef} />
          <button onClick={handleClick}>
            Focus the input
          </button>
        </>
      );
    }
    
  • 用法三:获取自定义组件的ref(forwardRef)

    const inputRef = useRef(null);
    
    return <MyInput ref={inputRef} />;
    
    
    //MyInput组件
    import { forwardRef } from 'react';
    const MyInput = forwardRef(({ value, onChange }, ref) => {
      return (
        <input
          value={value}
          onChange={onChange}
          ref={ref}
        />
      );
    });
    
    export default MyInput;
  • useImperativeHandle

  • 基本语法

    useImperativeHandle(ref, createHandle, dependencies?)
    //和useRef、forwardRef结合起来使用

    作用:能够自定义由ref暴露出来的句柄

        ref:从forwardRef函数中获得的第二个参数

        createHandle:该函数无需参数,返回想要暴露的 ref 的句柄,通常会返回一个包含想暴露的方法的对象

  • 使用示例

    import { forwardRef, useRef, useImperativeHandle } from 'react';
    
    const MyInput = forwardRef(function MyInput(props, ref) {
      const inputRef = useRef(null);
    
      useImperativeHandle(ref, () => {
        return {
          focus() {
            inputRef.current.focus();
          },
          scrollIntoView() {
            inputRef.current.scrollIntoView();
          },
        };
      }, []);
    
      return <input {...props} ref={inputRef} />;
    });

  • useCallback

  • 基本语法

    const cachedFn = useCallback(fn, dependencies)
    

    作用:在组件顶层调用 useCallback 能够在多次渲染中缓存函数

        fn:想要缓存的函数。React 将会在初次渲染而非调用时返回该函数。当进行下一次渲染时,如果 dependencies 相比于上一次渲染时没有改变,那么 React 将会返回相同的函数。否则,React 将返回在最新一次渲染中传入的函数,并且将其缓存以便之后使用。React 不会调用此函数,而是返回此函数

       dependencies:有关是否更新 fn 的所有响应式值的一个列表。响应式值包括 props、state,和所有在组件内部直接声明的变量和函数。如果没有使用依赖数组,useCallback 每一次都将返回一个新的函数

        返回值:在初次渲染时,useCallback 返回传入的 fn 函数。在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数,否则返回这一次渲染传入的 fn

  •  用法一:跳过组件的重新渲染(和memo结合使用)

        默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件,所以只要组件的props发生变化,父组件的更新就会引起子组件的重新渲染。而没有用useCallback定义的函数,在组件更新时都会生成一个不同的函数,若别的state变化,同时有函数作为prop传递给子组件,也会引起子组件的重新渲染,为解决这一问题,可以使用useCallBack和memo结合起来使用跳过重新渲染

//不使用useCallback的情况
function ProductPage({ productId, referrer, theme }) {
  // 每当 theme 改变时,都会生成一个不同的函数
  function handleSubmit(orderDetails) {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }
  
  return (
    <div className={theme}>
      {/* 这将导致 ShippingForm props 永远都不会是相同的,并且每次它都会重新渲染 */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

//使用useCallback的情况
function ProductPage({ productId, referrer, theme }) {
  // 在多次渲染中缓存函数
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // 只要这些依赖没有改变

  return (
    <div className={theme}>
      {/* ShippingForm 就会收到同样的 props 并且跳过重新渲染 */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

        注:用memo包裹的组件,如果 props 和上一次渲染时相同,那么 组件将跳过重新渲染 

  • 用法二:防止频繁触发useEffect

        如果想要在useEffect内部调用函数,那么就要把这个函数作为依赖项,但是每次渲染的时候函数都会改变,这导致每一次渲染useEffect都会调用,解决办法:

const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // ✅ 仅当 roomId 更改时更改

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // ✅ 仅当 createOptions 更改时更改

        注:最好消除对函数依赖项的需求,将函数移入 useEffect 内部

useEffect(() => {
    function createOptions() { // ✅ 无需使用回调或函数依赖!
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ 仅当 roomId 更改时更改
  • useMemo

  • 基本语法

    //useMemo(calculateValue, dependencies)
    const visibleTodos = useMemo(
        () => filterTodos(todos, tab),
        //此处的函数应该是一个没有任何参数的纯函数
        [todos, tab]
      );

        useMemo的用法和useCallback的用法很类似,不同之处在于useMemo缓存的是函数调用的结果,useCallback缓存的是函数本身 。

        React 将会在首次渲染时调用传入的纯函数,在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值。否则,将会再次调用 calculateValue 并返回最新结果,然后缓存该结果以便下次重复使用。

        useMemo同样能跳过组件的重新渲染(当传入给子组件的是变量时),也能记忆对另一个hoook的依赖

const searchOptions = { matchMode: 'whole-word', text };

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // 🚩 提醒:依赖于在组件主体中创建的对象
//此处,创建 searchOptions 对象的代码行将在每次重新渲染时运行,导致visibleItems每次也会重新运行

//解决方法:
const searchOptions = useMemo(() => {
    return { matchMode: 'whole-word', text };
  }, [text]); // ✅ 只有当 text 改变时才会发生改变

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // ✅ 只有当 allItems 或 serachOptions 改变时才会发生改变

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值