React18有哪些變化?

文/遊鹿javascript

React 16 -> 17 -> 18

多是React15到16的不兼容變動太多,開發者們升級至關痛苦,因此很長一段時間React開發者都沒有再發布新版本,而是在 v16 上集成各類新能力,16.3/16.8/16.12 幾乎每隔幾個版本就有有趣的新特性出現。 ​html

在長達2年半的 v16 版本後,React團隊發佈了 v17,同時宣佈這一版本的定位是一版技術改造的過渡版本,主要目標是下降後續版本的升級成本。在 v17 以前,不一樣版本的 React 沒法混用,很重要的一個緣由是以前版本中事件委託是掛在document上的,v17 開始,事件委託掛載到了渲染 React 樹的根 DOM 容器中,這使多 React 版本並存成爲了可能。(意味着React 17+可混用,老頁面維持 v17,新頁面使用v18 v19 等)前端

image.png 咱們愈來愈能感覺到,React的開發者把升級重點放到了**「漸進升級」**上,僅在v17發佈了2個小版本後,v18的alpha就出現了,而且只須要用戶作極小、甚至不須要改動就能讓現有React APP在 v18 上工做。那麼v18中有哪些新變化、新特性呢?java

注:本文內容來自 reactwg/react-18 的部分discussion,筆者翻譯閱讀理解後寫出來的。若是有理解不到位的地方,歡迎各位大佬評論區交流、討論、指正~react

React 18

React18的升級策略是「漸進升級」,包括名聲在外的併發渲染等在內的新能力都是可選的,不會馬上對組件行爲帶來任何明顯的破壞性變化。git

You can upgrade to React 18 with minimal or no changes to your application code, with a level of effort comparable to a typical major React release. github

你幾乎不須要對應用程序中的代碼進行任何改動就能夠直接升級到 React 18,並不會比以往的 React 版本升級要困難。 瀏覽器

**React官網 ** reactjs.org/blog/2021/0…安全

併發渲染是React底層的一次重要架構設計升級,併發渲染的優點在於提升React APP性能。當你使用了一些React18新特性後,你可能已經用上了併發渲染。服務器

新的 Root API

在React18中, ReactDOM.render()  正式成爲Legacy,並增長了新的RootAPI ReactDOM.createRoot() ,他們的用法差異以下:

import ReactDOM from ‘react-dom’;
import App from 'App';

ReactDOM.render(<App />, document.getElementById('root'));
複製代碼
import ReactDOM from ‘react-dom’;
import App from 'App';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(<App />);
複製代碼

能夠看到,經過新的API,咱們能夠爲一個React App建立多個根節點,甚至在將來能夠用不一樣版本的React來建立。React18 保留了上述兩種用法,老項目不想改仍然能夠用 ReactDOM.render() ;新項目想提高性能,能夠用 ReactDOM.createRoot() 借併發渲染的東風。

自動 Batching

什麼是Batching

Batching is when React groups multiple state updates into a single re-render for better performance.

爲了使應用得到更好的性能,React把屢次的狀態更新(state updates),合併到一次渲染中React17只會把瀏覽器事件(如點擊)發生期間的狀態更新合併掉。而React18會把事件處理器發生後的狀態更新也合併掉。舉個例子:

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClickPrev() {
   	setCount(c => c - 1); // Does not re-render yet
    setFlag(f => !f); // Does not re-render yet
    // React will only re-render once at the end (that's batching!)
  }
  
  function handleClickNext() {
    fetchSomething().then(() => {
      // React 17 and earlier does NOT batch these because
      // they run *after* the event in a callback, not *during* it
      setCount(c => c + 1); // Causes a re-render
      setFlag(f => !f); // Causes a re-render
    });
  }

  return (
    <div> <button onClick={handleClickPrev}>Prev</button> <button onClick={handleClickNext}>Next</button> <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1> </div>
  );
}
複製代碼

什麼是Automatic Batching

在React 18中,只要使用 新的 Root API ReactDOM.createRoot() 方法,就能直接享受自動batching的能力!這裏列舉一些自動更新的場景:

image.png

不使用automatic batching

batching 是安全的,但也存在一些特殊狀況不但願batching發生,好比:你須要在狀態更新後,馬上讀取新DOM上的數據等。這種狀況下請使用 ReactDOM.flushSync() (React官方不推薦常態化使用這一API):

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
}
複製代碼

對Hooks/Classes的影響

  • 對 Hooks 沒有任何影響
  • 對 Classes 大部分狀況下沒影響,關注一種模式:是否在兩次setState之間讀取了state值。差別以下:
handleClick = () => {
  setTimeout(() => {
    this.setState(({ count }) => ({ count: count + 1 }));

    // 在 React17 及以前,打印出來是 { count: 1, flag: false }
    // 在 React18,打印出來是 { count: 0, flag: false }
    console.log(this.state);

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};
複製代碼

若是不想經過調整代碼邏輯的方式進行修正,能夠直接採用 ReactDOM.flushSync() :

handleClick = () => {
  setTimeout(() => {
    ReactDOM.flushSync(() => {
      this.setState(({ count }) => ({ count: count + 1 }));
    });

    // 在 React18,打印出來是 { count: 1, flag: false }
    console.log(this.state);

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};
複製代碼

新的 Suspense SSR 架構

Suspense在React16/18的區別

Suspense早在React16就以試驗性API的形式出來了,相比較舊版本的Legacy Suspense,新版本的Concurrent Suspense更符合用戶直覺。 ​

React官方說這兩個版本存在比較小的差別,但因爲新版 Suspense 的實現是基於併發渲染的,因此這仍然是一個Breaking Changes,這裏介紹下差別:

<Suspense fallback={<Loading />}>
  <ComponentThatSuspends />
  <Sibling />
</Suspense>
複製代碼
舊版本Legacy Suspense 新版本Concurrent Suspense
從表現上看
  • **<Sibling>**** 組件是當即渲染到DOM中**
  • Effects/Lifecycles 會被當即觸發
  • 參考React官方DEMO
  • **<Sibling>**** 組件並不會當即渲染到DOM中**
  • Effects/Lifecycles 是在**<ComponentThatSuspends>**組件完成數據解析後(英文是resolve)纔會被觸發
  • 參考React官方Demo
  • 從原理上看
  • 當渲染到<ComponentThatSuspends>時,它會被跳過<Sibiling>組件會被渲染到DOM Tree中,只不過它會被設置 display: hidden 直到 <ComponentThatSuspends> resovle纔會出現。
  • 等全部<Suspense> 中的數據都resolve以後,纔會在一個單一的、一致的批次中同時提交整個樹。從開發角度來講,新版本的 <Suspense> 行爲更符合預期由於它可被預測。
  • 現存SSR架構的問題

    現存SSR架構原理很少解釋,它的問題在於,一切都是串行的,在任一前序任務沒完成以前,後一任務都沒法開始,也就是「All or Nothing」,通常是以下流程:

    1. 服務器內部獲取數據
    2. 服務器內部渲染 HTML
    3. 客戶端從遠程加載代碼
    4. 客戶端開始hydrate(水合)|

    React18新策略

    **React18提供了Suspense,打破了這種串行的限制,優化前端的加載速度和可交互所需等待時間。**這一SSR架構依賴兩個新特性:

    • 服務器端的「流式HTML」:使用API pipeToNodeWritable 
    • 客戶端的「選擇性Hydration」:使用 <Suspense> 

    新版本Suspense SSR速度更快的原理是什麼呢?如下面的結構爲例:

    流式 HTML

    <Layout>
      <NavBar />
      <Sidebar />
      <RightPane> <Post /> <Suspense fallback={<Spinner />}> <Comments /> </Suspense> </RightPane>
    </Layout>
    複製代碼

    如上一個頁面結構,<Comments> 是經過接口異步獲取的,這一過程數據請求比較慢,因此咱們把它包裹在 <Suspense> 裏。在普通的SSR架構裏,通常只能等<Comments>加載進來以後才能進行下一環節。在新模式下,HTML流首先返回的內容裏是不會有 <Comments> 組件相關HTML信息的,取而代之的是 <Spinnger> 的HTML:

    <main>
      <nav>
        <!--NavBar -->
        <a href="/">Home</a>
       </nav>
      <aside>
        <!-- Sidebar -->
        <a href="/profile">Profile</a>
      </aside>
      <article>
        <!-- Post -->
        <p>Hello world</p>
      </article>
      <section id="comments-spinner">
        <!-- Spinner -->
        <img width=400 src="spinner.gif" alt="Loading..." />
      </section>
    </main>
    複製代碼

    等服務端獲取到了 <Comments> 的數據後,React再把後加入的 <Comments> 的HTML信息,經過同一個流(stream)發送過去,React會建立一個超小的內聯

    <div hidden id="comments">
      <!-- Comments -->
      <p>First comment</p>
      <p>Second comment</p>
    </div>
    <script> // This implementation is slightly simplified document.getElementById('sections-spinner').replaceChildren( document.getElementById('comments') ); </script>
    複製代碼

    因此與傳統的 HTML 流不一樣,它沒必要按自上而下的順序發生。

    選擇性 Hydration

    代碼拆分是咱們經常使用的手段,咱們能夠用 React.lazy  把一部分代碼從主包中拆出來。

    import { lazy } from 'react';
    
    const Comments = lazy(() => import('./Comments.js'));
    
    // ...
    
    <Suspense fallback={<Spinner />}> <Comments /> </Suspense>
    複製代碼

    在React18之前,React.lazy 不支持服務端渲染,即使是最流行的解決方案也讓你們從「爲了代碼拆分不使用SSR」和「使用SSR但要在全部js加載完成後才能hydratie」中二選一。

    而在React18版本,被 <Suspense>  包裹的子組件能夠延後hydratie,這一行爲是React內部自動作掉的,因此React.lazy 也默認支持了SSR。 ​

    新特性startTransition

    爲了解決什麼問題?

    使用此 API 能夠防止內部函數執行拖慢 UI 響應速度。 ​

    以查詢選擇器爲例:用戶輸入關鍵詞,請求遠程數據並展現搜索結果。

    // Urgent: Show what was typed
    setInputValue(input);
    
    // Not urgent: Show the results
    setSearchQuery(input);
    複製代碼

    輸入文字時用戶是但願獲得即時反饋的,而查詢並展現結果則是容許有延遲的(事實上,開發者常常人爲地用一些手段讓他們延遲更新,好比debounce) ​

    在引入 startTransition 後用法是:

    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);
    });
    複製代碼

    什麼是transition?

    更新能夠分爲兩類:

    • 緊急更新(Urgent Updates):好比打字、點擊、拖動等,在直覺上須要當即響應的行爲,若是不當即響應會給人感受出錯了;
    • 過渡更新(Transition Updates):將 UI 從一個視圖過渡到另外一個視圖。它不須要即時響應,有點延遲是在預期範圍內、可接受的。

    其實在React應用中,大部分更新在概念上都應當是Transition Updates,可是出於向後兼容的角度考慮,transition是可選的,因此在React18中默認的更新方式仍然是Urgent Updates,想要使用Transition Updates能夠把函數用startTransition  包裹起來。 ​

    startTransition 與 setTimeout的區別是什麼?

    主要有兩點:

    1. startTransition更早於setTimeout處理渲染更新,這一差異在一些性能較差的機器上感知稍微明顯。在運行時,startTransition與普通函數同樣,都是當即執行的,只不過函數執行帶來的全部update會被標記爲"transition",React在處理更新時會使用這一標記做爲參考
    2. setTimeout內的大型的屏幕更新會鎖定頁面,在此期間用戶沒法與頁面交互。而被標記爲「transition」的更新是可被打斷的,

    什麼時候使用

    • 慢渲染:一些React須要花費大量時間的複雜渲染
    • 慢網絡:耗時的網絡請求
    相關文章
    相關標籤/搜索