用 React Actions Recorder 做爲 Store 寫 Todolist

後續內容有更新, 代碼例子不一致的地方按照倉庫的 README 爲準
https://github.com/jianliaoim/actions-recorder
抱歉沒有不少時間能夠更新這邊的文章細節, 例子大意是對的, 參數已經有調整css

關於

研究 Redux 以後仍是本身從頭實現了一套方案出來
以前也在微博上發過, 名字叫作 actions-recorder, 功能基本和 Redux 一致
https://github.com/teambition/actions-recorderhtml

近幾天配合簡聊作了一些調整, 因而針對新的版本(1.1.x)寫個 Demo 出來
https://github.com/jiyinyiyong/actions-recorder-demo
http://repo.tiye.me/actions-recorder-demo/
調試工具按照簡聊開發的須要, 對深度的 JSON 查看作了基本的優化react

下面是 1.1.x 版本調試工具的截圖:
git

但願對不喜歡 Redux 的同窗能有幫助
這篇文章會描述一下怎樣用 Actions Recorder 來實現 Store
某種程度也算是我對於 React 單向數據流的理解的一下沉澱github

Store

簡聊的應用中, 除了 View 和網絡相關代碼之外, 大體能夠分紅幾個部分:數據庫

  • Schema緩存

  • Updater性能優化

  • Actions網絡

隨後是處理頁面初次加載的代碼, 將這些部分組合起來數據結構

對於 Todolist 這部分會比較簡單, 按照順序依次建立文件
首先是 Schema, 實際上 Schema 是數據表的初始模板
模板在後續的操做當中會經過 immutable-js 添加數據做爲數據庫使用
Todolist 的 Store 這裏用 List 數據結構存儲, 而每一條任務包含三個字段:

# schema.coffee
Immutable = require 'immutable'

exports.store = Immutable.fromJS []

exports.task = Immutable.fromJS
  id: null
  text: ''
  done: false

Updater 是定義 Store 如何更新的函數, 相似數據庫的 Query Language
Updater 是個純函數, 返回結果也是 Store. 和 Redux 的 Reducer 基本一致
通常 Updater 部分我用一個文件夾存放, 其中一個文件做爲索引
索引文件主要用來引用其餘的文件, 而且用巨大的 switch 語句來調用代碼

# updater/index.coffee
todo = require './todo'

module.exports = (store, actionType, actionData) ->
  switch actionType
    when 'todo/create'
      todo.create store, actionData
    when 'todo/update'
      todo.update store, actionData
    when 'todo/toggle'
      todo.toggle store, actionData
    when 'todo/archive'
      todo.archive store, actionData
    else store

Updater 目錄當中的其餘文件用來寫具體的數據庫如何更新的邏輯
好比 Todolist 更新的一些基本邏輯, 用 immutable-js 實現一遍:

# updater/todo.coffee
schema = require '../schema'

exports.create = (store, id) ->
  store.push schema.task.set('id', id)

exports.update = (store, actionData) ->
  id = actionData.get('id')
  text = actionData.get('text')
  store.map (task) ->
    if task.get('id') is id
      task.set 'text', text
    else task

exports.toggle = (store, id) ->
  store.map (task) ->
    if task.get('id') is id
      task.update 'done', (status) ->
        not status
    else task

exports.archive = (store) ->
  store.filterNot (task) ->
    task.get('done')

而後是 Actions, 或者說 Actions Creator, 用來生成 Actions
注意參數的個數, actionType 的字符串是單獨寫的
而且這裏會有一個對 actions-recorder 的引用, 同時也是 .dispatch() 的入口:

# actions.coffee
shortid = require 'shortid'
recorder = require 'actions-recorder'

exports.create = ->
  recorder.dispatch 'todo/create', shortid.generate()

exports.update = (id, text) ->
  recorder.dispatch 'todo/update', {id, text}

exports.toggle = (id) ->
  recorder.dispatch 'todo/toggle', id

exports.archive = ->
  recorder.dispatch 'todo/archive', id

完成了上邊幾個組件的代碼, 最後就能夠用初始化代碼開始作整合了
首先是 .setup() 傳入初始化的數據, 主要是 initialupdater 是必須聲明的
其次是 .request() 方法, 從 Actions Recorder 內部請求初次渲染的數據
而後是 .subscribe() 方法作監聽, 以保證數據實時同步
render() 方法的兩個函數, 第一個是 store 也就是渲染頁面用的
第二個 core 是內部的私有數據, 惟一的用處是供 DevTools 審查:

# main.coffee
React = require 'react'
recorder = require 'actions-recorder'
ReactDOM = require 'react-dom'
Immutable = require 'immutable'

updater = require './updater'

require('volubile-ui/ui/index.less')

Page = React.createFactory require './app/page'

recorder.setup
  initial: Immutable.List()
  updater: updater

render = (store, core) ->
  ReactDOM.render Page({store, core}), document.querySelector('.demo')

recorder.request render
recorder.subscribe render

這樣, 基本的 Todolist 的數據流就整合完成了
數據從 Schema 初始化, 而後從 Actions 導入用戶操做, 經過 Updater 更新
以後, 就由 React.render 從應用頂層講數據更新到 DOM 當中

Node 環境執行

另外注意有些狀況頁面須要在 Node 環境渲染, 也是能夠支持的
固然這邊對於 updater 函數沒有強的依賴, 只是對初始數據依賴
渲染時調用 .request() 獲取數據而後用 renderToString 渲染就行了

# template.coffee
stir = require 'stir-template'
React = require 'react'
ReactDOM = require 'react-dom/server'
recorder = require 'actions-recorder'
Immutable = require 'immutable'

Page = React.createFactory require './src/app/page'

{html, head, title, body, meta, script, link, div, a, span} = stir

line = (text) ->
  div class: 'line', text

module.exports = (data) ->
  recorder.setup
    initial: Immutable.List()
    
  stir.render stir.doctype(),
    html null,
      head null,
        title null, "Todolist in actions-recorder"
        meta charset: 'utf-8'
        link
          rel: 'icon'
          href: 'http://tp4.sinaimg.cn/5592259015/180/5725970590/1'
        link
          rel: 'stylesheet'
          href: if data.dev then 'src/main.css' else data.style
        script src: data.vendor, defer: true
        script src: data.main, defer: true
      body null,
        div class: 'demo',
          recorder.request (store, core) ->
            ReactDOM.renderToString Page({store, core})

DevTools

DevTools 主要的功能是 Actions 的重演和 Store 的查看
簡聊的 Store 當中數據較多, 因此按照 key-value 查看少不了
引入 DevTools 須要花費一些代碼, 不過也簡單, 看這邊的例子
DevTools 的 props 數據主要仍是從前面的 store core 拿的

# part of app/page.coffee
React = require 'react'
Immutable = require 'immutable'

Devtools = React.createFactory require 'actions-recorder/lib/devtools'
Todolist = React.createFactory require './todolist'

updater = require '../updater'

div = React.createFactory 'div'

module.exports = React.createClass
  displayName: 'app-page'

  propTypes:
    store: React.PropTypes.instanceOf(Immutable.List).isRequired
    core: React.PropTypes.object.isRequired

  renderDevtools: ->
    core = @props.core

    if typeof window is 'undefined'
      width = 600
      height = 400
    else
      width = window.innerWidth * 0.6
      height = window.innerHeight

    Devtools
      store: @props.store
      updater: updater
      initial: core.initial
      pointer: core.pointer
      isTravelling: core.isTravelling
      records: core.records
      width: width
      height: height

  render: ->
    div style: @styleRoot(),
      Todolist tasks: @props.store
      @renderDevtools()

注意這裏的 core 爲了代碼簡單實際上用了 mutable data
也就是說這個引用直接用就不方便性能優化了, 如今也要注意別操做它
width height 因爲佈局方面存在一些 bug, 暫時須要用數字寫死
代碼中的例子, 爲了兼容服務端渲染, 會在 window 未定義時取默認值
完整的項目的例子, 請返回查看文章開頭的連接

另外關於調試工具的使用, 我說明一下 DevTools 實際上生成的是個 <div>
這個 <div> 具體如何渲染, 能夠靠本身控制. 好比快捷鍵, 大小, 移動, 等等
簡聊的方案是 Command Shift A 控制調試工具的隱藏和關閉
界面選擇了全屏, 緣由是桌面上拖拽效果並很差.. 並且數據也較多

DevTools 跟 Redux 同樣是單獨的組件, 因此本身能夠隨意開發的
也歡迎 Fork 代碼, 或者提交 Issue 一塊兒討論下需求如何
公司同事也提放到 Chrome DevTools 擴展裏的想法, 還在考慮中
有興趣來簡聊一塊兒改善這些工具, 也能夠發送簡歷到 <hr@teambition.com>

優劣

我本身不能公允地評價一遍大, 大體說一下個人見解

  • 單向數據流, Actions Recorder 跟 Redux 同樣都是很專一的

  • 性能, 最新的版本用的 Store 是緩存, 使用的性能並不受記錄 Actions 數量影響

  • JSON 查看工具性能, 這個涉及到 Actions 的重複運算, 略有性能影響

  • 項目直接依賴 immutable-js, 使用會存在必定的門檻

  • 和 Redux 複雜的方案相比, Actions Recorder 上手相對容易一些

  • 成熟度方面, Actions Recorder 還在更新當中, 還有不足

  • Actions Recorder 沒有中間件的概念, 某些需求可能須要手動處理

  • Updater 相比 Reducer 稍微難寫一些, 然而理解和搭配更加方便

相關文章
相關標籤/搜索