• 作者:老汪软件技巧
  • 发表时间:2024-12-27 11:04
  • 浏览量:

React 18 相对于 React 17 的主要升级内容1. 并发特性(Concurrent Features)

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

useTransition(react17)

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

const [isPending, startTransition] = useTransition();

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

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

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}>Updatebutton>
      <p>{count}p>
      <p>{text}p>
    div>
  );
}

react17不会自动批处理吗?

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

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

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

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 mebutton>
    div>
  );
}
export default App;

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

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 mebutton>
    div>
  );
}
export default App;

3.useIdHook

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

1、表单元素的关联

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

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。

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>

17-18现在的人生致富之路__升级内容主要包括

{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 Fieldbutton> form> ); } export default DynamicForm;

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

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

5. 新的 SSR API

React 18 引入了一些新的 SSR API,例如renderToPipeableStream和renderToReadableStream,用于支持流式渲染。React 18 引入了实验性的 React 服务器组件(React Server Components),允许在服务器端渲染组件并将其发送到客户端,从而减少客户端 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 会在开发环境中对某些生命周期方法(如componentDidMount和componentWillUnmount)进行双重调用。这是为了帮助开发者发现副作用和潜在问题。具体来说,React 会执行以下步骤:

2. 自动批处理(Automatic Batching)

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

3. 并发模式(Concurrent Mode)

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

示例代码

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

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)}>Incrementbutton>
    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 功能,使得处理异步操作和数据加载变得更加方便和高效。通过这些新特性,开发者可以更好地管理异步数据加载和状态更新,从而提升用户体验。

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,用于获取数据:

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

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

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 Suspenseh1>
      <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 组件的显示顺序:

import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
const ComponentA = React.lazy(() => new Promise(resolve => {
  setTimeout(() => resolve({ default: () => <div>Component Adiv> }), 1000);
}));
const ComponentB = React.lazy(() => new Promise(resolve => {
  setTimeout(() => resolve({ default: () => <div>Component Bdiv> }), 2000);
}));
function App() {
  return (
    <div>
      <h1>React 18 SuspenseListh1>
      <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 子组件会一起显示