一、实现一个简易的 Redux
由于公司项目的技术栈是 React,与之配套的公共状态管理的库是 Redux,最近也研究了其中的原理。由于我之前是硬背 Redux 的用法,时间搁久了总是忘记如何使用。每次要用的时候,就去翻文档,不仅效率低下,用起来也感觉到恶心自己了。搞清楚了背后的机制,写起来就很顺手。相信大家弄懂的基本的实现细节,用起来也会得心应手。
首先思考 Redux 解决了什么问题,为什么要使用它。每当有一些数据要在很多组件中使用的时候,而这些组件跨层级时,props 就不再适合。就需要把这些数据提取出来放在一个模块,我们把它称为公共状态,每当数据改变时,每当数据改变时,它会通知订阅它的说有组件。
要想实现这样的共享的数据,首先想到的必是建一个全局模块,里面存放很多公共的数据。就像这样
const obj = {
count: 0
}
当状态需要改变的时候,直接显示的赋值,当然,这里要通过观察者模式数据已经改变。不过这样肯定不好
- 容易误操作,试想我一不小心清空了对象,会影响其他组件都受到污染,肯定要有条件的改变公共状态
- 不易排查错误
1、 实现 getState
既然是共享数据,外部又不能直接改变它,当然就是一个闭包啦。没错,Redux 内部这样的一个机制,通过 store 的一些方法,我们可以搭出大致的骨架
function createStore() {
// 公共的状态
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch() {
}
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
毫无疑问,每次调用 getState 就可以拿到最新的 currentState 的值
2、实现 dispatch
在 Redux 里,每当要改变数据,都是调用 dispatch 一个 action,根据 actionType 的值来返回不同的 state,好了,我们就可以这样写
function createStore() {
// 公共的状态
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
swicth(action.type) {
case "increament":
return {
...currentState,
count: currentState.count + 1
}
default:
return currentState
}
}
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
这样就实现了一个 dispatch,但这样写不好,我们把 switch 这样的逻辑可以提取成一个函数,这也是所说的 reducer,那么,代码就成了这样
const initialState = {
count: 1
}
function reducer(state = initialState, {type, payload}) {
switch (type) {
case 'increament':
return {
...state,
count: count + payload
}
case 'decrement':
return {
...state,
count: count - payload
}
default:
return initialState
}
}
function createStore() {
// 公共的状态
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
currentState = reducer(currentState, action)
}
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
在我们创建 store 的时候,会初始化一次 state,也就是内部会 dispatch 一次,因而
function createStore() {
// 公共的状态
let currentState = {}
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
currentState = reducer(currentState, action)
}
dispatch({ type: '@@REDUX_INIT' })
function subscribe() {
}
return {
getState,
dispatch,
subscribe
}
}
到此,即完成了获取和改变状态的功能,验证一下代码
const store = createStore(reducer)
console.log(store.getState().count) // 1
store.dispatch({type: 'increament', payload: 5})
console.log(store.getState().count) // 6
尽管如此,当数据改变了,还是不能通知我视图更新。这里需要监听数据的变化,用到了观察者模式,也有人说是发布订阅模式,这两种模式基本的思想一致,但还是有一些区别的,这里观察者模式更加准确。状态数据相当于被观察者,在 createStore 中,我们要维护一个观察者的队列,当执行 subscribe时,把他们放入队列,dispatch的时候,执行队列里的回调
function createStore() {
// 公共的状态
let currentState = {}
// 存放观察者的队列
let observers = []
// getter
function getState() {
return currentState
}
// setter
function dispatch(action) {
currentState = reducer(currentState, action)
observers.foreach(fn => fn())
}
dispatch({ type: '@@REDUX_INIT' })
function subscribe(fn) {
observers.push(fn)
}
return {
getState,
dispatch,
subscribe
}
}
如此以来,就完成了数据变化,通知组件改变视图的功能,模拟组件运行代码
const store = createStore(reducer)
store.subscribe(() => console.log('组件1收到通知', store.getState()))
store.subscribe(() => console.log('组件2收到通知', store.getState()))
store.dispatch({ type: 'increament', payload: 5 })
一个最简单的 redux 的应用已经完成,这里还缺饭了大量的参数类型判断,异常处理,异步action、中间件等等。但基本的原理大致相同
二、实现 react-redux
在 react 的项目中,尽管 redux 能实现我们的需求,但写法太过冗余,和 react 的组件写法也不太契合,于是,就有了 react-redux。简单来说,它提供了两大功能:
- Provider 组件
- connect 高阶函数
Provider原理是把 store 绑定在 context 上,至于 context,可以为子孙组件的上下文提供 store 对象
class Provider extends Component {
static childContextTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.store = props.store;
}
getChildContext() {
return {
store: this.store
}
}
render() {
return this.props.children
}
}
connect 是一个高阶函数,接收 mapStateToProps, mapDispatchToProps 为参数,并且它返回一个高阶组件。其中,这两个参数都是函数,mapStateToProps 接收 state 返回一个对象,这搞清楚了,mapDispatchToProps 接收一个 dispatch 返回一个对象,我们再来实现它:
// 形如 mapTostate、mapDispatchToProps
const mapStateToProps = state => {}
const mapDispatchToProps = dispatch => {}
function connect(mapStateToProps, mapDispatchToProps) {
return function(WrappedComponent) {
class Connect extends Component{
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
this.mapDispatch = mapDispatchToProps(store.dispacth)
}
ComponentDidMount() {
}
<WrappedComponent {...this.props} {this.state} {this.mapDispatch} />
}
return Connect
}
}
知道这里,我们通过高阶函数把一些属性挂载到了高阶组件的 props 上,接下来就可以通过 this.props.xxx 调用。此时,我们被 connect 包裹的新组件的 props 上虽然有了值,但是还不具备自动更新的功能,继续改进 connect
function connect(mapStateToProps, mapDispatchToProps) {
return function(WrappedComponent) {
class Connect extends Component{
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
this.mapDispatch = mapDispatchToProps(store.dispacth)
}
ComponentDidMount() {
// 如果 store 中的状态改变会执行回调函数,此时新获取的 mappedState 和旧的做对比,如若有变化,就setState
this.unsub = store.subscribe(() => {
const mappedState = mapStateToProps(store.getState());
if(shallowEqual(this.state, mappedState)) {
return;
}
this.setState(mappedState);
});
}
<WrappedComponent {...this.props} {this.state} {this.mapDispatch} />
}
return Connect
}
}
如此,一个基本功能的 react-redux 已经实现。