一、介绍
Why?
javascript需要管理state状态。
这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
React作为一个组件化开发框架,组件之间存在大量通信,有时这些通信跨越多个组件,或者多个组件之间共享一套数据,简单的父子组件间传值不能满足我们的需求,自然而然地,我们需要有一个地方存取和操作这些公共状态。
三大原则
2.1、单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
2.2、state是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
2.3、使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
二、源码分析
重点:采用观察者设计模式设计state变化视图更新事件
1、存取状态createStore
export const createStore = () => {
let currentState = {} // 公共状态
function getState() {} // getter
function dispatch() {} // setter
function subscribe() {} // 发布订阅(观察者模式)
return { getState, dispatch, subscribe }
}
1.1、getState实现
export const createStore = () => {
let currentState = {} // 公共状态
function getState() { // getter
return currentState
}
function dispatch() {} // setter
function subscribe() {} // 发布订阅
return { getState, dispatch, subscribe }
}
1.2、dispatch实现
有条件地、具名地修改store的数据,我们需要给dispatch()传入一个action对象,这个对象包括我们要修改的state以及这个操作的名字(actionType)。根据type的不同,store会修改对应的state。
export const createStore = () => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
switch (action.type) {
case 'plus':
currentState = {
...state,
count: currentState.count + 1
}
}
}
function subscribe() {}
return { getState, subscribe, dispatch }
}
考虑代码格式优美, 修改state的规则抽离出来,建立我们熟悉的reducer。
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
}
function subscribe() {}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, dispatch, subscribe }
}
从外部引入reducer.js
//reducer.js
const initialState = {
count: 0
}
export function reducer(state = initialState, action) {
switch(action.type) {
case 'plus':
return {
...state,
count: state.count + 1
}
case 'subtract':
return {
...state,
count: state.count - 1
}
default:
return initialState
}
}
1.3、subscrib实现
已经能存取公用state,可并不会引起视图的更新,所以需要监听store的变化,这里应用了观察者设计模式实现监听事件。 第一步:创建观察者队列 第二步:将观察事件加入观察者队列 第三步:在目标发布改变时,执行观察者事件
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
// 当state发生变化时触发更新更新
observers.forEach(fn => fn())
}
function subscribe(fn) {
// 把观察者的fn触发事件加入队列
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
可执行:
const store = createStore(reducer) //创建store
store.subscribe(() => { console.log('组件1收到store的通知') })
store.subscribe(() => { console.log('组件2收到store的通知') })
store.dispatch({ type: 'plus' }) //执行dispatch,触发store的通知
到这里,一个简单的redux就已经完成,在redux真正的源码中还加入了入参校验等细节,但总体思路和上面的基本相同。
三、react-redux
想从store存取公用状态,需要进行四步操作: import引入store、getState获取状态、dispatch修改状态、subscribe订阅更新 从而出现了代码相对冗余,我们想要合并一些重复的操作。
react-redux提供Provider和connect两个API: Provider将store放进this.context里,省去了import这一步 connect将getState、dispatch合并进了this.props,并自动订阅更新
2.1、 Provider
Provider是一个组件,接收store并放进全局的context对象,就能在组件中通过this.context.store这样的形式取到store,不需要再单独import store。
意义:connect是自己写的,当然可以直接import store,但react-redux的connect是封装的,对外只提供api,所以需要让Provider传入store。
2.2 connect
connect中的装饰器模式:回顾一下connect的调用方式:connect(mapStateToProps, mapDispatchToProps)(App)其实connect完全可以把App跟着mapStateToProps一起传进去,看似没必要return一个函数再传入App,为什么react-redux要这样设计,react-redux作为一个被广泛使用的模块,其设计肯定有它的深意。 其实connect这种设计,是装饰器模式的实现,所谓装饰器模式,简单地说就是对类的一个包装,动态地拓展类的功能。connect以及React中的高阶组件(HoC)都是这一模式的实现。除此之外,也有更直接的原因:这种设计能够兼容ES7的装饰器(Decorator),使得我们可以用@connect这样的方式来简化代码,有关@connect的使用可以看这篇<react-redux中connect的装饰器用法>:
export function connect(mapStateToProps, mapDispatchToProps) {
return function(Component) {
class Connect extends React.Component {
componentDidMount() { //从context获取store并订阅更新
this.context.store.subscribe(this.handleStoreChange.bind(this));
}
handleStoreChange() {
// 触发更新
// 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新
this.forceUpdate()
}
render() {
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{ ...this.props }
// 根据mapStateToProps把state挂到this.props上
{ ...mapStateToProps(this.context.store.getState()) }
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{ ...mapDispatchToProps(this.context.store.dispatch) }
/>
)
}
}
//接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
收获
监听state变化运用了观察者设计模式,connect的封装采用了装饰器模式。