写文章的时候还是
1.0.x
, 现在已经3.x
了.
虽然主体 API 没改, 但是细节的 API 增加了很多, 甚至更简单的方案.
关注 Redux 很久了, 一直在等稳定版, 终于稳定版出来了
不过真的运行起来, 比我之前估计的复杂度高太多了
这边可以看我用 CirruScript 写的代码... 虽然效果是不怎么样
https://github.com/jiyinyiyong/redux-in-cirru
大概梳理下这两天遇到的东西, 为后面做准备
关于
关于 Redux 我遇到的中文社区已经有两篇文章, 还行
https://ruby-china.org/topics/26944
http://segmentfault.com/a/1190000003033033
另外中文文档也有同学在翻译, 速度飞快啊:
https://github.com/camsong/redux-in-chinese
其他大量关于 Redux 的资源, 在列表能找到, 热度很高的
https://github.com/xgrommx/awesome-redux
要开始写 Redux 的话, 其实文档是分布在三个仓库当中的:
https://github.com/gaearon/redux
https://github.com/gaearon/redux-devtools
https://github.com/rackt/react-redux
其中 redux-devtools 是调试工具, 也是一个 React 组件
这个组件需要在开发环境判断渲染, 同时避免发布到线上去
而 react-redux 则是对于 React 的绑定, 包含了一些工具函数
我了解得不大具体, 其中 react-devtools 文档并不齐全
甚至需要去源码当中看具体的例子才能把 Demo 跑起来:
https://github.com/gaearon/redux-devtools/blob/master/examples/counter/containers/App.js
而 Redux 具体的写法, 也少不了要去看源码的 example 才算可以
https://github.com/rackt/redux/tree/master/examples
combineStore
和绑定 props
要注意
Redux 原来给出的概念, 听起来很简单的, Store 部分很像 Elm
大致就是 Model 部分设计成为一个不可变数据, 然后渲染
然而实际情况好像要复杂一些, 目前的 Store 当中的数据不是这样的
按照文档, 一般会出现这样的写法 combineReducers
http://rackt.github.io/redux/docs/basics/Reducers.html
注意是 ES6 的对象, 省略了 property 的书写, 实际上是个 Object 的定义:
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
});
这个方案的问题就是, Store 的顶层数据, 其实是用 Object 模拟的
包括后边绑定数据到组件上, 也是用了其中的一些 trick
比如说有这样的代码, 用来指明 store 传入的数据怎样绑定到组件上
https://github.com/rackt/redux/blob/master/examples/counter/containers/CounterApp.js
function mapStateToProps(state) {
return {
counter: state.counter
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(CounterActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
现在 API 按说已经稳定, 但是个写法还是导致结果稍微复杂了一些
按作者说, Splitting Reducers, 化大为小, 是管理 Store 比较好的办法
但我总觉得应该是从不可变数据本身去找, 而不是增加一套写法
也许以后文档上或者教程上会说得明确一些, 现在我还不明白
细节要注意区分一下, 而且要按照代码跑一跑才行, 我描述得不清楚
由于上边这个结构的原因, store 本身定义的方法, 也许不方便直接用
Provider 的写法
关于把 Store 的数据传递到组件当中, Redux 提供了额外的绑定
结果也带出来了 Provider 组件, 接收属性, 还接收函数作为参数, 写法是:
https://github.com/rackt/redux/blob/master/examples/counter/containers/Root.js
注意, CounterApp
这边没写属性, 是通过签名提到的写法注入进去的
大致上是 mapStateToProps
函数, 具体细节恐怕需要看源码:
import { Provider } from 'react-redux';
export default class Root extends Component {
render() {
return (
<Provider store={store}>
{() => <CounterApp />}
</Provider>
);
}
}
而 Provider 的概念负责的事情似乎也多了一些, 具体到文档上看
http://rackt.github.io/redux/docs/basics/UsageWithReact.html
实现一个最简单的 Redux 应用, 需要的代码:
https://github.com/jackielii/simplest-redux-example/blob/master/index.js
Middlewares
中间件的概念我没看懂, 只是大致抄了一遍代码尝试了一遍
思路是用高阶函数对 store 做了一些封装, 插入了一些 Action 的操作
http://rackt.github.io/redux/docs/advanced/Middleware.html
DevTools
前面提到了 Devtools
是用 React 组件的方式提供的
没找到详细的文档, 具体的例子我查看代码的 examples 才知道的
https://github.com/gaearon/redux-devtools/blob/master/examples/counter/containers/App.js
export default class App extends Component {
render() {
return (
<div>
<Provider store={store}>
{() => <CounterApp />}
</Provider>
<DebugPanel top right bottom>
<DevTools store={store}
monitor={LogMonitor} />
</DebugPanel>
</div>
);
}
}
作者说调试工具是可以定制的, 因为仅仅是 React 组件而已
我估计大概是 LogMonitor 组件可以自己定义的关系
显示不可变数据
显示同时工具之后, 查看数据默认当做 JSON 对象处理和显示的,
在调试工具当中查看不可变数据稍微要加上一些代码:
https://github.com/gaearon/redux-devtools/issues/51
let selectDevToolsState = (state = {}) => Immutable.fromJS(state).toJS();
<DebugPanel top right bottom key="debugPanel">
<DevTools store={store} select={selectDevToolsState} monitor={LogMonitor} />
</DebugPanel>
其中 state
变量有可能为 undefined
的, 注意不要忘掉处理
纯函数 Reducer
我在写 Demo 时候刚开始写了 shortid.generate()
生成 id, 遇到个 bug
原因是这个生成 id 的函数是在 reducer 内部运行的,
似乎由于 DevTools 的存在, reducer 会被调用很多次, id 被创建了很多次
这不奇怪, 因为 Time Travel Debugger 就是会重新运行 Action 的
所以我才反应过来, 创建 id 在 Haskell 里也是跟 IO 有关的副作用函数
随机数还有读取外部环境的状态, 属于副作用, 会破坏纯函数
这个代码是不应该在 reducer 当中的写的, id 就放 Action Creator 里去了
这个可能一看暗示了 FRP 那样的编程思路引出的一个更深刻的问题
平时我们说 MVC, Model 是整个数据的核心, Model 可以被改变
在 FP 当中, Model 是以变化数据的 Stream 模拟它随着时间的改变
而这里, Store 作为 Model 却是因变量, 距离核心还隔着一步
数据的核心实际上是 initialState, 以及 Action 形成的 Stream
而 Model 实际上是通过 initialState 和 Actions 不断计算出来的
bindActionCreators
Redux 的例子当中, 处理 Action 是通过绑定到组件 props
来传递的
而不是我此前采用的, 直接用一个模块去调用的写法. 具体写法看这边:
https://github.com/rackt/redux/blob/master/docs/api/bindActionCreators.md
不清楚利弊. 我只是觉得这样设计太复杂了一些
总结
这篇文章算不上教程, 而是初步尝试 Redux 留下来的一些 Tips
我觉得大家关注 Redux 应该都是为的 Time Travel Debugger 的能力
现在看来 Redux 带来过多概念, 给我们项目跟进造成了门槛
总体思路上 Redux 比 Facebook 的方案清晰, 细节还期待更灵活一些
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。正好这两天我也 试用了一下 Redux ,感觉不知道应该怎么评价官方给的目录结构,自作聪明的调整了一下,把 action 和 reducer 写在了同一文件里,感觉这样子维护起来稍微会方便一点
Redux 的文档确实比较糟糕,和你一样,在试用过程中需要大量地参考示例代码,实在是比较无奈
关于“引入太多概念”这个我觉得你应该不用太过于担心,毕竟本身 React 开放的 API 是非常简单的,就算 Redux 复杂一点其实也到不了 Angular 的程度,但你看看, Angular 还有这么多人用呢……学习的负担对大家而言应该不会是什么问题
主要还是思维方式上的改变,为了实现一个异步的操作,我一开始也是适应了不少时间的……
@c4605 Redux 的文档差吗?http://rackt.github.io/redux/ 最多是有点啰嗦吧...
Redux 模仿的是 FRP 这个图形开发的模式, 实际上 FRP 复杂在概念上, Elm 用了复杂的类型系统导致门槛很高, 不是复杂在数据的传递问题上. 而 Redux 在数据传递上就搞出挺多概念的.
FRP 当中用 Stream 和高阶函数做很多事情, 在 Redux 里高阶函数是有了, 可是没看到 Stream 的样子.
@ustccjw 挺长的. 源码才几个文件, 具体用了那么大篇幅才解释清楚.
我没有了解过 Elm ,之前也不知道 FRP ,看到你的文章了以后才知道这些,然后看得稍微有点蒙 :P ,打算接下来好好读一读
我其实一开始想说的就是,感觉 Redux 对比其他框架也没有引入太多的东西,如果真的要用 Redux ,它对于很多人而言可能也只是“另外一个稍微有点奇怪的东西”,和刚刚接触 Angular 时发现官方竟然不建议在 controller 里直接操作 DOM 一样
如果真的推广起来,大家应该也会马上就能接受的吧……
接受起来确实... 有足够的例子就好了, 很快能上手.
不过考虑下 redux 其实实现的代码就这么短, 短到自己都可以实现一个,
那么自己实现出来会是什么样子, 是不是真的要那么多概念?
而且要不要那么多和 Elm 那样已经成熟的方案当中不一样的概念?
redux开发中,是要把所有的状态都放到store中吗,还是store中放置的是那些多组件公用的状态参数,求指导?
请问,我在angular中使用了react,要怎么使用redux,看例子都是在跟节点上使用Provider将store引入到react中,但是在angular 中部分使用react的组件,所有没有根组件,请问解决办法
@longxing 两个方案用在一起有点大胆, 对 Angular 我不熟悉这个没法说了, 到论坛问问看吧 http://react-china.org
间断的看了几天redux,也写完了todo例子,依然一知半解的,特别是applyMiddleware这部分。
推荐redux中文教程,讲解了redux的各方面,最后你会觉得redux是个“可预测”状态容器,并从中获取非凡的开发体验
https://github.com/lewis617/react-redux-tutorial
@题叶 不知道朋友你的redux研究到什么境界了,能不能再写文分享一下呢?
@ibufu 不研究了...
@题叶 为什么放弃了。。最近想用redux重构项目,听你这么一讲。。。
@ibufu Redux 前面的版本觉得太复杂, 我们在项目里重构太麻烦, 自己实现了一个 https://github.com/jianliaoim/actions-recorder 于是重构的地方挺少. 实际上自己写功能就够用了.