後續內容有更新, 代碼例子不一致的地方按照倉庫的 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
簡聊的應用中, 除了 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()
傳入初始化的數據, 主要是 initial
和 updater
是必須聲明的
其次是 .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 環境渲染, 也是能夠支持的
固然這邊對於 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 主要的功能是 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 稍微難寫一些, 然而理解和搭配更加方便