最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
一篇帶給你React18全覽

在 2021 年 6 月 8 號(hào),React 公布了 v18 版本的發(fā)布計(jì)劃,并發(fā)布了 alpha 版本。經(jīng)過(guò)將近一年的發(fā)布前準(zhǔn)備,在 2022 年 3 月 29 日,React 18 正式版終于和大家見(jiàn)面了。

React 18 應(yīng)該是最近幾年的一個(gè)重磅版本,React 官方對(duì)它寄予了厚望。不然也不會(huì)將 React 17 作為一個(gè)過(guò)渡版本,也不會(huì)光發(fā)布準(zhǔn)備工作就做了一年。

在過(guò)去一年,我們已經(jīng)或多或少了解到一些 React 18 的新功能。這篇文章我會(huì)通過(guò)豐富的示例,向大家系統(tǒng)的介紹 React 18 帶來(lái)的改變。當(dāng)然本文融入了很多個(gè)人理解,如有不對(duì),煩請(qǐng)指正。

Concurrent Mode

Concurrent Mode(以下簡(jiǎn)稱 CM)翻譯叫并發(fā)模式,這個(gè)概念我已經(jīng)聽(tīng)了好多年了,并且一度非常擔(dān)憂

  • React 官方憋了好多年的大招,會(huì)不會(huì)是一個(gè)破壞性不兼容的超級(jí)大版本?就像 VUE v3 和 v2。
  • 現(xiàn)有的生態(tài)是不是都得跟著大版本升級(jí)?比如 ant design,ahooks 等。

隨著對(duì) CM 的了解,我發(fā)現(xiàn)它其實(shí)是人畜無(wú)害的。

CM 本身并不是一個(gè)功能,而是一個(gè)底層設(shè)計(jì),它使 React 能夠同時(shí)準(zhǔn)備多個(gè)版本的 UI。

在以前,React 在狀態(tài)變更后,會(huì)開(kāi)始準(zhǔn)備虛擬 DOM,然后渲染真實(shí) DOM,整個(gè)流程是串行的。一旦開(kāi)始觸發(fā)更新,只能等流程完全結(jié)束,期間是無(wú)法中斷的。

在 CM 模式下,React 在執(zhí)行過(guò)程中,每執(zhí)行一個(gè) Fiber,都會(huì)看看有沒(méi)有更高優(yōu)先級(jí)的更新,如果有,則當(dāng)前低優(yōu)先級(jí)的的更新會(huì)被暫停,待高優(yōu)先級(jí)任務(wù)執(zhí)行完之后,再繼續(xù)執(zhí)行或重新執(zhí)行。

CM 模式有點(diǎn)類似計(jì)算機(jī)的多任務(wù)處理,處理器在同時(shí)進(jìn)行的應(yīng)用程序之間快速切換,也許 React 應(yīng)該改名叫 ReactOS 了。

這里舉個(gè)例子:我們正在看電影,這時(shí)候門(mén)鈴響了,我們要去開(kāi)門(mén)拿快遞。在 React 18 以前,一旦我們開(kāi)始看電影,就不能被終止,必須等電影看完之后,才會(huì)去開(kāi)門(mén)。而在 React 18 CM 模式之后,我們就可以暫停電影,等開(kāi)門(mén)拿完快遞之后,再重新繼續(xù)看電影。

不過(guò)對(duì)于普通開(kāi)發(fā)者來(lái)說(shuō),我們一般是不會(huì)感知到 CM 的存在的,在升級(jí)到 React 18 之后,我們的項(xiàng)目不會(huì)有任何變化。

我們需要關(guān)注的是基于 CM 實(shí)現(xiàn)的上層功能,比如 Suspense、Transitions、streaming server rendering(流式服務(wù)端渲染), 等等。

React 18 的大部分功能都是基于 CM 架構(gòu)實(shí)現(xiàn)出來(lái)的,并且這這是一個(gè)開(kāi)始,未來(lái)會(huì)有更多基于 CM 實(shí)現(xiàn)的高級(jí)能力。

startTransition

我們?nèi)绻鲃?dòng)發(fā)揮 CM 的優(yōu)勢(shì),那就離不開(kāi) startTransition。

React 的狀態(tài)更新可以分為兩類:

  • 緊急更新(Urgent updates):比如打字、點(diǎn)擊、拖動(dòng)等,需要立即響應(yīng)的行為,如果不立即響應(yīng)會(huì)給人很卡,或者出問(wèn)題了的感覺(jué)
  • 過(guò)渡更新(Transition updates):將 UI 從一個(gè)視圖過(guò)渡到另一個(gè)視圖。不需要即時(shí)響應(yīng),有些延遲是可以接受的。

我以前會(huì)認(rèn)為,CM 模式會(huì)自動(dòng)幫我們區(qū)分不同優(yōu)先級(jí)的更新,一鍵無(wú)憂享受。很遺憾的是,CM 只是提供了可中斷的能力,默認(rèn)情況下,所有的更新都是緊急更新。

這是因?yàn)?React 并不能自動(dòng)識(shí)別哪些更新是優(yōu)先級(jí)更高的。

const [inputValue, setInputValue] = useState();
const onChange = (e)=>{
setInputValue(e.target.value);
// 更新搜索列表
setSearchQuery(e.target.value);
}
return (

)

比如以上示例,用戶的鍵盤(pán)輸入操作后,setInputValue會(huì)立即更新用戶的輸入到界面上,是緊急更新。而setSearchQuery是根據(jù)用戶輸入,查詢相應(yīng)的內(nèi)容,是非緊急的。

但是 React 確實(shí)沒(méi)有能力自動(dòng)識(shí)別。所以它提供了 startTransition讓我們手動(dòng)指定哪些更新是緊急的,哪些是非緊急的。

// 緊急的
setInputValue(e.target.value);
startTransition(() => {
setSearchQuery(input); // 非緊急的
});

如上代碼,我們通過(guò) startTransition來(lái)標(biāo)記一個(gè)非緊急更新,讓該狀態(tài)觸發(fā)的變更變成低優(yōu)先級(jí)的。

光用文字描述大家可能沒(méi)有體驗(yàn),接下來(lái)我們通過(guò)一個(gè)示例來(lái)認(rèn)識(shí)下可中斷渲染對(duì)性能的爆炸提升。

示例頁(yè)面:https://react-fractals-git-react-18-swizec.vercel.app/[1]。

如下圖,我們需要畫(huà)一個(gè)畢達(dá)哥拉斯樹(shù),通過(guò)一個(gè) Slider 來(lái)控制樹(shù)的傾斜。

那我們的代碼會(huì)很簡(jiǎn)單,如下所示,我們只需要一個(gè) treeLeanstate 來(lái)管理狀態(tài)。

const [treeLean, setTreeLean] = useState(0)
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLean(value);
}
return (
<>



)

在每次 Slider 拖動(dòng)后,React 執(zhí)行流程大致如下:

  1. 更新 treeLean。
  2. 渲染 input,填充新的 value。
  3. 重新渲染樹(shù)組件 Pythagoras。

每一次用戶拖動(dòng) Slider,都會(huì)同步執(zhí)行上述三步。但當(dāng)樹(shù)的節(jié)點(diǎn)足夠多的時(shí)候,Pythagoras 渲染一次就非常慢,就會(huì)導(dǎo)致 Slider 的 value 回填變慢,用戶感覺(jué)到嚴(yán)重的卡頓。如下圖。

當(dāng)數(shù)的節(jié)點(diǎn)足夠大時(shí),已經(jīng)卡到爆炸了。在 React 18 以前,我們是沒(méi)有什么好的辦法來(lái)解決這個(gè)問(wèn)題的。但基于 React 18 CM 的可中斷渲染機(jī)制,我們可以將樹(shù)的更新渲染標(biāo)記為低優(yōu)先級(jí)的,就不會(huì)感覺(jué)到卡頓了。

const [treeLeanInput, setTreeLeanInput] = useState(0);
const [treeLean, setTreeLean] = useState(0);
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLeanInput(value)
// 將 treeLean 的更新用 startTransition 包裹
React.startTransition(() => {
setTreeLean(value);
});
}
return (
<>



)

以上代碼,我們通過(guò) startTransition 標(biāo)記了非緊急更新,讓樹(shù)的更新變成低優(yōu)先級(jí)的,可以被隨時(shí)中止,保證了高優(yōu)先級(jí)的 Slider 的體驗(yàn)。

此時(shí)更新流程變?yōu)榱耍?/p>

input 更新:

  • treeLeanInput 狀態(tài)變更。
  • 準(zhǔn)備新的 DOM。
  • 渲染 DOM。

樹(shù)更新(這一次更新是低優(yōu)先級(jí)的,隨時(shí)可以被中止):

  • treeLean 狀態(tài)變更。
  • 準(zhǔn)備新的 DOM。
  • 渲染 DOM。

React 會(huì)在高優(yōu)先級(jí)更新渲染完成之后,才會(huì)啟動(dòng)低優(yōu)先級(jí)更新渲染,并且低優(yōu)先級(jí)渲染隨時(shí)可被其它高優(yōu)先級(jí)更新中斷。

當(dāng)然,在低優(yōu)先狀態(tài)等待更新過(guò)程中,如果能有一個(gè) Loading 狀態(tài),那就更好了。React 18 提供了 useTransition來(lái)跟蹤 transition 狀態(tài)。

const [treeLeanInput, setTreeLeanInput] = useState(0);
const [treeLean, setTreeLean] = useState(0);
// 實(shí)時(shí)監(jiān)聽(tīng) transition 狀態(tài)
const [isPending, startTransition] = useTransition();
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLeanInput(value)
React.startTransition(() => {
setTreeLean(value);
});
}
return (
<>





)

自動(dòng)批處理 Automatic Batching

批處理是指 React 將多個(gè)狀態(tài)更新,聚合到一次 render 中執(zhí)行,以提升性能。比如:

function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 只會(huì) re-render 一次,這就是批處理
}

在 React 18 之前,React 只會(huì)在事件回調(diào)中使用批處理,而在 Promise、setTimeout、原生事件等場(chǎng)景下,是不能使用批處理的。

setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 會(huì) render 兩次,每次 state 變化更新一次
}, 1000);

而在 React 18 中,所有的狀態(tài)更新,都會(huì)自動(dòng)使用批處理,不關(guān)心場(chǎng)景。

function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 只會(huì) re-render 一次,這就是批處理
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 只會(huì) re-render 一次,這就是批處理
}, 1000);

如果你在某種場(chǎng)景下不想使用批處理,你可以通過(guò) flushSync來(lái)強(qiáng)制同步執(zhí)行(比如:你需要在狀態(tài)更新后,立刻讀取新 DOM 上的數(shù)據(jù)等。)

import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React 更新一次 DOM
flushSync(() => {
setFlag(f => !f);
});
// React 更新一次 DOM
}

React 18 的批處理在絕大部分場(chǎng)景下是沒(méi)有影響,但在 Class 組件中,如果你在兩次 setState 中間讀取了 state 值,會(huì)出現(xiàn)不兼容的情況,如下示例。

handleClick = () => {
setTimeout(() => {
this.setState(({ count }) => ({ count: count + 1 }));
// 在 React17 及之前,打印出來(lái)是 { count: 1, flag: false }
// 在 React18,打印出來(lái)是 { count: 0, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};

當(dāng)然你可以通過(guò) flushSync來(lái)修正它。

handleClick = () => {
setTimeout(() => {
ReactDOM.flushSync(() => {
this.setState(({ count }) => ({ count: count + 1 }));
});
// 在 React18,打印出來(lái)是 { count: 1, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};

流式 SSR

SSR 一次頁(yè)面渲染的流程大概為:

  1. 服務(wù)器 fetch 頁(yè)面所需數(shù)據(jù)。
  2. 數(shù)據(jù)準(zhǔn)備好之后,將組件渲染成 string 形式作為 response 返回。
  3. 客戶端加載資源。
  4. 客戶端合成(hydrate)最終的頁(yè)面內(nèi)容。

在傳統(tǒng)的 SSR 模式中,上述流程是串行執(zhí)行的,如果其中有一步比較慢,都會(huì)影響整體的渲染速度。

而在 React 18 中,基于全新的 Suspense,支持了流式 SSR,也就是允許服務(wù)端一點(diǎn)一點(diǎn)的返回頁(yè)面。

假設(shè)我們有一個(gè)頁(yè)面,包含了 NavBar、Sidebar、Post、Comments 等幾個(gè)部分,在傳統(tǒng)的 SSR 模式下,我們必須請(qǐng)求到 Post 數(shù)據(jù),請(qǐng)求到 Comments 數(shù)據(jù)后,才能返回完整的 HTML。






Hello world





First comment


Second comment



但如果 Comments 數(shù)據(jù)請(qǐng)求很慢,會(huì)拖慢整個(gè)流程。

在 React 18 中,我們通過(guò) Suspense包裹,可以告訴 React,我們不需要等這個(gè)組件,可以先返回其它內(nèi)容,等這個(gè)組件準(zhǔn)備好之后,單獨(dú)返回。






}>



如上,我們通過(guò) Suspense包裹了 Comments 組件,那服務(wù)器首次返回的 HTML 是下面這樣的,組件處通過(guò) loading進(jìn)行了占位。






Hello world





Loading...

當(dāng) 組件準(zhǔn)備好之后,React 會(huì)通過(guò)同一個(gè)流(stream)發(fā)送給瀏覽器(res.send 替換成 res.socket),并替換到相應(yīng)位置。


更多關(guān)于流式 SSR 的講解可見(jiàn):https://github.com/reactwg/react-18/discussions/37[2]

Server Component

Server Component 叫服務(wù)端組件,目前還在開(kāi)發(fā)過(guò)程中,沒(méi)有正式發(fā)布,不過(guò)應(yīng)該很快就會(huì)和我們見(jiàn)面的。

Server Component 的本質(zhì)就是由服務(wù)端生成 React 組件,返回一個(gè) DSL 給客戶端,客戶端解析 DSL 并渲染該組件。

Server Component 帶來(lái)的優(yōu)勢(shì)有:

零客戶端體積,運(yùn)行在服務(wù)端的組件只會(huì)返回最終的 DSL 信息,而不包含其他任何依賴。

// NoteWithMarkdown.js
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
const html = sanitizeHtml(marked(text));
return (/* render */);
}

假設(shè)我們有一個(gè) markdown 渲染組件,以前我們需要將依賴 marked和 sanitize-html打包到 JS 中。如果該組件在服務(wù)端運(yùn)行,則最終返回給客戶端的是轉(zhuǎn)換完成的文本。

組件擁有完整的服務(wù)端能力。

由于 Server Component 在服務(wù)端執(zhí)行,擁有了完整的 NodeJS 的能力,可以訪問(wèn)任何服務(wù)端 API。

// Note.server.js - Server Component
import fs from 'react-fs';

function Note({id}) {
const note = JSON.parse(fs.readFile(`${id}.json`));
return ;
}

組件支持實(shí)時(shí)更新

由于 Server Component 在服務(wù)端執(zhí)行,理論上支持實(shí)時(shí)更新,類似動(dòng)態(tài) npm 包,這個(gè)還是有比較大的想象空間的。也許 React Component as a service 時(shí)代來(lái)了。

當(dāng)然說(shuō)了這么多好處,Server Component 肯定也是有一些局限性的:

不能有狀態(tài),也就是不能使用 state、effect 等,那么更適合用在純展示的組件,對(duì)性能要求較高的一些前臺(tái)業(yè)務(wù)

  • 不能訪問(wèn)瀏覽器的 API。
  • props 必須能被序列化。
  • OffScreen。

OffScreen

目前也在開(kāi)發(fā)中,會(huì)在未來(lái)某個(gè)版本中發(fā)布。但我們非常有必要提前認(rèn)識(shí)下它,因?yàn)槟悻F(xiàn)在的代碼很可能已經(jīng)有問(wèn)題了。

OffScreen 支持只保存組件的狀態(tài),而刪除組件的 UI 部分??梢院芊奖愕膶?shí)現(xiàn)預(yù)渲染,或者 Keep Alive。比如我們?cè)趶?tabA 切換到 tabB,再返回 tabA 時(shí),React 會(huì)使用之前保存的狀態(tài)恢復(fù)組件。

為了支持這個(gè)能力,React 要求我們的組件對(duì)多次安裝和銷毀具有彈性。那什么樣的代碼不符合彈性要求呢?其實(shí)不符合要求的代碼很常見(jiàn)。

async function handleSubmit() {
setPending(true)
await post('/someapi') // component might unmount while we're waiting
setPending(false)
}

在上面的代碼中,如果發(fā)送請(qǐng)求時(shí),組件卸載了,會(huì)拋出警告。

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

警告:不能在已經(jīng)卸載的組件中更改 state。這是一個(gè)無(wú)用的操作,它表明你的項(xiàng)目中存在內(nèi)存泄漏。要解決這個(gè)問(wèn)題,請(qǐng)?jiān)?useEffect 清理函數(shù)中取消所有訂閱和異步任務(wù)。

所以我們一般都會(huì)通過(guò)一個(gè) unmountRef來(lái)標(biāo)記當(dāng)前組件是否卸載,以避免所謂的「內(nèi)存泄漏」。

function SomeButton(){
const [pending, setPending] = useState(false)
const unmountRef = useUnmountedRef();
async function handleSubmit() {
setPending(true)
await post('/someapi')
if (!unmountRef.current) {
setPending(false)
}
}
return (

)
}

我們來(lái)模擬執(zhí)行一次組件,看看組件的變化狀態(tài):

  1. 首次加載時(shí),組件的狀態(tài)為:pending = false。
  2. 點(diǎn)擊按鈕后,組件的狀態(tài)會(huì)變?yōu)椋簆ending = true。
  3. 假如我們?cè)谡?qǐng)求過(guò)程中卸載了組件,那此時(shí)的狀態(tài)會(huì)變?yōu)椋簆ending = true。

在 OffScreen 中,React 會(huì)保存住最后的狀態(tài),下次會(huì)用這些狀態(tài)重新渲染組件。慘了,此時(shí)我們發(fā)現(xiàn)重新渲染組件一直在 loading。

怎么解決?解決辦法很簡(jiǎn)單,就是回歸最初的代碼,刪掉 unmountRef的邏輯。至于「內(nèi)存泄漏」的警告,React 18 刪除了,因?yàn)檫@里不存在內(nèi)存泄漏(參考:https://mp.weixin.qq.com/s/fgT7Kxs_0feRx4TkBe6G5Q)。

async function handleSubmit() {
setPending(true)
await post('/someapi')
setPending(false)
}

為了方便排查這類問(wèn)題,在 React 18 的 Strict Mode 中,新增了 double effect,在開(kāi)發(fā)模式下,每次組件初始化時(shí),會(huì)自動(dòng)執(zhí)行一次卸載,重載。

* React mounts the component.
* Layout effects are created.
* Effects are created.
* React simulates unmounting the component.
* Layout effects are destroyed.
* Effects are destroyed.
* React simulates mounting the component with the previous state.
* Layout effects are created.
* Effects are created.

這里還是要再提示下:開(kāi)發(fā)環(huán)境,在 React 18 的嚴(yán)格模式下,組件初始化的 useEffect 會(huì)執(zhí)行兩次,也就是可能 useEffect 里面的請(qǐng)求被執(zhí)行了兩次等。

新 Hooks

useDeferredValue

const deferredValue = useDeferredValue(value);

useDeferredValue 可以讓一個(gè) state 延遲生效,只有當(dāng)前沒(méi)有緊急更新時(shí),該值才會(huì)變?yōu)樽钚轮怠seDeferredValue 和 startTransition 一樣,都是標(biāo)記了一次非緊急更新。

之前 startTransition 的例子,就可以用 useDeferredValue來(lái)實(shí)現(xiàn)。

const [treeLeanInput, setTreeLeanInput] = useState(0);
const deferredValue = useDeferredValue(treeLeanInput);
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLeanInput(value)
}
return (
<>



)

useId

const id = useId();

支持同一個(gè)組件在客戶端和服務(wù)端生成相同的唯一的 ID,避免 hydration 的不兼容。原理是每個(gè) id 代表該組件在組件樹(shù)中的層級(jí)結(jié)構(gòu)。

useSyncExternalStore

const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);

useSyncExternalStore 能夠讓 React 組件在 Concurrent Mode 下安全地有效地讀取外接數(shù)據(jù)源。在 Concurrent Mode 下,React 一次渲染會(huì)分片執(zhí)行(以 fiber 為單位),中間可能穿插優(yōu)先級(jí)更高的更新。假如在高優(yōu)先級(jí)的更新中改變了公共數(shù)據(jù)(比如 redux 中的數(shù)據(jù)),那之前低優(yōu)先的渲染必須要重新開(kāi)始執(zhí)行,否則就會(huì)出現(xiàn)前后狀態(tài)不一致的情況。useSyncExternalStore 一般是三方狀態(tài)管理庫(kù)使用,一般我們不需要關(guān)注。

useInsertionEffect

useInsertionEffect(didUpdate);

這個(gè) Hooks 只建議 css-in-js庫(kù)來(lái)使用。這個(gè) Hooks 執(zhí)行時(shí)機(jī)在 DOM 生成之后,useLayoutEffect 生效之前,一般用于提前注入