React16,17,18,19更新对比

发布于:2025-06-13 ⋅ 阅读:(26) ⋅ 点赞:(0)


在这里插入图片描述

前言

总结react 16,17,18,19所更新的内容,并且部分会涉及到原理讲解。


一、16更新

1、在16.8之前更新,还是基于class组件的升级和维护更新。并且更新了一个重要的架构,Fiber架构。
什么是Fiber:Fiber架构的核心是“Fiber”节点,它是工作的基本单位。每个React元素都对应一个Fiber节点,这些节点构成了一个工作单元的树状结构。Fiber节点包含组件的类型、其对应的DOM节点信息以及子节点和兄弟节点的引用等信息。
2、而16.8的更新,是react的一个重要更新呢版本。
更新内容:

  1. 引入Hooks,新增许多hooks的api

  2. Hooks解决了什么问题:
    ① 组件复用:
    之前:
    函数组件无法直接管理状态和生命周期,所以组件复用通常依赖类组件,和高阶组件实现。
    例如:

    // 高阶组件
    function withLoading(WrappedComponent) {
      return class extends React.Component {
        state = { isLoading: false };
    
        componentDidMount() {
          this.setState({ isLoading: true });
          setTimeout(() => {
            this.setState({ isLoading: false });
          }, 2000);
        }
        render() {
          const { isLoading } = this.state;
          return (
            <div>
              {isLoading ? <div>Loading...</div> : <WrappedComponent {...this.props} />}
            </div>
          );
        }
      };
    }
    // 被包裹的组件
    class MyComponent extends React.Component {
      render() {
        return <div>My Component Content</div>;
      }
    }
    // 使用高阶组件
    const MyComponentWithLoading = withLoading(MyComponent);
    // 渲染
    ReactDOM.render(<MyComponentWithLoading />, document.getElementById("root"));
    

    hooks出现之后:
    例如:

    // 自定义 Hook
    function useLoading() {
      const [isLoading, setIsLoading] = useState(false);
      React.useEffect(() => {
        setIsLoading(true);
        const timer = setTimeout(() => {
          setIsLoading(false);
        }, 2000);
        return () => clearTimeout(timer); // 清理副作用
      }, []);
      return isLoading;
    }
    // 函数组件
    function MyComponent() {
      const isLoading = useLoading();
      return (
        <div>
          {isLoading ? <div>Loading...</div> : <div>My Component Content</div>}
        </div>
      );
    }
    // 渲染
    ReactDOM.render(<MyComponent />, document.getElementById("root"));
    

    ② 生命周期函数复杂性:
    在类组件中,常见的生命周期方法包括:
    constructor:初始化状态。
    componentDidMount:组件挂载后执行。
    componentDidUpdate:组件更新后执行。
    componentWillUnmount:组件卸载前执行。
    使用 Hooks 模拟生命周期

    1. 初始化状态(constructor)
      在函数组件中,状态可以通过 useState 初始化。

    2. 模拟 componentDidMount
      使用 useEffect 的回调函数,当组件挂载后执行。

    3. 模拟 componentDidUpdate
      使用 useEffect 的依赖数组,当依赖项变化时执行。

    4. 模拟 componentWillUnmount
      使用 useEffect 的返回值(清理函数),在组件卸载前执行。
      直接示例:

      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            data: null,
            count: 0
          };
        }
      
        componentDidMount() {
          console.log("Component did mount");
          this.fetchData();
        }
      
        componentDidUpdate(prevProps, prevState) {
          console.log("Component did update");
          if (prevState.count !== this.state.count) {
            console.log(`Count changed from ${prevState.count} to ${this.state.count}`);
          }
        }
      
        componentWillUnmount() {
          console.log("Component will unmount");
          this.clearData();
        }
      
        fetchData = () => {
          // 模拟数据加载
          setTimeout(() => {
            this.setState({ data: "Loaded data" });
          }, 1000);
        };
      
        clearData = () => {
          console.log("Clearing data");
          this.setState({ data: null });
        };
      
        render() {
          const { data, count } = this.state;
          return (
            <div>
              <h1>{data || "Loading..."}</h1>
              <button onClick={() => this.setState({ count: count + 1 })}>
                Increment
              </button>
            </div>
          );
        }
      }
      

      hooks模拟:

      function MyComponent() {
        const [data, setData] = React.useState(null);
        const [count, setCount] = React.useState(0);
      
        // 模拟 componentDidMount
        React.useEffect(() => {
          console.log("Component did mount");
          fetchData();
        }, []); // 空依赖数组表示只在挂载时执行
      
        // 模拟 componentDidUpdate
        React.useEffect(() => {
          console.log("Component did update");
          console.log(`Count changed to ${count}`);
        }, [count]); // 依赖数组包含 count,表示当 count 变化时执行
      
        // 模拟 componentWillUnmount
        React.useEffect(() => {
          return () => {
            console.log("Component will unmount");
            clearData();
          };
        }, []); // 空依赖数组表示只在卸载时执行
      
        const fetchData = () => {
          // 模拟数据加载
          setTimeout(() => {
            setData("Loaded data");
          }, 1000);
        };
      
        const clearData = () => {
          console.log("Clearing data");
          setData(null);
        };
      
        return (
          <div>
            <h1>{data || "Loading..."}</h1>
            <button onClick={() => setCount(count + 1)}>Increment</button>
          </div>
        );
      }
      

二、17更新

1、新的JSX转化,不需要手动引入react

  1. React 16
    babel-loader会预编译JSX为 React.createElement(…)

  2. React 17
    React 17中的 JSX 转换不会将 JSX 转换为 React.createElement,而是自动从 React 的 package 中引入react并调用。而是React 的运行时调用jsx 和 jsxs 函数:react/jsx-runtime 模块提供了 jsx 和 jsxs 函数,分别用于处理单个子元素和多个子元素的情况。这些函数在运行时被调用,用于创建 React 元素。
    jsx与 React.createElement 相比,jsx 函数在处理子元素和 key 值时更加高效。例如,key 值在 jsx 函数中作为第三个参数直接传递,而不是像在 React.createElement 中那样作为属性对象的一部分。
    优势:
    减少包体积
    简化代码:开发者不再需要在每个组件文件顶部显式引入 React,使得组件代码更加简洁。
    优化包大小:由于不再需要导入整个 React 对象,构建工具可以更好地优化输出代码,从而减小输出包的大小。
    高效参数处理
    参数结构优化:jsx 函数的参数结构更加合理。它将 children 放在了 props 对象中,并将 key 作为单独的参数传递。这种参数结构使得 React 在处理元素时可以更高效地访问和处理 children 和 key,减少了不必要的属性查询和处理逻辑。
    子元素处理优化:在 React 17 之前,React.createElement 的子元素是作为后续参数传递的,这在处理多个子元素时可能会导致性能问题。而 jsx 函数将子元素作为数组传递给 children 属性,这种方式更加清晰且易于优化。
    性能优化空间
    静态分析优化:由于 jsx 函数的实现更加标准化和简洁,编译器可以更容易地对 JSX 代码进行静态分析和优化。例如,编译器可以在编译时进行常量提升、代码压缩等优化操作,从而生成更高效的代码。
    减少动态属性查询:jsx 函数消除了对动态属性查找的需要,这在一定程度上减少了运行时的性能开销。虽然现代 JavaScript 引擎对动态属性查询进行了优化,但在大规模应用中,这种优化仍然可以带来一定的性能提升

    例如:

    import { jsx as _jsx } from 'react/jsx-runtime';
    function App() {
      return _jsx('h1', { children: 'Hello' }, '1');
    }
    

2、事件代理更改
17中,不在document对象上绑定事件,改为绑定于每个react应用的rootNode节点,因为各个应用的rootNode肯定不同,所以这样可以使多个版本的react应用同时安全的存在于页面中,不会因为事件绑定系统起冲突。react应用之间也可以安全的进行嵌套。
3、事件池(event pooling)的改变

4、异步执行
17将副作用清理函数(useEffect)改为异步执行,即在浏览器渲染完毕后执行。
5、forwardRef 和 memo组件的行为
React 17中forwardRef 和 memo组件的行为会与常规函数组件和class组件保持一致。它们在返回undefined时会报错。

三、18更新

注意:v18的新特性是使用现代浏览器的特性构建的,彻底放弃对 IE 的支持。

  1. 并发模式
    18引入了并发特性,并发特性允许React在渲染过程中中断和恢复工作,从而更好地响应用户交互和系统事件,与传统的同步渲染不同,并发渲染将渲染任务拆分为多个小任务,并根据优先级动态调度这些任务。
    实现原理
    Fiber架构:React 18使用Fiber架构来管理渲染任务。Fiber节点包含组件的类型、状态、props等信息,并允许React在渲染过程中暂停和恢复。Fiber架构使用双端队列(work-in-progress tree和current tree)来管理渲染任务。
    时间切片:React将长时间的渲染任务拆分成多个较短的时间片,以避免阻塞主线程。虽然React内部自动管理时间切片,但开发者可以通过控制更新任务的优先级来间接影响时间切片的分配。
    优先级调度:React引入了任务优先级的概念,将任务分为不同的优先级,如高优先级任务(用户交互、动画等)和低优先级任务(数据加载、复杂计算等)。React会根据任务的优先级动态调度渲染任务,确保高优先级任务能够及时得到处理。
    中断与恢复:在渲染过程中,如果浏览器资源紧张或有其他高优先级的任务需要执行,React可以暂停当前的渲染任务,释放资源给更重要的任务。一旦资源变得可用,React会恢复之前的渲染任务,并继续执行剩余的小任务。
    如何使用并发特性
    启用并发模式:通过将应用切换到并发模式,开发者可以充分利用并发渲染的优势。示例代码如下:

    import ReactDOM from 'react-dom/client';
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);
    
  2. 更新render API

    // v17
    import ReactDOM from 'react-dom'
    import App from './App'
    ReactDOM.render(<App />, document.getElementById('root'))
    // v18
    import ReactDOM from 'react-dom/client'
    import App from './App'
    ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
    
  3. 自动批处理
    批处理是指 React 将多个状态更新,聚合到一次 render 中执行,以提升性能
    在v17的批处理只会在事件处理函数中实现,而在Promise链、异步代码、原生事件处理函数中失效。而v18则所有的更新都会自动进行批处理。

    // v17
    const handleBatching = () => {
      // re-render 一次,这就是批处理的作用
      setCount((c) => c + 1)
      setFlag((f) => !f)
    }
    // re-render两次
    setTimeout(() => {
       setCount((c) => c + 1)
       setFlag((f) => !f)
    }, 0)
    
    // v18
    const handleBatching = () => {
      // re-render 一次
      setCount((c) => c + 1)
      setFlag((f) => !f)
    }
    // 自动批处理:re-render 一次
    setTimeout(() => {
       setCount((c) => c + 1)
       setFlag((f) => !f)
    }, 0)
    
    

    如果不想批处理,可以用flushSync强制同步处理,同步执行更新

  4. 新api
    1、startTransition
    示例:如果一个渲染任务较重的tab页面切换,包裹后可以不阻塞其交互点击行为。
    原理:降低渲染优先级,优先处理用户交互行为,渲染行为延后

    import { startTransition } from 'react';
    function TabContainer() {
      const [tab, setTab] = useState('about');
      function selectTab(nextTab) {
        startTransition(() => {
          setTab(nextTab);
        });
      }
    }
    

    2、useTransition
    提供了一个带有isPending标志的 Hook useTransition来跟踪 transition 状态,用于过渡期。
    startTransition回调可以嵌套和处理异步方法

    function TabContainer() {
      const [isPending, startTransition] = useTransition();
      const [tab, setTab] = useState('about');
      function selectTab(nextTab) {
        startTransition(() => {
          setTab(nextTab);
        });
      }
    }
    

    3、useDeferredValue
    类似于上一个hooks标记一个state为延迟更新数据,标记为非紧急更新状态。

    import { useState, useDeferredValue } from 'react';
    function SearchPage() {
      const [query, setQuery] = useState('');
      const deferredQuery = useDeferredValue(query);
    }
    

    4、useId
    useId支持同一个组件在客户端和服务端生成相同的唯一的 ID,原理就是每个 id 代表该组件在组件树中的层级结构

四、19更新

1、支持元数据标签
文档元数据支持:支持在组件中渲染 <title><link><meta> 标签,并自动提升到文档的 <head> 部分
2、 新增Hooks

  1. useOptimistic
    用于管理乐观更新。当执行某个操作时,可以先假设操作成功,并立即更新 UI,然后在操作完成后根据实际结果调整状态。比如点赞、评论、加入购物车等功能,我们都可以先假设成功,再根据接口返回来调整。

  2. useActionState
    用于管理与用户操作相关的状态。它能够记录和回放用户操作,帮助实现更复杂的交互和调试功能。

    function ChangeName({ currentName }) {
      const [error, submitAction, isPending] = useActionState(async (prev, formData) => {
        const error = await updateName(formData.get("name"));
        if (error) return error;
        return null;
      });
      return (
        <form action={submitAction}>
        <input type="text" name="name" defaultValue={currentName} />
        <button type="submit" disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
        </form>
       );
      }
    
  3. use
    它可以让你读取类似于 Promise 或 context 的资源的值。可以在if中使用

    import { use } from 'react';
    function MessageComponent({ messagePromise }) {
      const message = use(messagePromise);
      const theme = use(ThemeContext);
    
  4. 优化
    React 编译器可以自动缓存计算结果来优化组件,减少不必要的重新读取。在许多情况下,开发人员无需明确使用记忆化钩子,如 useMemo 和 useCallback。例如,以往需要手动使用 useMemo 来缓存昂贵的计算结果,现在可以直接写代码,编译器会自动优化。

  5. 弃用forwardRef

    const MyInput = forwardRef(function MyInput(props, ref) {
      return (
        <label>
          {props.label}
          <input ref={ref} />
        </label>
      );
    });
    

    可以直接使用ref

    function MyInput(props) {
      return (
        <label>
          {props.label}
          <input ref={props.ref} />
        </label>
      );
    }
    
  6. 写法优化
    <Context> 作为提供者,可以直接使用 <Context> 作为提供者,而不是 <Context.Provider>


总结

React 16:引入 Fiber 架构,提升渲染性能,新增错误边界等功能。
React 17:事件系统重构,新的 JSX 转换机制,优化事件处理。
React 18:并发渲染、自动批处理、服务器组件,性能大幅提升。
React 19:引入 Actions 和表单状态管理 API,自动优化性能,简化开发体验