使用 React 的一些經驗

文章有些過期, 更多消息請往導航:
http://nav.react-china.orghtml

在公司用 React 寫界面的已經有一段時間了, 有些習慣能夠沉澱一下
目前代碼當中主要仍是我我的的使用習慣, 後面應該會改善
最近進行的一個頁面是參照 Flux 架構作的, 只能說嘗試 Flux 還太淺了
後面我會大體羅列一些點, 用 React 的同窗能夠對比下或者指點一些問題react

使用 CoffeeScript 寫 JSX

我對縮進語法的認同以及對重複寫打開關閉符號的反感不解釋了
Teambition 先後端代碼大部分是 CoffeeScript, 因此我不用有太多的顧慮
JSX 當中的語法, 好比是這樣:git

<div class="avatar" data-tip="name">{text}</div>

編譯的 JavaScript 是這樣:github

React.createElement("div", {class: "avatar", 'data-tip': "name"}, text)

用 CoffeeScript 目前是寫成這樣:編程

# $ = React.DOM
$.div className: 'avatar', 'data-tip': 'name', text

createElement0.12 出現的, 目前依然兼容 React.DOM 當中的方法調用
長期看也許會修改適應新的語法, 但目前暫時不動:canvas

# $ = React.createElement
$ 'div', className: 'avatar', 'data-tip': 'name', text

0.12 中調整增長了 createFactory, 因此實際項目當中處理並不優雅
目前導出模塊時會有不少這樣的代碼, 將來模塊化時是有必要調整的:後端

React.createFactory React.createClass
  displayName: 'app'
  render: ->
    $.div {}

使用 CoffeeScript 寫邏輯

由於 JSX 編譯結果當中一個標籤或者模塊就是調用函數返回對象
因此在 CoffeeScript 當中直接把標籤看成普通數據來處理就行了
undefined 這樣的數值, 在 React 當中自動忽略, 比較方便
因此常常會有相似模版引擎的用法:react-router

$.div className: 'demo',
  if @state.showMenu
    $.div className: 'menu', 'demo of menu'

實踐當中, 因爲有些位置業務邏輯複雜繁多, 一個 render 當中節點可能不少
經常使用的方案就是建立 renderMenu 這樣的方法, 在 render 當中調用
個人理解是複雜的結構中 render 適合寫邏輯判斷, 其餘方法寫具體的模塊
這樣, 之後複製代碼或者改邏輯, 在 render 一個方法裏就看完了
map 是列表裏經常使用的一個辦法, 一般我也會拆出 renderItem 這樣的方法架構

React 的 addons 裏有個 classSet 方法, 對於拼接 className 很是有用
http://facebook.github.io/react/docs/class-name-manipulation.html
我最初是本身寫公共函數拼接的, 後來切換到了這個方案併發

CoffeeScript 語法容易出錯的地方

CoffeeScript 很簡潔是一方面, 不過這個簡潔確實會有一些容易出現問題的地方
好比這樣兩行, 第一行結尾的逗號, 在修改代碼時常常容易錯誤刪除:

$.div className: 'a',
  $.span {}

還有 Object 在 CoffeeScript 裏的寫法, 若是屬性較多單行不夠也容易出錯:

# 這段代碼示範的是一個錯誤
$.input className: 'a',
  onClick: @onClick
# 訂正後若是用多行, 好比能夠這樣
$.input
  className: 'a'
  onClick: @onClick

另外 CoffeeScript 的 if 是隻返回最後一個值的, 注意不要寫這樣的代碼:

$.div {},
  if @state.showMenu
    # 這個例子裏第一行不能正確返回的..
    $.span {}, "first line"
    $.span {}, "second line"

上邊提到的 createFactory API 對於類庫也有一些影響
若是從第三方導入 API, 就可能要考慮用上邊的 API 進行封裝
相對 JSX 來講, 這裏是一個不方便的地方

模塊都是本身寫的

公司當前項目當中幾乎沒有用到第三方的 Component
緣由大體理解有這麼幾個:

  • 一般界面都挺簡單的, 不用費不少的事情就寫出來了

  • 幾乎全部的 UI, 設計界面有較明確的要求, 咱們要本身保持風格一致

  • React 沒有看到很是流行的組件庫

雖然追求模塊化當初對代碼重用的熱情是很是高的, 但 React 並不自動解決問題
我想說, 使用 React 拆分模塊的成本比 Backbone 拆分 View 低很是多
因而在個人應用中有着比 Backbone 稍微細的模塊拆分, 也就稍微多了一點重用
可是, 注意重用的模塊是不帶邏輯的很是小的模塊, 大的模塊通常很難重用

按照咱們公司設計對界面的不斷演進, 我注意到模塊當中邏輯會緩緩增長
這不斷給模塊複用增長難度, 由於我須要加上配置項才能繼續複用模塊
按我最近的微博上的討論, 我感到稍微大的模塊, 就應該以不進行復用來設想
http://weibo.com/1651843872/BA2tIks5x

Modal

大量使用 Modal 組件我認爲是對單頁面應用界面的探索不夠深刻形成的
當界面上須要更多的交互, 立刻想到不破壞初始的狀態, 彈一層出來...
不過確實是乾脆利落的辦法, 結果是咱們有不少的 Modal 須要處理

以往用 Backbone 和 BootStrap, Modal 是附着在 <body> 之下的
在 React 當中, 涉及到 Modal 打開狀態的問題, 我目前的處理是在模塊內
也就是說, Modal 是深深嵌套, 而後用 fixed 定位打開的:

$.div {},
  $.div {},
    $.div {},
      $.div {},
        if @state.showModal
          $.div
            className: 'modal-demo', # style: {position: 'fixed'}
            onClose: @onClose
            $.span {}, 'content demo'

一樣的方案, 也用在菜單上, 彈出菜單也須要一個狀態來控制打開個關閉
不過菜單會涉及到一些定位的問題, 處理起來更加麻煩一些

關於菜單有個全局惟一菜單的問題, 就是一個菜單出現後要關閉其餘菜單
首先, 點擊菜單內部的元素不能關閉菜單, 須要截斷事件冒泡
其次, 點擊菜單外部須要失焦關閉菜單, 須要在 window 上進行監聽
問題來了, React 假如建立好了菜單, 又監聽到冒泡, 關閉了怎麼辦?
目前我控制了編輯按鈕延時觸發, 避免監聽, 犧牲是打開菜單點擊觸發依然打開而不是關閉

我詢問了下公司 Backbone 裏的處理, 用到了對 event.target 父節點探測
可是在我目前的代碼當中, 一個模塊會被限制只獲取儘量少的數據...
並且因爲減小了 jQuery 的依賴, 意味着檢查父節點不會那麼容易
我尚未想到個清晰的方案, 須要繼續探索

方法的分類, 命名的習慣

大多的 Component, 定義的結構和命名的習慣大體已經開始固定下來了
我注意到定義模塊的分類還跟 Elm 有挺多的類似之處, 好比 Elm 是這樣:
https://github.com/evancz/elm-todomvc/blob/master/Todo.elm

  • Model 初始狀態

  • Update 對狀態能夠有哪些更新

  • View 模塊組合跟渲染

  • Input 綁定模塊和外部的交互

在我這邊項目當中一般是這樣的:

  • 標記 propTypes, 初始化 state, 綁定模塊生命週期的 hook

  • 一些自定義的方法

  • 事件綁定

  • 渲染相關的方法

好吧... 這個其實也不那麼像.. 並且一般 View 都是須要完成這幾部分功能的
跟 Backbone 區別是, Backbone 極可能由於不方便拆模快, 方法不少甚至變亂
我估計還有監聽數據更新操做 DOM 的方法太多, 以及處理其餘功能的方法等等
React 裏的方法和模塊比較緊密, 所以也就明確了幾個類別

關於命名, 自定義方法比較隨便, 但會使用和模塊 API 差異較大的名字
state 當中命名, 我目前統計下來以 show 開頭爲主, 用來控制界面
至於渲染, 天然是 render 開頭的, 好比 renderName, renderActions
在事件當中, 儘可能都是 on 開頭, 好比 onNameClick, onTextChange 之類
有一些事件是子模塊的事件, 我也用的是 on 作前綴來處理
不過有的模塊子模塊不少, 方法也就有些亂, 這也須要想點辦法改進

列表的節點改變順序的漸變

界面是這樣的, 一個列表, 會不停改變順序, 過程當中使用 CSS 進行動畫
須要注意, DOM 裏的順序改變, 節點刪除和建立, CSS 動畫就不生效了
因而就須要保持 DOM 順序不變, 可是經過 absolute 定位控制節點
我採用的手法是使用 id 進行排序, 保證 DOM 的順序不變:

data
.map (a) ->
  key: a.id
  data: a
.sort (wrap) ->
  wrap.id
.map (a, index) ->
  $.div style: {top: "#{index * 20}px"}

Ajax 代碼設計的問題

React 教程給的 Demo, Ajax 沒有很明確的介紹, 我也沒深刻挖..
Ajax 拿到的數據, 要麼設置在 props, 要麼設置在 state
固然我考慮的是, 應用當中有 Store, 就儘可能用 Store 了, 方便複用
不過實際的應用當中, 高達 10 層的模塊嵌套也可能存在, 並沒那麼自由

我以前一直操心 Ajax 代碼放在哪, 是在 Component 方法內部?
仍是放在 Store 當中? 仍是獨立存放在另外一個位置?
麻煩在於, Ajax 加載狀態可能跟模塊內部緊密相關..
同時, Ajax 代碼寫在模塊當中, 重用的可能性又明顯降低了
Ajax 大部分是能夠引發 Store 更新的, 那麼在 Store 裏也許不錯
但是, 不在 View 當中, View 就要用 action 通知 Ajax 了, 這很不方便
很差取捨... 當前犧牲的是模塊的複用性...

與此相關的一個問題是模塊被移除以後, 異步方法調用 setState 會報錯
修補的辦法是臨時加上 isMounted 對模塊進行檢驗...
不過這個方案讓我有點困惑, 顯然不是一個漂亮的能免於各類問題的方案..
也許異步調用盡可能放到模塊之外是更好的辦法, 但並不明朗..

強制使用 propTypes

我是寫了不少模塊後纔開始接觸到 propTypes 這個屬性的:
http://facebook.github.io/react/docs/reusable-components.html
這個能夠理解成函數來講, 是參數的類型, 起到提示(console.warn)的做用
當模塊參數多到必定程度, 也不太規則, 就很是須要定義好參數類型
一方面, 這個是文檔, 另外一方面, 進行重構時是很是重要的提醒

全局的多語言的處理

咱們的單頁面應用須要處理英文兩種, 以及設置語言後當即在界面範圍內更新
之前咱們模仿的是 Wunderlist, 用特性的屬性配合 jQuery 來切換
可是在 React 當中, 全局刷新是很是輕鬆愉快的事情,
不須要寫上屬性給選擇器調用, 也不會影響 DOM 狀態
同時須要注意, 爲了達到全局更新的效果, 根節點就須要監聽語言的改變
我目前在作的方案是將語言抽象爲全局的 Store, 做爲數據交給模塊渲染
而語言發生改變時, Store 立刻就發送事件, 界面天然就更新了

思惟的轉換, 數據流

React 編寫應用的方式和 Backbone 差異很大, 甚至和 MVVM 也有不小差異
我認爲這種轉變跟從過程式編程到函數式編程的轉變很是類似
在 Backbone 用進行怎樣的操做, 想的就是先作什麼, 後作什麼
甚至於發送怎樣的事件, 讓其餘的模塊來完成這一部分的功能
而在 React 當中, View 之間不是經過事件進行交流的, 而是 propsstate

而 React 的界面, 就像個不斷渲染的 canvas, 你只能修改數據來改變界面
而不是界面是不少個有狀態的 View, 每一個 View 都須要額外去照顧
因而就須要思考界面由幾個狀態控制, 用戶操做怎樣改變那幾個狀態等等
思考數據以怎樣的途徑流動, 每一個模塊根據這一個數據流如何更新本身
想要簡單, 就要儘量用簡單的途徑, 就是單向的數據流

不過比較可能的是, 咱們很難立刻用單向數據流解決到各類業務問題
而後須要一些手段, 按照 @kejunz 今天微博講的:
http://weibo.com/1640297597/BBnTtnxWz

組件上位後,組件間通信問題又出來了,以前好像要麼pub/sub這種模式,要麼回調,數據老是帶有業務邏輯色彩的,要和通用組件徹底隔離目前沒有特別理想的方案。組件併發工做的構想很誘人,實現和通信都有待解決。CSP沒細看過,感受是很酷的東西。期待有實踐的人發文章分享。

React 在數據層面的沒有給出相近的方案, 這方面有好多要探索的
一個趨勢是不少函數式編程的概念會被借鑑過來, 特別是從 Clojure
CSP 最初是 Go 當中的.. 不畏懼艱難的話嘗試一下 Clojure 當中的概念:
http://phuu.net/2014/08/31/csp-and-transducers.html

Router 模塊不穩定

這是我這兩天對付 react-router 遇到了問題, API 更新比較直接
我這會沒跟上 API 更新後具體如何使用, 雖然對其功能並不質疑
固然在項目里語法這樣的事情挺鬱悶的

react-router 和 RequireJS 搭配問題也挺多的, 模塊很特殊啊
代碼使用 CommonJS 寫的, 用 Browserify 打包, 並且須要全局要 React 對象
按我參與 Issue 上的記錄, 事情並不樂觀, 我只能儘可能考慮 CommonJS 的方案:
https://github.com/rackt/react-router/issues/171
固然將來還有 ES6 須要考慮, 我如今暫時先拋開模塊的問題了

Backbone 和 React 混合的架構

在 Backbone View 銷燬的時候, 須要調用一下下面的 API

React.unmountComponentAtNode

不然可能出現同時存在兩個節點的狀況, 在調試工具能夠看到

Modal 原先在 Backbone 當中掛在 body, React 中沒那麼靈活
若是把 Modal 放在外部, 就要考慮額外的, 好比用事件去通知外部了
在 React 當中使用事件是讓人不放心事情, 我考慮仍是放在嵌套的 DOM 內部

有些 Component 就是應該從 Store 取數據的

雖然一廂情願但願 Component 都是從 props 取數據, 實際上不合適
某些位置, 直接從 Store 的當中獲取, 能比較輕易得簡化的邏輯
特別明顯的好比, 從位置 A 打開的 Modal B, 且 B 是比較複雜的
而 A 的界面, 可能很簡單, 或許都不須要 Store 當中的數據
那麼這時, B 直接訪問 Store, 效果很是好. 我認爲大部分的 Modal 都是這樣的

這也就是說, 並非全部的 Component 都是適合模塊化的
React 的 Component 寫界面, 就是普普統統地會遇到各類數據傳遞的問題
有一部分數據, 特別是嵌入在簡單 UI 當中的複雜 UI, 極可能須要很大的權限
以此爲代價, B 上層的模塊 A 就能夠只傳入更少的數據, 從而被簡化

使用 state 即使數據很容易拿到

React Devtools 的 Chrome 插件, 很容易看到 Component 的 props 和 state
在設計 Component 的 state 和 props 時, 這是很好的一個參考
好比說, 哪些 UI 是很頻繁須要確認 props 或者 state, 就拆爲模塊
這種狀況下, 模塊拆分就可以帶來實際的用處, 就是方便查看狀態

相似的狀況下, 好比咱們用 Store, 而數據能夠很容易從 Store 拿到
一種可能但很差的作法是, 監聽更新, 而後調用 .forceUpdate() 更新
把這個數據放在 state 會是一個更好的作法, 能夠避免手動調用強制更新
同時, 經過調試工具查看模塊當前的 state 就很方便了
方便理解看成一個原則來控制 Component 的粒度應該仍是不錯的

相關文章
相關標籤/搜索