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

相关文章

react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接...
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc ...