Redux01 入门
Redux入门学习。
Redux是一个可预测状态的JavaScript容器
是否需要使用Redux
Redux的出现是为了解决React的两个问题:
1 | 1. 代码结构 |
是否使用Redux,取决于React应用是否存在上面的问题需要解决,一般来说,如果UI层比较简单,没有很多的交互,那么Redux就不是必须的,但是如果你面临的是下面的某些场景,那么就有必要考虑使用Redux了:
1 | 1. 用户的使用方式复杂 |
上面的情况才是Redux的适用场景:多交互、多数据源
从组件角度来看,如果组件有一下需求,可以考虑使用Redux:
1 | 1. 某个组件的状态需要共享 |
Redux提供了一个统一的位置和机制来管理状态、组织代码结构,是Web架构的一种解决方案
设计思想
Redux的设计思想:
1 | 1. Web应用是一个状态机,视图与状态是一一对应的 |
这是flux单向数据流图:
1 | _________ ____________ ___________ |
其中的每一个步骤和概念都是下面要介绍的一个部分,其实没有什么太多新的东西,更多是一种编程的范式和思想。
在flux的流程中,flux确保所有Action首先通过一个Dispatcher发送给Store,由Reducer计算后通知所有的监听器
概念
首先创建一个React应用,然后安装Redux
1 | npm install redux --save |
Store
Store是保存数据的地方,可以把它看成一个容器。==整个应用应该只有一个Stroe==。
1 | import { createStore } from 'redux'; |
Redux提供了createStore
函数,来生成Stroe,这个函数接受另一个函数作为参数,这个函数就是下面要提到的reducer,返回新生成的Stroe对象。
往createStore
传Reducer的过程就是给Redux绑定Action处理函数(也就是Reducer)的过程
State
Store对象包含所有数据,想要得到某一时刻(状态)下的数据,新需要对Store生成快照,生成的快照数据就是State
当前时刻的State,可以通过Store.getState
获得:
1 | import { createStore } from 'redux'; |
Redux中,一个State对应一个View,只要State相同,View就相同,反之亦然。
Action
State的变化就会导致View的变化,但用户接触不到State,用户通过View发出通知,告诉State要发生变化了,这个通知就是Action。
Action是一个对象,必须包含一个名为type
的属性,用来标识Action的名称,其他属性可以自由设置,可以参考这个规范进行设置。
1 | const action = { |
可以这样理解,Action描述当前发生的事情,它是唯一改变State的方法,将数据从View运送到Store。
Action Creator
View要发送多少种消息,就有多少种Action,如果都手写会很麻烦。可以定义一个函数来生成Action,这个函数就是Action Creator
1 | const ADD_TODO = '添加TODO'; |
Dispatch
有了Action,还需要一种行为将Action由View传递到Store,这种行为就是dispatch,store.dispatch
是View发出Action的唯一方法
1 | import { createStore } from 'redux'; |
store.dispatch
接受一个Action对象作为参数,并将它发送给Store,结合上面的Action Creator,可以改写为
1 | store.dispatch(addTodo('Learn Redux')) |
Reducer
Store收到Action后,需要对Action进行处理,返回一个新的State,这样View才会发生变化,这种State的计算过程就是Reducer
Reducer是一个函数,它接受当前State和一个Action作为参数,返回一个新的State:
1 | const reducer = function (state, action) { |
store.dispatch
会自动触发Reducer的自动执行(因为在使用CreateStore
时将Reducer作为参数传递给了Store)
1 | import { createStore } from 'redux'; |
==Reducer函数最重要的特征是,它是一个纯函数==,也就是说只要是同样的输入,必定得到同样的输出。
纯函数必须遵守以下的约束:
1 | - 不得改写参数 |
Reducer函数中不能改变State对象,必须返回一个==全新的对象==,参考下面的写法:
1 | // State 是一个对象 |
使用上面的ES7的对象展开进行拷贝时,只是浅拷贝,如果数据结构更复杂或者是嵌套的,那在处理State更新的时候可能要考虑一些不同的做法,可以考虑使用ImmutableJS,Redux对此是全无预设方式的,记住它只是一个状态的容器。
任何时候,与某个View对应的State总是一个不变的对象
这里有个常见模式:在Reducer里用switch
来响应对应的Action :
1 | const reducer3 = function (state = {}, action) { |
用switch
的时候, ==永远==不要忘记放个default
来返回State,否则,你的Reducer可能会返回undefined
(等于你的State就丢了)
Subscribe
现在,用户在View层,通过dispatch
发出了一个Action到Stroe,触发了对应的Reducer返回了一个新的State,但是这个State和View之间还需要关联起来,才能让视图进行封信
通过store.subscribe
可以设置监听函数,一旦State发生变化,设置的监听函数就会自动执行
1 | import { createStore } from 'redux'; |
所以需要吧View的更新函数(对于React就是组件的render
方法或setState
方法)放到listener
,就可以实现View根据State对象的变化而自动更新渲染。
store.subscribe
返回一个函数,调用这个函数可以解除监听
1 | let unsubscribe = store.subscribe(() => |
Store的实现
Store设对象提供了三种基本方法:
1 | - store.getState() |
Store对象是由Redux提供的createStore
方法创造的,这个方法除了接受一个Reducer作为第一个参数外,还接受第二个参数,表示State的初始状态,==这个状态会覆盖Reducer函数的默认参数==
下面是createStore
的简单实现,利用了闭包的原理(也证明,如果声明两个Store对象,其中保存的State对象是相互独立的)
1 | const createStore = reducer => { |
Reducer的拆分
Reducer函数负责生成State对象,但是由于整个应用只有一个State对象,包含所有数据,对于大型应用来说,这个State对象会很大
看这个例子:
1 | const chatReducer = (state = defaultState, action = {}) => { |
三种Action分别改变State的三个属性:
1 | - ADD_CHAT:chatLog属性 |
三个属性之间没有联系,因此可以将Reducer函数拆分,不同的函数负责处理不同的属性(即部分state),然后再合并为一个大的Reducer即可
1 | const chatReducer = (state = defaultState, action = {}) => { |
拆分后,每一个小的函数负责修改对应的属性,返回值都是完整的State对象
一开始我的理解有一点问题,以为返回一个对象,对象的内容是方法,根据传入的
action.type
的值来确定执行哪个方法。并不是这样,返回的对象的每一个内容都是State对象的一个属性,当一个Action发送到Stroe后,对象中的每一个子Reducer函数都会被执行一次,这样才能返回完整的State对象这个时候,每个属性对应的子Reducer内部还是要根据
action.type
来判断具体逻辑的,否则会应用在所有属性上
这种拆分与React应用的结构相吻合,一个React根组件由很多子组件构成,子组件与Reducer完全可以对应。
Redux提供了combineReducers
方法,用于Reducer的拆分,只要定义各个子Reducer函数,然后调用这个方法,将它们合成一个大的Reducer:
1 | mport { combineReducers } from 'redux'; |
combineReducers
产生一个整体的Reducer函数,根据State的key,分别执行子Reducer,并将返回的记过合并成为一个大的State对象
combineReducers的简单实现,注意返回的应该是一个函数(与常规的Reducer相同),给每个子Reducer传递的第一个参数不应该是整个State对象,而是对应的子对象State[key]
1 | const combineReducer = reducers => (state = {}, action) => { |
可以把所有子Reducer放在一个文件里面,然后统一引入
1 | import { combineReducers } from 'redux' |
工作流程
之前接触过Vuex,感觉里面一些概念是非常类似的,首先来看一下Redux的工作流程
1 | // 1. 用户发出Action |
实例:计数器
我将阮一峰老师的例子稍微改写了一下,只是形式上采用了Class组件的形式
首先在demo1.js
组件中,引入Redux
1 | const store = createStore(reducer); |
reducer
是从./reducers/index
导出的一个经由combineReducers
合成的Reducer(reducer2
没什么用,只是为了练习而引入的)
1 | import { combineReducers } from 'redux' |
在<Demo1>
中,<Count>
是一个函数式组件,只负责表现,具体的逻辑在<Demo1>
中,通过store.getState
来为this.state
中的变量赋值,当用户点击按钮而使用dispatch(action)
发出了Action,State变化,会触发监听函数变化,监听函数中再来调用setState
来触发视图的更新
1 | const Count = ({ value, onIncrement, onDecrement, onChange }) => { |