Post

React和它的小伙伴们(2) - Redux与状态管理

Redux

根据前一篇文章中最开始说的,React就是一个函数对应关系,那么相应的,如果作为一个大应用来说就可能有些状态是需要跨组件共用的,那么自然而然地就会想到用一个仓库来把它保存起来,这里就产生了Redux这个“思维模式”。

Redux其实是一个可以脱离React使用的思维模式,这种方式具有四个想法:

  1. 提高修改数据(应用状态)的门槛
    主要就是引入action和dispatch两个方式来对全局的状态进行修改,而不是仅仅在每个组件内部进行修改和处理。
  2. 抽象出一个初始化的createStore方法,内部包含一个store,dispatch和getState三种方法。
  3. 加入订阅者模式来改变视图。(也就是subscribe方法)
  4. 引入Reducer函数(纯函数)

有了这四个想法,那么就直接来代码:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//初始化的createStore创建全局状态集方法
function createStore(reducer) {
	//store中的state
	let state = null;
	//存储所有的监听事件
	const listners = [];
	//添加订阅者
	const subscribe = (listner) => listners.push(listner);
	//暴露给外部获取状态
	const getState = () => state;
	//暴露给外部根据初始化传入的reducer方案进行修改状态
	const dispatch = (action) => {
		state = reducer(state, action);
		listner.forEach((listner) => listner());
	}
	//初始化操作(reducer中switch的default事件)
	dispatch({});
	//暴露给外部的所有方案
	return {getState, subscribe, dispatch};
}


//使用方案
function reducer(state, action) {
	/*初始化state以及定义本store所需要的action*/
}
//调用初始化方案进行创建Store
let store  = createStore(reducer);
//将重新渲染视图这个时间加入订阅者中
store.subscribe(() => renderApp(store.getState()));
//根据初始化的state,初始化App
renderApp(store.getState());
//需要更改状态时,使用这个
store.dispatch(action);

React-Redux

下面要做的就是将Redux与React连接,那么如何将一个全局的State分发到所有的组件中呢?

初步考虑:把这个组件状态放入全局可以获得的Context中,并且向下传递。 那么这样的方案会带来两个问题:

  1. 有大量的重复逻辑 -> 每个组件都要取出context, 从中取出需要的store中的state
  2. 对context的依赖性过强 -> 如果这个组件需要跨应用复用就难以为继了。

真正的框架中,使用了高阶组件的方式来解决第一个问题。 那么第二个问题就是尽量使用dumb组件(类似于纯函数),这样仅仅依赖于props来渲染组件,提高组件的可复用性。

那么具体是如何实现的呢? Code please:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//connect函数首先接收两个map对应的方法,并且返回一个接收需要被包装组件作为参数的包装方法,并且return出去
//比如C组件,需要包装的话,调用方式为connect(mSTP, mDTP)(C);

export connnect = (mapStateToProps, mapDispatchToProps) => (Wrapped) => {
	export Connect extends Component {
		static contextTypes = {
			store: PropTypes.Object,
		}	
		constructor() {
			super();
			this.store = {allProps: {}};
		}
		_updateProps() {
			const {state} = this.context;
			let stateProps = mapStateToProps ? mapStateToProps(store.getState) : this.props;
			let dispatchProps = mapDispatchToProps(store.dispatch);
	this.setState({allProps:{...stateProps,...dispatchProps, ...this.props}});
		}

		render() {
			return <Wrapped {...this.state.allProps}/>	
		}
	}
	return Connect;
}

那么接下来来看重要附加内容,两个参数:

  1. mapStateToProps: 或得到的Props如何对应到数据结构中(本组件的状态存储)
  2. mapDispatchToProps: 如何修改state的dispatch方法(自定义)

那么最后一个问题就是怎么样分发这里的状态呢? 整合Context, 在最顶层给出一个Provider组件,它就是一个容器组件, 会把嵌套的内容原封不动作为自己的子组件渲染出来. 它还会把外界传给它的props.store 放到 context, 这样子组件 connect 的时候都可以获取到.

Let’s see it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Provider extends Component {
  static propTypes = {
    store: PropTypes.object,
    children: PropTypes.any
  }

  static childContextTypes = {
    store: PropTypes.object
  }

  getChildContext () {
    return {
      store: this.props.store
    }
  }

  render () {
    return (
      <div>{this.props.children}</div>
    )
  }
}

/*
在需要使用Redux的地方套用这个Provider组价最为根节点,这样connect函数获
取到的props内容就都是这里拿到的store
*/

以上就是React中对于状态管理的Redux方案。当然这里也就只描述了ClassCompnent情况下的内容,如果使用Hooks的话,对于状态管理其实也大同小异。这个话题可以留待下次讨论~。

修改的方式有了,那么如何获得数据对其进行修改?如果在变化视图之前需要进行一些其他的操作呢?那就需要下一次讲到的中间件的登场了。 本系列未完待续。

This post is licensed under CC BY 4.0 by the author.