一、createStore创建仓库
createStore
接收两个参数,第一个参数是reducer
,第二个参数是初始state
状态。此时的state
状态权重最大。
createStore
返回一个store
对象,该对象有三个核心方法getState()``subscribe()``dispatch()
,分别用来获取最新状态、添加订阅渲染函数、触发状态更新。
用法
import React from 'react';
import { createStore } from '../redux/createStore';
// reducer
const reducer = (oldState, action) => {
switch (action.type) {
case 'ADD':
return { number: oldState.number + 1 };
case 'MINUS':
return { number: oldState.number - 1 };
default:
return oldState;
}
}
// store
let store = createStore(reducer, { number: 10 });
class Counter extends React.Component {
state = {
number: store.getState().number
}
componentDidMount() {
// 添加订阅,状态变化后,触发setState,状态变更会执行render方法重新渲染
this.unSubscribe = store.subscribe(() => {
this.setState({ number: store.getState().number });
})
}
componentWillUnmount() {
this.unSubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => store.dispatch({ type: 'ADD' })}>加</button>
<button onClick={() => store.dispatch({ type: 'MINUS' })}>减</button>
</div>
)
}
}
export default Counter;
createStore
的实现原理:
发布订阅模式,执行dispatch
时,触发subscribe
订阅的执行函数。一般subscribe
订阅的是组件的渲染函数。
// /redux/createStore.js
/**
*
* @param {*} reducer 处理器
* @param {*} preloadedState 初始状态
*/
export const createStore = (reducer, preloadedState) => {
let state = preloadedState || {};
let listeners = [];
function getState() {
return state;
}
// 添加订阅,返回销毁函数
function subscribe(listener) {
listeners.push(listener);
return () => {
let index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
}
// 计算状态,发布
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
return state;
}
// 默认派发一次,让reducer设置的默认state生效
dispatch({type: '@@REDUX/INIT'})
const store = {
getState,
subscribe,
dispatch
}
return store;
}
二、bindActionCreaters 绑定actionCreaters
actionCreater
所谓actionCreater
,就是生成action
的函数。
function add(text) {
return { type: 'ADD', payload: text };
}
bindActionCreaters
bindActionCreaters
的功能是绑定actions
和dispatch
,当调用定义的actionCreator
时,自动触发dispatch(action)
。简化使用上的操作。
用法
import React from 'react';
import { createStore, bindActionCreators } from '../redux';
// reducer
const reducer = (oldState, action) => {
switch (action.type) {
case 'ADD':
return { number: oldState.number + 1 };
case 'MINUS':
return { number: oldState.number - 1 };
default:
return oldState;
}
}
let store = createStore(reducer, { number: 10 });
// actionCreator
function add(event, text) {
return { type: 'ADD', payload: text };
}
function minus(event, text) {
return { type: 'MINUS', payload: text };
}
// 创建actionCreator对象
const actions = { add, minus };
// 绑定actionCreator对象和dispatch,当调用actions中的方法时,自动dispatch action
const boundActions = bindActionCreators(actions, store.dispatch);
class Counter extends React.Component {
state = {
number: store.getState().number
}
componentDidMount() {
this.unSubscribe = store.subscribe(() => {
this.setState({ number: store.getState().number });
})
}
componentWillUnmount() {
this.unSubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={boundActions.add}>加</button>
<button onClick={boundActions.minus}>减</button>
<button onClick={(event) => boundActions.add(event, 2)}>加</button>
<button onClick={(event) => boundActions.add(event, 1)}>减</button>
</div>
)
}
}
export default Counter;
bindActionCreaters实现原理
function bindActionCreator(actionCreator, dispatch) {
// args默认是event合成事件
return function (...args) {
dispatch(actionCreator.apply(this, args));
}
}
/**
* @param {*} actionCreators actionCreators对象
* @param {*} dispatch 事件派发
*/
function bindActionCreators(actionCreators, dispatch) {
const boundActionCreators = {};
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
return boundActionCreators;
}
export default bindActionCreators;
三、combineReducers合并reducer
分reducer counter1
# 分reducer
// /reducers/counter1.js
import * as types from "../action-types";
const actions = {
add1(event, text) {
return { type: types.ADD1, payload: text }
},
minus1(event, text) {
return { type: types.MINUS1, payload: text }
},
minus(event, text) {
return { type: types.MINUS, payload: text }
}
}
export default actions;
分reducer counter2
# 分reducer
// /reducers/counter2.js
import * as types from "../action-types";
const actions = {
add2(event, text) {
return { type: types.ADD2, payload: text }
},
minus2(event, text) {
return { type: types.MINUS2, payload: text }
},
minus(event, text) {
return { type: types.MINUS, payload: text }
}
}
export default actions;
合并reducer
import { combineReducers } from '../../redux';
import counter1 from './counter1';
import counter2 from './counter2';
let reducers = {
counter1,
counter2
}
let rootReducer = combineReducers(reducers);
export default rootReducer;
combineReducers实现原理
/**
* 把一个reducers对象变成一个reducer函数
* @param {*} reducers
*/
function combineReducers(reducers) {
let rootReducer = function(state = {}, action) {
let nextState = {};
for (let key in reducers) {
// 分reducer
const reducer = reducers[key];
// 老的分状态
const previousStateForKey = state[key];
// 计算新的状态
const nextStateForKey = reducer(previousStateForKey, action);
// 新状态保存到nextState
nextState[key] = nextStateForKey;
}
return nextState;
}
return rootReducer;
}
export default combineReducers;
四、一个完整的例子
└── src
├── components
│ ├── Counter1.js # 待渲染的组件Counter1
│ └── Counter2.js # 待渲染的组件Counter2
├── redux
│ ├── createStore.js # 创建store
│ ├── bindActionCreators.js # 绑定action和dispatch
│ ├── combineReducers.js # 合并reducer
│ └── index.js # 入口
├── store
│ ├── actions # actionCreator
│ │ ├── counter1.js
│ │ └── counter2.js
| ├── reducers # 组件对应的分reducer
│ │ ├── counter1.js
│ │ ├── counter2.js
│ │ └── index.js
│ └── index.js # 创建store,传入rootReducer
└── index.js
1.合并reducer
// store/reducers/index.js
import { combineReducers } from '../../redux';
import counter1 from './counter1';
import counter2 from './counter2';
let reducers = {
counter1,
counter2
}
let rootReducer = combineReducers(reducers);
export default rootReducer;
// store/reducers/counter1.js
import * as types from '../action-types';
const initialState = { number: 5 };
const counter1 = (oldState = initialState, action) => {
switch (action.type) {
case types.ADD1:
return { number: oldState.number + 1 };
case types.MINUS1:
case types.MINUS:
return { number: oldState.number - 1 };
default:
return oldState;
}
}
export default counter1;
2.创建store
// /store/index.js
import { createStore } from '../redux';
// 导入reducers
import rootReducer from './reducers';
// 创建store
let store = createStore(rootReducer);
export default store;
3. 创建actionCreator
// /store/actions/counter1.js
import * as types from "../action-types";
const actions = {
add1(event, text) {
return { type: types.ADD1, payload: text }
},
minus1(event, text) {
return { type: types.MINUS1, payload: text }
},
minus(event, text) {
return { type: types.MINUS, payload: text }
}
}
export default actions;
4. 组件使用示例
// /src/components/Counter1.js
import React from 'react';
import { bindActionCreators } from '../redux';
import store from '../store';
import actions from '../store/actions/counter1.js';
// 绑定actionCreator对象
const boundActions = bindActionCreators(actions, store.dispatch);
class Counter1 extends React.Component {
state = {
number: store.getState().counter1.number
}
componentDidMount() {
this.unSubscribe = store.subscribe(() => {
this.setState({ number: store.getState().counter1.number });
})
}
componentWillUnmount() {
this.unSubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={boundActions.add1}>加</button>
<button onClick={boundActions.minus1}>减</button>
<button onClick={boundActions.minus}>减</button>
</div>
)
}
}
export default Counter1;
五、React-redux
从上面的例子我们发现,每个组件用使用redux
,需要订阅事件、绑定Action,获取状态调用getState(),比较繁琐,下面我们看一种简化操作的办法。
一个例子看看provider和connect
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')
);
// /src/components/Counter1.js
import React from 'react';
import actions from '../store/actions/counter1.js';
import { connect } from '../react-redux';
class Counter1 extends React.Component {
render() {
const { number } = this.props;
return (
<div>
<p>{number}</p>
<button onClick={this.props.add1}>加</button>
<button onClick={this.props.minus1}>减</button>
<button onClick={() => this.props.dispatch({type: 'MINUS'})}>减</button>
</div>
)
}
}
// 一个映射函数,可以把仓库的状态进行映射出来分状态,分状态会成为组件属性对象。
let mapStateToProps = state => state.counter1;
// connect的第二个参数可以直接传actions,也可以传mapDispatchToProps,也可以不传。
let mapDispatchToProps = (dispatch) => {
return {
add1() {
dispatch({ type: 'ADD1' });
},
minus1() {
dispatch({ type: 'MINUS1' });
},
minus() {
dispatch({ type: 'MINUS' });
}
}
}
// actions也会进行绑定,成为当前组件属性对象。
// export default connect(mapStateToProps, actions)(Counter1);
export default connect(mapStateToProps, mapDispatchToProps)(Counter1);
Provider
Provider
的原理是React.createContext()
上下文对象。子组件都可以通过props
访问store
。
// /react-redux/ReactReduxContext.js
import React from 'react';
const ReactReduxContext = React.createContext();
export default ReactReduxContext;
// react-redux/Provider.js
import React from 'react';
import ReactReduxContext from './ReactReduxContext';
function Provider(props) {
let value = { store: props.store };
return (
<ReactReduxContext.Provider value={ value }>
{ props.children }
</ReactReduxContext.Provider>
)
}
export default Provider;
connect
负责将store
和组件进行关联。
主要功能:
- mapStateToProps 组件state添加到组件props上
- mapDispatchToProps action添加到组件props上
- subscribe添加订阅,更新页面
import React from 'react';
import { bindActionCreators } from '../redux';
import ReactReduxContext from './ReactReduxContext';
/**
* @param {*} mapStateToProps
* @param {*} mapDispatchToProps actions
*/
function connect(mapStateToProps, mapDispatchToProps) {
// 返回一个新的函数组件
return function (OldComponent) {
// 组件包装一层,这里的props是传递给组件的属性
return function (props) {
const { store } = React.useContext(ReactReduxContext);
const { getState, dispatch, subscribe } = store;
const prevState = getState();
// 调用mapStateToProps,返回组件的state
const stateProps = React.useMemo(() => mapStateToProps(prevState), [prevState]);
// 绑定actions和dispatch,可以通过props.add调用dispatch({type: ADD})
let dispatchProps = React.useMemo(() => {
let dispatchProps;
// 1. mapDispatchToProps是一个对象(actions)
if (typeof mapDispatchToProps === 'object') {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
// 2.mapDispatchToProps是一个函数
} else if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(dispatch)
// 没有传mapDispatchToProps参数,this.props.dispatch({})直接调用
} else {
dispatchProps = { dispatch };
}
return dispatchProps;
}, [store.dispatch]);
// 添加订阅,利用useReducer,状态发生变化后会执行render实现页面刷新
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
// 当前组件渲染后执行,执行一次
React.useLayoutEffect(() => {
return subscribe(forceUpdate); // 返回取消订阅函数
}, [store]); // eslint-disable-line react-hooks/exhaustive-deps
return <OldComponent {...props} { ...stateProps } { ...dispatchProps } />
}
}
}
export default connect;
六、React-redux的两个hooks
我们看到connect
写法有些复杂,react-redux
提供了两个hooks
简化写法。
useDispatch
事件派发函数
import React from 'react';
import ReactReduxContext from '../ReactReduxContext';
function useDispatch() {
const { store } = React.useContext(ReactReduxContext);
return store.dispatch;
}
export default useDispatch;
useSelector
同mapStateToProps
功能一致,用于获取组件的state
。
import React from 'react';
import ReactReduxContext from '../ReactReduxContext';
function useSelectorWithStore(selector, store) {
let state = store.getState();
// 获取组件对应状态
let selectedState = selector(state);
// 更新视图
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
React.useEffect(() => {
return store.subscribe(forceUpdate);
});
return selectedState;
}
function useSelector(selector) {
const { store } = React.useContext(ReactReduxContext);
const selectedState = useSelectorWithStore(selector, store);
return selectedState;
}
export default useSelector;
一个例子
// /components/Counter1.js
import React from 'react';
import { useSelector, useDispatch } from '../react-redux';
class Counter1 extends React.Component {
render() {
let dispatch = useDispatch();
let mapStateToProps = state => state.counter1;
let state = useSelector(mapStateToProps);
return (
<div>
<p>{state.number}</p>
<button onClick={() => dispatch({type: 'ADD1'})}>加</button>
<button onClick={() => dispatch({type: 'MINUS1'})}>减</button>
<button onClick={() => dispatch({type: 'MINUS'})}>减</button>
</div>
)
}
}
export default Counter1;
七、中间件middleWare
- 中间件的机制,可以改变数据流,实现异步action,action过滤,AOP日志输出等功能。
- 没有中间件,Redux的工作流程是
action => reducer
,同步操作。 - 有中间件,Redux的工作流程是
action => middleware => reducer
。中间件可以扩充dispatch
的能力,添加一些自定义的处理。
applyMiddleware的原理
function applyMiddleware(middleware) {
return function (createStore) {
return function(reducer) {
let store = createStore(reducer);
let dispatch = middleware(store)(store.dispatch);
// 重写dispatch方法
return {
...store,
dispatch
}
}
}
}
编写中间件
中间件的编码格式是固定的
# 改变之前的创建store的方式
// /store/index.js
import { createStore } from '../redux';
// 导入reducers
import rootReducer from './reducers';
// 创建store
// let store = createStore(rootReducer);
function compose(...fns) {
return function(args) {
return fns.reduceRight((args, fn) => {
return fn(args);
}, args);
}
}
// 应用中间件(采用分层的思想)
function applyMiddleware(...middlewares) {
return function (createStore) {
return function(reducer) {
let store = createStore(reducer);
let dispatch;
let middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
let chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
// 重写dispatch方法
return {
...store,
dispatch
}
}
}
}
// 在调用dispatch(action)的前后做一些事
function logger({ getState, dispatch }) {
return function(next) {
return function (action) {
console.log('prev todo...');
next(action);
console.log('next todo...');
}
}
}
// 支持action传入的是函数
function thunk({ getState, dispatch }) {
return function(next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
}
}
// 支持action传入promise
function promise({ getState, dispatch }) {
return function(next) {
return function(action) {
if (typeof action.then === 'function') {
return action.then(newAction => dispatch(newAction))
}
return next(action);
}
}
}
// 创建store
let store = applyMiddleware(promise, thunk, logger)(createStore)(rootReducer);
export default store;
测试中间件
// index.js
import store from './store';
store.subscribe(() => console.log(store.getState()));
// 测试logger中间件
store.dispatch({type: 'ADD1'})
// 测试chunk中间件
store.dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({type: 'ADD1'});
}, 3000)
})
// 测试promise中间件
store.dispatch(new Promise((resolve, reject) => {
setTimeout(() => {
resolve({type: 'ADD1'});
}, 2000)
}))
八、connect-react-router
connect-react-router
,用来连接redux
和router
。
1)通常有两个主要功能:
- 可以通过派发动作实现路由跳转。
- 可以在store仓库拿到最新路径信息。
2)connect-react-router
主要有4个关键方法:
- push:是一个
actionCreator
,用来生成跳转路径的action
。 - routerMiddleware:是一个中间件,主要功能是根据
push函数
提供的action
,调用history方法进行页面跳转。 - ConnectedRouter:是一个组件,主要用于实现监听路径变化的功能,当路径发生变化,派发特定的
action
。 - connectRouter:是一个
reducer
,主要功能是识别ConnectedRouter
组件dispatch
的action
,然后更新store
中的路径状态信息。
└── src
├── components
│ ├── Counter.js # 待渲染的组件Counter1
│ └── Home.js # 待渲染的组件Counter2
├── connected-react-router
│ ├── action-types.js # 创建store
│ ├── actions # push locationChange
│ ├── ConnectedRouter.js # 连接路由组件
│ ├── connectRouter.js # 一个reducer,当切换路由时,更新store的路由信息
│ ├── routerMiddleware.js # 中间件,重写dispatch方法,拦截派发动作,实现路由跳转
│ └── index.js # 入口
├── store
│ ├── actions # actionCreator
│ │ └── counter.js # go => push action
| ├── reducers # 组件对应的分reducer
│ │ ├── counter.js
│ │ └── index.js # combineReducers
│ └── index.js # 创建store,传入rootReducer
└── index.js # 入口,使用了ConnectedRouter组件,传递history属性(本质上还是Router组件)
使用
// index.js 入口
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Route, Link } from 'react-router-dom';
import { ConnectedRouter } from './connected-react-router';
import history from './history';
import store from './store';
import Home from './components/Home';
import Counter from './components/Counter';
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<ul>
<li><Link to="/" exact="true">首页</Link></li>
<li><Link to="/counter">计数器</Link></li>
</ul>
<Route path="/" exact={true} component={Home}></Route>
<Route path="/counter" component={Counter}></Route>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
ConnectedRouter
需要store
,用来保存路由信息。使用react-redux
提供的Provider
将store
传递到子组件。
// /src/components/Counter.js
import React from 'react';
import { connect } from 'react-redux';
import actions from '../store/actions/counter';
class Counter extends React.Component {
handleClick = () => {
this.props.go('/');
}
render() {
return (
<div>
<p>{this.props.number}</p>
<button onClick={this.props.add}>+</button>
<button onClick={this.props.minus}>-</button>
<button onClick={this.handleClick}>Counter</button>
</div>
)
}
}
export default connect(state => state.counter, actions)(Counter);
1. 通过派发动作实现路由跳转
路由跳转和路径变化时更新store的两个action
:
// action-types.js
// 派发动作,切换路由
export const CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD';
// 向仓库派发动作,更新最新路由信息
export const LOCATION_CHANGE = '@@router/LOCATION_CHANGE';
生成路由跳转和路径变化两个action的actionCreator
:
// /src/connected-reaact-router/actions.js
import * as types from './action-types';
/**
* 这是一个用来跳转路径的actionCreator
* @param {*} path 跳转路径
*/
function push(path) {
return {
type: types.CALL_HISTORY_METHOD,
payload: {
method: 'push',
args: [path]
}
}
}
/**
* 路径变化的actionCreator
* @param {*} location
* @param {*} action
*/
function locationChange(location, action) {
return {
type: types.LOCATION_CHANGE,
payload: {
location,
action
}
}
}
export {
push,
locationChange
};
counter组件
的actions,通过connect
放到props
上
import * as types from '../action-types';
import { push } from "../../connected-react-router";
const actions = {
add() {
return { type: types.ADD };
},
minus() {
return { type: types.MINUS };
},
go(path) {
return push(path);
}
}
export default actions;
routerMiddleware
中间件,重写dispatch,当派发路径切换动作时,进行路由跳转。
import * as types from './action-types';
function routerMiddleware(history) {
return function(middlewareAPI) { // {getState, dispatch}
return function(next) {
return function(action) {
if (action.type !== types.CALL_HISTORY_METHOD) {
return next(action);
}
// 拿到push.js中的action
const { payload: { method, args }} = action;
// 路径跳转
history[method](...args);
}
}
}
}
export default routerMiddleware;
使用中间件,创建store
import { createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from '../connected-react-router';
import rootReducer from './reducers';
import history from '../history';
// routerMiddleware中间件,重写dispatch方法,如果发现是router,则保存最新路由信息
const store = applyMiddleware(routerMiddleware(history))(createStore)(rootReducer);
export default store;
2. 在store仓库拿到最新路径信息
ConnectedRouter
组件,用来监测路由变化,路由变化时,派发事件。
import React from 'react';
import { connect, ReactReduxContext } from 'react-redux';
import { Router } from 'react-router';
import { locationChange } from './actions';
// 依赖store
class ConnectedRouter extends React.Component {
componentDidMount() {
// 当路径发生变化的时候,会执行回调,传递最新的location和action
this.props.history.listen((location, action) => {
// 派发事件
this.props.dispatch(locationChange(location, action));
})
}
render() {
const { history, children } = this.props;
return (
<Router history={history}>
{ children }
</Router>
)
}
}
export default connect(state => state)(ConnectedRouter);
connectRouter
reducer,当派发路由切换动作时,更新store中的路由信息
import * as types from './action-types';
function connectRouter(history) {
const initialState = {
location: history.location,
action: history.action
}
// 返回一个reducer
return function (state = initialState, action) {
// 如果是触发路由action,则覆盖老状态的路由信息
if (action.type === types.LOCATION_CHANGE) {
return { ...state, ...action.payload };
} else {
return state;
}
}
}
export default connectRouter;
根reducers
入口
import { combineReducers } from 'redux';
import { connectRouter } from '../../connected-react-router';
import history from '../../history';
import counter from './counter';
let reducers = {
counter,
router: connectRouter(history)
}
let rootReducer = combineReducers(reducers);
export default rootReducer;
九、redux-saga
redux-saga
是一个redux的中间件,为redux
提供额外的功能。
我们知道reducers的操作都是同步且纯粹的,reducer都是纯函数,在执行过程中不会对外部产生副作用。
实际开发中,可能有一些异步请求,或者不纯的操作(改变外部状态等),这些在函数式编程规范中被称为副作用。
redux-saga
就是用来处理上述副作用的一个中间件。类似于前面写的promise,thunk、logger中间件。
redux-saga工作原理
- 使用
Generator
函数来yield
Effects
。 Generator
函数的作用是可以暂停执行,再次指向的时候从上次暂停的地方继续执行。Effect
是一个对象,该对象包含给middleware
解释执行的信息。- 可以使用
effects API
,如fork
,call
,take
,put
,cancel
来创建Effect
。
redux-saga分类
root saga
,启动saga
的唯一入口。watcher saga
,监听被dispatch
的actions
,当接收到action
或者知道其被触发时,调用worker
执行任务。worker saga
,执行具体的工作任务,如调用API,进行异步请求,获取异步封装结果。
redux-saga工作流程
channel
是一个管道,其实就是一个发布订阅模式实现的,用来控制生成器函数执行的函数。runSaga
是调度核心,负责向channel
订阅函数take
,执行channel
中的发布函数put
来控制生成器函数的执行,以及dispath(action)
来真正修改状态,更新页面。rootSaga
是用户定义的,root saga。
redux-saga目录结构
└── src
├── components
│ └── Counter.js # 待渲染的组件Counter
├── redux-saga
│ ├── channel.js # 提供take和put方法,用来控制生成器函数的执行
│ ├── effects.js # 返回指令对象
│ ├── effectTypes.js # 类型,TASK和PUT
│ ├── runSaga.js # 核心方法,负责执行生成器函数,调用channel方法,调用dispatch方法更新视图
│ └── index.js # createSagaMiddleware 返回一个中间件,重写dispatch方法
├── store
│ ├── actions # actionCreator
│ │ └── counter.js
| ├── reducers # 组件对应的分reducer
│ │ ├── counter.js
│ │ └── index.js
| ├── sagas # 根saga,一个生成器函数
│ │ └── index.js
│ └── index.js # 创建store,传入rootReducer
└── index.js
注册阶段
saga生成器函数
// src/store/sagas/index.js
function *rootSaga() {
console.log('启动rootSaga')
while (true) {
// task监听动作:等待有人向仓库派发一个ASYNC_ADD这样的命令,等到了就继续执行,等不到就停在这里
yield take(types.ASYNC_ADD); // { type: 'TAKE', actionType: 'ASYNC_ADD' };
// put派发一个动作:store.dispatch({type: types.ADD})
yield put({ type: types.ADD }); // { type: 'PUT', action: { type: 'ADD' } }
}
}
创建中间件,创建store
,传入中间件,执行中间件的run
方法,开始执行runSaga
函数。
// /src/store/index.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import createSagaMiddleware from '../redux-saga';
import rootSaga from './sagas';
// 创建saga中间件,dispatch时,发布put方法,继续往下执行
let sagaMiddleware = createSagaMiddleware();
// 创建store,传入中间件
let store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer);
// 启动saga,执行runSaga方法
sagaMiddleware.run(rootSaga);
export default store;
创建中间件,重写dispatch
当用户调用dispatch
时,同步action
直接触发,异步action
,执行channel
的put
方法,调用dispatch
,然后调用next
继续往下执行生成器函数。
提供的run
方法,实际上是执行runSaga
函数,这个函数会执行rootSata
生成器函数。
// /src/redux-saga/index.js
import runSaga from "./runSaga";
import createChannel from './channel';
import { take } from "./effects";
function createSagaMiddleware() {
let channel = createChannel(); // {take, put}
let boundRunSaga;
// 返回一个中间件
function sagaMiddleWare({ getState, dispatch }) {
boundRunSaga = runSaga.bind(null, { getState, dispatch, channel });
return function(next) {
// 重写dispatch方法
return function(action) {
// 触发dispatch(action),非异步action,直接这里触发
let result = next(action);
// 发布take保存的函数
channel.put(action);
return result;
}
}
}
// 中间件上的run方法,用于启动saga
sagaMiddleWare.run = (saga) => boundRunSaga(saga);
return sagaMiddleWare;
}
export default createSagaMiddleware;
runSaga
执行生成器函数,控制生成器函数的暂停和执行。异步action
在put
时调用。
// /src/redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
/**
* 执行或者启动saga的方法,类似于co库
* @param {*} saga
*/
function runSaga(env, saga) {
const { getState, dispatch, channel } = env;
// saga可能是一个生成器,也可能是一个迭代器
let it = typeof saga === 'function' ? saga() : saga;
function next(value) {
let { value: effect, done } = it.next(value);
// {type: "TAKE", actionType: "ASYNC_ADD"}
// {type: "PUT", action: {type: "ADD"}}
if (!done) {
// effect是一个迭代器
if (typeof effect[Symbol.iterator] === 'function') {
runSaga(env, effect);
next();
} else {
switch (effect.type) {
case effectTypes.TAKE:
channel.take(effect.actionType, next); // 订阅next函数,停止向下执行,等到dispatch(action),发布next()函数,后,继续往下执行
break; // 没有 next(),暂停执行
case effectTypes.PUT:
dispatch(effect.action); // 异步action在这里触发,触发同步的action ASYNC_ADD => ADD
next(); // 派发完后立刻向下执行
break;
default:
break;
}
}
}
}
next();
}
export default runSaga;
十、dva
dva
首先是一个基于 redux
和redux-saga
的数据流方案,然后为了简化开发体验,dva
还额外内置了 react-router
和fetch
,所以也可以理解为一个轻量级的应用框架。
dva
集成了react全家桶
,集成了redux
,react-redux
,react-router-dom
,connected-react-router
。
用法
// index.js
import React from 'react';
import dva, { connect, ConnectedRouter } from './dva';
import { Router, Route, Link, routerRedux } from 'dva/router';
// dva是一个函数,执行它可以得到一个app实例
const app = dva();
// model方法定义一个模型
app.model({
namespace: 'counter',
state: { number: 0 },
reducers: {
add(state) {
return { number: state.number + 1 };
}
},
effects: {
// action动作,effects = redux/effects(take, put, ...)
*asyncAdd(action, { call, put, select }) {
yield call(delay, 1000);
yield put({ type: 'add' }); // 不用加命名空间前缀
let state = yield select(state => state.counter);
console.log('state', state);
},
*goto({ payload }, { put }) {
// push是一个actionCreator,执行会返回一个action = {type: 'CALL_HISTORY_METHOD', payload: {method: 'push'}}
yield put(routerRedux.push(payload));
}
}
})
function Counter(props) {
return (
<div>
<p>{props.number}</p>
<button onClick={() => props.dispatch({ type: 'counter/add' })}>+</button>
<button onClick={() => props.dispatch({ type: 'counter/asyncAdd' })}>async +</button>
<button onClick={() => props.dispatch({ type: 'counter/goto', payload: '/' })}>跳转到home</button>
</div>
)
}
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
})
}
const ConnectedCounter = connect(state => state.counter)(Counter);
const Home = () => <div>home</div>
app.router((api) => (
<ConnectedRouter history={api.history}>
<>
<Link to="/">home</Link>
<Link to="/counter">counter</Link>
<Route path="/" exact="true" component={Home}></Route>
<Route path="/counter" component={ConnectedCounter}></Route>
</>
</ConnectedRouter>
));
app.start('#root');
原理实现
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider, connect } from 'react-redux';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import prefixNamespace from './prefixNamespace';
import * as sagaEffects from 'redux-saga/effects';
import createSagaMiddleware from 'redux-saga';
import { createBrowserHistory } from 'history';
import { routerMiddleware, connectRouter, ConnectedRouter } from 'connected-react-router';
export { connect, ConnectedRouter };
let history = createBrowserHistory();
function dva() {
const app = {
_models: [], // 当调用app.model时,保存传入的model
model,
_router: null,
router,
start
}
// 命名空间:reducer => counter/add: reducer(state, action)
const initialReducers = {
router: connectRouter(history)
};
// 定义数据模型
function model(model) {
// 给reducer添加命名空间
let prefixedModel = prefixNamespace(model);
app._models.push(prefixedModel);
}
// 定义路由,要渲染的节点
function router(router) {
app._router = router;
}
// 启动函数
function start(root) {
// 构建reducers对象
for (const model of app._models) {
initialReducers[model.namespace] = getReducer(model);
}
function createReducer() {
return combineReducers(initialReducers);
}
// 合并reduer,获取根reduer
let rootReducer = createReducer();
// saga
let sagas = getSagas(app);
let sagaMiddleware = createSagaMiddleware();
let store = applyMiddleware(routerMiddleware(history), sagaMiddleware)(createStore)(rootReducer);
sagas.forEach(sagaMiddleware.run);
// 渲染
ReactDOM.render(
<Provider store={store}>
{app._router({ history })}
</Provider>,
document.querySelector(root)
)
}
return app;
}
// effects
function getSagas(app) {
let sagas = [];
for (const model of app._models) {
sagas.push(getSaga(model.effects, model));
}
return sagas;
}
function getSaga(effects, model) {
return function *() {
for (const key in effects) {
const watcherSaga = getWatcherSaga(key, model.effects[key], model);
yield sagaEffects.fork(watcherSaga);
}
}
}
function getWatcherSaga(key, effect, model) {
return function *() {
yield sagaEffects.takeEvery(key, function *(action) {
yield effect(action, {
...sagaEffects,
// 重写put方法,兼容不加counter的写法
put: (action) => {
return sagaEffects.put({
...action,
type: prefix(action.type, model.namespace)
})
}
});
});
}
}
function prefix(actionType, namespace) {
// 已经加了前缀就直接返回,不加前缀帮忙加上
if (actionType.indexOf('/') === -1) {
return `${namespace}/${actionType}`;
} else {
return actionType;
}
}
// reducer
function getReducer(model) {
let { state: initialState, reducers } = model;
let reducer = (state = initialState, action) => {
let reducer = reducers[action.type];
// 找到对应处理函数,则返回处理函数的处理后的状态
if (reducer) return reducer(state);
// 没有找到对应处理函数,返回老状态
return state;
}
return reducer;
}
/**
state = { number: 0 }
reducers = {
add(state) {
return { number: state.number + 1 };
}
}
*/
export default dva;
十一、dva-cli
安装
npm install dva-cli -g
创建项目
dva new dva-antdesign
安装antd和按需加载插件
npm i antd -S
npm i babel-plugin-import -D
配置.webpackrc
,使babel-plugin-import
生效。
{
"extraBabelPlugins": [
["import", {"libraryName": "antd", "libraryDirectory": "es", "style": "css"}]
]
}
十二、umi
umi
是一个类似于next.js
的脚手架工具,与dva
深入融合,并通过约定、自动生成和解析代码等方式来辅助开发,减少我们开发者的代码量。
安装
npm install umi -g
生成页面
umi g page index
配置启动命令
"scripts": {
"dev": "umi dev",
"build": "umi build"
}