后续内容有更新,代码例子不一致的地方按照仓库的 README 为准
https://github.com/jianliaoim/actions-recorder
抱歉没有很多时间可以更新这边的文章细节,例子大意是对的,参数已经有调整
关于
研究 Redux 之后还是自己从头实现了一套方案出来
之前也在微博上发过,名字叫做 actions-recorder
,功能基本和 Redux 一致
https://github.com/teambition/actions-recorder
近几天配合简聊做了一些调整,于是针对新的版本(1.1.x
)写个 Demo 出来
https://github.com/jiyinyiyong/actions-recorder-demo
http://repo.tiye.me/actions-recorder-demo/
调试工具按照简聊开发的需要,对深度的 JSON 查看做了基本的优化
下面是 1.1.x
版本调试工具的截图:
希望对不喜欢 Redux 的同学能有帮助
这篇文章会描述一下怎样用 Actions Recorder 来实现 Store
某种程度也算是我对于 React 单向数据流的理解的一下沉淀
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()
传入初始化的数据,主要是 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 环境执行
另外注意有些情况页面需要在 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 一样都是很专注的
项目直接依赖
immutable-js
,使用会存在一定的门槛和 Redux 复杂的方案相比,Actions Recorder 上手相对容易一些
成熟度方面,Actions Recorder 还在更新当中,还有不足
Actions Recorder 没有中间件的概念,某些需求可能需要手动处理
Updater 相比 Reducer 稍微难写一些,然而理解和搭配更加方便