【第一批吃螃蟹】試用 React 18 !

React 團隊最近發佈了 React 18 的 alpha 版本。這個版本主要是加強 React 應用程序的 併發渲染 能力,你能夠在 React 18 中嘗試體驗如下幾個新特性:前端

  • 新的 ReactDOM.createRoot() API(替換 ReactDOM.render()
  • 新的 startTransition API(用於非緊急狀態更新)
  • 渲染的自動批處理優化(主要解決異步回調中沒法批處理的問題)
  • 支持 React.lazy 的 全新 SSR 架構(支持 <Suspense> 組件)

這不,這個版本纔剛剛發佈社區裏已經有不少小夥伴已經躍躍欲試了,我也火燒眉毛跟着社區的大佬們一塊兒嘗試了一下。感興趣的小夥伴們能夠一塊兒跟着個人記錄來試一下:react

安裝 React 18 Alpha

想要在你的項目裏試用 React 18 Alpha,能夠嘗試執行下面的命令:git

npm install react@alpha react-dom@alpha
# or
yarn add react@alpha react-dom@alpha
複製代碼

若是你是使用 Create React App 初始化的項目,你可能會遇到一個因爲 react-scripts 引發的 could not resolve dependency 錯誤:github

Could not resolve dependency:
peer react@">= 16" from react-scripts@4.0.3
複製代碼

你能夠在安裝的時候嘗試加上 --force 來解決這個問題:面試

npm install react@alpha react-dom@alpha --force
複製代碼

ReactDOM.createRoot()

在 React 18 版本中,ReactDOM.createRoot() 替代了一般做爲程序入口的 ReactDOM.render() 方法。npm

這個方法主要是防止 React 18 的不兼容更新致使你的應用程序崩潰。瀏覽器

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
const container = document.getElementById('root');
// Create a root.
const root = ReactDOM.createRoot(container);
// Render the top component to the root.
root.render(<App />);
複製代碼

當你更新到 React 18 時,若是你還使用 redner 函數做爲程序入口,控制檯會打印一個錯誤日誌來提醒你使用 createRoot() ,只有使用了這個方法後才能使用 React 18 新功能。微信

渲染的自動批處理

React 有一道經典面試題,setState 究竟是同步的仍是異步的,我面試的時候也會常常問,具體的我在兩年前的一篇文章中有介紹過:markdown

由實際問題探究setState的執行機制架構

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);   
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);   

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val); 
      this.setState({val: this.state.val + 1};
      console.log(this.state.val);  
    }, 0);
  }

  render() {
    return null;
  }
};
複製代碼

好比上面的代碼,咱們來考慮一下兩種狀況:

  • 假設 React 徹底沒有批處理機制,那麼執行一個 setState 就會當即觸發一次頁面渲染,打印順序應該是 一、二、三、4
  • 假設 React 有一個完美的批處理機制,那麼應該等整個函數執行完了以後再統一處理全部渲染,打印順序應該是 0、0、0、0

實際上,在 React 18 版本以前,上面代碼的打印順序是 0、0、二、3

出現這個問題的主要緣由就是在 React 的事件函數和異步回調中的狀態批處理機制不同。在異步回調外面,可以將全部渲染合併成一次,異步回調裏面,則不會合並,會渲染屢次。

實際上,在大部分的場景下,咱們都須要在調用一個接口或者作了一些其餘事情以後,再去回調函數裏更新狀態,上面的批處理機制就會顯得很是雞肋。

如今,React 18 版本解決了這個問題,不管你是在 Promise、setTimeout、或者其餘異步回調中更新狀態,都會觸發批處理,上面的代碼真的就會一直打印 0、0、0、0 了!

是否是很棒!React 幫咱們消滅的一道面試題 😎。

一般狀況下,批處理是沒什麼問題的,可是有可能在某些特殊的需求(好比某個狀態更改後要馬上從 DOM 中獲取一些內容)下不太合適,咱們可使用 ReactDOM.flushSync() 退出批處理:

import { flushSync } from 'react-dom'; // Note: react-dom, not react

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // React has updated the DOM by now
  flushSync(() => {
    setFlag(f => !f);
  });
  // React has updated the DOM by now
}
複製代碼

Ricky 在這篇文章(https://github.com/reactwg/react-18/discussions/21) 詳細介紹了 Automatic batching ,感興趣能夠一塊兒到評論區討論。

SSR 下的懶加載支持

React.lazy 函數能讓你像渲染常規組件同樣處理動態引入組件。React.lazy 接受一個函數,這個函數須要動態調用 import()。它必須返回一個 Promise,該 Promise 須要 resolve 一個 default export 的 React 組件。

const MonacoEditor = React.lazy(() => import('react-monaco-editor'));
複製代碼

React.lazy 必需要配合 <Suspense> 才能更好的使用,在 Suspense 組件中渲染 lazy 組件,可使用在等待加載 lazy 組件時作優雅降級(好比渲染一些 loading 效果 )。fallback 屬性接受任何在組件加載過程當中你想展現的 React 元素。

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // Displays <Spinner> until OtherComponent loads
    <React.Suspense fallback={<Spinner />}> <div> <OtherComponent /> </div> </React.Suspense>
  );
}
複製代碼

React 18 之前, SSR 模式下是不支持使用 Suspense 組件的,而在 React 18 中服務端渲染的組件也支持使用 <Suspense> 了:若是你把組件包裹在了 <Suspense> 中,服務端首先會把 fallback 中的組件做爲 HTML 流式傳輸,一旦主組件加載完成,React 會發送新的 HTML 來替換該組件。

<Layout> 
  < Article /> 
  <Suspense fallback={<Spinner />}> <Comments /> </Suspense>
 </Layout>
複製代碼

好比上面的代碼,<Article> 組件首先會被渲染,<Comments> 組件將被 fallback 替換爲 <Spinner> 。一旦 <Comments> 組件加載完成後,React 會纔將其發送到瀏覽器,替換 <Spinner> 組件。

Dan Abramov 在這篇文章(https://github.com/reactwg/react-18/discussions/37) 中詳細介紹了這個機制,感興趣能夠到評論區一塊兒討論。

startTransition API

startTransition 是 React 18 新增長的一個 API,它可讓你區分 非緊急 的狀態更新。

好比如今有這樣一個場景:咱們要去 Input 框輸入一個值,而後下面須要同時給出經過咱們輸入後的值過濾出來的一些數據。

由於你每次須要動態渲染出過濾後的值,因此你可能會將輸入的值存儲在一個 state 中,你的代碼多是下面這樣的:

setInputValue (input) ; 
setSearchQuery (input) ;
複製代碼

首先用戶輸入上去的值確定是須要馬上渲染出來的,可是過濾出來的聯想數據可能不須要那麼快的渲染,若是咱們不作任何額外的處理,在 React 18 以前,全部更新都會馬上被渲染,若是你的原始數據很是多,那麼每次輸入新的值後你須要進行的計算量(根據輸入的值過濾出符合條件的數據)就很是大,因此每次用戶輸入後可能會有卡頓現象。

因此,在之前咱們可能會本身去加一些防抖這樣的操做去人爲的延遲過濾數據的計算和渲染。

新的 startTransition API 可讓咱們把數據標記成 transitions 狀態。

import { startTransition } from 'react';


// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(input);
});
複製代碼

全部在 startTransition 回調中的更新都會被認爲是 非緊急處理,若是出現更緊急的更新(好比用戶又輸入了新的值),則上面的更新都會被中斷,直到沒有其餘緊急操做以後纔會去繼續執行更新。

怎麼樣,是否是比咱們人工實現一個防抖更優雅 😇

同時,React 還給咱們提供了一個帶有 isPending 過渡標誌的 Hook

import  {  useTransition  }  from  'react' ; 

const  [ isPending ,  startTransition ]  =  useTransition ( ) ;

複製代碼

你可使用它和一些 loading 動畫結合使用:

{ isPending  &&  < Spinner  / > }

複製代碼

Ricky 在這篇文章(https://github.com/reactwg/react-18/discussions/41) 詳細介紹了 startTransition ,感興趣能夠一塊兒到評論區討論。

React 18 發佈計劃

React 18 官方介紹(https://github.com/reactwg/react-18/discussions/4)中提到的其餘兩個 API useDeferredValue<SuspenseList> 還沒 released ,咱們下次再用,下面是 React 18 的發佈時間表:

  • React 18 Alpha 版本:如今就能用
  • 公開的 Beta 版:至少在 Alpha 版本後的幾個月
  • RC 版本:至少在 Beta 版發佈後的幾周
  • 正式版:至少在 RC 版本發佈以後的幾周

參考

最後

文中若有錯誤,歡迎在評論區指正,若是這篇文章幫助到了你,歡迎點贊和關注。

本文首發在個人我的公衆號:【code祕密花園】:試用 React 18 ! ,歡迎關注。

抖音前端正急缺人才,若是你想加入咱們,歡迎加我微信和我聯繫。另外若是你想加入前端、面試、理財等交流羣,或者你有任何其餘事情也能夠添加個人我的微信 ConardLi 一塊兒交流。

相關文章
相關標籤/搜索