react18与react17有哪些区别

React 18 相对于 React 17 的主要升级内容

1. 并发特性(Concurrent Features)

React 18 引入了并发特性,使得 React 能够在后台准备多个版本的 UI,从而提升应用的响应速度和用户体验。

  • 并发渲染(Concurrent Rendering) :允许 React 在后台准备多个版本的 UI,并在合适的时候应用这些更新。
  • useTransitionstartTransition:这些 API 用于处理 UI 过渡状态,允许你将某些状态更新标记为低优先级,从而保持 UI 的响应性。
  • useDeferredValue:用于延迟更新某些状态,避免频繁的重新渲染,从而提升性能。

useTransition(react17)

useTransition 是 React 18 中新增的一个 Hook。它主要用于处理 UI 中的过渡状态,特别是在需要处理用户输入或者其他需要响应的操作时,可以让 React 在后台处理一些状态更新,从而避免 UI 的卡顿。 useTransition 返回一个布尔值和一个函数。布尔值表示当前是否处于过渡状态,函数用于启动过渡状态更新。

jsx 复制代码
const [isPending, startTransition] = useTransition();
  • isPending:一个布尔值,表示当前是否有过渡中的状态更新。
  • startTransition:一个函数,用于启动过渡状态更新。

下面是一个使用 useTransition 的示例,展示如何在处理大量数据时保持 UI 的响应性。

jsx 复制代码
import React, { useState, useTransition } from 'react';

const App = () => {
  const [isPending, startTransition] = useTransition();
  const [input, setInput] = useState('');
  const [list, setList] = useState([]);

  const handleChange = (e) => {
    const value = e.target.value;
    setInput(value);
    startTransition(() => {
      const newList = Array(10000)
        .fill(0)
        .map((_, i) => `${value} ${i}`);
      setList(newList);
    });
  };

  return (
    <div>
      <input type="text" value={input} onChange={handleChange} />
      {isPending ? <p>Loading...</p> : null}
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};
export default App;

通过使用 useTransition,我们可以将一些计算量大的状态更新操作放入过渡状态,允许你将某些状态更新标记为低优先级,从而保持 UI 的响应性。React 会在后台处理这些过渡状态更新,使得用户体验更加流畅。

2. 自动批处理(Automatic Batching)

React 18 引入了自动批处理更新的功能,即使是在异步事件中,多个状态更新也会被批处理在一起,从而减少不必要的重新渲染。

jsx 复制代码
import { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  function handleClick() {
    setCount(c => c + 1);
    setText('Updated');
    // 在 React 18 中,这两个更新会自动批处理在一起
  }

  return (
    <div>
      <button onClick={handleClick}>Update</button>
      <p>{count}</p>
      <p>{text}</p>
    </div>
  );
}

react17不会自动批处理吗?

在 React 17 中,自动批处理(Automatic Batching)仅限于 React 事件处理程序内的状态更新。也就是说,如果你在 React 事件处理程序中进行多次状态更新,React 会将这些状态更新自动批处理,以减少重新渲染的次数。这有助于提高性能。但是,在其他异步操作(如 setTimeoutPromise 或者原生事件处理程序)中,React 17 并不会自动批处理状态更新。在 React 17 中,如果状态更新发生在异步操作中(如 setTimeoutPromise),则不会自动批处理。

从 React 18 开始,自动批处理的范围被扩展到了所有的异步操作,包括 setTimeoutPromise、原生事件处理程序等。

例如以下代码,如果点击按钮,react17会log两次,而react18只会log一次

jsx 复制代码
import React, { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  console.log('-------------------------------------', new Date().getTime());
  const handleClick = () => {
    setTimeout(() => {
      setCount(count + 1);
      setText('Updated');
      // 这两个状态更新不会被批处理,导致两次重新渲染
    }, 1000);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}

export default App;

在 React 17 中,如果需要在异步操作中手动批处理状态更新,可以使用 unstable_batchedUpdates 函数。 例如以下代码,如果点击按钮,react17只会log一次

jsx 复制代码
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { unstable_batchedUpdates } from 'react-dom';

function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const handleClick = () => {
    setTimeout(() => {
      unstable_batchedUpdates(() => {
        setCount(count + 1);
        setText('Updated');
        // 这两个状态更新会被批处理,导致一次重新渲染
      });
    }, 1000);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}

export default App;

3. useId Hook

React 18 引入了 useId Hook,通过使用 useId,我们可以确保生成的 ID 是唯一且稳定的,避免 ID 冲突,并在服务器端渲染和客户端渲染之间保持一致性。

1、表单元素的关联

在表单中,我们通常需要为每个输入元素生成一个唯一的 ID,以便 label 标签能够正确地关联到相应的输入元素。使用 useId 可以简化这个过程,并确保生成的 ID 是唯一且稳定的

jsx 复制代码
import { useId } from 'react';

function MyComponent() {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>Enter your name:</label>
      <input id={id} type="text" />
    </div>
  );
}

2、动态生成表单项

假设我们有一个动态生成的表单,其中的表单项可以根据用户的输入动态增加或减少。使用 useId 可以确保每个动态生成的表单项都有一个唯一的 ID。

jsx 复制代码
import React, { useState, useId } from 'react';

function DynamicForm() {
  const [fields, setFields] = useState([{ id: useId(), value: '' }]);

  const addField = () => {
    setFields([...fields, { id: useId(), value: '' }]);
  };

  const handleChange = (id, event) => {
    const newFields = fields.map(field =>
      field.id === id ? { ...field, value: event.target.value } : field
    );
    setFields(newFields);
  };

  return (
    <form>
      {fields.map(field => (
        <div key={field.id}>
          <label htmlFor={field.id}>Field:</label>
          <input
            id={field.id}
            type="text"
            value={field.value}
            onChange={event => handleChange(field.id, event)}
          />
        </div>
      ))}
      <button type="button" onClick={addField}>Add Field</button>
    </form>
  );
}

export default DynamicForm;

4. SSR 改进(Server-Side Rendering Improvements)

React 18 对服务端渲染(SSR)进行了改进,支持流式渲染(Streaming Rendering),使得页面加载更快。

  • 流式渲染:React 18 支持流式渲染,使得服务端可以在数据准备好后逐步发送 HTML 内容,从而提升首屏加载速度。
  • Selective Hydration:允许在客户端根据需要逐步激活(hydrate)不同部分的 UI,从而提升性能。

5. 新的 SSR API

React 18 引入了一些新的 SSR API,例如 renderToPipeableStreamrenderToReadableStream,用于支持流式渲染。React 18 引入了实验性的 React 服务器组件(React Server Components),允许在服务器端渲染组件并将其发送到客户端,从而减少客户端 JavaScript 的负担。

javascript 复制代码
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
  onShellReady() {
    pipe(response);
  },
});

6. 新的 Strict Mode 行为

React 18 中的严格模式(Strict Mode)引入了更多的开发时候检查,帮助开发者发现潜在的问题。例如,严格模式下会模拟卸载和重新挂载组件,以确保组件在不同生命周期阶段的行为一致。

React 的 Strict Mode 主要用于在开发环境中帮助识别潜在问题,并不影响生产环境中的行为。React 18 对 Strict Mode 做了一些增强,尤其是在组件的挂载和卸载方面。以下是一些关键变化:

1. 双重渲染(Double Invoking)

在 React 18 的 Strict Mode 下,React 会在开发环境中对某些生命周期方法(如 componentDidMountcomponentWillUnmount)进行双重调用。这是为了帮助开发者发现副作用和潜在问题。具体来说,React 会执行以下步骤:

  • 初次挂载 :第一次渲染组件,并调用 componentDidMount
  • 卸载 :立即卸载组件,调用 componentWillUnmount
  • 重新挂载 :再次渲染组件,并再次调用 componentDidMount。 这种行为的目的是确保组件在挂载和卸载过程中不会产生副作用。

2. 自动批处理(Automatic Batching)

React 18 引入了自动批处理功能,这意味着在事件处理程序之外的多个状态更新也会被自动批处理。在 Strict Mode 下,这种行为同样适用,有助于减少不必要的重新渲染。

3. 并发模式(Concurrent Mode)

虽然并发模式在 React 18 中并不是默认启用的,但它是 React 18 的一个重要特性。在 Strict Mode 下,React 会模拟并发渲染,帮助开发者识别和解决潜在的并发问题。

示例代码

以下是一个示例代码,展示了 React 18 中 Strict Mode 的新行为:

jsx 复制代码
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Component mounted or updated');

    return () => {
      console.log('Component will unmount');
    };
  }, []);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

在这个示例中,useEffect 中的 console.log 语句会在组件挂载和更新时执行,而清理函数会在组件卸载时执行。在 React 18 的 Strict Mode 下,你会看到 console.log('Component mounted or updated') 被调用两次,这是因为组件在开发环境中会被双重渲染。

7. 新的 Suspense 功能

React 18 引入了新的 Suspense 功能,使得处理异步操作和数据加载变得更加方便和高效。通过这些新特性,开发者可以更好地管理异步数据加载和状态更新,从而提升用户体验。

jsx 复制代码
import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

1、Suspense for Data Fetching

假设我们有一个异步函数 fetchData,用于获取数据:

jsx 复制代码
const fetchData = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Data fetched!");
    }, 2000);
  });
};

我们可以使用 Suspense 来等待数据加载:

jsx 复制代码
import React, { Suspense, useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

const DataComponent = React.lazy(() => fetchData().then(data => {
  return { default: () => <div>{data}</div> };
}));

function App() {
  return (
    <div>
      <h1>React 18 Suspense</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <DataComponent />
      </Suspense>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

在这个示例中,DataComponent 是一个懒加载组件,它会在数据加载完成后显示。在数据加载过程中,Suspense 会显示 fallback 内容(例如,"Loading...")。

2、SuspenseList 组件

SuspenseList 组件可以协调多个 Suspense 组件的显示顺序:

jsx 复制代码
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';

const ComponentA = React.lazy(() => new Promise(resolve => {
  setTimeout(() => resolve({ default: () => <div>Component A</div> }), 1000);
}));
const ComponentB = React.lazy(() => new Promise(resolve => {
  setTimeout(() => resolve({ default: () => <div>Component B</div> }), 2000);
}));

function App() {
  return (
    <div>
      <h1>React 18 SuspenseList</h1>
      <SuspenseList revealOrder="together">
        <Suspense fallback={<div>Loading A...</div>}>
          <ComponentA />
        </Suspense>
        <Suspense fallback={<div>Loading B...</div>}>
          <ComponentB />
        </Suspense>
      </SuspenseList>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

在这个示例中,SuspenseList 使用 revealOrder="together",表示所有 Suspense 子组件会一起显示

相关推荐
GISer_Jing1 小时前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪2 小时前
CSS复习
前端·css
咖啡の猫4 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲6 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5817 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路7 小时前
GeoTools 读取影像元数据
前端
ssshooter8 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友8 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry8 小时前
Jetpack Compose 中的状态
前端
dae bal9 小时前
关于RSA和AES加密
前端·vue.js