[譯]React 17終於發佈RC版本了 官方竟說17是個過渡版!

前言

半個月前Vue 3.0剛剛發佈了rc版本,React就緊隨其後發佈了rc版本。javascript

不過相比於Vue3對Vue2.x能力的巨大提高,React17對React16.x好像並無什麼很給力的更新。html

在GitHub上的reactjs/reactjs.org文檔中甚至出現了這樣一句話:

沒有任何新特性!這屆React有點皮啊!java

那它到底更新了個啥呢?我們來把這個文檔翻譯一下看看:react

譯文

文檔地址: https://github.com/reactjs/reactjs.org/blob/c30ff1e39b9fca747198c028a33300656a90e612/content/blog/2020-08-10-react-v17-rc.md
標題 做者
React 17.0 : 沒有新特性 gaearon rachelnabors

今天,咱們發佈了React v17的第一個 RC 版本。自上一個主要版本的React至今已經有兩年半的時間了,按照咱們的標準,時間跨度有些長了!在此篇博客中,咱們將講解這次主要版本對你的影響以及如未嘗試它。git

無新特性

React 17不太尋常,由於它沒有添加任何面向開發人員的新功能,而主要側重於升級簡化React自己github

咱們正在積極開發React的新功能,但它們並不屬於此版本。React 17是咱們進行深度推廣戰略的關鍵所在。web

此版本之因此特殊,你能夠認爲React 17是一個過渡版,它會使得由一個React版本管理樹嵌入到另外一個React版本管理樹中時會更加安全。shell

逐步升級

在過去七年的時間裏,React一直遵循着all-or-nothing的升級策略。你能夠繼續使用舊版本,也能夠將整個應用程序升級至新版本。但沒有介於二者之間的狀況。npm

此方式一直延續至今,但咱們確遭遇了all-or-nothing升級策略的侷限性。許多API的變動,例如,反對使用 legacy context API時,並不能以自動化的方式來完成。至今可能大多數應用程序從未使用過它們,但咱們仍然選擇在React中支持它們。咱們必須在無限期支持過期的API或針對某些應用仍使用舊版本 React 間進行選擇。但這兩個方案都不合適。react-native

所以,咱們想提供另外一種方案。

React 17開始支持逐步升級React版本。當從React 15升到16時(或者從 React 16升到17時),一般會一次升級整個應用程序。這適用於大部分應用程序。可是,若是代碼庫是在幾年前編寫的,而且並無獲得很好的維護,那麼升級它會變得愈來愈有挑戰性。儘管能夠在頁面上使用兩個版本的React,可是直到React 17依然會出現events問題。

咱們使用React 17解決了許多諸如此類的問題。這將意味着當React 18或將來版本問世時,你將有更多選擇。首選仍是像之前同樣,一次升級整個應用程序。但你也能夠選擇逐步升級你的應用程序。例如,你可能會將大部分應用程序遷移至React 18,但在React 17上保留一些延遲加載的對話框或子路由。

但這不意味着你必須逐步升級。對於大部分應用程序來講,一次性所有升級還是最好的解決方案。加載兩個React版本,即便其中一個是按需延遲加載的,仍然不太理想。可是,對於沒有積極維護的大型應用來講,能夠考慮此種方案,而且 React 17開始能夠保證這些應用程序不落伍。

爲了實現逐步升級,咱們須要對React的事件系統進行一些更改。而這些更改可能會對代碼產生影響,這也是React 17成爲主要版本的緣由。實際上,十萬個以上的組件中受影響的組件不超過20個,所以咱們但願大多數應用程序均可以升級到React 17,而不會產生太多影響。若是遇到問題的話能夠聯繫咱們

逐步升級的示例

咱們準備了一個示例(GitHub)倉庫,展現瞭如何在必要時延遲加載舊版本的React。該示例使用了Create React App進行構建,但對其餘工具採用相似的方法應該也適用。咱們歡迎使用其餘工具的開發者編寫demo並提交pr。

注意: 咱們已將 其餘的更新推遲到React 17以後。此版本的目標是實現逐步升級。若是升級React 17太困難的話,咱們的目標會沒法實現。

更改事件委託

從技術上講,始終能夠在應用程序中嵌套不一樣版本的React。但因爲React事件系統的工做原理致使很難實現。

在React組件中,一般會內聯編寫事件處理:

<button onClick={handleClick}>

與此代碼等效的DOM操做以下:

myButton.addEventListener('click', handleClick);

但對大多數事件來講,React並不會將它們附加到DOM節點上。相反,React會直接在document節點上爲每種事件類型附加一個處理器,這被稱爲事件委託。除了在大型應用程序上具備性能優點外,它還使添加相似於replaying events這樣的新特性變得更容易。

自從其發佈以來,React就一直自動進行事件委託。當document上觸發DOM事件時,React會找出調用的組件,而後 React事件會在組件中向上"冒泡"。但實際上,原生事件已經冒泡出了"document"級別,React是在document中安裝的事件處理器。

但這就是逐步升級的困難所在。

若是頁面上有多個React版本,他們都將在頂層註冊事件處理器。這會破壞e.stopPropagation() 若是嵌套樹結構中阻止了事件冒泡,但外部樹依然能接收到它。這會使不一樣版本React的嵌套變得十分困難。這種擔心並非沒有根據的 —— 例如,四年前Atom編輯器就遇到了相同的問題

這也是咱們爲何要改變React底層附加事件方式的緣由。

在React 17中,React將再也不向document添加事件處理器。而會將事件處理器附加到渲染React樹的根DOM節點中:

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);

在React 16或更早版本中,React會對大多數事件執行document.addEventListener()。React 17將會在底層調用rootNode.addEventListener()。

多虧了這個更改,如今能夠更加安全地進行新舊版本React樹的嵌套。請注意,要使其正常工做,兩個版本都必須爲17或更高版本,這就是爲何強烈建議升級到React 17的根本緣由。從某種意義上講,React 17是一個過渡版本,使逐步升級成爲可能。

此更改還讓React嵌入使用其餘技術構建的應用程序變得更加容易。例如,若是應用程序的"外殼"是用jQuery編寫的,但其中較新的代碼是用React編寫的,則React代碼中的e.stopPropagation()會阻止它影響jQuery的代碼 —— 就像你所期盼的那樣。換句話說,若是你再也不喜歡React並想重寫應用程序(好比用jQuery),則能夠從外層開始將 React轉換爲jQuery,而不會破壞事件冒泡。

經覈實,多年來在issue tracker上報告的許多問題都已被新特性解決,這些問題大多都與將React與非React代碼集成有關。

注意: 你可能想知道這是否會破壞根容器以外的 Portals。答案是React還會監聽portals容器上的事件,因此這不是問題。

解決隱患

與其餘重大更新同樣,可能須要對代碼進行調整。在Facebook,咱們在成千上萬個模塊中大約調整了十個模塊以適應此次更新。

例如,若是模塊中使用document.addEventListener(...)手動添加了DOM監聽,你可能但願能捕獲到全部React 事件。在React 16或更早版本中,即便你在React事件處理器中調用e.stopPropagation(),你建立的DOM監聽仍會觸發,這是由於原生事件已經處於document級別。使用React 17冒泡將被阻止(按需),所以你的document級別的事件監聽不會觸發:

document.addEventListener('click', function() {
  // 若是React組件調用了e.stopPropagation()
  // 那麼這個自定義監聽函數不會收到click事件
});

你能夠將監聽轉換爲使用事件捕獲來修復此類代碼。爲此,你能夠將{ capture: true }做爲 document.addEventListener的第三個參數傳遞:

document.addEventListener('click', function() {
  // 如今這個事件處理函數使用了事件捕獲,
  // 因此它能夠接收到全部的點擊事件!
}, { capture: true });

請注意,此策略在全局上具備更好的適應性。例如,它可能會修復代碼中現有的錯誤,這些錯誤在 React 事件處理器外部調用 e.stopPropagation() 發生。換句話說,React 17的事件冒泡更接近常規DOM

其餘重大更改

咱們將 React 17中的重大更改保持在最低水平。例如,它不會刪除之前版本中棄用的任務方法。可是,它的確包含一些其餘重大更改,根據經驗,這些更改會相對安全。整體而言,因爲這些因素的存在,在十萬個以上的組件中受影響的組件不超過20個。

對標瀏覽器

咱們對事件系統進行了一些較小的更新:

  • onScroll事件再也不冒泡,以防止出現一些混淆
  • React的onFocus和onBlur事件已在底層切換爲原生的focusin和focusout事件。它們更接近React現有行爲,有時還會提供額外的信息。
  • 捕獲事件(例如,onClickCapture)如今使用的是實際瀏覽器中的捕獲監聽器。

這些更改會使React與瀏覽器行爲更接近,並提升了互操做性。

注意: 儘管從React 17把focus事件切換成了focusin,但onFocus並未影響冒泡行爲。在React中,onFocus事件老是冒泡的,它在React 17中繼續冒泡,由於一般它是一個更有用的默認值。查看 這個sandbox,瞭解能夠針對不一樣的特定用例添加的不一樣檢查。

去除事件池

React 17中移除了"event pooling(事件池)"。它並不會提升現代瀏覽器的性能,甚至還會使經驗豐富的開發者一頭霧水:

function handleChange(e) {
  setData(data => ({
    ...data,
    // This crashes in React 16 and earlier:
    text: e.target.value
  }));
}

這是由於React在舊瀏覽器中重用了不一樣事件的事件對象,以提升性能,並將全部事件字段在它們以前設置爲null。在 React 16及更早版本中,使用者必須調用e.persist()才能正確的使用該事件,或者正確讀取須要的屬性。

在 React 17 中,此代碼能夠按照預期效果執行。舊的事件池優化操做已被完成刪除,所以,使用者能夠在須要時讀取事件字段。

這改變了行爲,所以咱們將其標記爲重大更新,但在實踐中咱們沒有看到它在Facebook上形成影響(甚至還修復了一些bug!)。請注意,e.persist()在 React事件對象中仍然可用,只不過沒有任何效果罷了。

反作用清理時機

咱們正在使useEffect和清理函數的時機保持一致。

useEffect(() => {
  // This is the effect itself.
  return () => {
    // This is its cleanup.
  };
});

大多數反作用(effect)不須要延遲刷新視圖,所以React在屏幕上反映出更新後當即異步執行它們(在極少數狀況下,你須要一種反作用來阻止重繪。例如,若是須要獲取尺寸和位置,請使用useLayoutEffect)。

然而,反作用清理函數(若是存在)在React16中同步運行。咱們發現,對於大型應用程序來講,這不是理想選擇,由於同步會減緩視圖的更新(例如,切換標籤)。

在React 17中,反作用清理函數會異步執行 —— 若是要卸載組件,則清理會在視圖更新後運行。

這反映了反作用自己如何更緊密地運行。在極少數狀況下,你可能但願依靠同步執行,能夠改用useLayoutEffect來代替。

注意: 你可能想知道這是否意味着你如今將沒法修復有關未掛載組件上的setState的警告。沒必要擔憂,React專門處理了這種狀況,而且不會在卸載和清理之間短暫間隔內發出setState的警告。 所以,取消代碼的請求或間隔幾乎老是能夠保存不變的。

此外,React 17會根據它們在tree中的位置,以與效果相同的順序執行cleanup。在之前的時候順序有時會不一樣。

潛在隱患

可複用的庫可能須要對此狀況進行深度測試,但咱們只遇到了幾個組件會由於此問題中斷執行。好比:

useEffect(() => {
  someRef.current.someSetupMethod();
  return () => {
    someRef.current.someCleanupMethod();
  };
});

問題在於someRef.current是可變的,所以在運行清除函數時,它可能已經設置爲null。解決方案是在反作用內部存儲會發生變化的值:

useEffect(() => {
  const instance = someRef.current;
  instance.someSetupMethod();
  return () => {
    instance.someCleanupMethod();
  };
});

咱們不但願此問題對你們形成影響,咱們提供的eslint-plugin-react-hooks/exhaustive-deps的lint插件(請確保在項目中使用它)會對此狀況發出警告。

返回一致的undefined錯誤

在React 16及更早版本中,返回undefined始終會報錯:

function Button() {
  return; // Error: Nothing was returned from render
}

這很容易無心間返回undefined:

function Button() {
  // 這裏忘記了寫ruturn,因此這個組件返回了一個undefined。
  // React會報錯而不會忽略它。
  <button />;
}

之前,React只對class和函數組件執行此操做,但並不會檢查forwardRef和memo組件的返回值。這是因爲編碼錯誤致使。

在React 17中,forwardRef和memo組件的行爲會與常規函數組件和class組件保持一致。在返回undefined時會報錯

let Button = forwardRef(() => {
  // 這裏忘記了寫ruturn,因此這個組件返回了一個undefined。
  // React17會報錯而不會忽略它。
  <button />;
});

let Button = memo(() => {
  // 這裏忘記了寫ruturn,因此這個組件返回了一個undefined。
  // React17會報錯而不會忽略它。
  <button />;
});

對於不想進行任何渲染的狀況,請return null。

原生組件棧

當你在瀏覽器中遇到錯誤時,瀏覽器會爲你提供帶有JavaScript函數的名稱及位置的堆棧信息。然而JavaScript堆棧一般不足以診斷問題,由於React樹的層次結構可能一樣重要。你不只要知道哪一個Button拋出了錯誤,並且還想知道 Button在React樹中的哪一個位置。

爲了解決這個問題,當你遇到錯誤時,從React 16開始會打印"組件棧"信息。儘管如此,它們仍然不如原生的JavaScript堆棧。特別是它們在控制檯中不可點擊,由於React不知道函數在源代碼中的聲明位置。此外,它們在生產中幾乎無用。不一樣於常規壓縮後的JavaScript堆棧,它們能夠經過sourcemap的形式自動恢復到原始函數的位置,而使用React組件棧,在生產環境下必須在堆棧信息和bundle大小間進行選擇。

在React 17中,使用了不一樣的機制生成組件堆棧,該機制會將它們與常規的原生JavaScript堆棧縫合在一塊兒。這使得你能夠在生產環境中得到徹底符號化的React組件堆棧信息。

React實現這一點的方式有點很是規。目前,瀏覽器沒法提供獲取函數堆棧框架(源文件和位置)的方法。所以,當 React捕獲到錯誤時,將經過組件上述組件內部拋出的臨時錯誤(並捕獲)來重建其組件堆棧信息。這會增長崩潰時的性能損失,但每一個組件類型只會發生一次。

若是你對此感興趣,能夠在這個PR中閱讀更多詳細信息,可是在大多數狀況下,這種機制不會影響你的代碼。從使用者的角度來看,新功能就是能夠單擊組件堆棧(由於它們依賴於本機瀏覽器堆棧框架),而且能夠像常規JavaScript錯誤那樣在生產中進行解碼。

構成重大變化的部分是,要使此功能正常工做,React將在捕獲錯誤後在堆棧中從新執行上面某些函數和某些class構造函數的部分。因爲渲染函數和class構造函數不該具備反作用(這對於SSR也很重要),所以這不會形成任何實際問題。

移除私有導出

最後,值得注意的重大變化是咱們刪除了一些之前暴露給其餘項目的React內部組件。特別是,React Native for Web過去經常依賴於事件系統的某些內部組件,但這種依賴關係很脆弱且常常被破壞。

在React 17中,這些私有導出已被移除。據咱們所知,React Native for Web是惟一使用它們的項目,它們已經完成了向不依賴那些私有導出函數的其餘方法遷移。

這意味着舊版本的React Native for Web不會與React 17兼容,可是新版本可使用它。實際上,並無太大的變化,由於React Native for Web必須發佈新版本以適應其內部React的變化。

另外,咱們刪除了ReactTestUtils.SimulateNative的helper方法。他們從未被記錄,沒有按照他們名字所暗示的那樣去作,也沒有處理咱們對事件系統所作的更改。若是你想要一種簡便的方式來觸發測試中原生瀏覽器的事件,請改用 React Testing Library

安裝

咱們鼓勵你儘快嘗試React 17.0 RC版本,在遷移過程當中遇到任何問題均可以向咱們提出請注意,候選版本沒有穩定版本穩定,所以請不要將其部署到生產環境。

經過 npm 安裝 React 17 RC 版,請執行:

npm install react@17.0.0-rc.0 react-dom@17.0.0-rc.0

經過 yarn 安裝 React 17 RC 版,請執行:

yarn add react@17.0.0-rc.0 react-dom@17.0.0-rc.0

咱們還經過CDN提供了React RC的UMD構建版本:

<script crossorigin src="https://unpkg.com/react@17.0.0-rc.0/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17.0.0-rc.0/umd/react-dom.production.min.js"></script>

有關詳細安裝說明,請參閱文檔

相關文章
相關標籤/搜索