码迷,mamicode.com
首页 > 其他好文 > 详细

应用数据流状态管理框架Redux简介、核心概念及工作流

时间:2017-02-05 11:32:19      阅读:233      评论:0      收藏:0      [点我收藏+]

标签:基本概念   lang   creates   onclick   使用场景   多少   否则   结合   document   

前几天给大家谈了谈React
不过它只是一个侧重于UI的框架
只能算作是MVC中的V(View视图)
而且只是DOM的一个抽象层,不是Web应用完整解决方案
如果仅仅用它构建大型项目
你会非常的吃力

简介

技术分享

14年,Facebook提出Flux架构意图解决这个问题
15年,Dan Abramov将 Flux 与函数式编程相结合,创造了Redux,由于简单易学就开始流行起来
16年,Dan Abramov被facebook挖走了

Redux体积很小,如果删掉源码的空行和注释,连500行代码都不到
别看它小(压缩版7KB),却非常有用

优点

使用它构建web应用有如下优点:

  • 预测
    始终有一个准确的数据源,就是store, 对于如何将actions以及应用的其他部分和当前的状态同步可以做到绝不混乱。
  • 维护
    具备可预测结果的性质和严格的组织结构让代码更容易维护。
  • 组织
    对代码应该如何组织更加严苛,这使代码更加一致,对团队协作更加容易。
  • 测试
    编写可测试代码的首要准则就是编写可以仅做一件事并且独立的小函数。Redux的代码几乎全部都是这样的函数:短小、纯粹、分离。
  • 服务端渲染
    可以带来更好的用户体验并且有助于搜索引擎优化,尤其是对于首次渲染。仅仅是把服务端创建的store传递给客户端就可以。
  • 开发者工具
    开发者可以实时跟踪在应用中正在发生的一切,从actions到状态的改变。
  • 社区与生态圈
    存在很多支持Redux的社区,使它能够吸引更多的人来使用。

可能有同学会说,这不是和百度百科一样么
这应该不能算抄吧,其实百度百科redux的词条是我建的o( ̄▽ ̄)d
不过编辑的蛮差劲的,希望各位有时间的时候能补充词条让更多人了解

使用场景

还要强调的一点就是,不是任何时候都需要用它
如果我们用React遇到瓶颈了,或许我们才需要用到Redux
一般情况它都是配合React使用的(因为React用state描述界面,Redux控制state,配合天衣无缝)
不过也可以配合其他框架(甚至原生JavaScript)

技术分享

阮大神在这方面总结的很详细
这些情况不需要用Redux

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

下列情况建议使用Redux

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

组件角度考虑的话,Redux的使用场景

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

设计原理

学Redux的时候就觉得很亲切,但不知道亲切在哪儿
后来知道了,这不就是命令模式么
而这个命令command对象就是store

命令模式的应用场景就是:有时需要向某些对象发送请求,但是不知道请求的接收者是谁,
也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,
使得请求发送者和请求接收者能够消除彼此之间的耦合关系

而我们经常会遇到这样的问题
触发事件后,必须向某些负责具体行为的对象发送请求(接受者receiver)
但还不知道接收者是什么,也不到它要做什么,这是我们就需要命令对象command解耦

所以说白了Redux就是一种设计模式——命令模式
(可见设计模式有多重要,有时间真的该深入研究一下)

设计思想

Redux设计思想就两句话,切记
(再次引用阮大神的总结)

  • Web应用是一个状态机(state machine),视图与状态一一对应
  • 所有状态(state)保存在一个对象中(store)

核心概念

其实Redux架构并不难
文件小,概念也没有多少
最最重要的就是这个

单一状态树

Redux中单一状态树这个概念很重要
什么是单一状态树呢?(也叫单一数据源)
所有的状态state对象(数据)都以树的形式储存唯一的store中
页面中的任何变动,首先都要去改变状态树(store),然后以某种形式呈现到页面
还记得刚刚了解的Redux设计思想吗
一个视图对应一个状态对象

举个例子
比如我们的一个state对象

{
    text: ‘a text‘,
    color: ‘red‘
}

它可能展示一个这样的视图

<p style="color: red;">a text<p>

现在状态改变了,变成了这个样子

{
    text: ‘another text‘,
    color: ‘green‘
}

状态改变了,视图也随之改变

<p style="color: green;">another text<p>

基本概念

Redux核心的概念就三个:Action、Reducer、Store

Action

Action是一个对象,表示页面发生的事情,也就相当于事件

内部其实就是简单的具有一个type属性(常为常量)的JS对象,
描述了Action的类型以及负载信息
比如上面我们视图变化的例子

//Action
{
    type: ‘TEXT_CHANGE‘,
    newText: ‘another text‘,
    newColor: ‘red‘
}

Action Creators

考虑到对它的复用,Action可以通过生成器(Action Creators)来创建
其实它就是返回Action对象的函数(我们自定义的函数)
参数可以适情况而定

//Action Creators
function change(text, color){
    return {
        type: ‘TEXT_CHANGE‘,
        newText: text,
        newColor: color
    }
}

Reducer

Reducer是一个函数,用于修改我们的单一状态树

还记得ES5的Array.prototype.reduce()函数么
我认为reduce中文释义中最接近这个函数功能的应该是“浓缩”
这个浓缩函数接收一个回调让我们将多个值浓缩为一个值
我们的reuder就可以作为(包含多个action对象的)数组的回调函数

Pure Function

Reducer是一个纯函数(Pure Function)
(相同的输入会得到同样的输出)

纯函数(Pure Function)
概念源于函数式编程

  • 不能改写参数
  • 不能调用系统I/O的API
  • 不能调用Math.random()或Date.now()等不纯方法
    (相同的输入要得到同样的输出)

它获取应用的state和action作为参数,通过判断action的type属性返回一个新的state
伪代码表示就是:state + action => state
我们例子中的Reducer可以这样来写

//Reducer
const initState = {
    text: ‘a text‘,
    color: ‘red‘
}
function reducer(state = initState, action){
    switch(action.type){
        case ‘TEXT_CHANGE‘:
            return {
                text: action.newText,
                color: action.newColor
            }
        default:
            return state;
    }
}

由于纯函数这个条件的限制
所以我们不能改写参数中的state
所以比如说我们的state要表示列表
于是使用了数组来表示state
那我们就不能使用state.push()
如果state是对象也是同样的道理
可选方案如下:

  • 数组
    • 使用ES3的arr.concat(),得到新数组后返回
    • 使用ES6的展开操作符[…state, newItem],得到新数组后返回
  • 对象
    • 使用ES6的Object.assign({ }, state, changeObj),得到新对象后返回
    • 使用ES6的展开操作符{…state, newState},得到新对象后返回

只要记住一点就好了,在reducer中不能改变参数state

combineReducers( )

上面我们说到了,整个应用的数据都保存在一个store中,就导致store非常庞大
可想而知,reducer也会十分巨大
不过,Redux库为我们提供了combineReducers( )函数
让我们可以自定义很多reducer函数,然后通过它来合并成一个大reducer
参数就是一个对象

import {combineReducers} from ‘redux‘;
const reducer = combineReducers({a,b,c});

这样利用ES6的语法,会让state的属性名与reducer的函数名相同
如果不同名的话,可以这样写

const reducer = combineReducers({
  a: reducerA(state.a, action),
  b: reducerB(state.b, action),
  c: reducerC(state.c, action)
});

模拟实现一个combineReducers( )函数

const combineReducers = reducersObj => {
  return (state = {}, action) => {
    return Object.keys(reducersObj).reduce(
      (nextState, key) => {
        nextState[key] = reducersObj[key](state[key], action);
        return nextState;
    },{});
  };
};

Store

Store是一个对象,它保存应用的状态并提供一些方法来存取状态,分发状态以及注册监听
全部state由一个Store来表示,Store维持着应用的state
Store 把Reducer和Action联系到一起

API

API如下:

  • 获取状态:store.getState( )
    • 获取当前时间点的数据集合state
  • 更新状态:store.dispatch( action )
    • 修改状态的唯一方式就是dispatch一个action,store接收到action后自动调用reducer
  • 绑定监听:store.subscribe( listener )
    • 一旦状态改变,就执行绑定的函数;方法返回一个函数,调用可以解除监听
  • 改变reducer:replaceReducer( new-reducer )
    • 替换创建store时的reducer页面,比如页面跳转时使用(新的API,好像好少用)

API的使用也非常简单
比如我们试着这样做

store.subscribe(()=>console.log(store.getState()));
store.dispatch(change(‘another text‘,‘green‘));
//输出:{color:‘green‘,text:‘another text‘}

首先我们(subscribe)绑定了一个listener
这个listener会让应用state更新后,就输出当前state(getState)
最后(dispatch)更新状态

createStore( )

我们可以通过redux库中的createStore( )方法来创建Store
参数如下:

  • reducer(必选):
    • 必须为store指定一个reducer函数
  • preloadedState(可选):
    • 整个应用state初始值,通常由服务器发出
    • 如果选择使用这个参数,就会覆盖reducer的默认state初始值
  • enhancer (可选):
    • 一个函数,如果选择使用它,则会返回一个强化版的store creator
    • 源码return enhancer(createStore)(reducer, preloadedState)
import {createStore} from ‘redux‘;
const store = createStore(reducer);
//或者干脆直接导入API 
//let {getState,dispatch,subscribe} from createStore(reducer)

了解了它的功能,我们可以试着模拟一个简单的createStore( )函数

const createStore = (reducer, preloadedState, enhancer) => {
  if(enhancer){
    return enhancer(createStore)(reducer, preloadedState);
  }
  let state = preloadedState,
      listeners = [];
  const getState = () => state;
  const dispatch = (action) => {
    state.reducer(state, action);
    listeners.forEach(listener => listener());
  }
  const subscribe = (listener) => {
    listeners.push(listener);
    let index = listeners.length;
    return () => listeners.splice(index, 1);
  }
  return {
    getStore,
    dispatch,
    subscribe
  }
}

这个只是帮助大家理解,所以没有写太细
更详细的可以参考源码

工作流

如果了解了核心概念后一脸懵逼不要紧
继续往下看就懂了
使用React配合Redux构建项目的工作流如下:

技术分享

Store由Redux的API-createStore(reducer)创建
页面触发事件由Action Creators返回action转发给Store
Store再将当前state和收到的action转给Reducer
Reducer处理后返回一个新state反馈给Store
Store管理的state改变了
就会引发React组件的重新渲染


是不是发现Redux也就这点儿东西,豁然贯通 ︿( ̄︶ ̄)︿
(其实这只是最最基本的Redux,还有很多有用的API)
当然如果仅仅是理解了,也只是纸上谈兵
由于本文只是浅谈Redux
就讲一个小小demo加深一下对Redux的理解

小实例

这个实例就是一个简单的手动计数器
既然由Redux来管理页面的state
React组件中就不必使用state了
我们使用React的时候都是利用props、state改变而触发内部的render重绘
但现在我们利用React+Redux
就要自定义一个render函数再利用store.subscribe()绑定
这样store管理的state改变,就会触发我们自定义的render函数重绘页面

结构以及绑定的事件
事件处理函数也很简单,将action传递给store

const Counter = React.createClass({
    reduceHandler(){
        store.dispatch({type: ‘REDUCE‘});
    },
    addHandler(){
        store.dispatch({type: ‘ADD‘});
    },
    render(){
        return (
            <div>
                <p>{this.props.value}</p>
                <button onClick={this.reduceHandler}>-</button>
                <button onClick={this.addHandler}>+</button>
            </div>
        )
    }
});

构建reducer和store

const reducer = (state = 0, action) => {
  switch (action.type) {
    case ‘ADD‘: return state + 1;
    case ‘REDUCE‘: return state - 1;
    default: return state;
  }
};
const store = createStore(reducer);

创建渲染listener并绑定,每次store改变,触发重绘

const render = () => {
  ReactDom.render(
    <Counter value={store.getState()}/>,
    document.getElementById(‘root‘)
  );
};
store.subscribe(render);

不要忘了首次需要进行手动渲染
否则页面什么都没有

render();

技术分享

==主页传送门==

应用数据流状态管理框架Redux简介、核心概念及工作流

标签:基本概念   lang   creates   onclick   使用场景   多少   否则   结合   document   

原文地址:http://blog.csdn.net/q1056843325/article/details/54784109

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!