这一部分仅仅介绍react基本的概念,因为react不仅仅可以用在react中,还可以用在其他框架甚至原生 js 中。  所以这里只介绍通用的概念。

redux使用场景

 redux和vue中的vuex是类似的,他们的使用都是为了使得组件之间数据的通信和实现良好的代码结构。

  

  但并不是说做react的项目就一定需要使用redux,因为如果项目的通信简单,那么完全没有没有必要。 而如果:

  • 用户的使用方式复杂
  • 与服务器大量交互,或者使用了WebSocket
  • View需要从多个来源获取数据

  则需要使用redux,即多交互、多数据源。

 另外,从组件的角度来看,如果某个组件的状态需要共享、某个状态需要在任何地方都可以拿到、一个组件需要改变全局状态、一个组件需要改变另一个组件状态(实际上前面说四者所提到的都是组件间的通信都是需要redux的

  如果对于上述几种方式也不适用redux,那么状态的管理就非常混乱 。

  因为web应用是一个状态机,视图和状态是一一对应的。并且对于redux而言,所有的状态都保存在一个对象里面。 

Redux基本概念

1、store

  和vue中的store一样,store可以理解为一个存储数据的仓库,一个应用就这么一个仓库,但本质上这个store是一个对象。 Redux通过 createStore 这个函数,来生成store对象:

import { createStore } from 'redux';
const store = createStore(fn);

  即createStore的作用就是为了生成一个store对象。

2、 state

  state就是当前的状态,那么store和state是什么关系呢? 我们可以认为 store 对象包括所有数据,如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。 

  通过store.getState()我们可以得到某一时点的 state。

import { createStore } from 'redux';
const store = createStore(fn); const state = store.getState();

  Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

  简单的说: getState() 会返回当前的state树。(当前很重要)。 即state是当前数据的状态。 

3、action

  action从表面理解就是一个动作。

  我们先来理解vue触发变化的方式 。

  我们知道像vue这种mvvm框架实现的是当数据发生变化时,视图同样发生了变化,视图发生变化,也会导致数据的变化,在vue中通过视图使得数据变化的方法只有一种,就是通过mutation来触发变化,而异步的action也是对mutation进行了保证,最终还是通过mutation来变化的。

  而react与此类似, 当数据变化时,视图会发生变化,而当视图发生变化时redux会通过 触发action 使得数据发生变化。 

  而action本身是一个对象,这个对象中的type属性是必须的,表明这个action的名称, 其他属性没有强制要求,但是有一套自己的规范,比如可以有payload(vue中也是广泛使用的)来传递这种变化。

  

  注意: action是触发state变化的唯一方式, 通过action , 可以改变当前唯一的store对象。 如:

const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};

  

4、 action creater 

  之前已经说了, 通过view来改变state的唯一方式就是触发action来改变store中的数据,并且这个action是一个对象,但是往往view的触发很多,并且有可能触发的类型相同只是传递的内容不同,那么我们每次都来写一个对象(并且写这个对象时每次都要写相同的类型),是很麻烦的。 所以我们可以定义一个函数来生成 action (实际上就是传递必要的参数返回一个符合action的对象)。 这个函数就是 action creater 。 

  

const ADD_TODO = '添加 TODO';

function addTodo(text) {
return {
type: ADD_TODO,
text
}
} const action = addTodo('Learn Redux');

  如上所示,我们就可以使用addTodo这个action creater很方便地来构造足够多的action。

5、 store.dispath()

  在3和4中我们都在讨论view层通过action来改变store从而改变当前的state,但是action只是一个对象而已,我们怎么才能把这个对象传递到 store 呢

  store.dispatch() 就是 view 发出 Action对象的唯一方法。

    dispatch的中文意思就是派遣、发送的意思。 即将action发送到store, 因为dispatch是为了store服务的,所以也就可以理解为 dispatch 是 store 的方法。

如下所示:

import { createStore } from 'redux';
const store = createStore(fn); store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});

通过action creater,我们可以更方便地通过dispatch来发送这个action。

6、 Reducer

  刚刚提到: 我们可以通过store.dispatch()将action送到store仓库,但是将这个action送到仓库之后, store又是怎么处理这个action的呢?  这时就需要用到 reducer 在store中来处理这个action了。

因此,到这里,我们就能理清楚这几个重要概念的关系了。 dispatch这家伙是store的手下,负责跑腿来运送非常重要的 action, action 可以看做一封信, 只是一个静态的东西, 而view层可以看做写信的东西, 我们可以通过触发view层来写信(发送action)。  那么reducer就是store内部的伙计,这伙计负责接收由 dispatch 送来的信,然后根据信的内容及时的改变store中的内容 。  而store中的内容也是可以随时改变的,然后告诉外面也做出相应的改变。 即 store、view是一个表里如一的汉子!

 

  reducer 在中文来说有还原剂的意思,就是当你action来到的时候,负责按照action的情况来还原当前真正地store

  

  言归正传, reducer实际上就是一个函数,他接收Action和当前的State作为参数, 返回一个新的State。 如下:

const reducer = function (state, action) {
// ...
return new_state;
};

  

  下面是一个例子:

const defaultState = ;
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
}; const state = reducer(, {
type: 'ADD',
payload:
});

  即首先定义了一个reducer函数,这个函数在内部使用switch来决定当遇到不同类型的 action 时应该怎么处理。 然后紧接着就开始调用这个reducer实现state的更新。 这里的reducer实现的是 state 的添加,当然,我们也可以定义不同的 reducer 来实现 state 的减法等等。 所以说 reducer 一定是和不同的 action 相互对应的,有一个 action ,就要有一个 reducer 来处理这个action。

  

  而在实际应用中,我们只需要写好reducer函数就好了,而不需要每次都把相应的 action 进行手工调用, 因为这在程序中是不现实的!   即store.dispacth 方法就会触发 Reducer 的自动执行。 所以啊,Store就需要知道相应的Reducer函数! 做法也很简单,就是在生成 store 的时候,将 Reducer 传入到 createStore 方法中,如下所示:

import { createStore } from 'redux';
const store = createStore(reducer);

  上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

疑问上面再生成store的时候,传入了一个reducer, 但是实际上应该有很多个reducer啊,难道要创建很多个 store ?  但是store在一个应用中应该只有一个才对,否则应用的状态就无法管理。 那么我们可以将很多个reducer写成一个数组然后传进去吗?  这里应该怎么解决呢?

    vue的做法是将所有的类似于reducer的东西放在mutations中,然后Mutations和 state等一块传入 Vuex.Store() 中被导出,接着传入构造函数vue中。

    如果按照vue的解决思路,那么redux中的reducer也应当是一个对象的集合,其中每一个键值对就是函数名和函数。 是这样吗? 

补充: 所以啊,reducer函数可以写成下面这样(使用switch针对不同的类型):

const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action;
switch (type) {
case ADD_CHAT:
return Object.assign({}, state, {
chatLog: state.chatLog.concat(payload)
});
case CHANGE_STATUS:
return Object.assign({}, state, {
statusMessage: payload
});
case CHANGE_USERNAME:
return Object.assign({}, state, {
userName: payload
});
default: return state;
}
};

为什么这个函数叫做 Reducer 呢?因为它可以作为数组的reduce方法的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组

const actions = [
{ type: 'ADD', payload: },
{ type: 'ADD', payload: },
{ type: 'ADD', payload: }
]; const total = actions.reduce(reducer, ); //

上面代码中,数组actions表示依次有三个 Action,分别是加0、加1和加2。数组的reduce方法接受 Reducer 函数作为参数,就可以直接得到最终的状态3

  

7、 纯函数

  首先得知道什么是纯函数 --- 只要是同样的输入,必定得到同样的输出。

     《纯函数的好处》这篇文章介绍了纯函数的好处,就是同样地输入就有同样地输出它是可靠的,是可以预测的。 

  

  追求纯的理由:

  • 可缓存性。
  • 可移植性、自文档化 --- 不会有偷偷摸摸的小动作 。(函数式编程所需要的)
  • 可预测性。
  • 合理性。

  

  注意,纯函数必须遵守下面的一些约束(因为下面的方法都会导致结果不同): 

不得改写参数
不能调用系统 I/O 的API
不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

  

  由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
} // State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}

    

  最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象

8、 store.subscribe()

  store允许使用 store.subscripbe 方法设置监听函数,一旦 state 发生变化, 就自动执行这个函数。 

  

import { createStore } from 'redux';
const store = createStore(reducer); store.subscribe(listener);

  

  即store发生变化之后, listener函数就会自动执行。

  

  显然,只要把 View 的更新函数(即当state变化时,更新view)(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。

  store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() =>
console.log(store.getState())
); unsubscribe();

  

Reducer的拆分

  Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。

  即这就是我之前所说的 reducer函数不止一个,应该针对不同类型的action有不同的reducer 。 

  即reducer函数可能是下面这样的:

const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action;
switch (type) {
case ADD_CHAT:
return Object.assign({}, state, {
chatLog: state.chatLog.concat(payload)
});
case CHANGE_STATUS:
return Object.assign({}, state, {
statusMessage: payload
});
case CHANGE_USERNAME:
return Object.assign({}, state, {
userName: payload
});
default: return state;
}
};

  即针对不同的type,我们给出不同的改变state的解决方案

  上面代码中,三种 Action 分别改变 State 的三个属性。

ADD_CHAT:chatLog属性
CHANGE_STATUS:statusMessage属性
CHANGE_USERNAME:userName属性

  

  这三个属性之间没有联系,这提示我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。(这里和vue的思路也是一致的)

  

const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}
};

  即reducer函数处理的任务不同,我们将其拆分为3个小的函数,每一个小函数精确地管理state的某个部分。   

  这样一拆,Reducer 就易读易写多了。而且,这种拆分与 React 应用的结构相吻合:一个 React 根组件由很多子组件构成。这就是说,子组件与子 Reducer 完全可以对应。

  另外, Redux 提供了一个 combineReducers 方法,用于Reducer的拆分。 用这个犯法就可以将小的方法合并成一个大的了。 如下所示:

  

import { combineReducers } from 'redux';

const chatReducer = combineReducers({
chatLog,
statusMessage,
userName
}) export default todoApp;

    

  上面的代码通过combineReducers方法将三个子 Reducer 合并成一个大的函数。

  这样的写法利用了es6方法,即在同名的时候可以这么写,那么属性名和属性值指的是同一个,但是如果 不同名称,我们就需要像下面这样写:

const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
}) // 等同于
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}

  总之,combineReducers()做的就是产生一个整体的 Reducer 函数。该函数根据 State 的 key 去执行相应的子 Reducer,并将返回结果合并成一个大的 State 对象。

  

  

  

工作流程

  

首先,用户发出 Action。

store.dispatch(action);

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action);

State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数
store.subscribe(listener);

listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {
let newState = store.getState();
component.setState(newState);
}

推荐文章: http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html

redux概念介绍的更多相关文章

  1. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)

    集群概念介绍(一)) 白宁超 2015年7月16日 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习 ...

  2. Linux LVM硬盘管理之一:概念介绍

    一.LVM概念介绍: LVM是 Logical Volume Manager(逻辑卷管理)的简写,它由Heinz Mauelshagen在Linux 2.4内核上实现.LVM将一个或多个硬盘的分区在逻 ...

  3. Java SE/ME/EE的概念介绍

    转自 Java SE/ME/EE的概念介绍 多数编程语言都有预选编译好的类库以支持各种特定的功能,在Java中,类库以包(package)的形式提供,不同版本的Java提供不同的包,以面向特定的应用. ...

  4. rocketMq概念介绍

    rocketMq官网 http://rocketmq.apache.org/ rocketMq逻辑概念介绍 rocketMq逻辑图 备注:    改图片分享自李占卫的网上家园 说明: 在rocketM ...

  5. java 并发多线程 锁的分类概念介绍 多线程下篇(二)

    接下来对锁的概念再次进行深入的介绍 之前反复的提到锁,通常的理解就是,锁---互斥---同步---阻塞 其实这是常用的独占锁(排它锁)的概念,也是一种简单粗暴的解决方案 抗战电影中,经常出现为了阻止日 ...

  6. Airflow Python工作流引擎的重要概念介绍

    Airflow Python工作流引擎的重要概念介绍 - watermelonbig的专栏 - CSDN博客https://blog.csdn.net/watermelonbig/article/de ...

  7. spring batch (一) 常见的基本的概念介绍

    SpringBatch的基本概念介绍 内容来自<Spring Batch 批处理框架>,作者:刘相. 一.配置文件 在项目中使用spring batch 需要在配置文件中声明: 事务管理器 ...

  8. helm-chart-1-简单概念介绍-仓库搭建-在rancher上的使用

    简单的概念介绍: Chart是helm管理的应用的打包格式,一个chart对应一个或一套应用.内部是一系列的yaml描述文件,以为为yaml 服务的文件. 三个部分,helm .tiller.repo ...

  9. Netty重要概念介绍

    Netty重要概念介绍 Bootstrap Netty应用程序通过设置bootstrap(引导)类开始,该类提供了一个用于网络成配置的容器. 一种是用于客户端的Bootstrap 一种是用于服务端的S ...

随机推荐

  1. 基于swoole搭建聊天室程序

    1. 创建websocket服务器 swoole从1.7.9版本开始, 内置了websocket服务器功能,我们只需几行简单的PHP代码,就可以创建出一个异步非阻塞多进程的WebSocket服务器. ...

  2. Long-distance navigation and magnetoreception in migratory animals(迁徙动物中的长距离导航和磁感应)

    摘要:For centuries, humans have been fascinated by how migratory animals find their way over thousands ...

  3. [转]How to Clean the Global Assembly Cache

    本文转自:https://www.techwalla.com/articles/how-to-clean-the-global-assembly-cache The Global Assembly C ...

  4. SqlLocalDB 的一些常用命令行

    Once installed, you can interact with SqlLocalDb using the command line. The following will tell you ...

  5. ORACLE ERP 的前世今生

    一个伟大的公司必有一个伟大的产品.如果说数据库是ORACLE在上世纪最后二十年赖以起家并奠定江湖地位的旗舰产品,那么,企业应用产品(或曰ERP)则毫无疑问是ORACLE在本世纪初的这近十年,征战疆场. ...

  6. C#基础入门 八

    C#基础入门 八 泛型 C#中的泛型能够将类型作为参数来传递,即在创建类型时用一个特定的符号,如"T"来作为一个占位符,代替实际的类型,等待实例化时用一个实际的类型来代替. pub ...

  7. ie11 兼容的问题

    碰到一个问题 下拉列表点不了. 测试后,只在ie11下有这个问题. 先是在head 加<meta http-equiv=”X-UA-Compatible” content="IE=8& ...

  8. LR中的迭代次数设置

    在参数化时,对于一次压力测试中均只能用一次的资源应该怎么参数化呢?就是说这些资源用了一次就不能在用了的. --参数化时,在select  next row选择unique,update value o ...

  9. .NET中Debug模式与Release模式差别

    Debug里的PDB是full,保存着调试和项目状态信息.有断言.堆栈检查等代码.Release 里的PDB是pdb-only,基本上:出什么错了+错误在哪行. 因为很多人把PDB理解成:调试文件.P ...

  10. linux 常用命令,开发记住这些基本能够玩转linux

    系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...