全面解析Redux

作者: fullbook | 来源:发表于2016-08-29 14:56 被阅读666次

    本文将会不断更新和整理。

    Store

    首先要区分 store和 state?
    state是应用状态,一般本质上是一个普通对象,例如:我们有一个 Web APP,包含 计数器 和 待办事项 两大功能,那么我们可以为该应用设计出对应的存储数据结构(应用初始状态):
    <pre>// 应用初始 state,本代码块记为 code-1
    {
    counter: 0,
    todos: []
    }</pre>

    store是应用状态state的管理者,包含下列四个函数:

    • 1、getState() # 获取整个 state
    • 2、dispatch(action) # ※ 触发 state 改变的【唯一途径】※
    • 3、subscribe(listener) # 您可以理解成是 DOM 中的 addEventListener
    • 4、replaceReducer(nextReducer) # 一般在 Webpack Code-Splitting 按需加载的时候用

    二者的关系是:state = store.getState()
    Redux 规定,一个应用只应有一个单一的 store,其管理着唯一的应用状态 state
    Redux 还规定,不能直接修改应用的状态 state,也就是说,下面的行为是不允许的:
    <pre>var state = store.getState();
    state.counter = state.counter + 1 // 禁止在业务逻辑中直接修改 state</pre>

    若要改变 state,必须 dispatch一个 action,这是修改应用状态的不二法门
    <pre>现在您只需要记住 action只是一个包含 type ** 属性的普通对象**即可例如 { type: 'INCREMENT' }</pre>

    上面提到,state是通过 store.getState()获取,那么 store又是怎么来的呢?想生成一个 store,我们需要调用 Redux 的 createStore:
    <pre>import { createStore } from 'redux'...
    const store = createStore(reducer, initialState) // store 是靠传入 reducer 生成的哦!</pre>

    Provider

    可以将从createStore返回的store放入connect中,使子集可以获取到store并进行操作。Provider 也是 react-redux 提供的工具组件。
    <pre>React.render(
    <Provider store={store}>
    {() => <MyRootComponent />}
    </Provider>, rootEl);</pre>

    Provider应该是你的 React Components 树的根组件。由于 React 0.13 版本的问题,Provider的子组件必须是一个函数,这个问题将在 React 0.14 中修复。Provider 和 connect 函数的配合,使得 React Component 在对 Redux 完全无感的情况下,仅通过 React 自身的机制来获取和维护应用程序的状态。

    Action

    上面提到,action(动作)实质上是包含 type属性的普通对象,这个 type是我们实现用户行为追踪的关键,例如:增加一个待办事项 的 action可能是像下面一样:
    <pre>//本代码块记为 code-2
    {
    type: 'ADD_TODO',
    payload: {
    id: 1,
    content: '待办事项1',
    completed: false
    }
    }</pre>

    当然,action的形式是多种多样的,唯一的约束仅仅就是包含一个 type属性罢了也就是说,下面这些 action都是合法的:
    <pre>//如下都是合法的,但就是不够规范
    {
    type: 'ADD_TODO',
    id: 1,
    content: '待办事项1',
    completed: false
    }
    {
    type: 'ADD_TODO',
    abcdefg: {
    id: 1,
    content: '待办事项1',
    completed: false
    }
    }</pre>

    虽说没有约束,但最好还是遵循之前flux规范
    如果需要新增一个代办事项,实际上就是将 code-2中的 payload“写入” 到 state.todos 数组中(如何“写入”?在此留个悬念):
    <pre>//本代码块记为 code-3
    {
    counter: 0,
    todos: [{
    id: 1,
    content: '待办事项1',
    completed: false
    }]
    }</pre>

    刨根问底,action是谁生成的呢?

    Action Creator

    Action Creator 可以是同步的,也可以是异步的。顾名思义,Action Creator 是 action的创造者,本质上就是一个函数,返回值是一个 action(对象)例如下面就是一个 “新增一个待办事项” 的 Action Creator:
    <pre>//本代码块记为 code-4
    var id = 1;
    function addTodo(content) {
    return {
    type: 'ADD_TODO',
    payload: {
    id: id++,
    content: content, // 待办事项内容
    completed: false // 是否完成的标识
    }
    }
    }</pre>

    将该函数应用到一个表单(假设 store为全局变量,并引入了 jQuery ):
    <pre>//本代码块记为 code-5
    <input type="text" id="todoInput" />
    <button id="btn">提交</button>
    <script>
    $('#btn').on('click', function() {
    var content = $('#todoInput').val() // 获取输入框的值
    var action = addTodo(content) // 执行 Action Creator 获得 action
    store.dispatch(action) // 改变 state 的不二法门:dispatch 一个 action!!!
    })
    </script></pre>

    在输入框中输入 “待办事项2” 后,点击一下提交按钮,我们的 state就变成了:
    <pre>//本代码块记为 code-6
    {
    counter: 0,
    todos: [{
    id: 1,
    content: '待办事项1',
    completed: false
    }, {
    id: 2,
    content: '待办事项2',
    completed: false
    }]
    }</pre>

    通俗点讲,Action Creator 用于绑定到用户的操作(点击按钮等),其返回值 action用于之后的 dispatch(action)
    刚刚提到过,action明明就没有强制的规范,为什么 store.dispatch(action)之后,Redux 会明确知道是提取 action.payload,并且是对应写入到 state.todos数组中?又是谁负责“写入”的呢?悬念即将揭晓...

    Reducer(必须是同步的纯函数)

    用户每次 dispatch(action)后,都会触发 reducer的执行,reducer的实质是一个函数,根据 action.type来更新 state并返回 nextState,最后会用 reducer的返回值 nextState完全替换掉原来的 state。
    注意:上面的这个 “更新” 并不是指 reducer可以直接对 state进行修改,Redux 规定:须先复制一份 state,在副本 nextState上进行修改操作例如,可以使用 lodash 的 deepClone,也可以使用 Object.assign / map / filter/ ...等返回副本的函数
    在上面 Action Creator 中提到的 待办事项的 reducer大概是长这个样子 (为了容易理解,在此不使用 ES6 / Immutable.js):
    <pre>/** 本代码块记为 code-7 **/
    var initState = {
    counter: 0,
    todos: []
    }
    function reducer(state, action) {
    // ※ 应用的初始状态是在第一次执行 reducer 时设置的(除非是服务端渲染) ※
    if (!state) state = initState
    switch (action.type) {
    case 'ADD_TODO':
    var nextState = _.deepClone(state) // 用到了 lodash 的深克隆
    nextState.todos.push(action.payload)
    return nextState

    default:
    // 由于 nextState 会把原 state 整个替换掉
    // 若无修改,必须返回原 state(否则就是 undefined)
    return state
    }
    }</pre>

    小结

    • store由 Redux 的 createStore(reducer)生成
    • state通过 store.getState()获取,本质上一般是一个存储着整个应用状态的对象
    • action本质上是一个包含 type属性的普通对象,由 Action Creator (函数) 产生,改变 state必须 dispatch一个 action
    • reducer本质上是根据 action.type来更新 state并返回 nextState的函数
      reducer 必须返回值,否则 nextState即为 undefined
    • 实际上,state就是所有 reducer返回值的汇总(本教程只有一个 reducer,主要是应用场景比较简单)

    Redux 与传统后端 MVC 的对照

    Redux 传统后端 MVC
    store 数据库实例
    state 数据库中存储的数据
    dispatch(action) 用户发起请求
    action: { type, payload } type表示请求的 URL,payload表示请求的数据
    reducer 路由 + 控制器(handler)
    reducer中的 switch-case分支 路由,根据 action.type路由到对应的控制器
    reducer内部对 state的处理 控制器对数据库进行增删改操作
    reducer返回 nextState 将修改后的记录写回数据库

    最简单的例子

    <pre><!DOCTYPE html>
    <html>
    <head>
    <script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script></head>
    <body>
    <script>
    /** Action Creators */
    function inc() {
    return { type: 'INCREMENT' };
    }
    function dec() {
    return { type: 'DECREMENT' };
    }
    function reducer(state, action) {
    // 首次调用本函数时设置初始
    state state = state || { counter: 0 };
    switch (action.type) {
    case 'INCREMENT':
    return { counter: state.counter + 1 };
    case 'DECREMENT':
    return { counter: state.counter - 1 };
    default: return state; // 无论如何都返回一个 state
    }
    }
    var store = Redux.createStore(reducer);
    console.log( store.getState() ); // { counter: 0 }
    store.dispatch(inc());
    console.log( store.getState() ); // { counter: 1 }
    store.dispatch(inc());
    console.log( store.getState() ); // { counter: 2 }
    store.dispatch(dec());
    console.log( store.getState() ); // { counter: 1 }
    </script>
    </body>
    </html></pre>

    相关文章

      网友评论

      本文标题:全面解析Redux

      本文链接:https://www.haomeiwen.com/subject/gnbxettx.html