Redux 入门教程(一):基本概念和用法
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及:
- 代码结构
- 组件之间的通信
对于大型的复杂应用来说,这两方面恰恰是最关键的。以此,只用 React 没法写大型应用。
零、你可能不需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
以下情况,都不需要使用 Redux:
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
一、Redux 的工作流程
- 用户发出 Action
- Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action
- Reducer 返回新的 State
- State 发生变化,Store 调用监听函数
二、基本概念
1. Store
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
1 | import { createStore } from 'redux'; |
2. State
Store 对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
1 | const state = store.getState(); |
3. Action
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
1 | const action = { |
4. Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
1 | const reducer = function (state, action) { |
三、实际使用示例
1. 创建 Store
1 | import { createStore } from 'redux'; |
2. 监听 State 变化
1 | store.subscribe(listener); |
3. 发送 Action
1 | store.dispatch(action); |
四、总结
Redux 是一个用于 JavaScript 应用的可预测状态容器,特别适合用于构建大型应用。它可以帮助你:
- 集中管理应用的状态
- 使状态变化可预测
- 便于调试和测试
- 提供强大的开发工具
但是要注意,不是所有应用都需要 Redux。对于简单的应用,使用 React 的本地状态管理就足够了。
1 | 代码结构 |
对于大型的复杂应用来说,这两方面恰恰是最关键的。以此,只用 React 没法写大型应用。
本篇文章介绍基本概念和用法。
零、你可能不需要 redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
1 | 用户的使用方式非常简单 |
上面这些情况,都不需要使用Redux。
1 | 用户的使用方式复杂 |
上面这些情况才是 Redux 的适用场景: 多交互、多数据源。
从组件角度看,如果你的应用有一下场景,可以考虑使用Redux
1 | 某个组件的状态需要共享 |
发生上面情况时,如果不适用 Redux 或者其他状态管理工具,不按照一定规律处理状态的读写,diam很快就会变成一团乱麻。你需要一种机制,可以在同一个地方查询状态、改变状态、传播状态的变化。
总之,不要把 Redux 当做万灵丹,如果你的应用没那么复杂,就没必要用它。另一方面,Redux 只是 Web 架构的一种解决方案,也可以选择其他方案。
#一、预备知识
阅读本文,你只需要懂 React。如果还懂 Flux,就更好了,会比较容易理解一些概念,但不是必需的。
Redux 有很好的 文档, 还有配套的小视频(前30集 后30集)。 你可以先阅读本文,再去官方材料详细研究。我的目标是,提供一个简洁易懂的、全面的入门级参考文档。
#二、设计思想
Redux 的设计思想很简单,就两句话。
1 | (1) Web 应用是一个状态机,视图与状态是一一对应的。 |
请务必记住这两句话,下面就是详细解释。
三、基本概念和API
##3.1 Store
store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个Store。
Redux 提供 creatStore
这个函数,用来生成 Store。
1 | import {createStore} from 'redux'; |
##3.2 State
Store
对象包含所有数据。如果想要得到某个时间点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。 当前时刻的 State,可以通过 store.getState()
拿到。
1 | import {createStore} from 'redux'; |
Redux 规定, 一个 State 对应一个View。只要 State 相同,View就相同。你知道 State,就知道 View 是什么样,反之亦然。
##3.3 Action
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。 所以,State 的变化必须是 View导致的。 Action就是View 发出的通知,表示 State 应该要发生变化了。
Action 是一个对象,其中的 type
属性是必须的,表示 Aciotn 的名称。其他属性可以自由设置,社区有一个 规范 可以参考。
1 | cosnt action ={ |
上面代码中, Action 的名称是 ADD_TODO
,它携带的信息是字符串 Learn Redux
。
可以这样理解, Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。
3.4 Action Creator
View 要发送多少种消息,就会有多少种 Action。 如果都手写,会很麻烦。可以定义一个函数来生成Action,这个函数就叫做 Action Creator。
1 | const ADD_TODO = '添加 TODO'; |
##3.5 store.dispatch()
store.dispatch()
是 View 发出 Action 的唯一方法。
1 | import {createStore} from 'redux'; |
上面代码中, store.dispatch
接受一个 Action 对象作为参数,将它发送出去。
结合 Action Creator ,这段代码可以改写如下。
1 | store.dispatch(addTodo('Learn Redux')); |
##3.6 Reducer
store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
1 | const reducer = function(state,action){ |
整个应用的初始状态,可以作为 State 的默认值。下面是一个实际的例子。
1 | const defaultState = 0; |
上面代码中, reducer
函数收到名为 ADD
的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 不同来实现。
实际应用中, Reducer 函数不用像上面这样手动调用,store.dispatch
方法会触发 Reducer 的自动执行。因此, Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将Reducer 传入 createStore
方法。
1 | import {createStore} from 'redux'; |
上面代码中, createStore
接受 Reducer 作为参数,生成一个 新的 Store。以后每当 store.dispatch
发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
为什么这个函数叫做 Reducer 呢? 因为它可以作为数组的 reduce
方法的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组。
1 | const actions = [ |
上面代码中,数组 actions
表示依次有三个Action,分别是加 0
、1
、2
。数组的 reduce
方法接受 Reducer 函数作为参数,就可以直接得到最终的状态 3
。
##3.7 纯函数
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。
纯函数时函数式编程的概念,必须遵守以下一些约束。
1 | 不得改写函数 |
由于 Reducer 是纯函数,就可以保证同样的 State,必定得到同样的View。但也正因为这一点, Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
1 | // State 是一个对象 |
最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是任何时候,与某个View 对应的 State 总是一个不变的对象。
##3.8 store.subscribe()
Store 允许使用 store.subscribe
方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
1 | import {createStore} from 'redux'; |
显然,只要把 View 的更新函数 (对于 React项目,就是组件的 render
方法或setState
方法)放入 listen
,就会实现 View 的自动渲染。
store.subscribe
方法返回一个函数,调用这个函数就可以解除监听。
1 | let unsubscribe = store.subscribe(()=> |
#四、 Store 的实现
上一节介绍了 Redux 涉及的基本概念,可以发现 Store 提供了三个方法。
1 | store.getState() |
1 | import {createStore} from 'redux'; |
createStore
方法还可以接接受第二个参数,表示 State 的最初状态。这通常是服务器给出的。
1 | let store = createStore(todoApp,window.STATE_FROM_SERVER); |
上面代码中, window.STATE_FROM_SERVER
就是整个应用的状态初始值。注意,如果提供了这个参数,他会覆盖 Reducer 函数的默认初始值。
下面是 createStore
方法的一个简单实现,可以了解一下 Store 是怎么生成的。
1 | const createStore = (reducer)=>{ |
#五、Reducer 的拆分
Reducer 函数负责生成 State。由于整个应用只要一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。
请看下面的例子
1 | const chatReducer = (state = defaultState, action={}) =>{ |
上面代码中,三种 Action 分别改变State 的三个属性。
1 | ADD_CHAT: chatLog属性 |
这三个属性之间没有联系,这提示我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。
1 | const chatReducer = (state = defaultState, action = {}) =>{ |
上面代码中, Reducer 函数被拆分成了三个小函数,每一个负责生成对应的属性。
这样一拆,Reducer 就易读易写多了。而且,这种拆分与 React 应用的结构相吻合: 一个React根组件由很多子组件构成,这就是说,子组件与子Reducer 可以完全对应。
Redux 提供了一个 combineReducers
方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合并成一个大的 Reducer。
这种写法有一个前提,就是 State 的属性名必须与子 Reducer 同名。如果不同名,就要采用下面的写法。
1 | cosnt reducer = combineReducers({ |
总之, combineReducers()
做的就是产生一个整体的 Reducer 函数。该函数根据 State 的key 去执行相应的子 Reducer ,并将返回结果合并成一个大的State 对象。
下面是 combineReducer
的简单实现。
1 | const combineReducers = reducers =>{ |
你可以把所有子 Reducer 放在一个文件里面,然后统一引入。
1 | import {combineReducers} from 'redux' |
#六、工作流程
本节对 Redux 的工作流程,做一个梳理。
首先,用户发出 Action。
1 | store.dispatch(action); |
然后,Store 自动调用 Reducer,并且传入两个参数: 当前 State 和收到的 Action。 Reducer 会返回新的 State。
1 | let nextState = todoApp(previousState,action); |
State 一旦有变化,Store 就会调用监听函数。
1 | //设置监听函数 |
listener
可以通过store.getState()
得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
1 | function listerner(){ |
#七、实例:计数器
下面我们来看一个最简单的实例。
1 | const Counter = ({value}) =>(<h1>{value}<h1>); |
上面是一个简单的计数器,唯一的作用就是把参数 value
的值,显示在网页上。Store 的监听函数设置为 render
,每次 State的变化都会导致网页重新渲染。
下面加入一点变化,为 counter
添加递增和递减的 Action。
1 | const Counter = ({value,onIncrement,onDecrement})=>( |
完整的代码请看这里
Redux 的基本用法就介绍到这里。