大家好,我是若川。面試、學習源碼系列、年度總結、JS基礎系列
譯者注:本譯文是在「在線對話 React.js 核心開發者」一個半小時直播的基礎上進行的原文翻譯,包括了直播中的所有問答內容,盡可能保留了 Dan 回答的中心語義,部分表達為了語句通順、方便理解,適當使用了成語或短語替代。文章超過 1.5w 字,文中包含了大量的專業概念與啟發性思想,通讀大約需要 30 分鐘。建議先收藏下來,慢慢閱讀。最后祝大家都能夠有所收獲!

主持人:Hi Dan,來跟觀看直播的小伙伴們打個招呼吧!
Dan:嗨,大家好!
主持人:現場大多數人都已經認識你了,可不可以再介紹你一下你自己?
Dan:謝邀,我是 Dan,是 React 的核心維護者之一,過去 5 年一直在 React Team 工作。這差不多就是我的自我介紹了。
12 歲開始編程
主持人:我從你的博客中看到,你從 12 歲就開始編程了,非常的厲害,你是怎樣對編程產生興趣的呢?
Dan:應該說是機緣巧合吧,我其實并不是自己主動去學習編程的。當時在學校做作業的時候,我很喜歡做一些展示。我不知道大家現在還用不用 PPT ,當時我用的是 PowerPoint 2003
,PPT 中內置了一種編程語言。如果你點擊右鍵打開菜單,可以完成一些宏錄制的操作。這些操作會產生很多小的程序,我起初不知道它們是什么,但是我非常的感興趣,之后我就買了一些書,從此開始了編程的學習。
React 狀態管理工具
主持人:這是你第一次和中國的工程師見面,我們這邊準備了很多問題,你準備好回答熱心網友們的提問了嗎?
Dan:我準備好了。
主持人:你對 React 狀態管理工具怎么看的?
Dan:我想不同的人可能會選用不同的庫,如果要我來做推薦的話,我可能不會去推薦任何一個特定的庫。真正重要的問題不是選擇什么狀態管理庫,而是理解「狀態」的類型,理解「狀態」是什么。因為「狀態」是一個比較籠統的、比較大的概念,我認為我們不應該去浮于表面地去討論哪個庫更好,而是去看你要處理的狀態是什么種類,使用這些庫的目的是什么,選用不同方案帶來的差異是什么。比如說,有一些 UI 狀態,針對的是顯示的 UI 組件。針對文本輸入框來說,文本內容有哪些,文本是否被選中,是否處于 focus / hover 狀態,或者是記錄當前頁面被選中的 Tab 之類的,這些都是 UI 的狀態。另一種狀態更像是 cache,緩存從服務器返回的數據,它可能更像是某種草稿。所以我們需要結合實際的需求去選擇狀態管理工具。對于 UI 狀態來說,我個人不推薦使用任何庫,使用 React 自帶的狀態就夠了。可以使用 React State 或者是 React Context,通過組件樹將狀態傳遞下去。對于數據緩存的場景來說,我并不推薦使用 “狀態管理” (此處 Dan 用手比了個引號 ????)相關的解決方案。我更建議你使用一些專門用來處理數據緩存的庫,比如 React Query
,Apollo
和 React Relay
。
主持人:現在很多人都想用 React Hooks 和 Context 去代替 Redux
,你剛才的回答也提到了類似的點,你可不可以分享一些 React Hooks 背后的設計理念?
Dan:現在 React Hooks
只能被用于函數組件,我們也沒有計劃讓它們兼容 class 組件,因為這是兩種不同的范式,把它們混淆在一起并不合理。
我認為,這更像是一個關于 Hooks 設計理念的問題,我不確定應該怎么回答。總的來說,Hooks 某種程度上反映了我們是如何看待 React Component 的。從概念上講,Component 就是一個函數,它接收一些 props 屬性,然后返回 UI。Hooks 是對這個函數的一個增強,組件樹上有了狀態的一席之地,你可以用 Hooks 去記錄狀態,或是去記錄行為等等。這就是一個大致的思路。順著這個思路走下去,React 可能不像一個庫,你可以把他想象成是 UI 的編程語言。在編程語言中,我們有函數、變量這些概念。如果把 React 看作是一門 “偽” 編程語言的話,Component 就對應編程語言中的函數,而 Hooks 就對應變量。這是我自己的想法,不知其他同學能不能 get 到。
快速上手 React
主持人:很多開發者認為 React 的入門非常的難,所以有沒有推薦新手快速上手的方法?
Dan:我認為這取決于大家為什么覺得入門很難,大家遇到的問題可能不盡相同。其中一點是,React 需要你有 JavaScript 編程基礎。如果你是第一次接觸編程的話確實會比較困難,因為它不是使用 HTML 和模板進行網站搭建的。對于有些庫和框架,你從模板開始,在模板上添加一些條件判斷或循環,通過不斷的小修小補,你就逐漸學會了如何進行編程。但在 React 這里,你是從編程語言開始,一上來就要寫代碼。如果你不會編程,React 的學習曲線就比較陡峭。有些人剛接觸 React 覺得很難上手,等過一段時間學會了 JavaScript ,再回來看 React 就不那么困難了,這就是 JavaScript 帶來的門檻。另一方面,我覺得有個好的開發環境很重要。比如 Next.js
或是 create-react-app
這類工具會幫你把項目開發所需的配置都準備好。有時候,人們覺得入門難只是因為搞不懂如何才能創建一個單頁應用(SPA)。這些并不是 React 的問題,但人們往往會將它們歸咎到 React 身上。如果你把 Next.js
作為項目起點的話,我覺得會是一個不錯的開始。
React 避坑
主持人:下一個問題。對于已經開始用 React 的人來說,可不可以給一些建議,幫助大家避免一些坑?
Dan:有一點很重要,如果你使用 React Hooks,那么通常都要使用配套的 lint 規則。我們對 Hooks 有兩個推薦的規則,可以使用插件的方式集成到你的項目里。此外,我覺得理解 React 中的兩個概念很重要。其一是 不變性(immutability ),你需要知道如何做到更新 (update) 狀態,而不改變(mutate)它。這一點你可以用 擴展操作符(...)或是其他語法實現。這里推薦一個很棒的庫叫 immer,它可以讓你在編程中使用 Vue / Svelte 風格的變更操作,但同時又能夠保持不變性。如果你對這點很苦惱的話不妨試一試。另一點是你要理解 React 的渲染流程應當是純粹的(Rendering is supposed to be pure),當組件進行渲染的時候,它就是在計算下一個 UI 應該長什么樣子,你不應該在渲染流程中摻雜其他的操作。我認為理解 UI 是計算結果 (UI is a calculation)這個范式是非常重要的。
函數式編程
主持人:目前 React 越來越靠近函數式編程(functional programming)了,但有人說 JavaScript 并不是一個注重函數式編程的語言,你如何看待這個矛盾?
Dan:可能 React 確實比其他 UI 庫更偏向函數式一些,但我不認為它是一個面向函數式編程的框架。人們在這方面其實有個共識,那就是所有喜歡函數式編程的人都不覺得 React 是函數式的,因為它不夠“純粹”或是一些其他的原因。對于 React,我更喜歡「functional-lite / functional-light programming」這個概念。React 從函數式編程中借鑒的一個很重要的思想就是將復雜的事物分解成幾個可以組合的函數,像是盡可能地去使用不變性(immutability)。但 React 的代碼看上去并不像傳統的函數式編程代碼,因為 React 傾向于使用更加直接的編碼風格。舉個例子,如果你需要一個循環,你可以直接在代碼中寫循環,而不需要寫更高階的函數,然后再用復雜的方式將他們組合起來。寫 React 的方法就和寫 JavaScript 一樣,所以代碼看上去也不像是 Ramda 或者 Lodash FP 那樣的「函數式」。所以 React 應該算是處于一種中間地帶吧。它既集成了函數式編程的想法與概念,又能讓你在大多數的時候使用 JavaScript 主流的編程方式去進行編碼,我覺得這應該不算是矛盾吧。
前端發展太快?
主持人:目前的前端是一個正在飛速發展的領域,你是如何不斷提高自己,跟隨技術進步的腳步的呢?
Dan:很有趣的問題,其實我覺得前端的發展并沒有那么快。我這些年沒有看到太多的新的東西,有可能是我自己看的不夠仔細吧。不過我看到的大多數成果都是已有功能的迭代與完善。有些東西每一年或者兩年發布個新的版本,但是大多數情況下不管是新的發行版還是新的庫,用的思想還是原來的思想。所以,如果你對已經存在的東西足夠熟悉、理解足夠深的話,你可能就不會對新出現的東西感到新奇了,因為它們某種程度上講是同質化的東西。所以我覺得準備迎接新事物的最好方法就是去熟悉已有的東西,當你足夠熟悉之后,你看到就只有相似性了。
主持人:React 的未來發展趨勢是怎樣的?
Dan:這是個大問題啊,像是未來十年的路線圖之類的,我們現在正在做的很多功能其實都與這個問題相關。
主要問題
主持人:那么目前最主要的 Issue 有哪些呢?
Dan:這一點很有意思,我們其實在面對很多不同的問題,也有不同的 team 在用不同的方式去解決它們。我可以給你舉幾個例子,比如說數據獲取(data-fetching)。如何設計一個具有良好擴展性的數據獲取方案,使得組件的數量不會引起請求 Waterfall,從而保證 UI 的一致性。你可以將邏輯寫在離被調用位置更近的地方,這樣數據和代碼就能實現并行加載。data-fetching 是一個相當復雜的問題,你還要考慮到如何讓用戶用著方便,現在的 data-fetching 其實還是挺惱人的。這是當下要解決的一個大問題,另一個大的主題是各種的優化,關于代碼分割(code-splitting),服務器端渲染(Server Side Rendering),如何將多余的東西從 JavaScript 的打包中剔除出去,如何讓 Hydration 的花銷更小等等。這可能聽上去像是一個完全不同的領域,但是類似的點還有很多,通過這些優化我們可以讓 React 從本質上優于第三方庫和其他解決方案。另一個大的主題是關于動畫(animation)的,但我們還沒有開始這個方向的相關工作。但對于上述的所有方向,我們都會嘗試與以往不同的方案去解決。很多 UI 框架或者庫都傾向于去獨立應對單點的問題,針對單個問題給出不同的解決方案,通過提供某些便利的 API 實現更優的開發體驗等等。但我們在我們看來,有些問題是彼此關聯的。我們會嘗試使用更理論、更系統的方式串聯起相關聯的問題,然后解決它們。所以如果要我講 React 未來的樣子的話,我希望它能夠成為一個工具,一個幫助我寫組件的工具,里面集成了 data-fetching,代碼分割,動畫渲染等等所有功能,而且這些功能都無縫地組合在一起。因為 React 的中心思想就是像樂高一樣,將各部分功能組合起來,我們希望在未來能夠支持這些功能。
Concurrent Mode
主持人:有些朋友們問到了 concurrent mode 的相關問題。React 的 concurrent mode 自 16 版本就已經提出了,但到了 React 18 才終于發布了出來。React Team 在設計這個功能的時候遇到了哪些問題和挑戰,你們對于這個功能在將來又有哪些計劃呢?
Dan:好的,我首先要說明一下,React 18 還沒有正式發布,我們目前只為庫的維護者們發布了一個 alpha 版本。如果你訪問 reactjs.org/blog,你會看到一篇名為《The Plan for React 18》的文章。如果朋友們想去了解 React 18 的新功能或者發布的時間計劃可以去看一下。我們還建立了一個工作小組,并邀請了 50 名左右的社區開發者來協助我們完成一些 React 18 發布的相關工作,比如確保整個 React 生態中的庫都能兼容新的版本。相關信息都在 GitHub Discussion 里面,非組員雖然不能評論,但是可以閱讀。你可以從中找到很多關于發版的信息,其中就包括你問的 concurrent mode 的信息。正如你所說,我們在很多年前就聲明了要做真正的 concurrent mode,不過這期間有一個很大的策略上的變化,那就是它不再作為一個單獨的模式(mode) 來呈現。所以我們最終添加到 React 中的可能并不是一個 concurrent mode,而是一種并發渲染的機制。本質上來說,并發意味著 React 有能力在同一時間更新多項狀態。比如你在更新狀態的時候,有些獲取數據的狀態更新會花費比較長的時間,渲染一個復雜頁面的時間成本就會很高。如果使用了并發渲染,這些場景下 React 就不會阻塞住瀏覽器。這意味著頁面在進行長時間的狀態更新的同時,你還可以去輸入文字,后臺的狀態更新不會阻塞你的交互。基本上,你可以把并發渲染想象成是在后臺跑 setState()
。這確實是我們一直想在 React 中實現的功能,但重要的是它不再是一個單獨的模式了。當你需要使用并發能力的時候,我們提供了一個叫 Transition 的功能,你可以用 Transition 包裹 setState()
的操作,這樣在調用 API 的時候 React 就會進行并發渲染。所以這個在將來會是一個可以隨時調用的特性,而不是作為一個 Web 應用的全局的模式,他只針對于你包裹的操作生效。就挑戰而言,我們確實遇到了不少挑戰。因為我們是從理論出發的,我們得出了很多理論性的想法,比如我們認為「并發是好的」,「長時間阻塞瀏覽器是不合理的」,「渲染應該是可以被打斷的」等等。然后我們基于這些想法著手去做,在 2015 年完成了一個原型,之后又重寫了 React 來讓它能夠真正實現這些想法,這就是 React 16 版本。之后我們確實花費了幾年的時間來思考如何能夠將這些功能應用到實際生產中。全新的 Facebook 官網 facebook.com 就使用了 React 的并發特性。諸如 「渲染應該是可中斷的」,「setState()
應該是并發的」以及「渲染應該在后臺完成」這些小小的想法實際上帶來了很多的后果,這也是我們花了一段時間才意識到的。這些特性帶來了更好的服務器端渲染(server-rendering),更好的代碼分割 (code-splitting) 和更好的數據獲取(data-fetching),在將來我們還會用相同的思路去實現更好的動畫效果。這個想法在很多地方都得到了體現,因為它本質上為 React 提供了讓渲染能夠「異步」的能力,這個世界上有很多東西天生就是異步的,比如我上面提到的那些功能。梳理這些功能,搞清楚怎么讓他們一起運作起來確實花了我們不少時間。但我們現在基本上都已經搞定了,這些特性都會集成到 React 18 里面。所以,我再一次建議感興趣的朋友去 React 官網的博客查看 React 18 的發布計劃,點擊相關的討論區查看相關的細節。
體驗 React 18 Alpha
主持人:所以聽眾們現在就可以使用 React 18 Alpha 版來體驗相關功能了對嗎?
Dan:是的,但有兩點需要注意。其一是要去博客看一下新版本的使用說明。比如項目升級了版本之后可能需要將 ReactDom.render()
方法替換成 ReactDom.createRoot()
方法,諸如此類會有一些新的 API 進來,如果你不做替換的話,程序可能會報 warning,但這就是你開始使用新特性的方式。另一點是 strict mode
的用戶們需要注意的,新版的 strict mode
會更加嚴格,這就意味著原有的東西在新版本下可能會 break 掉。所以如果你的程序在新版 React 中完全跑不起來了,可以嘗試移除 strict mode
,可能就是這個原因導致的。還有一點需要記住的是,alpha 版 React 主要是為庫的開發者們提供的版本,有很多庫,比如 Redux 等,本身還沒有更新支持。所以如果你的項目依賴了大量的第三方庫,那你的項目在更新 React 18 后很可能會崩潰,但這就這個發行版的目的,讓庫的作者有充足的時間去做升級。所以,如果你發現你的項目在新版 React 中完全運行不了也不要灰心,尤其是那些因為引入第三方庫導致程序崩潰的用戶們,等到 React 穩定版發布的時候,大多數流行的庫應該都已經修復了問題并且做到了兼容。
React Lane
主持人:能簡單介紹下 React Lane 是怎么設計的嗎?
Dan:這是一個相當技術性的問題。我們其實不希望別人知道有 React Lane 這個東西,因為它不是一個公開的 API,而是一個涉及到 React 內部實現的東西。但我可以簡要說明一下它是什么。正如我上面說的,我們為 React 添加了并發的功能,這意味著任意時刻都有可能有多個狀態在同步更新。并發對于我們的 Transition 特性尤為重要。比如說你在輸入框進行輸入,回車后頁面更新,這是一個常規的頁面更新操作。但如果你想要在輸入的同時實現自動補全,或者想要從網絡上獲取候選項列表需要連接后端,從前的做法是你使用 useEffect
自己去管理這些異步渲染的流程。但現在我們會在 React 內置這套流程,你可以用同步的代碼寫法來處理異步的數據操作。我一直認為 UI 是一個瞬間的狀態(state in time),或者說瞬間的時刻(moment in time),所以要表達這一點,你應該為列表的結果創建一個 Transition,這樣瀏覽器請求可能會花費一些時間,但是等數據回來的時候 React 會負責正確地去展示結果。Lanes 是一個 React 內部的機制,用來標記 React 當前正在處理哪項狀態的更新。你可以想象當你每次調用 setState()
的時候,React 會把他加到一個 bitmask 中,類似于很多的開關(switch)或者是復選框(checkbox)。有點像是任務列表,如果你用過任務列表你就知道任務有高優先級和低優先級。或者是 Github 上的 Label,Label 可以用來標記是高優先級,也可以用來標記 bug,feature,discussion。同樣對于 setState()
來說,React 也提供了一組標簽,來標記它是不是 Transition,是否是一個緊急更新等等,這個標簽就是 Lane。當 React 進行渲染的時候,它會根據 Lane 來選擇哪些狀態更新需要被執行。如果有一個緊急的狀態更新,需要一次緊急的頁面重新渲染,這時候就只會執行在 Urgent Lane 下的狀態更新。之后如果所有緊急的更新操作都完成了,我再去檢查 Transition Lane 中的狀態更新,如果這個時候從網絡上返回了數據,那就把它渲染出來,比如去加載自動補全的候選項列表。所以說 Lane 是 React 的一個內部機制,使用 bitmask 來將狀態更新與優先級關聯起來,之后 React 再根據它去更新狀態。
React Server Component
主持人:Server Component 的 RFC 草案去年年底發布了,請問 Server Component 的主要目的是什么。
Dan:是的,我們去年 12 月發布了一個 Server Component 的技術預覽,但它還處于比較早期的階段。這應該算是一個還處在研究階段的特性,與 React 18 相比更偏實驗性,也不會包含在 React 18 的特性之中,可能會在其之后才發布。但廣義上來說,它與 SSR 并不一樣,這也是人們常常混淆的點。最大的區別是,Server Component 只運行在服務器上,它不會被下載下來。你可以把它們想象成某種 API,以往的客戶端應用可能會請求 JSON API 來獲取數據, Server Component 與之類似,但是 API 換成了在服務器端運行的組件。這樣做的優勢是你不需要去下載任何代碼,可以達成性能上的優化。另一個優勢是,因為這些組件運行在服務器端,它們可以直接與數據庫、微服務、或者其他任何在這臺服務器上的資源進行通信。你不需要把這些資源暴露給客戶端,就把它們留在服務器上就好。
主持人:有些用戶已經嘗試過使用 Server Component 了,所以當我們需要在項目中使用服務器組件的時候,我們需要維護三個組件而不是一個,這帶來了額外的復雜性不是嗎?
Dan:好的,對于這個問題,我個人其實不太認同這種說法。我不想把這個問題定義為我們需要維護三個組件而非一個。我覺得更準確的說法是,在傳統的 React 中,我們只有一種類型的組件;而在 Server Component 中,我們把它們分成了 Server Component,Client Component 和 Shared Component。這個問題就好比你原本只有一部手機,現在你既有手機、又有手表、又有電視,但這并不意味著你要同時去用這三個東西,你只是有了更多的選擇。所以對于這個問題而言,不是說你的組件在將來都會以三種形態存在,而是你現在只能用 Client Component 的組件。如果只用 Client Component 能夠滿足需求的話固然很好,但是當這個 Server Component 這個特性落地的時候,你會有更多的選擇來做相同的事。到那個時候,如果你想要使用 React 寫博客,想要讀取文件系統,這些操作不需要很多復雜的框架也能做到。如果你只是想要使用 PHP 或者 Rails 類似的傳統 web 編程風格去做一些數據庫的讀取操作,Server Component 也能幫你做到。在大多數場景下,我們預期用戶們會通過框架來使用 Server Component 功能,比如使用 Next.js
。所以幸運的話,你并不需要想太多,也不需要創建什么 API,你只需要在文件名中加上 .server.js,這個文件就可以在服務器端運行,你就可以使用所有 Server Component 的功能了。
主持人:所以我們可以直接通過一些框架來使用這個功能?
Dan:是的,我覺得大部分情況應該都是如此。因為框架就像是整個項目的基礎設施一樣,會為你連接并組織好項目的各個部分。雖然你也可以去手動配置,但是肯定不如使用框架來的方便。特別是框架還可以實現無配置的啟用這些功能,如果你要自己動手,你就得自己把項目的各個部分串聯起來。目前我們還沒有針對如何使用 Server Component 給出使用的建議,但是鑒于 Next.js
已經支持了 Server Component,如果你想要部署自己的 Server Component,你可以去看看他們是怎么實現的,然后復制他們的做法。又或者你可以直接使用 Next.js
或者其他類似的框架。
React 與 Vue
主持人:人們常把 React 與 Vue 作比較。你個人是如何比較這二者的呢?希望能從設計、性能和使用目的來談一談。
Dan:首先我個人沒有使用過 Vue,所以我可能做不了特別詳細的比較。從技術上來講,兩者可能采取了不同的實現方式吧。Vue 建立在 mutability 的基礎上,可以直接去修改 state。這樣做帶來了一些好處,它寫起來更簡單,你可以用 mutative style 去寫代碼,這種寫碼方式也受到了許多開發者的喜愛。但它也有一些缺陷,比如站在更高的層次來看,有些功能可能是出了名的難以實現,或者根本不可能實現。比如我們正在做的 Transition 功能,又或者是我們想要做的新的動畫特性。二者之間的差異太大,就像被一道深深的技術鴻溝隔開了,Vue 在不斷探索我們可以用 mutability 做什么,而 React 在不斷探索 immutability 能夠做到的事。但我們對自己的方向非常有信心,因為我們的背后有近 50 年的函數式編程的研究成果,所以我們明白雖然在落實到實踐中可能會遇到一些困難,但是從理論分析的角度我們的方法還是合理的。Vue 和 React 只是在探索兩個不同的方向,這件事很好,你只需選擇自己喜歡的那個就好。談到設計的話,我覺得二者最大的區別是 Vue 更傾向于去做一些妥協與折衷,也更關注能不能去解決實際的問題。比如它會提供一些人們呼聲很高的功能,包括更加便利的動畫,更加便利的條件渲染、組件模板等等。但 React 可能不太一樣,我們希望確保 React 提供的每個解決方案都是完全可靠的、可信賴的。所以對于某些僅為了方便而存在的功能,我們抱著寧缺毋濫的態度,寧愿不去實現它們。比如 React 想要重做動畫功能,我們最終交付給用戶的功能一定比現有方案快上好幾倍。我們不會去標準化一些現有的方案,因為我們對如何實現動畫這個功能有自己的構想,這個構想會與 React 庫深入結合,它會不同于我們現在看到的所有框架。所以說,在 Vue 中,你可以更快地得到自己想要的功能,但是在 React 這里你可能找不到自己想要的功能,只能依賴第三方庫來實現,直到我們搞清楚解決問題的方法,知道如何把這個功能做對、做好,我們才會把它集成到 React 中來。這就有點像 Android 和 iOS 在設計理念上的區別。iOS 相對 Android 就經常會少一些功能,比如最初幾版 iOS 連復制粘貼的功能都沒有,直到出現了通知中心才有了復制粘貼功能。iOS 的特性可能確實會滯后一些,但是當它確定要做某件事的時候,它會把他實現的非常好,我想這也是我們的工作理念。我認為兩種思路各有優劣吧。
Flow 與 TypeScript
主持人:Vue 3 已經不再使用 Flow 了,而 React 還在使用,對這一點你怎么看?
Dan:我不覺得這一點很重要啊。這更多還是取決于庫本身是如何編寫的,我們目前還是需要去做一些靜態的類型檢查。不過這與用戶們使用的 React 類型沒有關系,只影響 React 庫自身的開發。我們使用 Flow 只是因為我們一開始就用了它,它也能滿足我們后續的需求。我們也可以使用 Typescript,但我覺得這不是很關鍵。這完全不會影響到 React 的用戶,因為我們內部的類型和我們暴露出去的類型也有差異。所以這件事不是很重要,我們并不 care。我們當然可以切到 Typescript,但遷移成本較高,不值得我們這樣做。
React 的競爭力
主持人:最近有些新出現的前端框架,比如 Svelte 和 Solid-js,他們都不再使用 Virtual-DOM 了,并且聲稱在性能表現和 bundle 體積上都超越了 React,你怎么看待這一點?
Dan:還是一樣,我沒有深度地體驗過這些框架,所以我不太好下論斷。但還是那句話,看上去總有新的東西在出現,但其實并不是這樣的。真正能夠走通的設計思路其實并不多,React 在走 immutable 這條路,Vue 和 Svelte 在走 mutable 這條路,然后 Svelte 和 Vue 之間在具體實現上可能會有些差異,Solid-js 應該也是這條路上的一個分支。說到 Virtual-DOM,我其實不是很喜歡這個術語。我們盡量不去使用這個詞,因為每個人對它都有不同的理解。但我覺得它不是一個與性能有關的東西。我想再重申一遍,我不愿去使用 Virtual-DOM 這個詞,因為它是一個非常容易混淆的概念。當我們提到 “Virtual-DOM” 這個詞的時候,我們說的其實是一種 UI 在內存中的表現形式。這應該是你期望得到的東西,因為它為開發者提供了更多的選擇。比如 Server Component 是運行在服務器端的,但是它需要定義一個數據格式來傳遞服務器輸出的結果,然后在客戶端接收。如果你使用 Server Component 做了一個頁面過渡的效果,你肯定希望將它與現有的 UI 合并起來。但這一點又跟 PHP 或者 Rails 那種傳統的客戶端渲染不一樣,傳統方法渲染的時候,老的頁面會消失,然后新的頁面逐漸加載,它并不保留任何的狀態。如果你有一個搜索框,你輸入一些東西,點擊搜索跳轉到新頁面,此時搜索框會被清空。但使用 Server Component 不會發生類似的事情,我們把組件樹發回給客戶端,我們可以在客戶端中將它和現有的 UI 做 diff ,然后渲染有差異的部分,所以搜索框的狀態可以被保留下來。該被替換的組件替換了,這一點之所以能夠實現,是因為 UI 在內存中有一種中間態的表現形式,它就是人們說到的 Virtual-DOM。這是用來說明 Virtual-DOM 作用的一個例子。另一個例子是,如果我們現在要將一個完整的動畫系統集成到 React 中,我應該怎么做。比如我們現在要做一個手勢拖動觸發的動畫,我們肯定不希望每一幀都去做渲染,我們希望只計算幾個版本的 UI。假定最左邊是 0% 的版本,最右邊是 100% 的版本,我們計算好幾個類似的關鍵幀,在拖動的時候就可以利用插值計算出當前 UI 樣子。但問題又來了,你怎么去生成那些關鍵幀呢?你需要一個 UI 在內存中的表現形式,這樣你才能在兩者之間做插值。這個例子也說明了 Virtual-DOM 的意義,有些特性離開它就沒法實現。所以 Virtual-DOM 與性能無關,它的存在只是為了讓一些特性變得可行,我們認為這種 UI 在內存中的表現形式還是很重要的。盡管在一些綜合性測試里面,我們可能會比其他方案慢 10% 左右,但你應該明白,這并不是一個問題。至少在我們的測試中,當我們分析 Facebook 一些復雜頁面渲染的時候。我們親眼看到的是,React 只花費了 10% 的時間,另外 90% 的時間開銷是應用代碼導致的。框架能夠優化的,可能也就只有 2% 的速度。所以當你去看那些測試的時候,測試代碼可能有 1000 行,但是只有一個 component。你可能就只關注到了那 2% 的差異,但它并不能反映應用的實際表現。所以真正影響性能的是整個 app 如何工作,以及我們如何能讓用戶代碼運行的更好。這就是 Concurrent Rendering,Server Component 以及所有新特性存在的意義。我們要去支持更大規模的用戶代碼,而不是試著去贏下這些基準測試比賽,有些基準測試只有一些不到 10 行的小組件, 它們不值得我們付出時間和努力。
主持人:最近有很多新的庫出來了,人們想知道 React 如何做到跟上潮流,保持競爭力。
Dan:是的,我想我已經從某種角度上回答了這個問題。我們正在努力從全局出發,用更加通用的方式去處理那些從單點很難以解決的問題,比如說 data-fetching,代碼分割,未來還有動畫以及其他的功能,React 在將來能讓它們的實現變得更簡單。我猜這就是 React 競爭力的來源吧。不過我個人覺得 React 并不需要某些特性來讓它“保持”競爭力。比方說當我要用 React 做一些原型的時候,我要去畫一些 UI,React 用起來會很自然,很順手。即便 React 在將來五年沒有新的 feature,我也會覺得使用嵌套的函數來表述 UI 是一件很自然的事情。我覺得這種開發方式很合理,我也很喜歡 React 現在的樣子,所以我并不覺得 React 要變得更加 “有競爭力” (Dan 用兩只手比劃了引號)人們才會使用它。但我還是要重申一下,我們現在正在做的很多功能,可以讓當前復雜的開發工作簡單化,我對它們的存在感到興奮。
SSR、CSR、NSR、ESR
主持人:Vue 和 React 都在解決網頁渲染的問題,但是當前渲染網頁有很多種方法,像是 SSR(Server Side Rendering), CSR(Client Side Rendering), NSR(Native Side Rendering), ESR(Edge Side Rendering),你是怎樣描繪未來五年前端的發展的呢?
Dan:這個問題是說,你有很多種不同的方式來運行你的代碼,你可以讓它在客戶端運行、在服務端運行,或者在其他地方運行,問題在于你如何去組織它們。但我覺得這里的大部分工作應該都會由框架來完成,所以再說一遍吧,我還是推薦你去使用框架。像 Next.js
就是一個不錯的選擇,它能讓你對這部分如何實現有一個大體的印象。Next.js
可以通過對 React 現有概念的包裝來簡化這部分的操作,比如你要使用 Server Component 的話,Next.js
有他自己的 API,getServerSideProps() 或者是類似的方法,你不需要使用 React 原生的方式的去組織項目,Next.js
會通過它自己的 API 將你的項目編排好,讓 Server Component 等類似的功能生效。所以我覺得未來... 哦我突然想到,如果你對 Server Component 感興趣的話,不妨去看一下 Shopify 家最近新出的框架,Hydrogen
。他們最近放出了一個 Demo 演示,如果你搜索 「Shopify Hydrogen Demo」 或者類似的關鍵詞就能看到,里面演示了他們是如何使用 Server Component 的,這也是對未來場景的展望吧。如果你只有一個渲染樹,比方說你在寫頁面或是寫個博客網站,你只需要去在服務器端的文件系統中讀取一些 Markdown 文件,然后將它們渲染到組件的對應位置,最后把渲染好的組件傳遞給客戶端,你只需要考慮組件就 OK 了。至于在哪里執行代碼邏輯,這完全是由你自己決定的。有些可能是在構建的時候執行的,有些頁面如果你愿意的話也可以在服務器端運行,只有那些與實際交互相關的代碼會被下發到客戶端,然后在客戶端運行。理想情況下,這整個流程還是一個單一的渲染樹,你不必去實時關注這部分的差異。你只需要給一些小提示,比如修改一下文件的擴展名,代碼就會自動地在最合理的位置去執行。所以你不用想那么多,想自己既要做服務端渲染,還要做客戶端渲染,有可能還要其他端的渲染。框架會幫你把這部分搞定的,你只需要使用同一個范式來寫代碼,它在所有位置都可以生效。
React 與框架
主持人:如何評價「React 更像是一個系統,而非一個框架」這種說法?
Dan:我不會說 React 自身是一個框架,我認為這個說法并不公平。首先我認為 React 只是一個庫,因為它并沒有去約束你工作的方式,沒有去約束你項目的結構,它只是給你提供了工具,讓你能夠去構建組件。但我確實覺得 React 正在成為一種架構(Architecture),但這和框架又有所區別。因為想要新建一個 React 的框架其實有很多種方式,但是現在 React 對某些技術的實現也有了自己的「觀點」,比如應該如何進行 data-fetching,如何去做路由,如何去做服務器端渲染,這些功能應該怎么組合起來。React 只是一種構建 UI 的方式,而不同的框架可以基于這點為用戶提供更加上層的能力。
吸引前端開發者特質
主持人:你認為好的科技公司最吸引前端開發者的特質有哪些?能不能簡單列舉兩三個?
Dan:嗯,我想一想。我覺得最重要的一點是「要能夠從身邊的人身上學到東西」。對我來說,有一個能夠無時不刻學習新事物的環境是最重要的。當然這不是說你要去不斷學習新的庫,不要因為對前端這一領域全貌的不了解就說「前端一直在變化」這種話。我認為事實并不是這樣的。正如我說的,大多數新事物都只繼承了舊的思想,雖然層出不求,卻只有相同的本質。如果你往更深的地方去探究,你就會發現它們都是同一個東西。但我覺得理想的環境就是,公司 / Team 鼓勵你去學習新的知識,團隊中有 10 年或者 15 年工作經驗的前輩,你可以從他們那里獲得收獲。組內的高度自治也很不錯,你不會被安排去做特定的工作,你可以從任務清單中完成自己想做的工作,這一點也會讓工作更有趣。被動的激勵自然很重要,但如果你能夠發揮主觀能動性,作出自己的選擇,那也是非常有價值的。我不知道這是否回答了你的問題。
如何學習 React 代碼
主持人:很多朋友對你的個人經歷感興趣,你剛加入 Facebook 的時候是如何學習 React 的結構、概念,并慢慢開始貢獻代碼的呢?
Dan:React 有一個相當復雜的 codebase,里面有很多復雜的上下文導致很難上手。一般來說當有新組員加入的時候,我們會花很多時間陪他們一起看一遍代碼。剛開始他們可能會做一些小的 bug 修復或者相對獨立的小功能點,來慢慢地熟悉代碼。整個流程最關鍵是要熟悉架構,一旦你對整個項目的架構有了充分的了解,接下來的所有工作就都順理成章了。有一個對我幫助很大的點,那就是去查看人們的 Issue,并去解決他們。在不同的時間點上,我都會過一遍現在處于 「Open」狀態的所有 Issue,看看自己是不是能夠解決這些問題,思考一下自己是不是能夠理解他們在說什么,如果不懂的話就去學習上下文。我想我個人可能已經看了數千個 Issue 了,如果你想要快速上手項目的話,我認為這是一個非常不錯的學習方式。你可以點擊查看處于「Open」狀態的 Issue,應該差不多有 500 個,你可以從頭看,也可以跳轉到最后一頁從最老的那個開始看。通過看 Issue 你逐漸就能理解當前有哪些問題,慢慢地去理解代碼,理解項目的更新歷史等等,所以這就是通過 Issue 學習的方法。幫助提出 Issue 的人,在我看來是做貢獻的最好方式,實話說我們并不需要人們去貢獻那么多的代碼,通常 Review 代碼也需要花費我們很長時間,而且人們其實很難把功能寫對。所以當我們收到社區提交的代碼的時候,我們其實并沒有從中收獲很多。但替我們回答人們的 Issue 確實對我們很有幫助,有時候用戶提交了一個 Bug 報告,但實際上是他們自己寫的代碼有問題。如果社區中有人幫助他們發現了問題,找到了 bug,我們就不用再在這些 Issue 上花費時間了,這也是我最歡迎的貢獻方式。
維護 React
主持人:React 有一個非常龐大的 codebase ,請問 React 開發組和社區是如何維護如此復雜的代碼倉庫的?
Dan:代碼倉庫確實帶來了一些挑戰,但是有挑戰的原因不是因為代碼倉庫大,而是因為一些其他的原因。首先你要知道怎么去進行開發,怎樣把代碼跑起來。舉個例子,我們依賴了非常多的自動化測試,大概有數千個測試要跑吧,我覺得數量可能在 5000 個以上。目前我們寫的測試代碼可能比源代碼還要多,我們非常依賴這一點。我們從中學到的一點是:一定要去針對 React 的 public API 寫測試腳本。我們之前的做法是對獨立的模塊進行單元測試,但這對 React 這種項目來說是一個非常糟糕的想法,因為一旦你要重寫 React,那些針對老代碼的測試用例就沒用了。在我們重寫 React 的過程中,我們不斷地意識到「哦,又有一堆測試代碼不能用了,因為之前的代碼不存在了」。所以我們把所有的測試用例修改成了僅對 public API 進行測試,來模擬用戶的實際行為。測試用例只能用 ReactDom.render() 或者類似的方法,并不能訪問內部的 API。在只針對 public API 進行測試的情況下,就算我們替換了原有的文件,測試樣例也照樣能夠跑通,還能順便測試我們的重寫是否是正確的。這算是我們從實踐中總結出的經驗吧。另一個有趣的點,也可能是比較有爭議的一個點,就是我們有很多文件都存在兩個版本。如果你看源碼的話,你會發現我們有 .old.js 或者 .new.js 這種文件。這些文件基本上是復制粘貼出來的,它們的內容也基本相同。我們用它們來測試一些可能會帶來風險更新,對于 Facebook 網站,我們可以同時部署多個版本。我們有時候會將這些實驗性的更新寫到 .new 文件里面,然后在 Facebook 的實驗環境里面進行回歸測試,如果測試各項指標沒有劣化的話,再將這些更新復制到 .old 文件中去,所以說我們的網站在任何時刻都有兩個版本。在實驗測試的過程中,其他人也可以去做其他部分的代碼提交。這聽上去可能比較奇怪,但實際用起來效果還是不錯的。
主持人:所以這就是你們在開發中保持 React 代碼質量的秘訣嗎?
Dan:是的,我覺得是 Facebook 的測試環境真的非常好,我們不僅有針對 React 倉庫本身的測試,還有很多針對 Facebook 進行的測試。有時候我們在 React 的開發過程中把功能搞壞了,我們可以在測試環節發現問題。在生產環節中,甚至是生產環節之后,我們都可以部署實驗測試,然后去觀察哪些指標出現了下降。比如我們去年十月進行了一次重構,那之后我們不得不將手頭的工作暫停了兩個月,因為我們看到某些指標下降了 1%,我記得好像是網站的評論數之類的少了 1%。我們就要查清楚到底是由性能問題導致的,還是因為 bug 或者其他什么原因。你要知道,其他的框架往往做不到這一點,它們往往需要先發布一個版本,然后可能一年之后才會有人發現里面的 bug,然后上報。但我們不會這樣做,我們要確保 React 的高質量,所以我們花費了數月的時間來查這個問題。用二分法不斷地分割 commit 提交,不斷地去做實驗測試,甚至要精確到每個 commit。最終,我們找到了 bug,我們部署的測試環境中確實有一個 bug。當我們將其修復之后各項指標就再次回歸正常了,這個結果也給了我們信心。你要知道,在 Facebook 這種體量的公司中,即便只有 1% 的指標劣化,也會影響數百萬人。所以如果有大的問題的話會比較容易發現。
閱讀 React 源碼?
主持人:作為一個使用 React 的前端開發者,我們需要去閱讀 React 庫的源碼嗎?如果需要的話,有沒有好的閱讀代碼的方式?
Dan:我覺得沒有必要,這可能會是一項相當困難的工作,因為我們沒有在任何其他地方提及 React 自身的架構是怎樣設計的。如果你上來就開始讀代碼,你可能會感到非常困惑,不明白為什么這樣設計。這可能也是我們將來需要改進的一點,將來到了某個時間點,我們可能會去解釋這里面的實現原理。但我覺得如果你只是想玩玩的話,這個過程應該也算不上痛苦。比如我自己就很喜歡做一件事,用 debugger 的步進功能(step-in) 來一行行跑代碼,看看代碼會跑到哪個函數,運行代碼會用到哪些不同的文件。另一件你可以做的事是使用 Chrome Performance Tools,你可以打開 Chrome 的 Performance Tab,點擊錄制,然后在你的應用中進行一些操作,之后點擊停止,你會看到一張火焰圖或者火焰表。這個分析結果非常有用,它就像是某種堆棧,你可以看到函數的調用順序,看到代碼中正在發生的事。它常用來測試性能,你可以看到你代碼中的哪一部分運行的比較慢,但你也可以用它來做一個當前函數總覽,因為上面展示了函數的名稱。你可以看到狀態變化會引發哪些不同的事情。你可能會發現「哦?這個函數在所有的地方都被調用了,它是做什么的?」。之后你可以點進去,看看里面到底執行了什么。沿著這種性能測試來一步步探索代碼我覺得也是一個不錯的學習方式,可以了解到 React 在哪部分花了時間,哪些函數是其核心之類的。
保持對 React 熱情
主持人:你是如何保持對 React 的熱情的?
Dan:我就是很喜歡,不知道為啥,仔細想想的話,可能是因為 React 非常符合我對 UI 代碼的看法吧。在進入 Facebook 之前,我就開始使用 React 了,那時候我還在一家小的創業公司。我們當時在開發一個非常復雜的應用,試著將它從 Backbone 遷移到 React。我們遷移不是因為當時 React 是大趨勢,而是因為當時用 Backbone 開發一個復雜的 UI 真的非常的困難,相比起來用 React 來實現真的是太簡單了。其中心思想就是寫一個狀態的函數,來表述當前屏幕上應該展示哪些東西。這也是我常常問自己的問題,我的組件應該長什么樣子,哪些內容應該展示在屏幕上。這種思想和我的編程思路天然就是契合的。但確實有些東西不太好歸納到這個范式里邊,比如 data-fetching 就是一個典型。你真正想要考慮的是屏幕上有哪些內容,但一涉及 data-fethcing,你就要去思考如何與服務端通信、怎么等待服務端返回結果、等待的時候可能還要設置某個狀態,這樣用戶再次發起請求的時候才能忽略掉上次請求的結果。我原本只想考慮哪些東西應該展示在頁面上,但這些東西卻將問題復雜化了。這也是我們想要為用戶提供 data-fetching 能力,比如 Suspense 功能的原因,它可以幫助你減少思考問題的復雜性。我只要考慮想要看到什么,從哪里獲取,即便是外部 URL 也不用去考慮時間。我只想要表達目前屏幕上存在哪些東西,之后交由 React 決定如何去展示它們。我覺得讓我非常激動的一點是,現在 React 已經能夠很好地實現 UI 的組合與嵌套了,但是還有 data-fetching,動畫,代碼分割,data-asynchronous 這些目前難以實現的東西,我希望這些功能能變得更加簡單易用。我希望在五年以后,我們能夠用更加簡單的方式構建復雜的應用,而這份簡單來源于 React 幫助用戶處理了這些復雜性。這就是我的想法。
如何像你一樣優秀
主持人:如果我想要變得和你一樣優秀,有沒有什么好的前端學習資料可以推薦的?
Dan:我不確定自己算不算優秀,我其實在很多方面都沒有跟上時代的腳步。比如,如果你讓我去做一個好看的應用,可能很難去完成。因為我對 CSS 的知識還停留在 2010 年,我對于 CSS Grid 和 Flexbox 也并不是非常了解,我不知道這是不是你希望聽到的。但是如果你需要我推薦學習資源的話,我覺得一個很有幫助的點是你可以去挑選一些 UI 的樣例,然后從零去實現它們。這個過程中不要去使用 React,也不要去使用其他任何庫。比如試著去實現一個帶自動補全的輸入框,或者是對話框中的一個 tab 之類的,體驗一下完成這些操作的復雜度。另一點我很喜歡的是做一些小游戲,這也很有幫助,比如做一個井字棋,或者是貪吃蛇。做游戲會推動你去思考,思考程序如何去設計,思考如何去解決問題,而這一點是你平時寫表單、寫界面所訓練不到的。總結來說,我推薦你做一些體量小,但是有深度的東西,然后從中獲得收獲吧。
如何度過閑暇時光
主持人:請問工作之余,你是如何度過你的閑暇時光的?
Dan:我工作之余并沒有做太多有意義的事。我過去非常愛玩堡壘之夜,我玩的其實并不是很好,但還是玩了很久。我建筑建的很爛,如果有人打我,我就建一堵墻,但通常我都會被嚇一跳,然后手忙腳亂地溜走。但我其實也有一陣子沒玩了。現在的話一般就聽聽歌,散散步,做一些業余項目之類的。
justjavascript.com
主持人:你寫了一個 「Just Javascript」 的系列文章,我個人也非常喜歡這個系列,已經等不及要看下一期了,想問一下新文章的進展。
Dan:好的,有些朋友們可能還不知道,這其實也是我個人的業余項目,叫做 justjavascript.com
。它像是一個 JavaScript 的課程,它現在還是免費的,但是再過幾周可能就不免費了。這個課還是很特別的,它不像其他課程一樣用傳統的方式講 JavaScript 的知識,它更多是去教你怎么理解代碼,課程里面有很多可視化的東西,比如動畫呀、圖表呀之類的。它也會教你去從零實現一些東西。我想要通過它告訴人們如何去正確地閱讀代碼,如何正確地理解代碼的運作方式。當然我們也在制作一些新的內容。現在我們正在將整個課程打包,然后上傳到網站上,完成后這將是一個付費課程,你購買后可以看到里面的內容,所有的課程、繪圖與測試問題都會呈現在網站上。我們還不確實是否會有人買這個課程,如果這樣做能夠賺錢的話,我們可能會更新更多的內容。這個項目之前一直是免費的,已經有一年半的時間了,我們想要看看它是不是一個可行的商業化產品,之后再決定如何去運營它。這個項目幾周后就會正式上線,想要支持的朋友可以關注一下。
對中國開發者說點什么
主持人:有沒有什么想要對中國開發者們說的話?
Dan:我不知道該說點兒啥。我不確定在中國有多少人在用 React,我只知道 Vue 在中國非常的流行。但我覺得有更多的選擇是件好事,我非常感謝那些 React 文檔的翻譯者們,以及很多 React 庫的中國開發者們。我不知道 React 能不能在中國流行起來,這個遠在異國的我們可能影響不了。但是如果你對 React 非常感興趣,你們有機會改變周圍的環境,讓它流行起來。如果 React 流行起來,以后找工作可能會更容易吧(笑)。我們很高興看到人們翻譯博客文章,傳播知識,舉辦會議,真心地感謝為此付出的每一個人。
主持人:你今后有沒有想要在中國的 React 社區中更活躍一點,我們其實很想跟你多多交流。
Dan:當然了,我其實也很想,但是不知道怎么做。正好有你們邀請了我,我感覺這次活動很酷,之后我們可以更經常地交流。
主持人:好的 ,我這邊問完了所有觀眾的提問,非常感謝 Dan 陪我們進行了這次漫長的在線對話。
Dan:感謝你們邀請我。
主持人:感謝,我們下次再見!
Dan:再見!
附錄
英 | 中 |
---|---|
State Management | 狀態管理 |
Single Page Application | 單頁面應用 |
Immutability | 不變性 |
Spread Operator | 擴展操作符 |
Funtional Programming | 函數式編程 |
Funtional-lite programming | 輕函數式編程 |
Data-fetching | 數據獲取 |
server-rendering | 服務器端渲染 |
Flame gragh | 火焰圖 |
Tic-tac-toe | 井字棋 |
Snake game | 貪吃蛇 |
Fortnite | 堡壘之夜 |
最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。
推薦閱讀
我在阿里招前端,該怎么幫你(可進面試群)
畢業年限不長的前端焦慮和突破方法
前端搶飯碗系列之Vue項目如何做單元測試
老姚淺談:怎么學JavaScript?
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。
點擊上方卡片關注我、加個星標
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~