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 子组件会一起显示

相关推荐
鱼钓猫的小鱼干1 分钟前
table 表格转成 excell 导出
前端·vue·excel
一只搬砖的猹3 分钟前
cJson系列——常用cJson库函数
linux·前端·javascript·python·物联网·mysql·json
CodeClimb15 分钟前
【华为OD-E卷-租车骑绿道 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
懒羊羊我小弟22 分钟前
包管理工具npm、yarn、pnpm、cnpm详解
前端·npm·node.js·yarn·cnpm
ppo_wu26 分钟前
更改 pnpm 的全局存储位置
前端·vue
"追风者"38 分钟前
前端(八)js介绍(1)
前端·javascript
博客zhu虎康1 小时前
用 ElementUI 的日历组件 Calendar 自定义渲染
前端·javascript·elementui
叶浩成5201 小时前
elementUI——upload限制图片或者文件只能上传一个——公开版
前端·javascript·elementui
丁总学Java1 小时前
去除 el-input 输入框的边框(element-ui@2.15.13)
javascript·vue.js·elementui
yqcoder1 小时前
同源策略详解
xml·前端·javascript