- 作者:老汪软件技巧
- 发表时间: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>
{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 子组件会一起显示