From 614228ae50251fb7fee9213adcafe3a2d052aca4 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Wed, 19 Jul 2023 11:50:50 +0800
Subject: [PATCH] docs: add suspense doc (#43643)
---
docs/blog/suspense.en-US.md | 79 +++++++++++++++++++++++++++++++++++++
docs/blog/suspense.zh-CN.md | 79 +++++++++++++++++++++++++++++++++++++
2 files changed, 158 insertions(+)
diff --git a/docs/blog/suspense.en-US.md b/docs/blog/suspense.en-US.md
index 26b9d96ce0..9b9b40caf1 100644
--- a/docs/blog/suspense.en-US.md
+++ b/docs/blog/suspense.en-US.md
@@ -177,6 +177,85 @@ useLayoutEffect(() => {
Measure logic in `useLayoutEffect` is executed before injecting style, resulting in incorrect size information. It can also be predicted that this will have an impact on developers. So we have to compromise, and in React 17 version, it will be downgraded to the original `useMemo` insertion.
+## New Problem under React 17
+
+With the above solution, `useInsertionEffect` perfectly solve the rendering problem. But in React 17 and below versions, we still insert styles in the render phase, but we will increase the reference count in the effect phase. But this brings a new problem, let's look at a piece of code ([CodeSandbox](https://codesandbox.io/s/aged-cdn-qjxmpz?file=/src/App.tsx:23-886)):
+
+```tsx
+import React from 'react';
+
+const A = () => {
+ React.useMemo(() => {
+ console.log('A render');
+ }, []);
+
+ React.useEffect(() => {
+ console.log('A mounted');
+ return () => {
+ console.log('A unmounted');
+ };
+ }, []);
+
+ return
A
;
+};
+
+const B = () => {
+ React.useMemo(() => {
+ console.log('B render');
+ }, []);
+
+ React.useEffect(() => {
+ console.log('B mounted');
+ return () => {
+ console.log('B unmounted');
+ };
+ }, []);
+
+ return B
;
+};
+
+export default function App() {
+ const [show, setShow] = React.useState(true);
+
+ const toggle = () => {
+ setShow((prev) => !prev);
+ };
+
+ return (
+
+ );
+}
+```
+
+In this code (strict mode), clicking the button will switch the rendering of A and B. So what will the order of console be when switching from A to B? The answer is:
+
+```
+B render
+B render
+A unmounted
+B mounted
+B unmounted
+B mounted
+```
+
+We can see that the rendering of the new component is before the unmount callback of the old component. Remember the processing logic of `cssinjs` in React 17? Let's mark it:
+
+```
+B render // Write to cache and insert style tag
+B render // Write to cache and insert style tag
+A unmounted // **Reference count--** (Reference count changed from 1 to 0, so the style was unloaded)
+B mounted // Reference count++ (Reference count changed from 0 to 1, but the style was inserted before unloaded)
+B unmounted // Reference count--
+B mounted // Reference count++
+```
+
+We finally find out that due to reference count is not updated in time, the style was unloaded, which in not as expected.
+
+And the solution is simple: when the count changes from 0 to 1, style will be inserted again.
+
## Summary
Suspense brings rendering performance improvements, but it also makes timing very important. It is not the best way to only 'work on' StrictMode. Different logic is used for different React versions is not good choice since it will have timing problem. `render` will trigger from parent node to child node in turn, while `useInsertionEffect` is the opposite. However, from the perspective of antd, the component styles are independent of each other, so this problem will not affect us.
diff --git a/docs/blog/suspense.zh-CN.md b/docs/blog/suspense.zh-CN.md
index 27d84129fb..df1c263ca3 100644
--- a/docs/blog/suspense.zh-CN.md
+++ b/docs/blog/suspense.zh-CN.md
@@ -175,6 +175,85 @@ useLayoutEffect(() => {
测量的 `useLayoutEffect` 先于注入样式执行,导致获取了错误的尺寸信息。也可以预测到这会对开发者产生影响。因而我们退而求其次,在 React 17 版本时会降级为原先的 `useMemo` 插入。
+## 新的兼容问题
+
+在上面的方案中,我们启用了 `useInsertionEffect` 从而完美解决了渲染问题。但在 React 17 及以下版本,我们仍然会在 render 阶段插入样式,但是会在 effect 阶段让引用计数加一。但是这带来了新的问题,我们来看一段代码 ([CodeSandbox](https://codesandbox.io/s/aged-cdn-qjxmpz?file=/src/App.tsx:23-886)):
+
+```tsx
+import React from 'react';
+
+const A = () => {
+ React.useMemo(() => {
+ console.log('A render');
+ }, []);
+
+ React.useEffect(() => {
+ console.log('A mounted');
+ return () => {
+ console.log('A unmounted');
+ };
+ }, []);
+
+ return A
;
+};
+
+const B = () => {
+ React.useMemo(() => {
+ console.log('B render');
+ }, []);
+
+ React.useEffect(() => {
+ console.log('B mounted');
+ return () => {
+ console.log('B unmounted');
+ };
+ }, []);
+
+ return B
;
+};
+
+export default function App() {
+ const [show, setShow] = React.useState(true);
+
+ const toggle = () => {
+ setShow((prev) => !prev);
+ };
+
+ return (
+
+ );
+}
+```
+
+在这段代码(严格模式)中,点击按钮会切换 A 与 B 的渲染。那么从 A 切换到 B 时,顺序会是什么样的呢?答案是:
+
+```
+B render
+B render
+A unmounted
+B mounted
+B unmounted
+B mounted
+```
+
+可以看到新组件的渲染是在旧组件的卸载回调之前的。还记得 cssinjs 在 React 17 一下的处理逻辑吗?我们来标记一下:
+
+```
+B render // 写入 cache,插入样式
+B render // 写入 cache,插入样式(虽然是重复的,但是有缓存,不会有冗余)
+A unmounted // **引用计数减一** (此时原本的计数是 1,执行后变为 0,触发了样式卸载)
+B mounted // 引用计数加一 (此时计数是 1,但是样式已经被 A 连带卸载)
+B unmounted // 引用计数减一
+B mounted // 引用计数加一
+```
+
+这样就可以发现,当 A 与 B 共用一段样式时,由于计数没有几十更新,导致样式先被卸载了,后续也并没有触发插入逻辑,所以依然会导致丢失。
+
+解决方案也很简单,当计数从 0 变为 1 时,重新插入样式即可。
+
## 总结
Suspense 在带来渲染能力提升的同时也让时序变得十分重要,仅仅对 StrictMode 进行处理并不是一个最优的方式。针对不同的 React 版本使用不同的逻辑其实会存在不同版本之间的时序问题,`render` 会从父节点到子节点依次触发,而 `useInsertionEffect` 则相反。不过从 antd 角度来说,组件样式之间相互独立,所以这种时序问题并不会对我们产生影响。