ReactEurope 2016 小記 - 上

不知不覺間,2016 年已經過去了一半的時間。在這過去的六個月中,前端開發界又發生了許多激動人心的變化。與過去幾年不同的是,各個前端社區討論的重點已經從幾個通用框架(React、Angular、Vue)的優劣轉向了各個通用框架周邊生態環境的建設,以及如何更好地使用這些通用框架。

作為國內最早實踐 React 的團隊,我們一直都在關注著 React 及其相關生態環境的發展,包括但不限於 Redux、Immutable、GraphQL、Relay 等。

兩個星期前,ReactEurope 2016 在法國巴黎如期舉行。與 ReactEurope 2015 不同的是,在這次的大會上,我們看到整個 React 的生態系統都呈現出了百花齊放的狀態。

大會 Day 1 的第一位演講者從去年 React 及 React Native 的開發者 Christopher Chedeau 換成了今年 Redux 的作者 Dan Abramov。從這一有趣的變化中,我們可以看出目前 React 本身已經廣泛地被業界所接受,整個 React 社區的下一個重點是如何優雅地解決因為引入 React 而帶來的一些 side effect(副作用)以及如何圍繞 React 構建起一套完善的解決方案並提升廣大開發者們的開發體驗。

The Redux Journey - Dan Abramov

在 React 社區中,過去的一年是屬於 Redux 的。

上線一年以來,Redux 在 npm 上的下載量已經超過了三百萬次,使用 Redux 完成的項目總數正在以驚人的速度上升。Dan 在演講中還特意提到了 Twitter 團隊目前也已經引入了 Redux,這讓 Dan 感到非常榮幸,因為他自己一直以來都是 Twitter 的忠實用戶。

Dan 分享了自己在開發、推廣 Redux 中的寶貴經驗。不同於傳統框架,Redux 並沒有從 Features 及 APIs 的維度去定義自己,而是相應地設置了一些 nnConstraints 以及 Contracts。

Features => Constraints (Should be Useful)

  • Single state tree
  • Actions describe updates, no setters
  • Reducers apply updates

APIs => Contracts (Should be Social)

  • Reducers
  • Selectors
  • Middleware
  • Enhancers

從功能或特色上來講,Redux 為開發者提供了時間旅行、數據持久化等功能,讓開發者可以很方便地記錄(方便收集數據)並復現(方便 debug)用戶的所有操作。而為了獲得以上提到的這些好處,開發者們又必須遵循 Redux 制定的一些規範。Redux 希望所有的使用者都可以理解 Redux 的思路,而不是去折騰一些毫無意義的配置文件然後實現一些簡單的功能。

Redux 的思路:

Redux 是什麼?

  • Redux 是一個擁有當前狀態的狀態變化發送機。

Redux 有什麼功能?

  • 定義所有的事件監聽器及當前狀態

  • 增加或刪除事件監聽器
  • 讀取當前狀態

Redux 如何更改狀態?

  • 發送一個 action 以更改狀態
  • 傳入 action 及當前狀態至相應的 reducer,返回更改後的狀態
  • 更新狀態並再次調用所有的事件監聽器

而從另一方面來講,以 Contracts 的模式來定製 Redux 的 API,使得 Redux 具有了非常強大的擴展性。開發者不僅可以定義自己的 reducer、selector,也可以為 dispatch 函數增加特殊的行為(Middleware)甚至擴展 Redux 本身的 state store。

從我個人的角度來看,Redux 的火熱一方面是因為它規範了 React 中 state 的使用方法並使得開發者獲得上述的諸多好處,另一方面是因為它本身良好的擴展性又催生了一批優秀的 Redux 擴展,做到了即使其本身不完美也可以通過開源社區的力量去修正或擴展自己。

在演講的最後,Dan 提到了 Redux 未來的發展方向,並提供了一些他認為在特定領域有希望解決問題的第三方庫,有興趣的朋友們可以提前加一下 star。

  • Declarative data fetching (relay)
  • Built-in optimistic mutations (relay)
  • Easy to test async control flow (redux-observable)
  • Combining local and server data (no idea yet)
  • Great developer experience (redux-devtools)

A cartoon guide to performance in React - Lin Clark

當整個前端開發界都在為 React 而瘋狂時,到底又有多少人真正了解 React 本身的實現方式,並願意繼續去思考如何提升 React 在實際應用中的表現呢?

來自 Mozilla 的 Lin Clark 以動畫的形式帶我們重溫了一遍 React 的核心思路並提出了以下四點可以提升 React 在實際應用中表現的方法。

Each child in an array or iterator should have a unique 『key』 prop.

關於這點推薦大家閱讀 @twobin 撰寫的 React 源碼剖析系列 - 不可思議的 react diff 中有關 element diff 的章節。

shouldComponentUpdate: if I give you next props and state, should I update you?

在 React 中,你可以通過讓 shouldComponentUpdate 返回 false 的方式來跳過當前組件的 render 過程以提升渲染效率。

但盲目地相信或使用 shouldComponentUpdate 也會帶來一些潛在的問題:

handleClick = () => {n let nextItems = this.state.items;n n nextItems.push(msg);nn this.setState({n items: nextItems,n });n}n

這是一段我們非常熟悉的使用 setState( ) 方法更新界面的代碼,但實際運行的結果卻是新的 msg 永遠也不會更新到界面上去,原因就是因為 this.state.items 和 nextItems 永遠都會指向同一個對象,也就是說 shouldComponentUpdate 永遠都會返回 false。而如果你每次都為 nextItems 指定一個新的對象,這樣即使 this.state.items 和 nextItems 的真實值是相同的,shouldComponentUpdate 也會返回 true 並執行一遍毫無意義的 render 函數。

關於這個問題的解決方案,讓我們來看 Lin 提到的下一個優化點。

Immutability

在 React 中,Immutability 的思路就是如果當前 state 和 nextState 的真實值保持相同,那麼他們都將會被指向同一個對象,這樣 shouldComponentUpdate 可以快速地返回 false,減少不必要的渲染。而如果當前 state 和 nextState 的真實值不同,就會為 nextState 創建一個新的對象並賦予其真實值,這樣 shouldComponentUpdate 就會返回 true 並執行相應的界面更新。

Using setState( ) or connect( ) at lower levels

假設我們有如下的組件:

<List values = {this.state.values} />nn// List Componentn<div>n <button>Click</button>n {n this.props.values.map((v, i) =>n <div key={i}>{v.name}</div>)n }n</div>n

如果我們是以上述的方法去架構這個 List 組件,即以數組的形式向子組件傳遞數據,那麼只要這個數組中任意一個元素的值變化了,每次傳下去的 props 的值都會是不同的,也就意味著我們需要重新渲染整個 List 組件。

換一種方式,如果我們只從 List 組件向下傳遞一組 uid,然後讓每個 div 根據拿到的 uid 去渲染相應的數據,在上述的相同場景中就可以避免每次都重新渲染整個 List 組件,而只是重新渲染某一個特定的 div,從而加快整體界面的重繪速度。

上述方法總結起來,就是在數據真正變化的那層去 setState( ) 而不是在其上面的某層,但考慮到實際項目中的數據結構很有可能不能滿足前端工程師每次都以這樣的方式去架構組件,所以這個優化點還需要以後在業務中多加探索,以獲得最大的收益成本比。

回到 React 本身,Lin 也為我們解釋了 React 本身是如何去加速 DOM 更新的,那就是通過減少和批量處理 DOM 變化的方式。關於減少 DOM 變化,還是推薦大家閱讀 @twobin 撰寫的 React 源碼剖析系列 - 不可思議的 react diff,而關於批量處理 DOM 變化,推薦大家看一下 @楊森 撰寫的這篇 React 源碼剖析系列 - 解密 setState。

Recomposing your React application - Andrew Clark

熟悉 JavaScript 特性的朋友們應該都知道在 js 中可以使用高階函數來簡化一些常用的操作,即編寫一個接受其他函數作為參數的函數。

舉個例子:

function compose(f, g) {n return function() {n return f.call(this, g.apply(this, arguments));n };n}nnvar square = function(x) { return x*x };nvar sum = function(x,y) { return x+y };nvar squareofSum = compose(square, sum);nsquareofSum(2,3); // => 25n

在 React 中,我們同樣可以復用同樣的思想來編寫一些 higher-order components(高階組件)為我們的基礎組件增加一些新的功能,而 HOC 也就是 Andrew 演講的主題。

高階組件可以為輸入的原始組件增加新的功能,也非常適合處理一些每個組件都需要去做的重複性工作,比如獲取數據並將獲得的數據傳遞下去。另一方面,為了使高階組件具有更強的可復用性,Andrew 並不建議開發者去寫一些本身非常複雜的高階組件,而是建議將許多簡單的高階組件複合使用以達到實現複雜功能的目的。

舉個例子:

const enhance = compose(n withProps({n object: { a: a, b: b },n c: cn }),n flattenProp(object)n)nconst Abc = enhance(BaseComponent)nn// Base component receives props: { a: a, b: b, c: c }n

即先使用 withProps 高階組件向 BaseComponent 傳入 props,再使用 flattenProp 高階組件將傳入的 props 拍平,以達到方便 BaseComponent 使用的目的。

看到這裡,我相信許多朋友應該會覺得以上的這個例子有些殺雞焉用牛刀的感覺。的確是這樣,從我個人的角度來看,高階組件最大的意義應該是幫助開發者們去開拓一種新的解決問題的思路,而不是任何問題都盲目地去使用高階組件解決。Andrew 在他的演講中也提到,使用高階組件最大的問題並不來源於技術,而是團隊合作。如果你是團隊中唯一喜歡經常寫高階組件的開發者,那麼當別人來看你的代碼時就會感到異常痛苦,因為他經常需要去翻閱許多文件來了解這個組件到底做了一件什麼樣的事情。

使用高階組件是一件非常考驗使用者架構能力的事情,所以在使用前需要三思後行。

在這一節的最後,還是替 Andrew 推薦一下他所撰寫的高階組件庫 recompose,如果各位有時間的話,建議移步 Andrew 的 github 主頁閱讀下 recompose 的文檔,說不定就能發現一個可以節省你許多開發時間的高階組件呢。

小結

在這次的 ReactEurope Conf 上,共有超過二十位優秀的開發者為聽眾們帶來了非常精彩的演講。從中我們不僅可以學到許多實用的技巧,更重要的是可以了解到世界範圍內的 React 開發者們正在思考並解決一些什麼樣的問題。因為他們今天所遇到的問題,很有可能就是我們在未來會踩到的坑,只有知道了正在使用的開源框架的優點及局限,我們才能更好地使用他們並避免因為框架本身的缺陷而對我們的項目造成重大影響。

因為這次大會上值得提及的點實在太多,我們不得不將這次大會的小記分為兩篇文章,在《ReactEurope 2016 小記 - 下》中,我們將會提到更多關於 GraphQL、Relay 等其他方面的內容,敬請期待。


推薦閱讀:

如何通俗易懂地解釋 Redux 和 Flux 的區別和實現意義?
vue-router源碼分析-整體流程

TAG:React | 前端开发 | 前端框架 |