什么是Redux
Redux是JavaScript的一个状态容器,用来帮助JS进行状态的管理和追踪。
Redux的核心理念
store action reducer
store
通过创建一个容器来规范各个组件对容器内状态的调用,让数据变的可追踪。
action
- Redux要求必须使用action来更新数据
- 所有的数据的变化,都必须通过派发action来更新
- action是一个普通的JS对象,用来描述这次更新的type和content
reducer
- reducer负责将state和action联系起来
- reducer是一个纯函数
- reducer负责将传入的state和action结合起来形成一个新的state
Redux三大原则
- 单一数据源
- state是只读的
- 确保修改state的方法一定是触发action,不能试图从其他地方用任何方式来修改state
- 确保了view或网络请求都不能直接修改state,只能通过action来描述自己想要如何修改state
- 这样可以保证所有的修改都集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竞态)的问题
- 使用纯函数来执行修改
- 通过reducer将旧state好actions联系在一起,并且返回一个新的state
- 随着应用程序复杂度的增加,我们可以将reducer拆分成多个小的reducers,分别操作不同的state tree的一部分
- 所有的reducer都应该是纯函数,不能产生任何的副作用。
单独使用redux
新建一个store文件
-
constance.js存放抽离出来的type类型常量
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const SUB_NUMBER = "SUB_NUMBER";
-
reducer.js存放纯函数reducer
import { INCREMENT, DECREMENT, SUB_NUMBER } from "./constance";
const initialState = { counter: 10 };
export const reducer = (state = initialState, { type, number }) => {
switch (type) {
case INCREMENT:
return { ...state, counter: state.counter + 1 };
case DECREMENT:
return { ...state, counter: state.counter - 1 };
case SUB_NUMBER:
return { ...state, counter: state.counter - number };
default:
return state;
}
};
- actionCreaters.js存放action代码
import { DECREMENT, INCREMENT, SUB_NUMBER } from "./constance";
export const incrementAction = {
type: INCREMENT,
};
export const decrementAction = {
type: DECREMENT,
};
export const subNumberAction = (number) => ({
type: SUB_NUMBER,
number,
});
- index.js整合redux文件导出redux
import { createStore } from "redux";
import { reducer } from "./reducer";
export const store = createStore(reducer);
- 然后在页面中订阅redux,并在销毁前取消订阅,然后再派发actions
import React, { PureComponent } from "react";
import { store } from "../store";
import { decrementAction, subNumberAction } from "../store/actionCreaters";
export default class home extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
//订阅redux
componentDidMount() {
this.sub = store.subscribe(() => {
this.setState({
counter: store.getState().counter,
});
});
}
//取消订阅redux
componentWillUnmount() {
this.sub();
}
render() {
return (
<div>
<h1>home</h1>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={(e) => this.decrement()}>-1</button>
<button onClick={(e) => this.subNmuber()}>-5</button>
</div>
);
}
//派发acitons
decrement() {
store.dispatch(decrementAction);
}
subNmuber() {
store.dispatch(subNumberAction(5));
}
}
redux融入react
单独的使用redux,在react中,每个组件都要重复的调用派发action,订阅,取消订阅redux,过于臃肿。
我们可以自定义一个connect函数,来将组件和redux联系起来,减少重复的代码。
首先我们可以定义个connect高阶函数,将需要使用redux的组件传入,并在该函数中引入redux,返回一个新的组件,并将需要的数据添加到props中
相对于组件来说,如果有人提供它和redux的联系,那么它只要告诉这个人,它需要从redux中获得什么,并给他什么。所以connect函数的三个参数可以确定下来。要什么,给什么,哪个组件要
这样需要的数据都能从props中获得,那么组件的代码可以删减成如下
接下来我们需要具体定义mapStateToProps(要什么)、mapDispatchToProps(给什么)。当前组件需要redux中state的counter数据,需要给redux派发两个不同的actions。
有了上述的需求,那么我们这个connect函数可以具体设计出来。首先他需要接收一个需要和redux联系的组件,然后收下这个组件的要求:要redux什么、给redux什么,然后返回一个新的和redux联系的组件。
然后从redux中拿出需要的状态,并派发相应的actions,都传递给返回的新组件
最后需要将这些数据通过组件state联系起来,让react去处理页面ui的变化。
手动联系redux和react的代码
目录结构
connect函数部分
import React, { PureComponent } from "react";
import { store } from "../store";
export function connect(mapStateToProps, mapDispatchToProps) {
return function enhanceHOC(WrappedComponent) {
return class extends PureComponent {
constructor(props) {
super(props);
this.state = {
mapState: store.getState(),
};
}
//订阅redux
componentDidMount() {
this.sub = store.subscribe(() => {
this.setState({
mapState: store.getState(),
});
});
}
//取消订阅redux
componentWillUnmount() {
this.sub();
}
render() {
return (
<WrappedComponent
{...this.props}
{...mapStateToProps(store.getState())}
{...mapDispatchToProps(store.dispatch)}
></WrappedComponent>
);
}
};
};
}
Home组件部分
import React, { PureComponent } from "react";
import { decrementAction, subNumberAction } from "../store/actionCreaters";
import { connect } from "../utils/connect";
class Home extends PureComponent {
render() {
const { decrement, subNmuber, counter } = this.props;
return (
<div>
<h1>home</h1>
<h2>当前计数:{counter}</h2>
<button onClick={(e) => decrement()}>-1</button>
<button onClick={(e) => subNmuber(5)}>-5</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
counter: state.counter,
});
const mapDispatchToProps = (dispatch) => ({
decrement: () => {
dispatch(decrementAction);
},
subNmuber: (num) => {
dispatch(subNumberAction(num));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
彻底抽离connect函数方法
上面虽然实现了手动联系redux和recat的方法,但是这个connect函数,仍然依赖我自己项目中的某个路径下导入的store,不能很好的去复用,所以我们对该函数进行一个优化,让用户自己传入需要使用的store。
那么我们只要定义一个新的context方法,让它去给connect函数传递需要的store
然后我们在index.js全局入口处,将store通过context的方式给子孙组件通信传递数据。
接着既然后续组件能收到根组件传递过来的redux中的store,那么我们可以在connect函数中,获取到connect对象,并从中取出,根组件传递过来的对象。
修改后的connect函数部分代码
import React, { PureComponent } from "react";
import { StoreContext } from "./context";
export function connect(mapStateToProps, mapDispachToProp) {
return function enhanceHOC(WrappedComponent) {
class EnhanceComponent extends PureComponent {
constructor(props, context) {
super(props, context);
this.state = {
storeState: mapStateToProps(context.getState()),
};
}
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({
storeState: mapStateToProps(this.context.getState()),
});
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<WrappedComponent
{...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispachToProp(this.context.dispatch)}
/>
);
}
}
EnhanceComponent.contextType = StoreContext;
return EnhanceComponent;
};
}