Redux 初步尝试

题叶

写文章的时候还是 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 的方案清晰, 细节还期待更灵活一些

阅读 39.9k

题叶
ClojureScript 爱好者.

ClojureScript 爱好者.

17.2k 声望
2.6k 粉丝
0 条评论
你知道吗?

ClojureScript 爱好者.

17.2k 声望
2.6k 粉丝
文章目录
宣传栏