听说你还不知道React18新特性?看我给你整明白!
前言
目前react的最新版本是18.2.0。React 团队在 2022 年 3 月 29 日正式发布了 React 的第 18 个版本 是 React 框架的最新版本,它主要着眼于解决 React 应用在性能、稳定性、开发体验等方面的问题。本文将介绍 React 18 的升级内容、新特性、新的 API、底层逻辑更新等方面的内容,并通过示例展示其使用效果。 我将在这篇文章里简单介绍 React 18 的新特性,React Concurrent Mode(并发模式)的实现,以及简要的升级指南。
升级
- react18 已经不支持IE浏览器
- 新项目: 直接用
npm
或者yarn
安装最新版依赖即可(如果是js,可以不需要安装types类型声明文件) - 改变根节点的挂载方式使用新的 API
createRoot
,使用旧的 API 仍然兼容,只有在使用createRoot
了之后才会有 React 18 的新特性。
1ReactDOM.createRoot(document.getElementById('root')!).render(
2 <React.StrictMode>
3 <Provider store={store}>
4 <App/>
5 </Provider>
6 </React.StrictMode>,
7)
8
在这个示例中,我们使用了 ReactDOM.createRoot
方法创建了一个根节点,并使用 render
方法将组件渲染到根节点中。这样可以让 React 应用更快地响应用户操作,提高用户体验。
react18 setState异步同步
在 React 18 中, setState
的行为有一些改变,它将更倾向于以异步方式进行更新,但也提供了一些选项来控制同步更新。下面是关于 React 18 中 setState
的异步和同步行为的解释:
1. 异步更新(默认行为):
在 React 18 中,默认情况下, setState
方法会以异步方式进行更新。这意味着它会将多个状态更新批量处理,并在适当的时机进行合并和应用,以优化性能。这样做可以减少不必要的重渲染,并提高应用程序的响应性。
1
2// 异步更新
3this.setState({ count: this.state.count + 1 });
4
2. 同步更新(使用 flushSync
):
尽管 setState
默认以异步方式进行更新,但在某些情况下,您可能需要立即获取更新后的状态。为了实现此目的,React 18 提供了 flushSync
方法,可以强制执行同步更新。
1
2import { flushSync } from 'react-dom';
3
4// 同步更新
5
6flushSync(() => {
7
8 this.setState({ count: this.state.count + 1 });
9
10});
11
通过使用 flushSync
包裹 setState
的调用,您可以确保在执行下一个任务之前立即获取到更新后的状态。请注意,使用 flushSync
可能会对性能产生影响,并且应谨慎使用,以避免阻塞主线程。
需要注意的是,React 18 引入了一种新的异步渲染优先级机制,称为 useTransition
。通过使用 useTransition
,您可以控制异步更新的优先级。这对于在高优先级工作(例如用户交互)和低优先级工作(例如懒加载数据)之间进行平衡非常有用。然而,它不直接影响 setState
的异步/同步行为,而是影响更新的优先级。
总结一下,在 React 18 中, setState
通常以异步方式进行更新,并且使用 flushSync
可以实现同步更新。此外,您还可以使用 useTransition
提供的优先级控制来平衡不同任务之间的更新。
React18 新增API
React 18 是 React 的一个重要版本,它包含了一些新的特性和改进,其中一些会对应用程序的开发流程、性能和用户体验产生重要影响。以下是 React 18 中新增的一些 API:
1. startTransition
startTransition
是一个新的 React API,旨在帮助开发者优化应用程序的性能和用户体验。这个函数可以告诉 React 在下次重新渲染组件时,应该延迟更新状态。这样,一些较慢的操作(例如异步请求等)就可以在后台执行,不会影响应用程序的交互性能。
1
2import { startTransition } from 'react';
3
4function App() {
5
6 const [searchTerm, setSearchTerm] = useState('');
7
8 const [results, setResults] = useState([]);
9
10 function handleSearch(event) {
11
12 setSearchTerm(event.target.value);
13
14 startTransition(() => {
15
16 fetch(`/api/search?query=${searchTerm}`)
17
18 .then(response => response.json())
19
20 .then(data => setResults(data));
21
22 });
23
24 }
25
26 return (
27
28 <div>
29
30 <input type="text" value={searchTerm} onChange={handleSearch} />
31
32 <ul>
33
34 {results.map(result => (
35
36 <li key={result.id}>{result.title}</li>
37
38 ))}
39
40 </ul>
41
42 </div>
43
44 );
45
46}
47
在上述代码中,我们使用 startTransition
函数将异步请求和状态更新操作包裹起来,以告诉 React 在下一次重新渲染之前应该延迟更新状态。
2. useTransition
useTransition
是 startTransition
的 hook 版本。它可以在函数组件中使用,从而让开发者更方便地控制异步操作的状态。
1
2import { useState, useTransition } from 'react';
3
4function App() {
5
6 const [searchTerm, setSearchTerm] = useState('');
7
8 const [results, setResults] = useState([]);
9
10 const [isPending, setIsPending] = useState(false);
11
12 const [startTransition, isPendingTransition] = useTransition({ timeoutMs: 3000 });
13
14 function handleSearch(event) {
15
16 setSearchTerm(event.target.value);
17
18 startTransition(() => {
19
20 setIsPending(true);
21
22 fetch(`/api/search?query=${searchTerm}`)
23
24 .then(response => response.json())
25
26 .then(data => setResults(data))
27
28 .finally(() => setIsPending(false));
29
30 });
31
32 }
33
34 return (
35
36 <div>
37
38 <input type="text" value={searchTerm} onChange={handleSearch} />
39
40 {isPendingTransition ? <p>Loading...</p> : null}
41
42 <ul>
43
44 {results.map(result => (
45
46 <li key={result.id}>{result.title}</li>
47
48 ))}
49
50 </ul>
51
52 </div>
53
54 );
55
56}
57
在上述代码中,我们使用 useTransition
hook 来控制异步请求的状态,并在加载数据时显示一个 Loading... 的提示信息。
3. createRoot
createRoot
是一个新的入口函数,用于创建根 React 组件。它可以替代原先的 ReactDOM.render
方法,使得开发者可以将多个根节点渲染到一个页面上。
1
2import { createRoot } from 'react-dom';
3
4function App() {
5
6 return (
7
8 <div>Hello, world!</div>
9
10 );
11
12}
13
14// 原先的使用方式
15
16ReactDOM.render(<App />, document.getElementById('root'));
17
18// 新的使用方式
19
20const rootElement = document.getElementById('root');
21
22createRoot(rootElement).render(<App />);
23
在上述代码中,我们使用 createRoot
函数来创建根 React 组件,并将其渲染到页面上。这样,我们就可以使用多个根节点来构建各种复杂的应用程序界面。
4. useDeferredValue
useDeferredValue
是一个新的 hook,可以将某个状态值的更新延迟一段时间后再执行,从而提高应用程序的性能和用户体验。
1
2import { useState, useDeferredValue } from 'react';
3
4function App() {
5
6 const [searchTerm, setSearchTerm] = useState('');
7
8 function handleSearch(event) {
9
10 setSearchTerm(event.target.value);
11
12 }
13
14 const deferredSearchTerm = useDeferredValue(searchTerm, {
15
16 timeoutMs: 1000
17
18 });
19
20 return (
21
22 <div>
23
24 <input type="text" value={searchTerm} onChange={handleSearch} />
25
26 <p>Search term: {deferredSearchTerm}</p>
27
28 </div>
29
30 );
31
32}
33
在上述代码中,我们使用 useDeferredValue
hook 将搜索词的更新延迟了一秒钟。这样,用户在快速输入搜索词时,不会因为频繁的重新渲染而出现卡顿等问题。
5. useTransition
上面已经提到过了,在 React 18 中新增了 useTransition
hook,用于帮助开发者控制异步操作的状态。
6. useMutableSource
useMutableSource
是一个新的 hook,用于获取可变数据源,并可以在多个组件之间共享状态。它可以帮助开发者拆分组件逻辑,并使其更加灵活和可复用。
1
2import { useMutableSource } from 'react';
3
4const myDataSource = {
5
6 get: () => ({ count: 0 }),
7
8 subscribe: (handleUpdate) => {
9
10 const intervalId = setInterval(() => {
11
12 handleUpdate({ count: Math.floor(Math.random() * 100) });
13
14 }, 1000);
15
16 return () => clearInterval(intervalId);
17
18 }
19
20};
21
22function Counter() {
23
24 const [dataSource, setDataSource] = useState(() => myDataSource);
25
26 const [count, setCount] = useState(0);
27
28 function handleUpdate(data) {
29
30 setCount(count => count + data.count);
31
32 }
33
34 useEffect(() => {
35
36 const unsubscribe = dataSource.subscribe(handleUpdate);
37
38 return unsubscribe;
39
40 }, [dataSource]);
41
42 return (
43
44 <div>
45
46 <p>Count: {count}</p>
47
48 <button onClick={() => setDataSource(myDataSource)}>Restart</button>
49
50 </div>
51
52 );
53
54}
55
56function App() {
57
58 const [dataSource, setDataSource] = useState(() => myDataSource);
59
60 const [, forceUpdate] = useReducer(x => x + 1, 0);
61
62 function handleRestart() {
63
64 setDataSource(myDataSource);
65
66 forceUpdate();
67
68 }
69
70 const count = useMutableSource(dataSource, ({ count }) => count);
71
72 return (
73
74 <div>
75
76 <p>Count: {count}</p>
77
78 <button onClick={handleRestart}>Restart</button>
79
80 </div>
81
82 );
83
84}
85
在上述代码中,我们使用 myDataSource
作为可变数据源,并将其共享到多个组件中。在 Counter
组件中,我们订阅了数据源的更新,并实时反映出计数器的变化。在 App
组件中,我们使用了 useMutableSource
hook 来获取数据源的值,从而实现了多组件之间的状态共享。
总而言之,React 18 中引入了许多有用的新特性和 API,包括 startTransition
、 useTransition
、 createRoot
、 useDeferredValue
、 useMutableSource
等。这些新特性和 API 可以让开发者更方便地构建高性能、灵活和可复用的 React 应用程序。
新增Hooks
React 18 引入了一些新的 hooks,以帮助开发者更好地管理状态和副作用。以下是 React 18 中新增的一些 hooks:
1. useTransition
useTransition
允许开发者在处理潜在的延迟操作时控制异步更新的优先级。它接受一个配置对象,可以设置超时时间和中断标志等选项。
1
2import { useTransition } from 'react';
3
4function MyComponent() {
5
6 const [isPending, startTransition] = useTransition({ timeoutMs: 2000 });
7
8 function handleClick() {
9
10 startTransition(() => {
11
12 // 执行某个需要较长时间的操作
13
14 });
15
16 }
17
18 return (
19
20 <div>
21
22 <button onClick={handleClick}>开始操作</button>
23
24 {isPending && <p>操作进行中...</p>}
25
26 </div>
27
28 );
29
30}
31
在上述代码中,我们使用了 useTransition
hook 来控制长时间操作的优先级,并在操作进行中显示一个提示信息。
2. useDeferredValue
useDeferredValue
允许开发者将某个状态的更新推迟到未来的帧中。这对于处理与用户输入相关的操作非常有用,可以避免在频繁输入时产生连续的重渲染。
1
2import { useState, useDeferredValue } from 'react';
3
4function MyComponent() {
5
6 const [searchTerm, setSearchTerm] = useState('');
7
8 function handleChange(event) {
9
10 setSearchTerm(event.target.value);
11
12 }
13
14 const deferredSearchTerm = useDeferredValue(searchTerm, {
15
16 timeoutMs: 500
17
18 });
19
20 return (
21
22 <div>
23
24 <input type="text" value={searchTerm} onChange={handleChange} />
25
26 <p>搜索词: {deferredSearchTerm}</p>
27
28 </div>
29
30 );
31
32}
33
在上述代码中,我们使用了 useDeferredValue
hook 来将搜索词的更新推迟了 500ms。这样,在频繁输入时,只有用户停止输入一段时间后,才会执行搜索操作。
3. useMutableSource
useMutableSource
允许开发者访问可变的数据源,并在多个组件之间共享状态。这对于高性能的数据订阅和共享非常有用。
1
2import { useMutableSource } from 'react';
3
4const myDataSource = {
5
6 get: () => ({ count: 0 }),
7
8 subscribe: (callback) => {
9
10 const interval = setInterval(() => {
11
12 callback({ count: Math.floor(Math.random() * 100) });
13
14 }, 1000);
15
16 return () => clearInterval(interval);
17
18 }
19
20};
21
22function MyComponent() {
23
24 const [, forceUpdate] = useState({});
25
26 const count = useMutableSource(myDataSource, source => source.get());
27
28 function handleRestart() {
29
30 forceUpdate({});
31
32 }
33
34 return (
35
36 <div>
37
38 <p>Count: {count}</p>
39
40 <button onClick={handleRestart}>重启</button>
41
42 </div>
43
44 );
45
46}
47
在上述代码中,我们使用了 useMutableSource
hook 来获取可变数据源中的值,并在计数器组件中共享该状态。
这些是 React 18 中新增的一些重要 hooks。通过使用这些 hooks,开发者可以更好地管理状态、处理潜在的延迟操作,并实现高性能的数据共享。除了这些新增的 hooks,React 18 也支持其他常用的 hooks,如 useState
、 useEffect
、 useCallback
等。
严格模式
React 严格模式(Strict Mode)是一个开发模式,可以帮助开发者发现一些潜在的问题,以提高应用程序的质量。启用严格模式后,React 会执行额外的检查和警告,以帮助开发者发现一些常见问题,并尽早地解决它们。
启用 React 严格模式可以通过在代码中添加如下代码实现:
1
2import React from 'react';
3
4import ReactDOM from 'react-dom';
5
6ReactDOM.render(
7
8 <React.StrictMode>
9
10 <App />
11
12 </React.StrictMode>,
13
14 document.getElementById('root')
15
16);
17
在上述代码中,我们使用 React.StrictMode
组件来包裹应用程序的顶层组件 <App>
。这样,React 将会在严格模式下执行应用程序,并对常见问题进行检查和提示。
React 严格模式主要包含以下几个方面的检查和提示:
- 识别不安全的生命周期方法,提示开发者修改,这些方法可能会导致意外的副作用或错误。
- 检测意外的副作用,例如:多余的重新渲染、不符合预期的函数调用等。
- 检测某些过时的 API 使用,提供更好的替代方案。
- 检测警告信息,使其更加明显和易于发现。
需要注意的是,React 严格模式只在开发环境下工作,不会影响生产环境下的应用程序。因此,在开发过程中启用严格模式可以帮助开发者及早发现问题,并尽可能将这些问题解决,以提高应用程序的稳定性和质量。
总而言之,React 严格模式是一种非常有用的开发模式,可以帮助开发者发现常见问题并提高应用程序的质量。通过在顶层组件中添加 <React.StrictMode>
包裹,我们可以启用严格模式,并享受其带来的好处。
如何禁用严格模式
在 React 应用中禁用严格模式可以通过以下两种方式实现:
1. 直接移除 <React.StrictMode>
组件
最简单的方法是将应用程序顶层组件中的 <React.StrictMode>
组件直接移除。这样,React 将不会启用严格模式,也不会执行额外的检查和警告。
1
2import React from 'react';
3
4import ReactDOM from 'react-dom';
5
6import App from './App';
7
8ReactDOM.render(
9
10 <App />,
11
12 document.getElementById('root')
13
14);
15
2. 在应用程序启动时禁用严格模式
在一些情况下,移除 <React.StrictMode>
组件可能不太方便,例如:在大型项目中或已经存在大量的 console.log
调用等代码片段。此时,可以在应用程序启动时禁用严格模式。
在应用程序启动文件中,我们可以使用 React 的 unstable_disableDevMode()
函数来禁用严格模式:
1
2import React from 'react';
3
4import ReactDOM from 'react-dom';
5
6import App from './App';
7
8React.unstable_disableDevMode();
9
10ReactDOM.render(
11
12 <React.StrictMode>
13
14 <App />
15
16 </React.StrictMode>,
17
18 document.getElementById('root')
19
20);
21
在上述代码中,我们在调用 ReactDOM.render
之前调用了 React.unstable_disableDevMode()
函数,以禁用严格模式。该函数并不在文档中明确提供支持,因此请谨慎使用。
需要注意的是,禁用严格模式可能会导致一些潜在问题无法被及早发现,因此建议仅在必要时使用。同时,需要确保 React 版本兼容性,并遵循最佳实践和安全规则。
并发模式
React 并发模式(React Concurrent Mode)是 React 的一项新功能,旨在改善在复杂应用程序中的用户体验和性能。在传统的 React 中,更新组件树时会阻塞用户界面的响应,可能导致卡顿和延迟。而并发模式通过将任务分解为多个小步骤,让 React 在执行渲染和布局时可以中断和恢复任务,从而提供更平滑和响应式的用户体验。
在 React 并发模式中,引入了两个主要概念:任务调度和优先级。任务调度器负责决定哪些任务执行、何时执行以及中断和恢复任务。优先级允许 React 根据任务的紧迫性来安排任务的执行顺序,确保响应度更高的任务能够优先执行。
利用并发模式,React 可以将渲染过程分解为多个小任务,并根据优先级来动态调整任务执行的顺序。这样,在浏览器空闲时间或网络请求等异步操作期间,React 可以暂停当前任务,执行其他具有更高优先级的任务,以实现更爽快的用户交互体验。
总而言之,React 并发模式通过任务调度和优先级机制,提供了更好的用户体验和性能,使得 React 应用程序能够更加平滑地响应用户操作。
以下是一个简单的示例代码,展示了 React Concurrent Mode 的基本用法:
1import React, { useState, useEffect, unstable_ConcurrentMode as ConcurrentMode } from 'react';
2
3function App() {
4
5 const [count, setCount] = useState(0);
6
7 useEffect(() => {
8
9 const timer = setInterval(() => {
10
11 setCount((prevCount) => prevCount + 1);
12
13 }, 1000);
14
15 return () => {
16
17 clearInterval(timer);
18
19 };
20
21 }, []);
22
23 return (
24
25 <ConcurrentMode>
26
27 <div>
28
29 <h1>计数器</h1>
30
31 <p>{count}</p>
32
33 </div>
34
35 </ConcurrentMode>
36
37 );
38
39}
40
41export default App;
42
在上面的示例中,我们使用了 unstable_ConcurrentMode
组件来包裹根元素。这表示该组件下的子组件可以享受到并发模式的好处。
在 App
组件中,我们使用了 useState
来声明一个状态变量 count
,并通过 setCount
来更新它的值。在 useEffect
中,我们使用定时器每秒钟增加 count
的值。注意,我们传递了空数组作为第二个参数,表示只在组件挂载时执行一次。
最后,在组件的返回值中,我们使用 <ConcurrentMode>
组件包裹了整个应用程序的 UI。这样,React 将会利用并发模式来处理渲染任务,以提供更平滑和响应式的用户体验。
服务端渲染
React 18 并没有专门针对服务端渲染(SSR)进行大规模的改进,但它仍然提供了一些与 SSR 相关的 API 和改进。以下是一些我们在 React 18 中可以使用的 SSR 相关功能:
1. useOpaqueIdentifier
useOpaqueIdentifier
允许开发者生成与数据不相关的、不透明的标识符,并在 SSR 上使用这些标识符来生成唯一的 DOM ID。
1import { useOpaqueIdentifier } from 'react';
2
3function MyComponent() {
4 const id = useOpaqueIdentifier();
5
6 return <div id={`my-component-${id}`}>My component</div>;
7}
8
在上述代码中,我们使用了 useOpaqueIdentifier
hook 来生成一个不透明的标识符,并将其用于组件的 DOM ID 中。由于这个标识符与数据无关,因此在 SSR 上也可以正确地生成唯一的 ID。
2. ReactDOMServer.renderToStringAsync
ReactDOMServer.renderToStringAsync
允许开发者异步地渲染组件并输出 HTML。这样可以避免在 SSR 期间阻塞主线程,在数据加载和计算时保持响应性。
1import ReactDOMServer from 'react-dom/server';
2
3async function renderApp(req, res) {
4 const app = <MyApp />;
5 const html = await ReactDOMServer.renderToStringAsync(app);
6 res.send(html);
7}
8
在上述代码中,我们使用了 ReactDOMServer.renderToStringAsync
方法异步地将 <MyApp />
组件渲染为 HTML,并在 Express 中将其发送到客户端。
3. Concurrent Mode
Concurrent Mode 是 React 18 中引入的一个新特性,它通过异步渲染和交互优先级控制等方式提升了应用程序的响应性。在 SSR 中,Concurrent Mode 可以帮助开发者更好地处理异步数据加载和渲染等任务。
1import { unstable_createRoot } from 'react-dom';
2
3async function renderApp(req, res) {
4 const app = <MyApp />;
5 const root = unstable_createRoot(document.createDocumentFragment());
6 await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟数据加载延迟
7 root.render(app);
8 const html = ReactDOMServer.renderToString(root);
9 res.send(html);
10}
11
在上述代码中,我们使用了 unstable_createRoot
方法来创建一个 Concurrent Mode 的根节点。在数据加载完成后,我们渲染了应用程序,并将其输出为 HTML。
这些是 React 18 中与 SSR 相关的一些功能和改进。通过使用这些功能,开发者可以更好地处理异步数据加载和渲染,并提升应用程序的响应性。