ref

  1. refReact 提供的一种机制,用于访问和操作 DOM 元素或 React 组件的实例;
  2. 它可以用于获取某个 DOM 元素的引用,从而执行一些需要直接操作 DOM 的任务,例如手动设置焦点、选择文本或触发动画;

使用 ref

  1. 步骤:

    1. 创建一个 ref:​使用 React.createRefuseRef Hook 创建一个 ref
    2. 将 ref 传递给 React 元素:​将 ref 赋值给 React 元素的 ref 属性;
    3. 访问 ref:​通过 ref.current 访问元素或组件实例;
  2. 示例:

    import { createRoot } from "react-dom/client";
    import { useRef, useEffect } from 'react';
    
    function TextInput() {
      const inputRef = useRef(null);
    
      useEffect(() => {
        // 组件挂载后,input 元素获取焦点​
        inputRef.current.focus();
      }, []);
    
      return <input ref={inputRef} type="text" />;
    }
    
    createRoot(document.getElementById("root")).render(<TextInput />);
    

ref 的函数形式

  1. ref 可以直接赋值或通过函数进行赋值,在早期 ref 还支持传递字符串,但是新版本已经不再推荐这种写法;

  2. 示例:

    import { createRoot } from "react-dom/client";
    import { useRef, useEffect } from 'react';
    
    function TextInput() {
      const inputRef = useRef(null);
    
      useEffect(() => {
        // 组件挂载后,input 元素获取焦点​
        inputRef.current.focus();
      }, []);
    
      return <input ref={node => inputRef.current = node} type="text" />;
    }
    
    createRoot(document.getElementById("root")).render(<TextInput />);
    

forwardRef

  1. forwardRef 是一个用于转发 ref 到子组件的方式,它允许父组件访问子组件的 DOM 节点或组件实例;

  2. 它常用于封装第三方 UI 库组件或实现高阶组件;

使用 forwardRef​

  1. 步骤:

    1. 定义一个函数组件:使用 React.forwardRef 包裹函数组件,使其能够接收 ref
    2. 转发 ref:​在组件内部,将 ref 传递给某个子组件或 DOM 元素;
  2. 示例:

    import { createRoot } from "react-dom/client";
    import { useRef, useEffect, forwardRef } from 'react';
    
    const FancyButton = forwardRef((props, ref) => (
      <button ref={ref} className="fancy-button">
        { props.children }
      </button>
    ));
    
    function App() {
      const buttonRef = useRef(null);
    
      useEffect(() => {
        console.log(buttonRef.current); // 输出 <button> 元素​
      }, []);
    
      return <FancyButton ref={buttonRef}>Click me</FancyButton>;
    }
    
    createRoot(document.getElementById("root")).render(<App />);
    

Suspense

  1. SuspenseReact 提供的一个组件,用于处理异步操作 (如数据获取或组件懒加载)

  2. 在异步操作完成之前显示一个备用的 UI (如加载指示器)

基础使用

  1. 包裹异步组件:​使用 Suspense 组件包裹需要进行异步操作的组件,并提供一个 fallback 属性,指定加载时的备用 UI;​

  2. 异步加载组件:​使用 React.lazy 实现组件的懒加载;

    import { createRoot } from "react-dom/client";
    import React, { Suspense } from 'react';
    
    const OtherComponent = React.lazy(() => import('./OtherComponent'));
    
    function App() {
      return (
        <div>
          <Suspense fallback={<div>Loading...</div>}>
            <OtherComponent />
          </Suspense>
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<App />);
    

进阶用法

  1. 新版本 Suspense 支持异步操作;

  2. 如果其子组件中存在异步处理,那么通过返回一个 promise 状态值来标识当前子组件加载状态,在子组件为 pending 时,Suspense 会渲染 fallback ui,直至异步完成;

    import { createRoot } from "react-dom/client";
    import { Suspense } from 'react';
    
    // 创建一个支持 Suspense 的数据获取资源
    function createResource(promise) {
      let status = 'pending';
      let result;
    
      const suspender = promise.then(
        (data) => {
          status = 'success';
          result = data;
        },
        (error) => {
          status = 'error';
          result = error;
        }
      );
    
      return {
        read() {
          if (status === 'pending') {
            throw suspender;
          } else if (status === 'error') {
            throw result;
          } else {
            return result;
          }
        }
      };
    }
    
    // 获取用户数据
    const userResource = createResource(new Promise((resolve) => {
      setTimeout(() => { resolve({ name: 'zhangsan', email: '1105389168@qq.com' }) }, 1000);
    }));
    
    function UserProfile() {
      // 读取资源,如果未准备好会抛出Promise
      const user = userResource.read();
    
      return (
        <div>
          <h1>{user.name}</h1>
          <p>Email: {user.email}</p>
        </div>
      );
    }
    
    function App() {
      return (
        <Suspense fallback={<div>Loading user profile...</div>}>
          <UserProfile />
        </Suspense>
      );
    }
    
    createRoot(document.getElementById("root")).render(<App />);
    

原理

  1. React Suspense 的工作原理基于一种新的渲染模式,称为 “可中断的渲染”(Interruptible Rendering);这一模式允许 React 暂停在某些组件上的渲染,并在这些组件准备好之后继续渲染;

  2. 以下是 Suspense 的核心机制:

    1. ​异步边界:当 React 渲染一个组件时,如果它遇到一个 Suspense 组件,并且 fallback 触发了,则会暂停该组件树的渲染;
    2. 资源的异步加载​:React.lazy 用于异步加载 React 组件;这些组件会在它们的模块加载完成之前暂停渲染;
    3. 数据的异步获取​:Suspense 可以与数据获取库结合使用;在这种情况下,React 会等待数据加载完成后再继续渲染;
    4. 缓存机制​:React 的新架构使用缓存来存储加载的数据或组件;这确保了相同的数据或组件在不同的渲染周期中可以被复用,减少了不必要的加载;
    5. 协调(Reconciliation)和优先级调度​:React 的调度器会根据任务的优先级来协调更新;高优先级的任务(如用户交互)会优先处理,而低优先级的任务(如数据获取)会被延迟处理;这种机制使得 React 可以在任务之间进行切换,提高应用的响应速度和性能;

lazy

  1. 使用 React.lazy

    1. 定义一个懒加载组件:​使用 React.lazy 动态导入组件;
    2. 使用 Suspense 包裹懒加载组件:​将懒加载组件放在 Suspense 中,并提供 fallback 以显示加载指示器;
  2. 示例:

    import { createRoot } from "react-dom/client";
    import React, { Suspense } from 'react';
    
    const LazyComponent = React.lazy(() => import('./LazyComponent'));
    
    function App() {
      return (
        <div>
          <Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
          </Suspense>
        </div>
      );
    }
    
    createRoot(document.getElementById("root")).render(<App />);
    

memo

memo 允许你的组件在 props 没有改变的情况下跳过重新渲染;

语法

  1. 使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本;通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染;但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证;

    memo(Component, arePropsEqual?)
    
  2. 参数:

    1. Component:要进行记忆化的组件;memo 不会修改该组件,而是返回一个新的、记忆化的组件;它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件;
    2. 可选参数 arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props;如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true;否则返回 false;通常情况下,不需要指定此函数;默认情况下,React 将使用 Object.is 比较每个 prop
  3. 返回值:

    1. memo 返回一个新的 React 组件;
    2. 它的行为与提供给 memo 的组件相同,只是当它的父组件重新渲染时 React 不会总是重新渲染它,除非它的 props 发生了变化;

用法

  1. 当 props 没有改变时跳过重新渲染

  2. 使用 state 更新记忆化组件

    1. 即使一个组件被记忆化了,当它自身的状态发生变化时,它仍然会重新渲染;
    2. 记忆化只与从父组件传递给组件的 props 有关;
  3. 使用 context 更新记忆化组件

    1. 即使组件已被记忆化,当其使用的 context 发生变化时,它仍将重新渲染;
    2. 记忆化只与从父组件传递给组件的 props 有关;
  4. 最小化 props 的变化

    1. 当使用 memo 时,只要任何一个 prop 与先前的值不是 浅层相等 的话,组件就会重新渲染;这意味着 React 会使用 Object.is 比较组件中的每个 prop 与其先前的值;注意,Object.is(3, 3)true,但 Object.is({}, {})false
    2. 为了最大化使用 memo 的效果,应该尽量减少 props 的变化次数;例如,如果 props 是一个对象,可以使用 useMemo 避免父组件每次都重新创建该对象:
      function Page() {
        const [name, setName] = useState('Taylor');
        const [age, setAge] = useState(42);
      
        const person = useMemo(
          () => ({ name, age }),
          [name, age]
        );
      
        return <Profile person={person} />;
      }
      
      const Profile = memo(function Profile({ person }) {
        // ...
      });
      
    3. 最小化 props 的改变的更好的方法是确保组件在其 props 中接受必要的最小信息;例如,它可以接受单独的值而不是整个对象:
      function Page() {
        const [name, setName] = useState('Taylor');
        const [age, setAge] = useState(42);
        return <Profile name={name} age={age} />;
      }
      
      const Profile = memo(function Profile({ name, age }) {
        // ...
      });
      
    4. 即使是单个值有时也可以投射为不经常变更的值;例如,这里的组件接受一个布尔值,表示是否存在某个值,而不是值本身:
      function GroupsLanding({ person }) {
        const hasGroups = person.groups !== null;
        return <CallToAction hasGroups={hasGroups} />;
      }
      
      const CallToAction = memo(function CallToAction({ hasGroups }) {
        // ...
      });
      
  5. 指定自定义比较函数

    1. 在极少数情况下,最小化 memoized 组件的 props 更改可能是不可行的;
    2. 在这种情况下,可以提供一个自定义比较函数,React 将使用它来比较旧的和新的 props,而不是使用浅比较;这个函数作为 memo 的第二个参数传递;它应该仅在新的 props 与旧的 props 具有相同的输出时返回 true;否则应该返回 false
      const Chart = memo(function Chart({ dataPoints }) {
        // ...
      }, arePropsEqual);
      
      function arePropsEqual(oldProps, newProps) {
        return (
          oldProps.dataPoints.length === newProps.dataPoints.length &&
          oldProps.dataPoints.every((oldPoint, index) => {
            const newPoint = newProps.dataPoints[index];
            return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;
          })
        );
      }
      
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. ref
    1. 1.1. 使用 ref
    2. 1.2. ref 的函数形式
  2. 2. forwardRef
    1. 2.1. 使用 forwardRef​
  3. 3. Suspense
    1. 3.1. 基础使用
    2. 3.2. 进阶用法
    3. 3.3. 原理
  4. 4. lazy
  5. 5. memo
    1. 5.1. 语法
    2. 5.2. 用法