Notice
React 18
We have given up ie11
support for and will 2022年6月15日
stop supporting it ie
. If you need compatibility, you need to roll back to React 17
version.
React 18 中引入的新特性是使用现代浏览器的特性构建的,在IE中无法充分polyfill,比如micro-tasks
upgrade
- New project: Use directly
npm
oryarn
install the latest version of dependencies (if it is js, you do not need to install the types declaration file)
npm i react react-dom --save
npm i @types/react @types/react-dom -D
- Old project: First change the version number in the dependency to the latest, then delete
node_modules
the folder and reinstall:
npm i
new features
1. Render API
For better management root节点
, React 18
a new one has been introduced root API
. The new root API
also supports new concurrent renderer
(concurrent mode rendering), which allows you to enter concurrent mode
(concurrent mode).
// React 17
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
const root = document.getElementById('root')!;
ReactDOM.render(<App />, root);
// React 18
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = document.getElementById('root')!;
ReactDOM.createRoot(root).render(<App />);
At the same time, when uninstalling the component, we also need to unmountComponentAtNode
upgrade to root.unmount
:
// React 17
ReactDOM.unmountComponentAtNode(root);
// React 18
root.unmount();
Tips: If we React 18
use the old one in render api
, you will see a warning in the console after the project is started:
This means you can upgrade your project directly to React 18
version without causing a direct hit break change
. If you need to maintain React 17
version features, you can ignore this error because it 18
is compatible across versions.
In addition to this, it has been removed React 18
from the method because it generally does not have the expected results when used.render
回调函数
Suspense
In the new version, if you need to render
use a callback function in the method, we can implement it in the component through useEffect
:
// React 17
const root = document.getElementById('root')!;
ReactDOM.render(<App />, root, () => {
console.log('渲染完成');
});
// React 18
const AppWithCallback: React.FC = () => {
useEffect(() => {
console.log('渲染完成');
}, []);
return <App />;
};
const root = document.getElementById('root')!;
ReactDOM.createRoot(root).render(<AppWithCallback />);
Finally, if your project uses ssr
server-side rendering, you need to hydration
upgrade to hydrateRoot
:
// React 17
import ReactDOM from 'react-dom';
const root = document.getElementById('root');
ReactDOM.hydrate(<App />, root);
// React 18
import ReactDOM from 'react-dom/client';
const root = document.getElementById('root')!;
ReactDOM.hydrateRoot(root, <App />);
In addition, you also need to update TypeScript
the type definition. If your project uses it TypeScript
, the most noteworthy change is that now when defining props
the type, if you need to get the subcomponent children
, then you need to 显式的定义它
, for example:
// React 17
interface MyButtonProps {
color: string;
}
const MyButton: React.FC<MyButtonProps> = ({ children }) => {
// 在 React 17 的 FC 中,默认携带了 children 属性
return <div>{children}</div>;
};
export default MyButton;
// React 18
interface MyButtonProps {
color: string;
children?: React.ReactNode;
}
const MyButton: React.FC<MyButtonProps> = ({ children }) => {
// 在 React 18 的 FC 中,不存在 children 属性,需要手动申明
return <div>{children}</div>;
};
export default MyButton;
2. setState automatic batch processing
React 18
Performance improvements are implemented out of the box by performing batch processing by default.
Batch processing means that in order to obtain better performance, 多个状态更新
batch processing is merged into batches at the data layer 一次更新
(and 多个渲染
merged into batches at the view layer 一次渲染
).
1. Before React 18:
Now React 18 之前
, we only React 事件处理函数
do batch updates in . By default, updates in promise
, setTimeout
, 原生事件处理函数
in , or 任何其它事件内
are not batched:
Scenario 1: React event handler function
import React, { useState } from 'react';
// React 18 之前
const App: React.FC = () => {
console.log('App组件渲染了!');
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
return (
<button
onClick={() => {
setCount1(count => count + 1);
setCount2(count => count + 1);
// 在React事件中被批处理
}}
>
{`count1 is ${count1}, count2 is ${count2}`}
</button>
);
};
export default App;
Click the button to print console.log:
As you can see, the number of renderings and the number of updates are the same. Even if we update two states, the component is only rendered once each time it is updated.
However, if we put the status update inside promise
or setTimeout
:
Case 2: setTimeout
import React, { useState } from 'react';
// React 18 之前
const App: React.FC = () => {
console.log('App组件渲染了!');
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div
onClick={() => {
setTimeout(() => {
setCount1(count => count + 1);
setCount2(count => count + 1);
});
// 在 setTimeout 中不会进行批处理
}}
>
<div>count1: {count1}</div>
<div>count2: {count2}</div>
</div>
);
};
export default App;
Click the button to reprint console.log:
As you can see, every time you click to update the two states, the component will be rendered twice and batch updates will not be performed.
Scenario 3: Native js events
import React, { useEffect, useState } from 'react';
// React 18 之前
const App: React.FC = () => {
console.log('App组件渲染了!');
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
useEffect(() => {
document.body.addEventListener('click', () => {
setCount1(count => count + 1);
setCount2(count => count + 1);
});
// 在原生js事件中不会进行批处理
}, []);
return (
<>
<div>count1: {count1}</div>
<div>count2: {count2}</div>
</>
);
};
export default App;
Click the button to reprint console.log:
It can be seen that in the native js event, the result is the same as the second situation. Every time you click to update the two states, the component will be rendered twice, and batch updates will not be performed.
2. In React 18:
In React 18
the three examples above this will only happen once render
as all updates will be automatically batched. This undoubtedly greatly improves the overall performance of the application.
However, the following example will React 18
execute render twice in :
import React, { useState } from 'react';
// React 18
const App: React.FC = () => {
console.log('App组件渲染了!');
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div
onClick={async () => {
await setCount1(count => count + 1);
setCount2(count => count + 1);
}}
>
<div>count1: {count1}</div>
<div>count2: {count2}</div>
</div>
);
};
export default App;
Summarize:
- Before 18, batch processing would be automatically executed only in the react event processing function, and in other cases it would be updated multiple times.
- After 18, batch processing will be automatically performed in any case, and multiple updates will always be merged into one
3. flushSync
Batch processing is a 破坏性改动
, if you want to exit batch update, you can use flushSync
:
import React, { useState } from 'react';
import { flushSync } from 'react-dom';
const App: React.FC = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div
onClick={() => {
flushSync(() => {
setCount1(count => count + 1);
});
// 第一次更新
flushSync(() => {
setCount2(count => count + 1);
});
// 第二次更新
}}
>
<div>count1: {count1}</div>
<div>count2: {count2}</div>
</div>
);
};
export default App;
Note: flushSync
Multiple inside the function setState
are still updated in batches , so that unnecessary batch updates can be precisely controlled.
For more information about 批处理
and flushSync
, you can refer to React’s official Automatic batching deep dive .
4. Update status warning when uninstalling components
When we are developing, we occasionally encounter the following errors:
This error means: Unable to perform a status update on an unmounted (unmounted) component. This is an invalid operation and indicates a memory leak in our code.
In fact, this error is rare, and in previous versions this warning was widely misunderstood and somewhat misleading.
The original intention of this error was to target some special scenarios, such 你在useEffect里面设置了定时器,或者订阅了某个事件,从而在组件内部产生了副作用,而且忘记return一个函数清除副作用,则会发生内存泄漏……
as
But in actual development, more scenarios are, 我们在 useEffect 里面发送了一个异步请求,在异步函数还没有被 resolve 或者被 reject 的时候,我们就卸载了组件
. In this scenario, the warning will also be triggered. However, in this case, there is no memory leak inside the component because the async function has been garbage collected, and at this point, the warning is misleading .
Regarding this point, React also has an official explanation:
To sum up the above reasons, in React 18
, this error was officially deleted.
For more information about this error, you can refer to React’s official instructions, click here to view.
5. About the return value of React components
- In React only allows returning
React 17
one if you need to . If you return explicitly , the console will throw an error at runtime.空组件
null
undefined
- In
React 18
,undefined
crashes caused by returning are no longer checked. It can both returnnull
and returnundefined
(but the fileReact 18
ofdts
will still be checked and only return is allowednull
. You can ignore this type error).
Official explanation about component return value: github.com/reactwg/rea…
6. Strict Mode
Console logs are no longer suppressed:
严格模式
React works with each component as you use it 两次渲染
, allowing you to observe some unexpected results. In React 17
, 其中一次渲染
console logging has been suppressed to make the logs easier to read.
To address community confusion about this issue, in React 18
, this restriction was officially lifted. If you have it installed React DevTools
, log messages for the second render will be grayed out and displayed in the console in a soft manner.
Official explanation about Strict Mode: github.com/reactwg/rea…
7. Suspense no longer requires fallback to capture
In the component React 18
of Suspense
, officials 空的fallback
have made changes to the way attributes are handled: no longer skip the boundaries of 缺失值
or . Instead, the boundary is captured and looked outside, and if not found, will be rendered as .值为null
fallback
Suspense
fallback
null
Before update:
Previously, if your Suspense
component didn't provide fallback
a property, React would silently skip it and continue searching up to the next boundary:
// React 17
const App = () => {
return (
<Suspense fallback={<Loading />}> // <--- 这个边界被使用,显示 Loading 组件
<Suspense> // <--- 这个边界被跳过,没有 fallback 属性
<Page />
</Suspense>
</Suspense>
);
};
export default App;
The React team found that this could lead to confusing, difficult-to-debug situations. For example, if you are debugging a problem and you test a problem by throwing a bound in a component without a fallback
property Suspense
, it may bring some unexpected results and 不会警告
say it 没有fallback
has a property.
Updated:
Now, React will use the current component's Suspense
as the bounds, even if Suspense
the value of the current component's null
is or undefined
:
// React 18
const App = () => {
return (
<Suspense fallback={<Loading />}> // <--- 不使用
<Suspense> // <--- 这个边界被使用,将 fallback 渲染为 null
<Page />
</Suspense>
</Suspense>
);
};
export default App;
This update means us 不再跨越边界组件
. Instead, we'll capture at the boundary and render fallback
as if you provided a null
component that returns a . This means that the suspended Suspense
component will behave as expected, and fallback
there will be no problem if you forget to provide the attribute.
Official explanation about Suspense: github.com/reactwg/rea…
New API
1. useId
const id = useId();
Supports the same component to generate the same unique ID on the client and server to avoid hydration
incompatibility. This solves the existing problems in React 17
and below versions. 17
Because our server provides HTML
Yes when rendering 无序的
, useId
the principle is that each id
represents the hierarchical structure of the component in the component tree.
For more information about useId, see useId post in the working group .
二、useSyncExternalStore
useSyncExternalStore
It is a new API that has undergone a modification and was useMutableSource
changed from. It is mainly used to solve the problem of external data tearing.
useSyncExternalStore allows React components to safely and effectively read external data sources under CM by forcing synchronous update data. In Concurrent Mode, a React rendering will be executed in slices (in fiber units), and higher priority updates may be interspersed in the middle. If public data (such as data in redux) is changed in a high-priority update, the previous low-priority rendering must be restarted, otherwise the state will be inconsistent.
useSyncExternalStore
It is generally used by third-party state management libraries, and we do not need to pay attention to it in daily business. Because React
it useState
has natively solved tear(撕裂)
the problems under the concurrency feature. useSyncExternalStore
Mainly for framework developers, for example , it may not be used redux
directly when controlling the state. Instead, it maintains an external object to implement data updates. Without the management of , it cannot automatically solve the tearing problem. . Therefore, such an API is provided to the outside world.React
state
store
发布订阅模式
React
React
React
Currently React-Redux 8.0
it has been useSyncExternalStore
implemented based on .
For more information about useSyncExternalStore, see the useSyncExternalStore overview post and useSyncExternalStore API details .
3. useInsertionEffect
const useCSS = rule => {
useInsertionEffect(() => {
if (!isInserted.has(rule)) {
isInserted.add(rule);
document.head.appendChild(getStyleForRule(rule));
}
});
return rule;
};
const App: React.FC = () => {
const className = useCSS(rule);
return <div className={className} />;
};
export default App;
This Hooks is only recommended css-in-js
for use by libraries. The execution time of this Hooks is DOM
after and before generation useLayoutEffect
. Its working principle is roughly the useLayoutEffect
same as that of , except that DOM
the reference of the node cannot be accessed at this time. It is generally used to inject <style>
scripts in advance.
For more information about useInsertionEffect, see the Library Upgrade Guide for<style>
.
Concurrent Mode
Concurrent Mode (hereinafter referred to as CM
) is translated as concurrent mode. We may have heard this concept many times. In fact, this concept has become very mature last year and React 17
can be 试验性
turned on through some APIs in CM
.
CM 本身并不是一个功能,而是一个底层设计
Concurrent mode helps apps stay responsive and adjust appropriately based on the user's device capabilities and network speed, fixing limitations by making rendering interruptible 阻塞渲染
. In Concurrent
mode, React
multiple states can be updated simultaneously.
It may be too complicated to say, but it can be summed up in one sentence:
React 17
The difference between and React 18
is: from 同步不可中断更新
changed to 异步可中断更新
.
Here comes the important point, please don’t skip the following part:
We mentioned at the beginning of the article: In React 18
, new ones are provided root api
. We only need to render
upgrade createRoot(root).render(<App />)
to enable concurrent mode.
So at this time, some students may ask: Does turning on 并发模式
mean turning on 并发更新
?
NO! In React 17
some experimental features in , turning on 并发模式
means turning on 并发更新
, but React 18
after the official version was released, due to official policy adjustments, React no longer relies on 并发模式
turning 并发更新
on.
In other words: Just because it’s turned on 并发模式
doesn’t necessarily mean it’s turned on 并发更新
!
One sentence summary: In 18
, there are no longer multiple models, but based 是否使用并发特性
on 是否开启并发更新
actions .
From the oldest version to the current one v18
, how many versions are there on the market React
?
It can be summarized from an architectural perspective. There are currently two architectures:
递归
Updated in a non-interruptible mannerStack Reconciler
(old architecture)遍历
Updated in an interruptible mannerFiber Reconciler
(new architecture)
You can choose whether to enable the new architecture , so there are four situations 并发更新
for all versions currently on the market :React
- Old architecture (v15 and previous versions)
- New architecture, concurrent updates are not enabled, and the behavior is consistent with case 1 (v16 and v17 fall into this case by default)
- New architecture, concurrent updates are not enabled, but concurrent mode and some new features are enabled (for example
Automatic Batching
, this is the case in v18 by default) - New architecture, enable concurrent mode, enable concurrent updates
并发特性
Refers to 并发更新
features that can only be used after being turned on, such as:
useDeferredValue
useTransition
relation chart:
stateDiagram-v2
[*] --> React18
React18 --> ReactDOM.render
React18 --> ReactDOM.createRoot
ReactDOM.render --> 未开启并发模式
ReactDOM.createRoot --> 开启并发模式
未开启并发模式 --> 未开启自动批处理
开启并发模式 --> 开启自动批处理
未开启自动批处理 --> 未开启并发更新
开启自动批处理 --> 未使用并发特性
开启自动批处理 --> 使用并发特性
未使用并发特性 --> 未启并发更新
使用并发特性 --> 开启并发更新
After understanding their relationship clearly, we can continue to explore 并发更新
:
Concurrency features:
1. startTransition
Run the following code in v18:
import React, { useState, useEffect, useTransition } from 'react';
const App: React.FC = () => {
const [list, setList] = useState<any[]>([]);
const [isPending, startTransition] = useTransition();
useEffect(() => {
// 使用了并发特性,开启并发更新
startTransition(() => {
setList(new Array(10000).fill(null));
});
}, []);
return (
<>
{list.map((_, i) => (
<div key={i}>{i}</div>
))}
</>
);
};
export default App;
Since it is executed (used ) in the callback function of , it setList
will trigger .startTransition
并发特性
setList
并发更新
startTransition
, mainly to keep the UI responsive under a large number of tasks. This new API can “过渡”
significantly improve user interaction by marking specific updates as urgent. In simple terms, renderings triggered by startTransition
callbacks setState
are marked as non-urgent renderings, and these renderings may be preempted by others 紧急渲染
.
- For more information about startTransition, see Patterns for startTransition .
二、useDeferredValue
Returning a delayed response value allows a state
delay to take effect. The value will become the latest value only when there is no urgent update. useDeferredValue
Like startTransition
, both are marked as a non-urgent update.
From the introduction point of view useDeferredValue
, useTransition
does it feel very similar to ?
- Same:
useDeferredValue
Essentially the same as the internal implementationuseTransition
, it is marked as延迟更新
a task. - The difference
useTransition
is that the update task is turned into a delayed update task, anduseDeferredValue
a new value is generated, which is used as the delayed status. (One is used to wrap methods and one is used to wrap values)
Therefore, startTransition
we can also use the above example useDeferredValue
to implement:
import React, { useState, useEffect, useDeferredValue } from 'react';
const App: React.FC = () => {
const [list, setList] = useState<any[]>([]);
useEffect(() => {
setList(new Array(10000).fill(null));
}, []);
// 使用了并发特性,开启并发更新
const deferredList = useDeferredValue(list);
return (
<>
{deferredList.map((_, i) => (
<div key={i}>{i}</div>
))}
</>
);
};
export default App;
Then start the project and check the printed execution stack diagram:
At this time, our tasks are split into different frames for each frame task
, and JS脚本
the execution time is roughly around 10% 5ms
. This way, the browser has remaining time to perform style layout and style drawing , reducing the possibility of dropped frames.
- For more information about useDeferredValue, see New in 18: useDeferredValue .
3. Ordinary situations
We can turn off the concurrency feature and run the project in a normal environment:
import React, { useState, useEffect } from 'react';
const App: React.FC = () => {
const [list, setList] = useState<any[]>([]);
useEffect(() => {
setList(new Array(10000).fill(null));
}, []);
return (
<>
{list.map((_, i) => (
<div key={i}>{i}</div>
))}
</>
);
};
export default App;
Start the project and check the printed execution stack diagram:
You can see from the printed execution stack diagram that due to the large number of components (10,000), the JS execution time is, which 500ms
means that without the concurrency feature: when 10,000 tags are rendered at one time, the page will It will block for a while 0.5秒
, causing lag, but if concurrent updates are turned on, there will be no such problem.
这种将长任务分拆到每一帧中,像蚂蚁搬家一样一次执行一小段任务的操作,被称为时间切片(time slice)
in conclusion
- The meaning of concurrent updates is
交替执行
different tasks. When the reserved time is not enough,React
the thread control is returned to the browser, waiting for the next frame time to arrive, and then continuing the interrupted work 并发模式
It is并发更新
the basic premise for the realization of时间切片
It is并发更新
a specific means of realizing
Conclusion
The above is the general content of this React
upgrade. If there are any errors, please correct them.