三年 React 開發經驗的我,遷移到 Vue 的心路歷程

發表於2018-07-25

三年 React 開發經驗的我,遷移到 Vue 的心路歷程

前幾年我一直在使用 React。最初僅有 React,後來使用 Redux 和 React 的其他庫(react-router、react-redux、prop-types 等)配合使用。我喜歡 React 的簡單和方便,使用 React 的時光一直都很快樂。我喜歡這個時代,有太多的好工具幫助我們更快更好地開發應用。

近三個月我在用 Vue 構建 Web 應用,在此我想分享一些我作為一名 React 擁護者的 Vue 使用經驗。我不想寫成一篇 Vue/React 比較的文章,這種文章太多了,包括官方的 Vue 文件(https://vuejs.org/v2/guide/comparison.html)。它只是一些關於切換庫的個人觀點。

如果你使用過 Vue 和 React,或者像我一樣剛剛從 React 切換到 Vue 正在適應,或者只是想多一些瞭解,我希望這篇文章能對你有幫助。

React 和 Angular 相似的地方

相比 React,Vue 有時更多地被拿來和 Angular 比較。實際上,瀏覽 Vue 模板時我們首先看到的就是雙向繫結和 directive,與 Angular 非常類似:

儘管 Vue 支援 JSX,但通常的方法還是將模板和 JavaScript 分開。雖然React JSX 的語法很像原生語法,並且反映了通常的 JavaScript 語法,但 Vue 的模板語法非常高階,它包含 directive、快捷方式和條件渲染,使得 Vue 更像 Angular。不過,相似性也就到此為止了。

當然,前後端使用同一種模板可能會有很大好處(比如 node.js/Pug + Vue/Pug),而且儘管 Vue 提供的眾多 directive 可能很有用,但對於我來說,從 React 的 JSX 切換到 Vue 的模板依然很痛苦。

Redux vs. Vuex

在應用中,React 通常會與某種資料流庫結合使用,最流行的就是 Redux。Vue 也有個類似的資料流庫,叫做 Vuex,我很高興地發現它和 Redux 非常相似。實際上,從 Redux 切換到 Vuex 沒有任何痛苦,因為與 React 跟 Vue 相比,這兩個庫有更多的共同點。

主要的區別就是 Redux 嚴重依賴於狀態的不可修改性。原因就是 Redux 從 React 的思想而來(https://redux.js.org/faq/immutable-data#why-is-immutability-required-by-redux),而且儘管 React 本身能處理可改變的資料,但在 React 中的推薦做法是不要修改 props 或 state 的資料(https://reactjs.org/docs/optimizing-performance.html#the-power-of-not-mutating-data),以便 React 獲得最好的效率。

在 React 中,元件 state 的變化會觸發該元件以下的整個元件子樹的重新渲染。為了避免不必要的子元件重新渲染, 我們需要使用 PureComponent,或儘量實現 shouldComponentUpdate。還需要使用不可變的資料結構讓 state 的變化更容易被優化。(https://vuejs.org/v2/guide/comparison.html#Optimization-Efforts)

然而 Vuex 完全不關心 state 是否不可修改。

在 Vue 中,元件的依賴會自動在渲染過程中跟蹤,因此當 state 發生變化時,系統可以精確地知道哪個元件需要渲染。(https://vuejs.org/v2/guide/comparison.html#Optimization-Efforts)

因此,React/Vue 與元件互動的方式有一些區別,下面我來介紹下這些區別。

Dispatch 和 Commit

Redux 中的資料流十分嚴格且直接。元件會 dispatch action,而 action 由 action 的建立器函式返回。然後 reducer 會根據收到的 action 返回新的 state。最後,元件會通過 store 監聽 state 的變化,並在 connect() 函式的幫助下訪問state中的屬性。

三年 React 開發經驗的我,遷移到 Vue 的心路歷程

每個 action 都會通過 action 建立器。儘管理論上來說可以直接從元件中 dispatch 一個 action,但通常不這樣做。action 的語法本身就鼓勵我們將 action 的邏輯封裝在 action 建立器函式中,即使是最簡單的 action:

儘管 Vuex 的資料流很相似,但它並不嚴格要求元件與 state 互動的方式。首先,元件可以 dispatch action。這通常是一些非同步動作,比如從後臺獲取資料等。之後,action 會 commit 一個 mutation。mutation 函式與 reducer 的相似之處就是,它是唯一能夠改變 state 的東西。但還有另一種方法:元件可以直接 commit 一個 mutation,有時候跳過 action 直接修改資料是很方便的 。

三年 React 開發經驗的我,遷移到 Vue 的心路歷程

從元件 commit mutation 的行為不僅沒有被嚴格禁止,Vuex 的文件甚至鼓勵在非同步的情況下直接使用 action(https://vuex.vuejs.org/en/mutations.html#committing-mutations-in-components)。由於我習慣了 React 的更嚴格的資料流,我更主張嚴格分離的概念——不管什麼情況下,即使是同步或者非常簡單的情況,commit mutation 也應該只能由 action 實施。

如果元件只能通過 action 來建立 mutation,那麼元件和 mutation 之間就會有一個額外的層,保證元件和 mutation 之間的低耦合,最終使得程式碼更容易維護和修改。

從 store 中獲取資料

為了與 React 元件內部的 store 互動,我們需要使用 connect() 函式。我認為 React/Redux 最讓人不爽的一點就是,你不得不判斷哪些元件該用 connect(),哪些不該用。使用 connect() 的元件通常被稱為容器,而不使用 connect() 的一般稱為表現元件,或者“笨”元件。

但 connect() 不能用得太多,因為它的效能很差。但如果只在頂層元件使用的話,需要傳遞給下層元件的 props 就會迅速增多。這個問題曾多次被討論(如這裡https://redux.js.org/docs/faq/ReactRedux.html#react-multiple-components和這裡https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0),但實際上,即使容器元件的數量還算合理,傳遞給下層的 props 也挺讓人頭疼的。

我很意外地返現,在 Vue 中我根本不需要考慮這個問題。store 可以從任何 Vue 元件中訪問,非常簡單:

也就是說,從一個元件傳遞給另一個元件的 props 數量非常少,而且只需要傳遞那些沒有儲存在 store 中的資料。不過,在 Vue 中傳遞 props 的語法卻非常不方便:

這裡我們要給子元件(TodoItem)傳遞 props,但卻不能在子元件定義的位置傳遞,而必須在模板裡傳遞。相比之下,React 中的 props 傳遞更加自然,是在子元件渲染時完成的:

儘管在 Vue 中傳遞 props 很不方便,但好處是,由於 store 能在任何元件中訪問,實際需要傳遞的 props 比 React 中少得多,而在 React 中,即使有足夠多的容器元件,平均每個元件收到的 props 數量也非常大。

更新:新的 React Context API(https://reactjs.org/docs/context.html)提供了一種在元件樹中直接訪問資料而不需要在每層手動傳遞 props。

結論

如開頭所說,本文只是一些我在從 React 遷移到 Vue 時發現的一些最重要的問題。這並不是一篇嚴謹的比較,不能作為選擇庫的依據。但如果你也像我一樣不得不從一個庫切換到另一個庫,或者只是想了解更多的關於兩個庫的資訊,這篇文章也許會有幫助。

總結一下重點:

  • Vue 預設不包含 JSX,很強調指令碼和模板分離;
  • Redux 和 Vuex 背後的資料流思想很相似;
  • Redux 十分依賴於 state 的不可改變性,而 Vuex 不關心 state 是否不可改變;
  • Vue 允許 dispatch,也允許直接從元件中 commit,但最好還是嚴格些,只允許 dispatch 會比較好;
  • 任何 Vue 元件都可以直接訪問 store。

相關文章