React19进阶特性

118 阅读29分钟

相关问题

说说你对 React Hooks 的理解

  • React Hooks 是 React 16.8引入的一组API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、 context等)。
  • Hooks通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 React 应用编码实现。
  • 优势
    • 简化状态管理和副作用处理
    • 代码复用和组织
    • 解决高阶组件复用问题
    • 避免“胖”组件
    • 更好的性能

React Suspense

  • React Suspense 是 React 中用于处理异步操作的功能。
  • 它可以让你“等待”某些操作(如数据获取)完成,然后再渲染组件。Suspense的核心理念是让组件在准备好之前显示一个备用的 UI(例如加载指示器),从而提高用户体验。

React 进阶特性

ref

  • 通过 ref.current 访问元素或组件实例
  • ref的函数形式
    • ref 可以直接赋值或通过函数进行赋值,在早期,ref 还支持传递字符串,但是新版本已经不再推荐这种写法

forwardRef

  • forwardRef 是一个用于转发 ref 到子组件的方式,它允许父组件访问子组件的DOM节点或组件实例。它常用于封装第三方 UI 库组件或实现高阶组件。 六5

Suspense

是 React 提供的一个组件,用于处理异步操作(如数据获取或组件懒加载),在异步操作完成之前显示

基础使用

  • 包裹异步组件
    • 使用 Suspense 组件包裹需要进行异步操作的组件,并提供一个fallback 属性,指定加载时的备用 UI
  • 异步加载组件
    • 使用 React.lazy 实现组件的懒加载。

进阶使用

  • 新版本 Suspense 支持异步操作,如果其子组件中存在异步处理,那么通过返回一个 promise 状态值来标识当前子组件加载状态,在子组件为 pending时,Suspense 会渲染fallback ui,直至异步完成。

React.lazy

  • 使用 React.lazy 动态导入组件
  • React.lazy 是一个非常有用的功能,它允许你按需加载组件,这对于优化应用性能特别有帮助,尤其是对于大型应用来说。下面我将给出一个使用箭头函数形式的函数组件的例子,展示如何使用 React.lazy 来实现代码分割。
    首先,请确保你的项目已经配置好支持动态导入(dynamic imports)。大多数现代的React项目创建工具如Create React App已经默认支持这一特性。

示例代码

import React, { Suspense } from 'react';
// 使用 React.lazy 动态加载组件
const LazyComponent = React.lazy(() => import('./path/to/YourComponent'));
const App = () => (
  <div>
    <h1>欢迎来到我的应用</h1>
    {/* 使用 <Suspense> 包裹懒加载的组件 */}
    <Suspense fallback={<div>正在加载...</div>}>
      <LazyComponent />
    </Suspense>
  </div>
);
export default App;

在这个例子中:

  • 我们通过 React.lazy 函数和 ES6 的动态导入语法来延迟加载 YourComponent 组件。
  • <Suspense> 组件用于包裹所有使用了 React.lazy 加载的组件。当这些组件正在被加载时,会显示 fallback 属性指定的内容。这里我们简单地设置了一个 "正在加载..." 的文本作为占位符。
  • App 是一个箭头函数形式定义的React函数组件。
    请根据实际情况替换 './path/to/YourComponent' 为你实际想要懒加载的组件路径。

React Hooks

useState

  • 用于在函数组件中添加状态变量

当然可以!useState 是 React 中用来管理组件状态的一个 Hook。下面我将给出一个简单的示例,展示如何使用 useState 来创建和更新状态。

示例 : 表单输入处理

接下来是一个稍微复杂一点的例子,它展示了如何使用 useState 来处理表单中的文本输入。

import React, { useState } from 'react';
function FormExample() {
    const [inputValue, setInputValue] = useState('');
    const handleChange = (event) => {
        // 更新 inputValue 状态以匹配输入框中的值
        setInputValue(event.target.value);
    };
    const handleSubmit = (event) => {
        event.preventDefault();  // 阻止表单默认提交行为
        alert('您输入的内容是: ' + inputValue);
    };
    return (
        <form onSubmit={handleSubmit}>
            <label>
                输入内容:
                <input type="text" value={inputValue} onChange={handleChange} />
            </label>
            <button type="submit">提交</button>
        </form>
    );
}
export default FormExample;

这里的关键点在于:

  • 我们通过 value 属性将输入框与 inputValue 状态关联起来。
  • 当用户在输入框中键入文字时,onChange 事件触发,从而更新 inputValue
  • 提交表单时,会显示一个警告框显示出用户输入的内容。

useEffect

  • 用于在函数组件中执行副作用操作,例如数据获取、订阅或手动更改 DOM。 useEffect 会在组件渲染后执行。

当然可以。useEffect 是 React 中的一个 Hook,用于在函数组件中执行副作用操作,比如数据获取、订阅或手动更改 React 组件的 DOM 等。下面是一个使用 useEffect 的基本示例,该示例展示了如何在组件挂载时执行某些操作,并且当依赖项变化时重新运行这些操作。

示例:模拟数据加载

假设我们有一个简单的应用,它从一个 API 获取用户信息并在页面上显示出来。我们将使用 useEffect 来处理这个异步的数据请求过程。

import React, { useState, useEffect } from 'react';
function UserProfile() {
  const [user, setUser] = useState(null); // 用于存储用户信息的状态
  const [loading, setLoading] = useState(true); // 加载状态
  const [error, setError] = useState(null); // 错误信息
  useEffect(() => {
    // 模拟网络请求
    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then(response => {
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        return response.json();
      })
      .then(data => {
        setUser(data); // 设置用户信息
        setLoading(false);
      })
      .catch(error => {
        console.error('There was an error fetching the user data:', error);
        setError(error.message);
        setLoading(false);
      });
  }, []); // 空数组表示此effect只会在组件挂载时运行一次
  if (loading) return <p>Loading...</p>; // 如果还在加载则显示加载提示
  if (error) return <p>Error: {error}</p>; // 如果有错误,则显示错误信息
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
      <p>Website: <a href={user.website}>{user.website}</a></p>
    </div>
  );
}
export default UserProfile;

在这个例子中:

  • 我们定义了三个状态变量:user 用来保存用户的详细信息,loading 表示数据是否正在加载中,error 用来捕获任何可能出现的错误。
  • 使用 useEffect 钩子来发起 HTTP 请求。这里使用了 fetch API 来获取 JSON 数据。
  • useEffect 内部,设置了空依赖数组 [],这意味着这段代码仅在组件首次渲染(即挂载)时执行一次。
  • 根据 loadingerror 的值决定渲染什么内容给用户看。如果数据已经成功加载,则显示用户的信息;如果是加载中,则显示“Loading...”;如果有错误发生,则显示错误消息。

useContext

  • 用于在函数组件中访问上下文(context)

当然可以!useContext 是 React 中用于在组件树中传递数据的一种方式,避免了通过 props 一层层传递的繁琐。下面我将给出一个简单的示例来展示如何使用 useContext

示例

假设我们有一个应用程序,需要在整个应用的不同层级间共享主题(如:深色模式或浅色模式)。我们可以创建一个 Context 来存储这个信息,并让任何需要它的组件都可以访问到它。

步骤 1: 创建 Context

首先,我们需要定义一个 Context 对象。这可以通过调用 React.createContext() 函数来完成。这里我们创建一个名为 ThemeContext 的上下文:

import React from 'react';
const ThemeContext = React.createContext();  // 默认值为 undefined
export { ThemeContext };
步骤 2: 提供 Context 值

接下来,在你的应用的某个高层级组件中,使用 ThemeContext.Provider 来提供当前的主题状态。这里假设我们在 App 组件中设置它:

import React, { useState } from 'react';
import { ThemeContext } from './path/to/ThemeContext';  // 根据实际情况调整路径
import ChildComponent from './ChildComponent';
function App() {
    const [theme, setTheme] = useState('light');  // 初始主题设为浅色
    return (
        <ThemeContext.Provider value={{ theme, toggleTheme: () => setTheme(theme === 'light' ? 'dark' : 'light') }}>
            <div className={`app ${theme}`}>
                <h1>切换主题示例</h1>
                <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
                    切换至{theme === 'light' ? '暗' : '亮'}色主题
                </button>
                <ChildComponent />
            </div>
        </ThemeContext.Provider>
    );
}
export default App;
步骤 3: 使用 useContext 钩子

现在,任何嵌套于 <ThemeContext.Provider> 内部的组件都可以通过 useContext 钩子访问到主题信息。比如在 ChildComponent 中:

import React, { useContext } from 'react';
import { ThemeContext } from './path/to/ThemeContext';  // 确保路径正确
function ChildComponent() {
    const { theme } = useContext(ThemeContext);
    return (
        <div style={{ backgroundColor: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
            <p>这里是子组件,当前主题为: {theme}</p>
        </div>
    );
}
export default ChildComponent;

这样,就可以看到 ChildComponent 能够根据其父级提供的主题进行样式调整。这种模式非常适合处理那些跨多层组件共享的状态管理问题。

useReducer

  • 用于管理复杂的状态逻辑,类似于 Redux 的 reducer 概念
  • useReducer 是 React 中的一个 Hook,它提供了一种更灵活的方式来管理组件的状态逻辑,特别是当状态逻辑变得复杂时(例如下一个状态取决于前一个状态)或需要处理多个子值的情况。

示例

  • 这个例子将展示如何用 useReducer 来管理计数器应用的状态。
    首先定义 reducer 函数,然后在组件中使用 useReducer Hook。这里是一个基本的例子:
import React, { useReducer } from 'react';
// 定义reducer函数
const counterReducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      throw new Error(`未知的动作类型:${action.type}`);
  }
};
function Counter() {
  // 初始化状态和dispatch方法
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  return (
    <div>
      <p>当前计数: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>增加</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </div>
  );
}
export default Counter;

在这个例子中:

  • 我们定义了一个名为 counterReducer 的 reducer 函数,它接收两个参数:当前状态 (state) 和动作对象 (action)。
  • 根据不同的 action.type 值,counterReducer 将返回新的状态。
  • Counter 组件内部,我们通过 useReducer 调用 counterReducer 并初始化状态为 { count: 0 }
  • 组件渲染了三个按钮,每个按钮点击时会触发不同的 action,从而更新组件的状态。

useRef

  • 用于访问 DOM 元素或保存不需要触发重新渲染的变量
  • 还可以用来缓存无需触发更新渲染的变量,通常也会考虑使用它来做性能优化的事情。
    • 比如我们想获取某个状态本次更新前的值,就可以使用ref 存储

示例

import React, { useRef } from 'react';
const InputComponent = () => {
  const inputRef = useRef(null);
  const focusInput = () => {
    inputRef.current.focus();
  };
  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
};
export default InputComponent;

在这个例子中:

  • 我们导入了 useRef 钩子。
  • 使用 useRef(null) 初始化了一个名为 inputRef 的引用变量。这里的 null 是初始值,在这个上下文中表示还没有关联到任何实际的DOM元素。
  • <input> 元素上设置了 ref={inputRef} 属性,这样就将这个引用与实际的输入框元素绑定起来了。
  • 定义了一个 focusInput 方法,当点击按钮时调用该方法,它会触发 inputRef.current.focus() 来使输入框获得焦点。
    这是一个非常基础但实用的例子,展示了如何利用 useRef 直接操作DOM元素而不依赖于React的状态管理机制。希望对你有所帮助!

useMemo

  • 用于记忆化计算结果,优化性能

示例

import React, { useMemo } from 'react';
const App = ({ count }) => {
  // 使用useMemo缓存复杂的计算结果,仅在count变化时重新计算
  const expensiveCalculation = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
      sum += i * Math.random();
    }
    return sum;
  }, [count]);  // 依赖项列表
  return (
    <div>
      <p>Count: {count}</p>
      <p>Expensive Calculation Result: {expensiveCalculation.toFixed(2)}</p>
    </div>
  );
};
export default App;

在这个例子中,expensiveCalculation 是一个模拟耗时操作的结果,这里通过循环加上随机数来简单表示。useMemo 确保了只有当 count 发生改变时才会重新执行这个计算过程,从而避免了不必要的重复计算,提高了应用性能。这种方式非常适合处理那些依赖于某些状态但不需要每次渲染都更新的数据处理逻辑。

useCallback

  • 用于记忆化函数,优化性能
  • useCallback 是 React 中的一个 Hook,用于返回一个 memoized 回调函数。这在处理性能优化时特别有用,比如当将回调传递给经过优化的子组件时(这些子组件依赖于引用相等性来避免不必要的渲染)。

示例

在这个例子中,我们将创建一个计数器应用,其中有一个按钮,每次点击该按钮都会增加计数,并且这个点击事件处理器会被 useCallback 优化以避免在不需要的时候重新创建。

import React, { useState, useCallback } from 'react';
const Counter = () => {
  const [count, setCount] = useState(0);
  // 使用useCallback包裹你的事件处理器或任何其他函数
  // 当这里提供的第二个参数数组[]没有变化时,useCallback会返回同一个函数实例。
  const increment = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);  // 注意:如果increment依赖于某些状态或其他props,你需要把它们添加到依赖数组里
  return (
    <div>
      <h1>当前计数: {count}</h1>
      <button onClick={increment}>+1</button>
    </div>
  );
};
export default Counter;

这段代码中,我们定义了一个名为 Counter 的功能组件,它内部使用了 useState 来管理状态以及 useCallback 来记忆化 increment 函数。由于 useCallback 的第二个参数是空数组 [],这意味着只要组件被初始化后,increment 就不会改变,直到组件卸载。如果你的函数依赖于某些变量或props,你应该把这些依赖加入到 useCallback 的第二个参数数组中,这样每当这些依赖发生变化时,useCallback 会生成新的函数实例。
这种做法有助于防止因父组件更新导致子组件接收到的新prop其实只是旧函数的新引用而引起的不必要的渲染。

useLayoutEffect

  • 类似于 useEffect,但在所有 DOM变更之后同步调用

示例

当然,useLayoutEffect 是 React 中的一个 Hook,它的行为类似于 useEffect,但有一些关键的区别。主要区别在于执行时机:useLayoutEffect 会在所有的 DOM 变更之后同步调用效果。这意味着在浏览器绘制之前,它会先执行。这对于需要测量布局、读取布局信息或导致视口变化的操作特别有用。
下面是一个使用 useLayoutEffect 的简单示例,在这个例子中,我们将实现一个功能,当组件挂载时调整某个元素的宽度为窗口的一半,并且每当窗口大小改变时也重新计算宽度:

import React, { useLayoutEffect, useState, useRef } from 'react';
function WindowHalfWidthBox() {
    const [windowWidth, setWindowWidth] = useState(window.innerWidth);
    const boxRef = useRef(null);
    // 当组件挂载后或者窗口尺寸变化后设置盒子宽度
    useLayoutEffect(() => {
        const handleResize = () => {
            setWindowWidth(window.innerWidth);
        };
        // 初始化设置盒子宽度
        if (boxRef.current) {
            boxRef.current.style.width = `${windowWidth / 2}px`;
        }
        // 添加事件监听器以响应窗口大小的变化
        window.addEventListener('resize', handleResize);
        // 清理函数,移除事件监听器
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, [windowWidth]);  // 依赖数组中加入 windowWidth,以便每次窗口宽度更新时都触发 effect
    return (
        <div ref={boxRef} style={{border: '1px solid black'}}>
            这个盒子的宽度是窗口宽度的一半。
        </div>
    );
}
export default WindowHalfWidthBox;
  • useLayoutEffect: 在DOM渲染完成后立即执行,这使得它可以用来直接修改DOM而不影响用户界面的表现。
  • ref: 使用 useRef 创建了一个引用 (boxRef) 来获取到实际的DOM节点。
  • 事件监听器: 通过添加对 resize 事件的监听来动态更新状态(即窗口宽度),进而触发 useLayoutEffect 再次运行并更新盒子的宽度。
  • 清理机制: 返回一个清除函数用于在组件卸载时取消事件监听,防止内存泄漏。
    此代码片段展示了一种典型的场景,其中 useLayoutEffect 被用来处理与布局相关的副作用。

uselmperativeHandle

  • 自定义 ref,暴露给父组件

当然可以。useImperativeHandle 是 React 中的一个 Hook,它允许你自定义通过 ref 暴露给父组件的实例值。这对于需要在父组件中直接访问子组件某些方法或属性时非常有用。
下面是一个简单的例子来展示如何使用 useImperativeHandle。在这个例子中,我们将创建一个子组件 ChildComponent,并通过 ref 向其父组件暴露一个特定的方法 focusInput,该方法用于将焦点设置到输入框上。

示例

子组件: ChildComponent
import React, { useRef, useImperativeHandle } from 'react';
const ChildComponent = React.forwardRef((props, ref) => {
  const inputRef = useRef(null);
  // 定义我们希望父组件能够调用的方法
  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };
  // 使用 useImperativeHandle 来控制子组件通过 ref 暴露给父组件的内容
  useImperativeHandle(ref, () => ({
    focus: focusInput,
  }));
  return (
    <div>
      <input type="text" ref={inputRef} placeholder="点击按钮聚焦于此"/>
    </div>
  );
});
export default ChildComponent;
父组件: ParentComponent
import React, { useRef, useEffect } from 'react';
import ChildComponent from './ChildComponent';  // 假设ChildComponent在同一目录下的另一个文件中
const ParentComponent = () => {
  const childRef = useRef();
  useEffect(() => {
    // 在这里你可以调用子组件中的focus方法
    childRef.current.focus();
  }, []);
  return (
    <div>
      <button onClick={() => childRef.current && childRef.current.focus()}>聚焦输入框</button>
      <ChildComponent ref={childRef} />
    </div>
  );
};
export default ParentComponent;

这段代码演示了如何利用 useImperativeHandleforwardRef 让父组件可以直接调用子组件的方法。当用户点击“聚焦输入框”按钮时,会触发子组件内的 focusInput 方法,从而让输入框获得焦点。这种方式提供了一种更加灵活的方式来管理组件之间的交互。

useId

  • useId 是一个用于生成唯一ID 的 Hook,通常用于无障碍特性(例如绑定 label 和 input)
  • useId 是 React 18 中引入的一个 Hook,用于生成一个全局唯一的 ID。这对于确保客户端和服务器端渲染之间的一致性特别有用,尤其是在处理表单元素时。

示例

import React, { useId } from 'react';
const MyComponent = () => {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>Name:</label>
      <input type="text" id={id} name="name" />
    </div>
  );
};
export default MyComponent;

在这个例子中,我们使用了 useId 来为 <input> 元素生成一个唯一的 id 属性,并将其传递给 <label>htmlFor 属性,以确保标签与输入框正确关联。这样做的好处是无论是在客户端还是服务器端渲染时,都能保证这个 id 是唯一的。

useTransition

  • useTransition 是一个用于处理UI 过渡的 Hook,允许你将某些更新标记为过渡,从而避免阻塞界面。

当然可以。useTransition 是 React 中的一个 Hook,用于在状态更新时平滑地处理用户界面的变化,避免因大量计算或渲染导致的卡顿现象。

示例

下面是一个简单的示例,展示了如何使用 useTransition 来实现一个延迟加载的效果。

import React, { useState, useTransition } from 'react';
const App = () => {
  const [isPending, startTransition] = useTransition();
  const [inputValue, setInputValue] = useState('');
  const handleInputChange = (event) => {
    // 使用startTransition包裹的状态更新会被视为低优先级更新
    startTransition(() => {
      setInputValue(event.target.value);
    });
  };
  return (
    <div>
      <input 
        type="text" 
        value={inputValue} 
        onChange={handleInputChange} 
        placeholder="Type something..."
      />
      {isPending ? (
        <p>Updating...</p>
      ) : (
        <p>You typed: {inputValue}</p>
      )}
    </div>
  );
};
export default App;

在这个例子中:

  • 我们导入了 useStateuseTransition
  • useTransition 返回了一个布尔值 isPending 和一个函数 startTransition。当 startTransition 包裹的状态更新正在进行时,isPending 会变为 true
  • 当输入框的值发生变化时,我们通过 startTransition 包裹的状态更新来设置 inputValue,这样即使这个更新比较耗时(例如,如果它触发了大量的子组件重新渲染),UI 仍然保持响应性。
  • 如果 isPendingtrue,则显示 "Updating..." 的提示信息;否则,显示当前输入的内容。
  • 这种方式非常适合于那些需要进行复杂计算或者有潜在性能瓶颈的状态更新场景。

useDeferredValue

  • useDeferredValue 是一个用于延迟更新的Hook,它会返回一个新的值,这个值会在某个时间点更新到最新的值,用于优化性能。
  • useDeferredValue 是 React 18 引入的一个 Hook,用于延迟更新某些状态的值,以优化用户体验,尤其是在处理大量数据或复杂计算时。这个 Hook 特别适用于当用户输入速度较快,而你不想让组件对每一次输入都立即做出反应的情况。
    其中我们利用了 useDeferredValue 来实现一个搜索框的功能。

示例

  • 在这个例子中,随着用户输入的变化,我们将展示一个列表项,但这些列表项将基于延迟后的搜索词来过滤显示,从而减少渲染次数,提高性能。
import React, { useState, useDeferredValue } from 'react';
const SearchComponent = () => {
  const [searchTerm, setSearchTerm] = useState('');
  // 使用 useDeferredValue 创建一个延迟版本的 searchTerm
  const deferredSearchTerm = useDeferredValue(searchTerm);
  // 模拟的数据源
  const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape'];
  
  // 根据延迟后的搜索词过滤数据
  const filteredItems = items.filter(item =>
    item.toLowerCase().includes(deferredSearchTerm.toLowerCase())
  );
  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="搜索..."
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};
export default SearchComponent;
  • useState 用来跟踪用户的输入。
  • useDeferredValue 接收当前的状态(即 searchTerm)作为输入,并返回一个新的状态(deferredSearchTerm),该状态会在一段时间后才更新到最新值。
  • 当用户快速连续地键入字符时,实际用于过滤列表的 deferredSearchTerm 不会立刻响应最新的输入,而是等待一段时间后再更新,这样就减少了不必要的重新渲染,提高了应用的响应性和流畅度。

useSyncExternalStore

  • useSyncExternalStore 是一个用于订阅外部存储的 Hook, 确保外部存储的值与 React 的渲染保持同步
  • useSyncExternalStore 是 React 中用于与外部状态管理库(如 Redux、MobX 等)同步的一个 Hook。它允许你在函数组件中以一种高效且安全的方式订阅外部存储。

示例

下面是一个使用 useSyncExternalStore 的示例,假设我们有一个简单的计数器应用,并且我们使用一个外部状态管理库来管理这个计数器的状态。
首先,我们需要定义一个外部存储和相应的订阅/获取状态的方法。这里为了简化起见,我将直接在代码中模拟这些功能:

// 外部存储
const externalStore = {
  state: { count: 0 },
  listeners: new Set(),
  // 获取当前状态
  getState() {
    return this.state;
  },
  // 订阅状态变化
  subscribe(listener) {
    this.listeners.add(listener);
    return () => {
      this.listeners.delete(listener);
    };
  },
  // 更新状态
  dispatch(action) {
    if (action.type === 'increment') {
      this.state.count += action.payload;
    } else if (action.type === 'decrement') {
      this.state.count -= action.payload;
    }
    this.listeners.forEach(listener => listener());
  }
};
// 定义 useCounter 自定义 Hook
function useCounter() {
  const snapshot = useSyncExternalStore(
    // 订阅回调
    externalStore.subscribe,
    // 获取当前状态
    () => externalStore.getState()
  );
  // 提供更新状态的方法
  const increment = (payload = 1) => {
    externalStore.dispatch({ type: 'increment', payload });
  };
  const decrement = (payload = 1) => {
    externalStore.dispatch({ type: 'decrement', payload });
  };
  return [snapshot, { increment, decrement }];
}
// 使用 useCounter 的函数组件
function Counter() {
  const [state, actions] = useCounter();
  return (
    <div>
      <h1>Count: {state.count}</h1>
      <button onClick={() => actions.increment(1)}>+1</button>
      <button onClick={() => actions.decrement(1)}>-1</button>
    </div>
  );
}
// 渲染组件
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<Counter />, document.getElementById('root'));

在这个例子中:

  • externalStore 模拟了一个外部状态管理库。
  • useCounter 是一个自定义 Hook,它使用 useSyncExternalStore 来订阅外部存储并获取最新状态。
  • Counter 组件使用 useCounter Hook 来获取计数器的状态和操作方法,并渲染 UI。
    这样,你就可以在函数组件中安全地使用外部状态管理库了。

封装hook

第三方hooks

封装自定义hooks

深入理解 Hooks原理

Hooks 调用规则

  • 只在最顶层调用Hooks:不要在循环、条件或嵌套函数中调用Hook。确保每次渲染时都按照相同的顺序调用 Hook
    • 不能用循环,嵌套是因为会导致虚拟dom对不上
  • 只在 React 函数组件和自定义 Hook 中调用 Hooks:不要在普通的 JavaScript 函数中调用 Hook。

状态管理

  • Hooks使用闭包的特性来捕获组件的状态和上下文。每次组件重新渲染时,React 会根据Hook 的调用顺序匹配之前保存的state,确保状态在渲染之间保持一致。

依赖数组

useEffect 和重新运行该 Hook 中的逻辑。useMemo 等Hook 使用依赖数组来确定何时重新执行逻辑。依赖数组中的变量变化时,React会

样式方案

React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案

  • 内联样式
  • module css
  • css in js
  • tailwindess

内联样式:

import React from 'react';
const StyledComponent = () => (
  <div style={{color: 'red', padding: '10px'}}>
    Inline Style Example
  </div>
);
export default StyledComponent;

在这个例子中,我们创建了一个名为StyledComponent的箭头函数组件,它返回一个带有内联样式的<div>元素。这个样式对象设定了文字颜色为红色(color: 'red')和内部填充为10像素(padding: '10px')。这种方式非常适合于简单快速地添加少量样式到您的React组件中。

优点

简单直观:直接在JSX中定义样式,容易理解和使用。

避免全局命名冲突:因为样式是局部的,不会与其他组件的样式冲突。

动态样式:可以方便地使用 JavaScript 表达式动态生成样式。

缺点

样式复用差:无法轻易复用样式,导致样式代码重复。

缺乏伪类和伪元素支持:无法使用:hover、:active 等伪类和伪元素。

CSS 功能有限:许多 CSS 功能(如媒体查询、关键帧动画等)不能使用。

CSS 模块(CSS Modules)

CSS 模块允许你为CSS类名添加局部作用域,避免样式冲突。文件名通常以 .module.css 结尾

用法

1. 创建一个CSS模块文件

创建一个名为 styles.module.css 的文件,在其中定义一些基本样式:

/* styles.module.css */
.title {
  color: blue;
  font-size: 24px;
}
.container {
  padding: 20px;
  border: 1px solid #ccc;
}

2. 使用CSS模块的React组件

接下来,我们创建一个简单的React箭头函数组件,并导入上面创建的CSS模块:

import React from 'react';
import styles from './styles.module.css'; // 导入CSS模块
const MyComponent = () => (
  <div className={styles.container}>
    <h1 className={styles.title}>Hello, CSS Modules!</h1>
  </div>
);
export default MyComponent;

在这个例子中:

  • 我们通过import styles from './styles.module.css'语句来引入CSS模块。
  • 在JSX中,通过className={styles.className}的方式引用特定的类名。这里的styles对象包含了所有从styles.module.css文件导出的类名作为其属性。
    这种方式不仅避免了全局样式冲突的问题,也使得样式管理更加直观、易于维护。

Styled Components

  • 使用 styled-components 库可以在 Javascript 中编写实际的CSS,提供了组件级别的样式管理

用法

  • 首先,请确保你已经安装了 styled-components 库。如果还没有安装,可以通过 npm 或 yarn 来添加它:
npm install styled-components
# 或者
yarn add styled-components

接着是具体的代码实现:

import React from 'react';
import styled from 'styled-components';
// 使用 styled 创建一个名为 Button 的样式化组件
const Button = styled.button`
  background: #5cb85c;
  color: white;
  border: none;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.3s ease;
  &:hover {
    background: #4cae4c;
  }
`;
// 定义一个函数组件
const MyComponent = () => (
  <div>
    <h1>欢迎来到我的网站</h1>
    <Button>点击我</Button>
  </div>
);
export default MyComponent;

在这个例子中:

  • 我们导入了 styled 函数来创建带有样式的组件。
  • Button 是通过 styled.button 创建的一个新组件,这里我们直接定义了 CSS 样式。
  • MyComponent 函数组件里,我们使用了 <Button> 组件,这样就可以自动应用上面定义的所有样式了。
  • 这个按钮还包括了一个简单的悬停效果(:hover)以改善用户体验。

Emotion

  • Emotion 是一个强大的CSS-in-JS 库,提供了灵活的样式管理方案

用法

  • 这个例子使用了箭头函数来定义组件,并且通过 @emotion/react 来创建样式。
    首先,确保你已经安装了 @emotion/react@emotion/styled
npm install @emotion/react @emotion/styled

然后,你可以创建如下所示的组件:

import React from 'react';
import { css } from '@emotion/react';
// 定义样式
const buttonStyle = css`
  background-color: #6200ea;
  color: white;
  border: none;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  border-radius: 5px;
  &:hover {
    background-color: #3700b3;
  }
`;
// 函数组件
const MyButton = () => (
  <button css={buttonStyle}>点击我</button>
);
export default MyButton;

在这个例子中:

  • 我们导入了 css 函数来自 @emotion/react,它允许我们以模板字符串的形式编写CSS。
  • buttonStyle 变量保存了我们的样式定义,包括背景颜色、文字颜色、边框等基本属性,以及鼠标悬停时的背景色变化。
  • MyButton 组件里,我们将 css 属性设置为之前定义的 buttonStyle,这样就能将这些样式应用到 <button> 元素上了。

Tailwind CSS

  • Tailwind CsS是一个实用工具优先的CSS框架,可以在 React 项目中使用。

用法

  • 首先,确保你已经在项目中安装了Tailwind CSS。如果你还没有这样做,可以通过npm或yarn来添加它,并配置好你的项目以支持Tailwind。
# 使用npm
npm install -D tailwindcss
npx tailwindcss init
# 或者使用yarn
yarn add -D tailwindcss
yarn tailwindcss init

接着,在tailwind.config.js文件中进行必要的设置,并在CSS文件中引入Tailwind的基础样式。然后你可以开始编写你的React组件了。
下面是一个基于React箭头函数的简单卡片组件示例:

import React from 'react';
const Card = ({ title, description }) => (
  <div className="max-w-sm rounded overflow-hidden shadow-lg bg-white p-6">
    <div className="font-bold text-xl mb-2">{title}</div>
    <p className="text-gray-700 text-base">{description}</p>
    <button className="mt-4 inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out">了解更多</button>
  </div>
);
export default Card;

在这个例子中:

  • Card 组件接收两个props:titledescription
  • 使用了多种Tailwind类来快速定义样式,比如rounded, overflow-hidden, shadow-lg等用于创建美观的视觉效果。
  • 按钮也利用了Tailwind提供的颜色、边距、阴影等样式属性来增强用户体验。
    这样的组件非常灵活且易于维护,同时由于使用了Tailwind CSS,使得调整布局和外观变得异常简单。希望这对你有所帮助!如果有更多问题或者需要进一步的帮助,请随时告诉我。

React19新特性

useTransition

Acons 简化了异步操作的处理,自动管理待处理状态、错误、乐观更新和表单提交。开发者用处理待处理状态,确保 UI 在数据变化时保持响应性。

useTransition是React 18中引入的一个新特性,它可以帮助我们平滑处理状态更新导致的UI重绘问题,特别是在用户输入时保持界面响应性。

示例

下面是一个使用useTransition的简单示例。在这个例子中,我们将模拟一个搜索功能,当用户输入时,会有一个延迟来模拟网络请求或复杂计算,并且我们希望在等待期间保持UI的响应性。

import React, { useState, useTransition } from 'react';
const SearchComponent = () => {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const handleSearch = (event) => {
    const newQuery = event.target.value;
    setQuery(newQuery);
    // 使用startTransition包裹非紧急的状态更新
    startTransition(() => {
      // 模拟异步操作,比如API调用
      setTimeout(() => {
        // 假设这里是从后端获取的数据
        const mockData = ['apple', 'banana', 'cherry'].filter(item =>
          item.includes(newQuery)
        );
        setResults(mockData);
      }, 500); // 延迟500毫秒以模拟网络请求
    });
  };
  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleSearch}
        placeholder="Search..."
      />
      {isPending && <p>Loading...</p>}
      <ul>
        {results.map((result, index) => (
          <li key={index}>{result}</li>
        ))}
      </ul>
    </div>
  );
};
export default SearchComponent;

这段代码展示了如何在一个简单的搜索组件中利用useTransition来确保即使是在进行可能耗时的操作时(如这里的setTimeout模拟),也能保持UI的流畅性和响应性。当startTransition被调用时,它允许React优先处理其他更紧急的任务(例如用户的点击事件)然后再执行传入的回调函数中的状态更新。这样可以避免因长时间运行的操作而阻塞UI更新,提高了用户体验。

新的Hook:useOptimistic

useOptimistic 用于在异求进行时乐观地显示最终状态,提升用户体验。

示例

import React, { useState, useOptimistic } from 'react';

const CommentBox = () => {
  const [comments, setComments] = useState([]);
  const [text, setText] = useState('');

  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state, newComment) => [...state, newComment]
  );

  const handleSubmit = async (e) => {
    e.preventDefault();
    const comment = { id: Date.now(), text };

    addOptimisticComment(comment); // 立即更新 UI
    setText('');

    // 模拟异步提交
    await fakeSubmitToServer(comment);
    setComments(prev => [...prev, comment]);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
        placeholder="Add a comment"
      />
      <button type="submit">Post</button>
      <ul>
        {optimisticComments.map(c => (
          <li key={c.id}>{c.text}</li>
        ))}
      </ul>
    </form>
  );
};

const fakeSubmitToServer = (comment) =>
  new Promise(resolve => setTimeout(() => resolve(comment), 1000));

export default CommentBox;

  • useOptimistic(state, updater) 返回 [optimisticState, updateOptimistic]
  • 你调用 updateOptimistic(input)optimisticState 会立即应用更新逻辑,而不会等到 setState 生效。
  • 适合用于表单提交、点赞、拖拽排序等交互响应即时的场景

新的 API:use

useAPI 允许在渲染中读取资源,如 Promise 和上下文,简化异步数据处理。

示例

import React, { use } from 'react';

const fetchUser = async () => {
  const res = await fetch('/api/user');
  if (!res.ok) throw new Error('Failed to fetch user');
  return res.json();
};

const userPromise = fetchUser();

const UserInfo = () => {
  const user = use(userPromise); // ⬅️ 等待 Promise

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
};

export default UserInfo;

  • use(promise) 会在组件渲染时自动“暂停”渲染,直到 Promise 解析。
  • 要求外层包裹 <Suspense>,React 会在资源未加载完前 fallback
import React, { Suspense } from 'react';
import UserInfo from './UserInfo';

const App = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <UserInfo />
  </Suspense>
);
  • 关键点
要点说明
🔄 Suspenseuse()
只能在 <Suspense>
环境中使用
📦 异步资源要提前开始比如像 userPromise
那样,组件渲染前就启动
❗错误处理可结合 ErrorBoundary
处理异常

服务器组件(Server Components)

react 19 有了稳定服务器组件的支持,允许在服务器端渲染部分或全部,提升性能和 SEO

示例

// components/UserList.server.jsx
import React from 'react';
import { fetchUsers } from '../lib/api'; // 仅在服务器执行的数据获取

const UserList = async () => {
  const users = await fetchUsers(); // 服务器端直接 await

  return (
    <ul>
      {users.map(u => (
      <li key={u.id}>
        {u.name} — {u.email}
      </li>
    ))}
    </ul>
  );
};

export default UserList;

// components/SearchBar.client.jsx
'use client';
import React, { useState } from 'react';

const SearchBar = () => {
  const [q, setQ] = useState('');
  return (
    <input
      value={q}
      onChange={e => setQ(e.target.value)}
      placeholder="搜索用户"
    />
  );
};

export default SearchBar;

// app/page.jsx
import React, { Suspense } from 'react';
import UserList from '../components/UserList.server';
import SearchBar from '../components/SearchBar.client';

const Page = () => (
  <main>
    <h1>用户列表</h1>

    {/* 客户端交互组件 */}
    <SearchBar />

    {/* 服务器组件可在后台并行加载 */}
    <Suspense fallback={<p>加载中…</p>}>
      <UserList />
    </Suspense>
  </main>
);

export default Page;

关键点

  • 后缀区分
    • .server.jsx:完全在服务器执行,不打包到客户端
    • .client.jsx:包含客户端交互逻辑,需要打包到浏览器
  • 异步组件
    • 在服务器组件中可直接使用 await 拿到数据,无需钩子
  • 部分 SSR
    • 关键渲染(如列表)由服务器负责,交互部分由客户端组件处理
  • 性能与 SEO
    • 首屏 HTML 即含完整列表,搜索引擎抓取友好,用户感知更快

这个模式让你根据功能需求,将页面拆分为:

  • “数据密集、首屏优先” 的 Server Components
  • “交互密集” 的 Client Components。

支持自定义元素

  • React 19完全支持自定义元素

示例

  • 在 React 19 中如何定义并使用原生自定义元素(Web Component)
<!-- index.html -->
  <script>
  // 定义自定义元素
  class FancyButton extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' }).innerHTML =
        `<button><slot></slot></button>`;
    }
    connectedCallback() {
      this.shadowRoot
        .querySelector('button')
        .addEventListener('click', () =>
          this.dispatchEvent(new CustomEvent('fancyClick'))
                         );
    }
  }
customElements.define('fancy-button', FancyButton);
</script>
  <div id="root"></div>

// App.jsx
import React, { useRef } from 'react';

const App = () => {
  const btnRef = useRef();

  const handleFancyClick = () => {
    alert('Fancy button clicked!');
  };

  return (
    // React 19 直接支持 onFancyClick 绑定到 fancyClick 事件
    <fancy-button ref={btnRef} onFancyClick={handleFancyClick}>
      点我
    </fancy-button>
  );
};

export default App;

  • .define() 后可以在 React JSX 中直接使用 <fancy-button>
  • React 19 会自动将 onFancyClick 映射到自定义事件 fancyClick
  • 不需要额外的 addEventListener 或 wrapper,使用更简洁

文档元数据支持

  • React 19允许直接在组件中渲染 、 等标签,自动提升到文档头部化 SEO 管理

示例

  • 在组件中直接渲染<title><meta><link> 等标签,React 会自动提升到文档头部,方便 SEO 管理
// Page.jsx
import React from 'react';

const Page = () => (
  <>
    {/* 这些标签会被自动移动到 <head> 中 */}
    <title>我的应用 · 首页</title>
    <meta name="description" content="这是使用 React 19 自动提升 head 标签的示例页面。" />
    <link rel="canonical" href="https://example.com/" />

    {/* 正常的页面内容 */}
    <main>
      <h1>欢迎来到我的网站</h1>
      <p>使用 React 19,SEO 头部标签写在组件里更直观。</p>
      <a href="/about">关于我们</a>
    </main>
  </>
);

export default Page;

  • 在组件树任意位置写 <title> / <meta> / <link>
  • React 19 会智能地将它们提取并渲染到文档的 <head>
  • 这样就无需在外层模板或框架配置里单独管理头部标签,SEO 更直观、代码更统一

样式表优先级设置

  • React 19引入了样式表优先级设置,允许开发者控制样式表的应用顺序,确保预期应用

示例

// App.jsx
import React from 'react';

const App = () => (
  <>
    {/* 低优先级全局样式 */}
    <style priority="low">
      {`
        .text {
          font-size: 16px;
          color: black;
        }
      `}
    </style>

    {/* 高优先级覆盖样式 */}
    <style priority="high">
      {`
        .text {
          color: red;
        }
      `}
    </style>

    <p className="text">这段文字会变成红色。</p>
  </>
);

export default App;

  • <style priority="...">:React 19 新增属性,可设 "low", "normal", "high"(亦可用数字)。
  • React 会根据 priority 自动调整样式表的注入顺序,确保高优先级样式能覆盖低优先级样式。
  • 在组件中直接写样式,且能精准控制冲突解决,避免手动管理顺序或依赖第三方工具

在任何组件中渲染异步脚本

  • React 19允许在任何组件中渲染异步脚本,自动处理去重,简化脚本管理

示例

// components/PayScript.jsx
import React from 'react';

const PayScript = () => (
  <>
    {/* React 19 自动提升和去重异步脚本 */}
    <script
      src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID"
      async
    />
  </>
);

export default PayScript;

// app/page.jsx
import React from 'react';
import PayScript from '../components/PayScript';

const Page = () => (
  <main>
    <h1>支付页面</h1>
    {/* 任意位置加载脚本,无需手动管理重复 */}
    <PayScript />
    {/* 其他组件中也可重复引用,React 19 自动去重 */}
    <PayScript />
  </main>
);

export default Page;

  • **组件中直接写 ****<script>**,无需集中管理或放在 head
  • **React 19 会自动去重并提升 **<script>** 到 ****<head>**
  • 简化第三方 SDK(如 PayPal、Map、Analytics)管理流程
  • 支持 asyncdefer 等属性。

这让你可以将脚本声明与功能组件绑定,更模块化、更可维护

相关资料