
Redux
1.redux解决什么问题
- redux 出现为了解决state里的数据问题
- react中,数据在组件中是单向流动的
- 数据从一个方向父组件流向子组件(通过props),由于这个特征,非父子关系的组件(或者兄弟组件)之间的通信比较麻烦
2.redux设计思想
- redux是将整个应用状态存储到一个地方,称为store
- 里面保存一棵状态树
state tree
- 组件可以派发
dispatch
行为action
给store
,而不是直接通知其他组件
- 其他组件可以通过订阅
store
中的状态(state)
来刷新自己的视图

3.redux三大原则
- 整个应用中
state
被存储在一棵object tree
中,并且这个object tree
只存储唯一一个store
中
state
是只读的,唯一改变state
的方法就是触发action
,action
是一个用于描述已发生事件的普通对象,使用纯函数来执行修改,为了描述action
如何改变state tree
,你需要编写reducers
- 单一数据源的设计让
react
的组件之间通信更加方便,同时也便于状态的统一管理
4.原生计算器

4.1.public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
<div id="counter">
<p id="counter-value"></p>
<button id="add-btn">Add +1</button>
<button id="minus-btn">Minus -1</button>
</div>
</body>
</html>
4.2.src/index.js
import { createStore } from './redux';
const counterValue = document.getElementById('counter-value');
const addBtn = document.getElementById('add-btn');
const minusBtn = document.getElementById('minus-btn');
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const initialState = { num: 0 };
const reudcer = (state = initialState, action) => {
switch (action.type) {
case INCREMENT:
return { num: state.num + 1 };
case DECREMENT:
return { num: state.num - 1 }
default:
return state;
}
}
const store = createStore(reudcer, { num: 1 })
function render() {
counterValue.innerHTML = store.getState().num + '';
}
store.subscribe(render);
render();
addBtn.addEventListener('click', () => {
store.dispatch({ type: INCREMENT });
})
minusBtn.addEventListener('click', () => {
store.dispatch({ type: DECREMENT });
})
4.3.redux/createStore
function creatStore(reducer, preloadedState) {
let state = preloadedState;
const listeners = [];
function getState() {
return state;
}
function subscribe(listener) {
listeners.push(listener);
return () => {
const idx = listeners.indexOf(listener);
listeners.splice(idx, 1);
}
}
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
return action;
}
dispatch({ type: '@@REDUX/INIT' });
const store = {
getState,
subscribe,
dispatch,
}
return store;
}
export default creatStore;
4.4.redux/index.js
export { default as createStore } from './createStore';
5.react中使用

5.1.src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter'
ReactDOM.render(<Counter />, document.getElementById('root'));
5.2.components/Counter.js
import React from 'react';
import { createStore } from '../redux';
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const initialState = { num: 0 };
const reudcer = (state = initialState, action) => {
switch (action.type) {
case INCREMENT:
return { num: state.num + 1 };
case DECREMENT:
return { num: state.num - 1 }
default:
return state;
}
}
const store = createStore(reudcer, { num: 0 });
function Counter() {
const [num, setNum] = React.useState(0);
React.useEffect(() => {
const unsubscribe = store.subscribe(() => setNum(store.getState().num))
return () => {
unsubscribe();
}
}, [])
return <div>
<p>{num}</p>
<button onClick={() => store.dispatch({ type: INCREMENT })}>add +1</button>
<button onClick={() => store.dispatch({ type: DECREMENT })}>minus -1</button>
</div>
}
export default Counter;
6.bindActionCreators

6.1.components/Counter.js
import React from 'react';
+ import { createStore, bindActionCreators } from '../redux';
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const initialState = { num: 0 };
const reudcer = (state = initialState, action) => {
switch (action.type) {
case INCREMENT:
return { num: state.num + 1 };
case DECREMENT:
return { num: state.num - 1 }
default:
return state;
}
}
const store = createStore(reudcer, { num: 0 });
+ function add() {
+ return { type: INCREMENT };
+ }
+ function minus() {
+ return { type: DECREMENT };
+ }
+ const actions = { add, minus };
+ const boundActions = bindActionCreators(actions, store.dispatch);
function Counter() {
const [num, setNum] = React.useState(0);
React.useEffect(() => {
const unsubscribe = store.subscribe(() => setNum(store.getState().num))
return () => {
unsubscribe();
}
}, [])
return <div>
<p>{num}</p>
+ <button onClick={boundActions.add}>add +1</button>
+ <button onClick={boundActions.minus}>minus -1</button>
</div>
}
export default Counter;
6.2.redux/bindActionCreators.js
function bindActionCreators(actionCreators, dispatch) {
const boundActionCreators = {};
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
return boundActionCreators;
}
function bindActionCreator(actionCreator, dispatch) {
const boundActionCreator = function (...args) {
const action = actionCreator.apply(this, args);
dispatch(action);
}
return boundActionCreator;
}
export default bindActionCreators;
6.3.redux/index.js
export { default as createStore } from './createStore';
+ export { default as bindActionCreators } from './bindActionCreators';
7.combineReducers

7.1.src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1'
import Counter2 from './components/Counter2'
ReactDOM.render(<div>
<Counter1 />
<Counter2 />
</div>, document.getElementById('root'));
7.2.componets/Counter1.js
import React from 'react'
import { bindActionCreators } from '../redux'
import store from '../store';
import actions from '../store/actions/counter1';
const boundAction = bindActionCreators(actions, store.dispatch);
function Counter1() {
const [num, setNum] = React.useState(store.getState().Counter1.num);
React.useEffect(() => {
return store.subscribe(() => {
setNum(store.getState().Counter1.num);
})
})
return <div>
<p>{num}</p>
<button onClick={boundAction.add1}>+</button>
<button onClick={boundAction.minus1}>-</button>
</div>
}
export default Counter1;
7.3.components/Counter2.js
import React from 'react'
import { bindActionCreators } from '../redux'
import store from '../store';
import actions from '../store/actions/counter2';
const boundAction = bindActionCreators(actions, store.dispatch);
function Counter2() {
const [num, setNum] = React.useState(store.getState().Counter1.num);
React.useEffect(() => {
return store.subscribe(() => {
setNum(store.getState().Counter2.num);
})
})
return <div>
<p>{num}</p>
<button onClick={boundAction.add2}>+</button>
<button onClick={boundAction.minus2}>-</button>
</div>
}
export default Counter2;
7.4.store/index.js
import { createStore } from '../redux';
import reducers from './reducers';
const store = createStore(reducers);
export default store;
7.5.store/action-types.js
const ADD1 = 'ADD1';
const MINUS1 = 'MINUS1';
const ADD2 = 'ADD2';
const MINUS2 = 'MINUS2';
export {
ADD1,
ADD2,
MINUS1,
MINUS2
}
7.6.store/reducers/index.js
import { combineReducers } from '../../redux';
import Counter1 from './counter1';
import Counter2 from './counter2';
const rootReducers = combineReducers({
Counter1,
Counter2
})
export default rootReducers;
7.7.store/reducers/counter1.js
import * as types from '../action-types';
const initialState = { num: 1 };
function reducer(state = initialState, action) {
switch (action.type) {
case types.ADD1:
return { num: state.num + 1 };
case types.MINUS1:
return { num: state.num - 1 };
default:
return state;
}
}
export default reducer;
7.8.store/reducers/counter2.js
import * as types from '../action-types';
const initialState = { num: 1 };
function reducer(state = initialState, action) {
switch (action.type) {
case types.ADD2:
return { num: state.num + 1 };
case types.MINUS2:
return { num: state.num - 1 };
default:
return state;
}
}
export default reducer;
7.9.store/actions/counter1.js
import * as types from '../action-types';
const actions = {
add1() {
return { type: types.ADD1 };
},
minus1() {
return { type: types.MINUS1 };
}
}
export default actions;
7.10.store/actions/counter2.js
import * as types from '../action-types';
const actions = {
add2() {
return { type: types.ADD2 };
},
minus2() {
return { type: types.MINUS2 };
}
}
export default actions;
7.11.combineReducers实现
function combineReducers(reducers) {
return (state = {}, action) => {
const nextState = {};
let changed = false;
for (let key in reducers) {
const reducer = reducers[key];
const previouseStateForKey = state[key];
const nextStateForKey = reducer(previouseStateForKey, action);
if (previouseStateForKey !== nextStateForKey) {
changed = true;
}
nextState[key] = nextStateForKey;
}
return changed ? nextState : state;
}
}
export default combineReducers;
export { default as createStore } from './createStore';
export { default as bindActionCreators } from './bindActionCreators';
+ export { default as combineReducers } from './combineReducers';
8.react-redux

8.1.src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1'
import Counter2 from './components/Counter2'
import { Provider } from './react-redux'
import store from './store'
ReactDOM.render(<Provider store={store}>
<Counter1 />
<Counter2 />
</Provider>, document.getElementById('root'));
8.2.components/Counter1.js
import React from 'react'
import { connect } from '../react-redux';
import actions from '../store/actions/counter1';
class Counter1 extends React.Component {
render() {
const { num, add1, minus1 } = this.props
return <div>
<p>{num}</p>
<button onClick={add1}>+</button>
<button onClick={minus1}>-</button>
</div>
}
}
const mapStateToProps = state => state.Counter1;
export default connect(
mapStateToProps,
actions
)(Counter1);
8.3.components/Counter2.js
import React from 'react'
import { connect } from '../react-redux';
import actions from '../store/actions/counter2';
class Counter1 extends React.Component {
render() {
const { num, add2, minus2 } = this.props
return <div>
<p>{num}</p>
<button onClick={add2}>+</button>
<button onClick={minus2}>-</button>
</div>
}
}
const mapStateToProps = state => state.Counter2;
export default connect(
mapStateToProps,
actions
)(Counter1);
8.4.react-redux/Provider.js
import React from 'react';
import ReactReduxContext from './ReactReduxContext';
function Provider(props) {
return (
<ReactReduxContext.Provider value={{ store: props.store }}>
{props.children}
</ReactReduxContext.Provider>
)
}
export default Provider;
8.5.react-redux/ReactReduxContext.js
import React from 'react';
export default React.createContext();
8.6.react-redux/connect.js
import React from 'react';
import ReactReduxContext from './ReactReduxContext';
import { bindActionCreators } from '../redux';
/**
* 把组件和仓库关联
* @param {*} mapStateToProps 仓库状态映射为属性
* @param {*} mapDispatchToProps store.dispatch映射为属性
* @returns
*/
function connect(mapStateToProps, mapDispatchToProps) {
return OldComponent => {
return props => {
const { store } = React.useContext(ReactReduxContext);
const { getState, dispatch, subscribe } = store;
const prevState = getState();//仓库状态
//映射后的状态,useMemo做优化
const stateProps = React.useMemo(() => mapStateToProps(prevState), [prevState]);
let dispatchProps = React.useMemo(() => {
if (typeof mapDispatchToProps === 'object') {
return bindActionCreators(mapDispatchToProps, dispatch);
} else if (typeof mapDispatchToProps === 'function') {
return mapDispatchToProps(dispatch, props);
} else {
return { dispatch };
}
}, [dispatch])
//用于数据改变刷新
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
React.useLayoutEffect(() => {//订阅
return subscribe(forceUpdate)
}, [subscribe])
return <OldComponent {...props} {...stateProps} {...dispatchProps} />
}
}
}
export default connect;
8.7.react-redux/index.js
export { default as Provider } from './Provider';
export { default as connect } from './connect';
9.hooks
9.1.components/Counter1.js
import React from 'react'
import { useSelector, useDispatch } from '../react-redux'
function Counter1() {
const state = useSelector(state => state.Counter1);
const dispatch = useDispatch();
return <div>
<p>{state.num}</p>
<button onClick={() => dispatch({ type: 'ADD1' })}>+</button>
<button onClick={() => dispatch({ type: 'MINUS1' })}>-</button>
</div>
}
export default Counter1;
9.2.react-redux/hooks/index.js
export { default as useDispatch } from './useDispatch';
export { default as useSelector } from './useSelector';
9.3.react-redux/index.js
export { default as Provider } from './Provider';
export { default as connect } from './connect';
+ export { useSelector, useDispatch } from './hooks';
9.4.react-redux/hooks/useDispatch.js
import React from 'react';
import ReactReduxContext from '../ReactReduxContext';
const useDispatch = () => {
return React.useContext(ReactReduxContext).store.dispatch;
}
export default useDispatch;
9.5.react-redux/hooks/useSelector.js
import React from 'react';
import ReactReduxContext from '../ReactReduxContext';
function useSelector(selector) {
const { store } = React.useContext(ReactReduxContext);
const selectorState = useSelectorWithStore(selector, store);
return selectorState;
}
function useSelectorWithStore(selector, store) {
const { getState, subscribe } = store;
const storeState = getState();//总状态
const selectorState = selector(storeState);//获取分状态
//为了刷新用
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
React.useLayoutEffect(() => {
return subscribe(forceUpdate)//订阅和销毁
}, [store])
return selectorState;
}
export default useSelector;
10.中间件
- 如果没有中间件的运用,redux的工作流程会是
action->reducer
,这是相当于同步操作,由dispatch
触发action后,直接去
reducer`执行相应的动作
- 但是如果没有中间件,对于一些复杂逻辑会存在问题。比如:我们点击一个按钮->请求数据->新数据渲染视图,此时因为请求数据是异步的,这个时候同步的redux是无法满足的,因此引入了中间件的概念,有了中间件redux的工作流程变成了
action
->middlewares
->reducer
,点击按钮就相当于dispatch
触发了action
,接着获取服务器数据middlewares
执行,当middlewares
成功获取服务器数据后就会触发reducer
对应的动作更新视图
- 中间件的机制可以让我们改变数据流,实现如异步
action
,action过滤
,日志输出
等


10.1.日志中间件
function logger(middlewareApi) {
return next => {
return action => {
const { getState } = middlewareApi;
console.log('老状态', getState());
next(action);
console.log('新状态', getState());
}
}
}
export default logger;
10.2.promise中间件
const promise = middlewareApi => next => action => {
const { dispatch } = middlewareApi;
if (typeof action.then === 'function') {
return action.then(dispatch)
}
next(action);
}
export default promise;
10.3.thunk中间件
const thunk = middlewareApi => next => action => {
const { dispatch } = middlewareApi;
if (typeof action === 'function') {
return action(dispatch)
}
next(action);
}
export default thunk;
10.4.redux/compose.js
function compose(...fns) {
return fns.reduce((a, b) => (...args) => a(b(...args)));
}
export default compose;
10.5.redux/applyMiddleware.js
import compose from './compose'
function applyMiddleware(...middlewares) {
return createStore => {
return reducers => {
const store = createStore(reducers);
let dispatch;
const middlewareApi = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
const chain = middlewares.map(middleware => middleware(middlewareApi));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
}
}
}
}
export default applyMiddleware;
10.6.src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1'
import Counter2 from './components/Counter2'
import Counter3 from './components/Counter3'
import { Provider } from './react-redux'
import store from './store'
ReactDOM.render(<Provider store={store}>
<Counter1 />
<Counter2 />
+ <Counter3 />
</Provider>, document.getElementById('root'));
10.7.components/Counter3.js
import React from 'react';
import { connect } from '../react-redux'
import actions from '../store/actions/counter3'
class Counter3 extends React.Component {
render() {
const { num, asyncAdd, pMinus } = this.props;
return <div>
<p>{num}</p>
<button onClick={asyncAdd}>asyncAdd</button>
<button onClick={pMinus}>promise minus</button>
</div>
}
}
const mapStateToProps = state => state.Counter3;
export default connect(mapStateToProps, actions)(Counter3);
10.8.store/index.js
import { createStore,applyMiddleware } from '../redux';
import reducers from './reducers';
+ import logger from '../redux-logger'
+ import promise from '../redux-promise'
+ import thunk from '../redux-thunk'
+ const store = applyMiddleware(promise, thunk, logger)(createStore)(reducers);
export default store;
10.9.store/actions/counter3.js
import * as types from '../action-types';
const actions = {
asyncAdd() {
return dispatch => {
setTimeout(() => {
dispatch({ type: types.ADD3 })
}, 3000)
}
},
pMinus() {
return dispatch => {
new Promise((resolve) => {
resolve(1)
}).then(res => {
dispatch({ type: types.MINUS3 })
})
}
}
}
export default actions;
10.10.store/reducers/counter3.js
import * as types from '../action-types';
const initialState = { num: 1 };
function reducer(state = initialState, action) {
switch (action.type) {
case types.ADD3:
return { num: state.num + 1 };
case types.MINUS3:
return { num: state.num - 1 };
default:
return state;
}
}
export default reducer;
10.11.store/action-types.js
const ADD1 = 'ADD1';
const MINUS1 = 'MINUS1';
const ADD2 = 'ADD2';
const MINUS2 = 'MINUS2';
+ const ADD3 = 'ADD3';
+ const MINUS3 = 'MINUS3';
export {
ADD1,
ADD2,
MINUS1,
MINUS2,
+ ADD3,
+ MINUS3
}