从零开始的 Redux
Redux 是什么
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。 目前一般与 React 配合使用。React 提供了 React-redux 库,两者能轻松结合起来。
开始之前需要知道的东西
为什么需要状态管理?
- 多次向下传递 props 过于繁琐
- 同一个 api 可能在不同情况下请求多次
如果你有 Vuex 的开发经验,那么上手起来会很快。
简单上手
在 Redux 中,状态 (state) 是通过 action 改变的,而 action 其实调用了 reducer 方法。一个 action 能告诉 reducer,下一步做出的动作 (type) 是什么,应该如何去改变数据。
对比 Vuex,一般在 Vuex 中我们通过 action 提交 (commit) 一个 state 的更改。而在 Redux 中是 action 调用了 reducer。
首先我们新建一个 store。
// src/store/index.js
import { createStore } from 'redux';
import reducer from './reducers';
export default createStore(reducer);
新建 store 需要传入一个 reducer。在这里我们来写一个小范例,state 记录一个数据,通过 action 改变他的大小。
import T from '../actions/types';
// import { add } from '../actions';
import { combineReducers } from 'redux';
const initState = {
num: 1
};
const change = (state = initState, action) => {
const { type, num } = action;
console.log(num);
switch (type) {
case T.ADD:
return {
num: ++state.num
};
case T.REDUCE:
return {
num: --state.num
};
default:
return state;
}
};
export default combineReducers({
change
});
前面提到 action 调用 reducer,所以我们需要一个 action。
import T from './types';
export function add(num) {
return {
type: T.ADD,
num
};
}
export function reduce(num) {
return {
type: T.REDUCE,
num
};
}
// store/actions/types.js
export const types = Object.freeze({
ADD: 'ADD',
REDUCE: 'REDUCE'
});
export default types;
一个 action 由一个 type 和一个 payload 组成,type 是告诉 reducer 应采取哪种更新方式。
我们再来看一下 reducer 由什么组成。
// src/store/reducers/index.js
import T from '../actions/types'; // 一般可以把 actionTypes 统一记录到一个文件中
import { combineReducers } from 'redux';
const initState = { // 原始 states
num: 1
};
// ADD 变量名作为 getStates 中的 key
const change = (state = initState, action) => {
// state 默认不存在所以需要制定默认值,也就是初始化,初始化之后每次调用都会传入未被更新的 state
// action 中记录了我们制定的 type, payload, 这里是 num
const { type, num } = action;
switch (type) {
// 判断类型,改变数据。应返回一个新的 state。注意:必须是新对象而不是一个引用
case T.ADD:
return {
num: state.num + num
};
case T.REDUCE:
return {
num: --state.num
};
default:
// 不匹配的 action 类型,直接返回。
// 一个 action 会被多个 reducer 接收,注意类型的监听。
return state;
}
};
// 使用 combineReducers 连接多个 reducers,虽然这里就一个
export default combineReducers({
change
});
至此一个存储数字,并能通过 action 改变他的大小的 store 就写好了。然后我们在 react 中使用它。
// app.js
import store from './store';
ReactDOM.render(
<App {...store} />,
document.getElementById('root')
);
将 store 实例挂载到根组件,下一层组件能通过 props 接收。
通过 getState 方法我们可以拿到 store 中存储的值,比如我想拿到 change
reducer 中的 state。
console.log(this.props.getState().change);
也可以通过 dispatch 方法修改 state 的值。this.props.dispatch(add(1))
到这里 redux 基本算是入门了,接下来是和 React 绑定。不知道有没有注意到开始从根组件传参只能传一层,违背了 store 随时随地使用的原理。这时候 react-redux 登场了。
react-redux 提供一个 Provider 高阶组件,传入一个 store,接下来在下层的所有子组件中用只要使用 connect 方法就可获取到 store。
import store from './store';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
在子组件中不再导出一个默认的 Component,而是导出一个 connect Component。
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { add, reduce } from '../store/actions';
import styles from './style.module.css';
class Demo extends Component {
render() {
console.log(this.props);
return (
<div className={styles['wrap']}>
<button onClick={() => this.props.add(1)}>+</button>
<span style={{ color: '#fff' }}>{this.props.num}</span>
<button onClick={() => this.props.reduce(1)}>-</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
num: state.change.num
};
};
const mapDispatchToProps = dispatch => {
return {
add: num => dispatch(add(num)),
reduce: num => dispatch(reduce(num))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Demo);
mapStateToProps
仔细看是不是和 mapGetter 有点像。mapStateToProps
接收一个 state,返回一个对象,这个对象会直接被挂载到 props 上。
mapDispatchToProps
又如同 mapActions,他返回一个对象,这个对象也会直接被挂载到 props 上。这个对象中的函数返回一个函数,可以接收参数,并调用 dispatch 改变 state。