目录
一、JavaScript纯函数
二、为什么需要redux(传送门)
三、Redux的核心理念 - Store
四、Redux的核心理念 - action
五、Redux的核心理念 - reducer
六、Redux的三大原则
七、Redux测试项目搭建
1、安装redux:
2、redux项目创建:
八、 Redux的使用过程
九、Redux结构划分
十、Redux使用流程
十一、Redux官方图
十二、redux融入react代码
十三、高阶函数
十四、高阶组件
十五、高阶组建的定义
十六、高阶组建应用---- props的增强
十七、高阶组建应用---- 渲染判断鉴权
十八、高阶组建应用---- 生命周期劫持
十九、自定义connect函数
二十、使用redux的connect:
二十二、hoist-non-react-statics使用
二十三、组件中异步操作
二十四、redux-thunk中间件
1、如何在redux中进行异步网络请求呢?
2、如何使用redux-thunk
二十五、redux-devtools
二十六、generator
二十七、redux-saga的使用
二十八、中间件
1、打印日志需求 (案例使用第七点搭建的learn-redux项目的基础上进行修改)
2、合并中间件
二十九、Reducer
1、Reducer代码拆分
2、Reducer文件拆分
3、combineReducers函数
4、combineReducers源码解读
函数式编程中有一个概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
在React中,纯函数的概念非常重要,在接下来我们学习的Redux中也非常重要,所以先了解一下纯函数。
function sum(num1 ,num2){
return num1 + num2;
}
它的输出是依赖我们的输入内容,并且中间没有产生任何副作用;let foo = 5;
function addNum(num){
return num + foo;
}
函数依赖一个外部的变量,变量发生改变时,会影响:确定的输入,产生确定的输出;UI = render(state)
npm install redux --save
或
yarn add redux
yarn init
或 yarn init -y
yarn add redux
// reducer store reducer
var redux = require('redux');
const initialState = {
counter: 0,
};
//reducer
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + 1 };
case 'DECREMENT':
return { ...state, counter: state.counter - 1 };
case 'ADD_NUMBER':
return { ...state, counter: state.counter + action.num };
case 'SUB_NUMBER':
return { ...state, counter: state.counter - action.num };
default:
return state;
}
}
//store
const store = redux.createStore(reducer);
//actions
const action1 = { type: 'INCREMENT' };
const action2 = { type: 'DECREMENT' };
const action3 = { type: 'ADD_NUMBER' ,num:5 };
const action4 = { type: 'SUB_NUMBER' ,num:20};
//派发action
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
store.dispatch(action4);
// 订阅store的修改
store.subscribe(() => {
console.log('counter:' ,store.getState().counter);
});
这里需要注意的是:该订阅的代码必须放在action派发之前,因为在执行node命令时,代码是从上往下一次执行,完整代码:
// reducer store reducer
var redux = require('redux');
const initialState = {
counter: 0,
};
//reducer
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + 1 };
case 'DECREMENT':
return { ...state, counter: state.counter - 1 };
case 'ADD_NUMBER':
return { ...state, counter: state.counter + action.num };
case 'SUB_NUMBER':
return { ...state, counter: state.counter - action.num };
default:
return state;
}
}
//store
const store = redux.createStore(reducer);
//actions
const action1 = { type: 'INCREMENT' };
const action2 = { type: 'DECREMENT' };
const action3 = { type: 'ADD_NUMBER' ,num:5 };
const action4 = { type: 'SUB_NUMBER' ,num:20};
// 订阅store的修改(该订阅的代码必须放在action派发之前,因为在执行node命令时,代码是从上往下一次执行)
store.subscribe(() => {
console.log('counter:' ,store.getState().counter);
});
//派发action
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
store.dispatch(action4);
然后终端执行node命令:
node index.js
控制台打印结果:
learn-redux % node index.js
counter: 1
counter: 0
counter: 5
如果我们将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护。
创建store/index.js文件:
import redux from 'redux';
import reducer from './reducer.js';
const store = redux.createStore(reducer);
export default store;
创建store/reducer.js文件:
import {
ADD_NUMBER,
SUB_NUMBER,
DECREMENT,
INCREMENT
} from './constants.js'
const defaultState = {
counter: 0
}
function reducer(state = defaultState ,action){
switch (action.type) {
case INCREMENT:
return { ...state, counter: state.counter + 1 };
case DECREMENT:
return { ...state, counter: state.counter - 1 };
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num };
case SUB_NUMBER:
return { ...state, counter: state.counter - action.num };
default:
return state;
}
}
export default reducer;
创建store/actions.js文件:
import {
ADD_NUMBER,
SUB_NUMBER,
DECREMENT,
INCREMENT
} from './constants.js'
export const addAction = num => ({
type: ADD_NUMBER,
num,
});
export const subAction = num =>({
type:SUB_NUMBER,
num
})
export const decrementAction = _ =>({ type:DECREMENT })
export const incrementAction = _ =>({ type:INCREMENT })
创建store/constants.js文件:
export const ADD_NUMBER = 'ADD_NUMBER'
export const SUB_NUMBER = 'SUB_NUMBER'
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
终端运行:
yarn start
输出结果:
learn-redux2 % yarn start
yarn run v1.22.4
warning ../../../../package.json: No license field
$ node --experimental-modules index.js
(node:38435) ExperimentalWarning: The ESM module loader is experimental.
{ counter: 5 }
{ counter: -5 }
{ counter: -6 }
{ counter: -5 }
✨ Done in 0.23s.
目前我使用的node版本是v13.7.0,从node v13.2.0开始,node才对ES6模块化提供了支持:
node v13.2.0之前,需要进行如下操作:
在package.json中添加属性: "type": "module";
在执行命令中添加如下选项:node --experimental-modules src/index.js;
node v13.2.0之后,只需要进行如下操作:
在package.json中添加属性: "type": "module";
注意:导入文件时,需要跟上.js后缀名;
{
"name": "learn-redux2",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"type": "module",
"scripts": {
"start": "node --experimental-modules index.js"
},
"dependencies": {
"redux": "^4.0.5"
}
}
redux在实际开发中的流程:
目前redux在react中使用是最多的,所以我们需要将之前编写的redux代码,融入到react当中去。
import React from 'react';
import About from './pages/about';
import Home from './pages/home';
function App() {
return (
);
}
export default App;
import React, { PureComponent } from 'react'
import store from '../store';
import {incrementAction ,addAction} from '../store/actions';
export default class Home extends PureComponent {
constructor(props){
super(props);
this.state = {
counter: store.getState().counter
}
}
componentDidMount() {
this.unsubscribue = store.subscribe(() => {
this.setState({
counter: store.getState().counter
})
})
}
componentWillUnmount() {
this.unsubscribue();
}
increment = ()=> {
store.dispatch(incrementAction());
}
addNumber = (num)=>{
store.dispatch(addAction(num));
}
render() {
return (
home
当前计数:{this.state.counter}
)
}
}
import React, { PureComponent } from 'react';
import store from '../store';
import { decrementAction, subAction } from '../store/actions';
export default class About extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: store.getState().counter,
};
}
componentDidMount() {
this.unsubscribue = store.subscribe(() => {
this.setState({
counter: store.getState().counter,
});
});
}
componentWillUnmount() {
this.unsubscribue();
}
decrement = () => {
store.dispatch(decrementAction());
};
subNumber = (num) => {
store.dispatch(subAction(num));
};
render() {
return (
about
当前计数:{this.state.counter}
);
}
}
案例:
import React, { PureComponent } from 'react';
class App extends PureComponent {
render() {
return aaa;
}
}
function enhanceComponent(WrappedComponent) {
return class NewComponent extends PureComponent {
render() {
return ;
}
};
}
const EnhanceComponent = enhanceComponent(App);
export default EnhanceComponent;
const EnhanceComponent = enhanceComponent(App);
function enhanceComponent(WrappedComponent) {
return class NewComponent extends PureComponent {
render() {
return ;
}
};
}
function enhanceComponent(WrappedComponent) {
return class extends PureComponent {
render() {
return ;
}
};
}
运行结果:省略之后默认显示它所继承的父组建名称App.displayName = 'AAA';
function enhanceComponent(WrappedComponent) {
const NewComponent = class extends PureComponent {
render() {
return ;
}
};
NewComponent.displayName = 'BBB';
return NewComponent;
}
运行结果:案例:
import React, { PureComponent } from 'react'
class About extends PureComponent {
render() {
return About:{`名字:${this.props.name} 区域:${this.props.region}`}
}
}
class Home extends PureComponent {
render() {
return About:{`名字:${this.props.name} 区域:${this.props.region}`}
}
}
function enhanceRegionProps(WrappedComponent){
return props => {
return
}
}
const EnhanceAbout = enhanceRegionProps(About);
const EnhanceHome = enhanceRegionProps(Home);
export default class App extends PureComponent {
render() {
return (
)
}
}
function enhanceRegionProps(WrappedComponent ,otherProps){
return props =>
}
import React, { PureComponent, createContext } from 'react'
//创建context
const UserContext = createContext({
region:"重庆",
nickname:'默认昵称'
})
class Home extends PureComponent{
render(){
return(
{
user => {
return Home: {`昵称: ${user.nickname} 区域: ${user.region}`}
}
}
)
}
}
class About extends PureComponent {
render() {
return (
{
user => {
return About: {`昵称: ${user.nickname} 区域: ${user.region}`}
}
}
)
}
}
export default class App extends PureComponent {
render() {
return (
)
}
}
利用高阶组建修改后代码:import React, { PureComponent, createContext } from 'react'
//定义高阶组建
function withUser(WrappedComponent){
return props => {
return (
{
user => {
return
}
}
)
}
}
//创建context
const UserContext = createContext({
region:"重庆",
nickname:'默认昵称'
})
class Home extends PureComponent{
render(){
return Home: {`昵称: ${this.props.nickname} 区域: ${this.props.region}`}
}
}
class About extends PureComponent {
render() {
return About: {`昵称: ${this.props.nickname} 区域: ${this.props.region}`}
}
}
const UserHome = withUser(Home);
const UserAbout = withUser(About);
export default class App extends PureComponent {
render() {
return (
)
}
}
分析:
import React, { PureComponent } from 'react'
function withAuth(WrappedComponent){
const NewCpn = props => {
const {isLogin} = props;
if (isLogin){
return
}else{
return
}
}
NewCpn.displayName = 'AuthCpn';
return NewCpn;
}
class LoginPage extends PureComponent{
render(){
return LoginPage
}
}
class CartPage extends PureComponent{
render(){
return CartPage
}
}
const AuthCartPage = withAuth(CartPage);
export default class App extends PureComponent {
render() {
return (
)
}
}
高阶组建:开发中是否登录的状态一般存储在本地直接获取登录状态,不需要外面传入登录状态
function withAuth(WrappedComponent){
const NewCpn = props => {
//开发中是否登录的状态一般存储在本地直接获取登录状态,不需要外面传入登录状态
const {isLogin} = global;
if (isLogin){
return
}else{
return
}
}
NewCpn.displayName = 'AuthCpn';
return NewCpn;
}
我们也可以利用高阶函数来劫持生命周期,在生命周期中完成自己的逻辑:
默认代码:
import React, { PureComponent } from 'react';
class Home extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`Home渲染时间: ${interval}`)
}
render() {
return Home
}
}
class About extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`About渲染时间: ${interval}`)
}
render() {
return About
}
}
export default class App extends PureComponent {
render() {
return (
)
}
}
修改之后代码:
import React, { PureComponent } from 'react';
function withRenderTime(WrappedComponent) {
return class extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
}
render() {
return
}
}
}
class Home extends PureComponent {
render() {
return Home
}
}
class About extends PureComponent {
render() {
return About
}
}
const TimeHome = withRenderTime(Home);
const TimeAbout = withRenderTime(About);
export default class App extends PureComponent {
render() {
return (
)
}
}
创建connect.js文件:
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 = {
storeState: mapStateToProps(store.getState())
}
}
componentDidMount(){
this.unsubscribe = store.subscribe(()=> {
this.setState({
storeState: mapStateToProps(store.getState())
})
})
}
componentWillUnmount(){
this.unsubscribe()
}
render() {
return ;
}
};
};
}
about.js文件:
import React from 'react';
import { decrementAction, subAction } from '../store/actions';
import {connect} from '../utils/connect'
const About = (props)=> {
return (
about
当前计数:{props.counter}
);
}
const mapStateToProps = state => {
return {
counter: state.counter
};
};
const mapDispatchToProps = dispatch => {
return {
decrement: function (){
dispatch(decrementAction())
},
subNumber: function (num){
dispatch(subAction(num))
}
};
};
export default connect(mapStateToProps , mapDispatchToProps)(About);
connect的功能基本实现了,但是如果有一个致命缺陷,就是在connect.js文件中需要引入store:
import store from '../store';
作为一个工具文件,或者三方库,需要引入别人业务层的sotre,显然是不行的,我们需要使用context,将store由外面传入:
创建context.js文件:
import React from 'react'
const StoreContext = React.createContext();
export {
StoreContext
}
继续改造connect.js文件:将store的地方使用context替换
import React ,{ PureComponent } from 'react';
import {StoreContext} from './context'
export function connect(mapStateToProps, mapDispatchToProps) {
return function enhanceHOC(WrappedComponent) {
class EnhanceComponet 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 ;
}
};
EnhanceComponet.contextType = StoreContext;
return EnhanceComponet;
};
}
然后在外面使用创建的context传入store:
index.js文件:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {StoreContext} from './utils/context'
import store from './store'
ReactDOM.render(
,
document.getElementById('root')
);
接着上面的代码,我们在项目中引入react-redux,终端输入:
yarn add react-redux
然后将Provider和connect替换为react-redux的就ok了:
二十一、react-redux源码解读
当使用高阶组建包装组建时,即原始的组建被容器组建包裹后,会导致新组建会丢失原始组建的所有静态方法,还是上面的例子:
我们给About组建添加一个测试方法:
这个时候我们在调用高阶组建的时候,就会丢失这里的test静态方法,除非进行一一拷贝给新组建:
这样做很显然你是需要知道原始组建的所有静态方法的,作为一个三方的库而已,显然不现实,这里就可以用到hoist-non-react-statics库,帮你自动拷贝所有非React的静态方法:
hoistStatics(EnhanceComponet ,WrappedComponent)
继续上面的案例
yarn add redux-thunk
import { createStore ,applyMiddleware } from 'redux';
import reducer from './reducer.js';
import thunkMiddleware from 'redux-thunk'
const storeEnhancer = applyMiddleware(thunkMiddleware);
const store = createStore(reducer ,storeEnhancer);
export default store;
yarn add axios
// redux-thunk异步请求
export const fetchDataAction = (dispatch, getState) => {
axios({
url: "http://localhost:3000/data",
}).then(res => {
const data = res.data;
console.log(data);
dispatch({
type: FETCH_SERVICE_DATA,
data
});
})
};
import { createStore ,applyMiddleware } from 'redux';
import reducer from './reducer.js';
import thunkMiddleware from 'redux-thunk'
const storeEnhancer = applyMiddleware(thunkMiddleware);
const store = createStore(reducer ,storeEnhancer);
export default store;
store/actions.js:
import axios from 'axios';
import {
ADD_NUMBER,
SUB_NUMBER,
DECREMENT,
INCREMENT,
FETCH_SERVICE_DATA
} from './constants.js'
export const addAction = num => ({
type: ADD_NUMBER,
num,
});
export const subAction = num =>({
type:SUB_NUMBER,
num
})
export const decrementAction = () =>({ type:DECREMENT })
export const incrementAction = () =>({ type:INCREMENT })
// redux-thunk异步请求
export const fetchDataAction = (dispatch, getState) => {
axios({
url: "http://localhost:3000/data",
}).then(res => {
const data = res.data;
console.log(data);
dispatch({
type: FETCH_SERVICE_DATA,
data
});
})
};
store/constants.js
export const ADD_NUMBER = 'ADD_NUMBER'
export const SUB_NUMBER = 'SUB_NUMBER'
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
export const FETCH_SERVICE_DATA = 'FETCH_SERVICE_DATA'
store/reducer.js
import {
ADD_NUMBER,
SUB_NUMBER,
DECREMENT,
INCREMENT,
FETCH_SERVICE_DATA,
} from './constants.js';
const defaultState = {
counter: 0,
};
function reducer(state = defaultState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, counter: state.counter + 1 };
case DECREMENT:
return { ...state, counter: state.counter - 1 };
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num };
case SUB_NUMBER:
return { ...state, counter: state.counter - action.num };
case FETCH_SERVICE_DATA:
return { ...state, data: action.data };
default:
return state;
}
}
export default reducer;
home.js
import React, { PureComponent } from 'react'
import {incrementAction ,addAction ,fetchDataAction} from '../store/actions';
import {connect} from 'react-redux';
class Home extends PureComponent {
componentDidMount() {
this.props.fetchData();
}
render() {
const {data = []} = this.props;
return (
home
当前计数:{this.props.counter}
{
data.map((item)=>{
return (
- {item.title}
)
})
}
)
}
}
const mapStateToProps = state => ({
counter:state.counter,
data:state.data
})
const mapDispatchToProps = dispatch => ({
increment: ()=> {
dispatch(incrementAction())
},
addNumber: (num)=> {
dispatch(addAction(num))
},
fetchData: ()=> {
dispatch(fetchDataAction)
}
})
export default connect(mapStateToProps ,mapDispatchToProps)(Home)
这里需要注意的是,由于引入了redux-thunk中间件后,home.js文件中dispatch的一定是个函数,而不是函数的调用:
该dispatch传入一个函数fetchDataAction,redux-thunk内部会对fetchDataAction函数进行调用,因此一定不能写成函数调用
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer.js';
import thunkMiddleware from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const storeEnhancer = applyMiddleware(thunkMiddleware);
const store = createStore(reducer, composeEnhancers(storeEnhancer));
export default store;
就可以调试了,当然如果需要用到trace功能,还需要添加:trace参数为true:const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace:true}) || compose;
运行:saga中间件使用了ES6的generator语法,简单了解下generator的用法
Document
//生成器
function* foo(){
}
//iterator 迭代器
const result = foo();
console.log(result);
//2、生成器函数的定义
//生成器函数
function* foo(){
yield "aaaa"
yield "bbbb"
yield "cccc"
}
//iterator 迭代器
const result = foo();
console.log(result);
//3、使用迭代器
//调用一次next,就会消耗一次迭代器
const res1 = result.next();
console.log(res1);
//3、使用迭代器
//调用一次next,就会消耗一次迭代器
const res1 = result.next();
console.log(res1);
const res2 = result.next();
console.log(res2);
const res3 = result.next();
console.log(res3);
const res4 = result.next();
console.log(res4);
//执行顺序
//2、生成器函数的定义
//生成器函数
function* foo() {
console.log('111')
yield "aaaa"
console.log('222')
yield "bbbb"
console.log('333')
yield "cccc"
console.log('444')
}
//iterator 迭代器
const result = foo();
console.log(result);
//3、使用迭代器
//调用一次next,就会消耗一次迭代器
const res1 = result.next();
console.log(res1);
const res2 = result.next();
console.log(res2);
const res3 = result.next();
console.log(res3);
const res4 = result.next();
console.log(res4);
//小练习:一次生成1-10个数字,需要用的时候再一次去那对应的值
function* generatorNumber(){
for (let i = 0; i < 10; i++) {
yield i;
}
}
const num = generatorNumber();
console.log(num.next().value);
// 6 、generator和Promise结合使用
function* bar() {
console.log('1111')
const result = yield new Promise((resolve, reject) => {
//模拟异步请求
setTimeout(() => {
console.log('即使结束,准备返回结果')
resolve('3s过去了,generator');
}, 3000);
});
console.log('拿到result:', result);
}
console.log('开始调用bar()')
const it = bar();
console.log('bar()调用完成得到迭代器')
it.next().value.then(res => {
console.log('执行完第一次next,拿到then的res')
it.next(res);
console.log('执行完第二次next')
});
redux-saga是另一个比较常用在redux发送异步请求的中间件,它的使用更加的灵活。redux官网是有提到redux-saga(传送门)
依然是上面的案例
Redux-saga的使用步骤如下
安装redux-saga
yarn add redux-saga
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer.js';
import thunkMiddleware from 'redux-thunk';
import createSagaMiddleware from 'redux-saga'
import mySaga from './saga'
//创建composeEnhancers函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace:true}) || compose;
//应用中间件
//1、引入thunkMiddleware中间件
//2、引入sagaMiddleware中间件
const sagaMiddleware = createSagaMiddleware();
const storeEnhancer = applyMiddleware(thunkMiddleware ,sagaMiddleware);
const store = createStore(reducer, composeEnhancers(storeEnhancer));
sagaMiddleware.run(mySaga);
export default store;
import { takeEvery, put, all, takeLatest } from 'redux-saga/effects';
import axios from 'axios';
import { FETCH_SERVICE_SAGA_DATA } from '../store/constants';
import { changeSagaAction } from './actions';
function* fetchSagaDataAction() {
const res = yield axios({
url: 'http://localhost:3000/saga',
});
console.log(res);
yield put(changeSagaAction(res));
//也可以执行多个put
// yield all([
// yield put(changeAction1(data)),
// yield put(changeAction2(data))
// ])
}
function* mySaga() {
// takeEvery会监听拦截action事件的类型 FETCH_SERVICE_SAGA_DATA,
//然后执行fetchSagaDataAction生成器函数
yield takeEvery(FETCH_SERVICE_SAGA_DATA, fetchSagaDataAction);
// takeLatest takeEvery区别:
// takeLatest: 依次只能监听一个对应的action
// takeEvery: 每一个都会被执行
//多个执行放在all函数的数组里
// yield all([
// takeLatest(FETCH_SERVICE_SAGA_DATA, fetchSagaDataAction),
// // takeLatest(ADD_NUMBER, fetchHomeMultidata),
// ]);
}
export default mySaga;
//saga文件中put的函数,用于派发到reducer
export const changeSagaAction = (sagaData)=> ({
type: FETCH_SERVICE_SAGA_TYPE,
sagaData
})
这里的FETCH_SERVICE_SAGA_TYPE类型在constants.js添加一个就ok了。case FETCH_SERVICE_SAGA_TYPE:
return { ...state, sagaData: action.data };
fetchSagaData: ()=> {
dispatch(fetchSagaDataAction)
}
// redux-saga拦截的action
export const fetchSagaDataAction = {
type: FETCH_SERVICE_SAGA_DATA
}
import axios from 'axios';
import {
ADD_NUMBER,
SUB_NUMBER,
DECREMENT,
INCREMENT,
FETCH_SERVICE_DATA,
FETCH_SERVICE_SAGA_DATA,
FETCH_SERVICE_SAGA_TYPE
} from './constants.js'
export const addAction = num => ({
type: ADD_NUMBER,
num,
});
export const subAction = num =>({
type:SUB_NUMBER,
num
})
export const decrementAction = () =>({ type:DECREMENT })
export const incrementAction = () =>({ type:INCREMENT })
// redux-thunk异步请求
export const fetchDataAction = (dispatch, getState) => {
axios({
url: "http://localhost:3000/data",
}).then(res => {
const data = res.data;
console.log(data);
dispatch({
type: FETCH_SERVICE_DATA,
data
});
})
};
// redux-saga拦截的action
export const fetchSagaDataAction = {
type: FETCH_SERVICE_SAGA_DATA
}
//saga文件中put的函数,用于派发到reducer
export const changeSagaAction = (sagaData)=> ({
type: FETCH_SERVICE_SAGA_TYPE,
sagaData
})
home.js完整代码:import React, { PureComponent } from 'react'
import {incrementAction ,addAction ,fetchDataAction ,fetchSagaDataAction} from '../store/actions';
import {connect} from 'react-redux';
class Home extends PureComponent {
componentDidMount() {
this.props.fetchData();
this.props.fetchSagaData();
}
render() {
const {data = []} = this.props;
return (
home
当前计数:{this.props.counter}
{
data.map((item)=>{
return (
- {item.title}
)
})
}
)
}
}
const mapStateToProps = state => ({
counter:state.counter,
data:state.data
})
const mapDispatchToProps = dispatch => ({
increment: ()=> {
dispatch(incrementAction())
},
addNumber: (num)=> {
dispatch(addAction(num))
},
fetchData: ()=> {
dispatch(fetchDataAction)
},
fetchSagaData: ()=> {
dispatch(fetchSagaDataAction)
}
})
export default connect(mapStateToProps ,mapDispatchToProps)(Home)
reducer.js完整代码:import {
ADD_NUMBER,
SUB_NUMBER,
DECREMENT,
INCREMENT,
FETCH_SERVICE_DATA,
FETCH_SERVICE_SAGA_TYPE,
} from './constants.js';
const defaultState = {
counter: 0,
};
function reducer(state = defaultState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, counter: state.counter + 1 };
case DECREMENT:
return { ...state, counter: state.counter - 1 };
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num };
case SUB_NUMBER:
return { ...state, counter: state.counter - action.num };
case FETCH_SERVICE_DATA:
return { ...state, data: action.data };
case FETCH_SERVICE_SAGA_TYPE:
return { ...state, sagaData: action.data };
default:
return state;
}
}
export default reducer;
注意:案例使用第七点搭建的learn-redux项目的基础上进行修改
前面我们已经提过,中间件的目的是在redux中插入一些自己的操作:
比如我们现在有一个需求,在dispatch之前,打印一下本次的action对象,dispatch完成之后可以打印一下最新的store 、state;
也就是我们需要将对应的代码插入到redux的某部分,让之后所有的dispatch都可以包含这样的操作;
//注意,node环境路径必须写全,只有在webpack环境下才能省略:/index.js
import store from './store/index.js';
import { addAction, subAction } from './store/actions.js';
//1、基本方法----------------------
console.log('dispatch前---dispatch action' ,addAction(5));
store.dispatch(addAction(5));
console.log('dispatch前---dispatch action' ,store.getState());
console.log('dispatch前---dispatch action' ,addAction(10));
store.dispatch(subAction(10));
console.log('dispatch前---dispatch action' ,store.getState());
//2、封装一个函数--------------------
function dispatchAndLoggin(action) {
console.log('dispatch前---dispatch action', action);
store.dispatch(addAction(5));
console.log('dispatch前---dispatch action', store.getState());
}
dispatchAndLoggin(addAction(5))
dispatchAndLoggin(addAction(10))
//3、在函数的基础上进行优化:修改原有的dispatch--------------------
//hack技术: monkeyingpatch 修改原有api的能力
let next = store.dispatch;
function dispatchAndLoggin(action) {
console.log('dispatch前---dispatch action', action);
next(addAction(5));
console.log('dispatch前---dispatch action', store.getState());
}
store.dispatch = dispatchAndLoggin;
store.dispatch(addAction(5))
store.dispatch(addAction(10))
这样就意味着我们已经直接修改了dispatch的调用过程; //4、将上面的代码封装成一个函数--------------------
//当需要使用我们的这块代码的逻辑的时候,只需要调用该函数,这个打印日志功能就会生效
//可以把这部分代码封装到一个独立的文件中并export出这个函数即可
function patchLogging(store) {
let next = store.dispatch;
function dispatchAndLoggin(action) {
console.log('dispatch前---dispatch action', action);
next(addAction(5));
console.log('dispatch前---dispatch action', store.getState());
}
store.dispatch = dispatchAndLoggin;
}
patchLogging(store)
store.dispatch(addAction(5))
store.dispatch(addAction(10))
实现thunk需求:
//5、将上面的代码封装成一个函数--------------------
function patchThunk(store) {
let next = store.dispatch;
function dispatchAndThunk(action) {
if (typeof action === 'function') {
action(store.dispatch, store.getState);
} else {
next(action);
}
}
store.dispatch = dispatchAndThunk;
}
patchThunk(store);
//传入对象
store.dispatch(addAction(5));
store.dispatch(addAction(10));
//传入函数
function foo(dispatch ,getState){
console.log(dispatch ,getState)
dispatch(subAction(10))
}
store.dispatch(foo);
function applyMiddlewares(...middlewares){
middlewares.forEach(middleware => {
store.dispatch = middleware(store);
});
}
完整代码: //6、封装 applyMiddleware --------------------
function patchThunk(store) {
let next = store.dispatch;
function dispatchAndThunk(action) {
if (typeof action === 'function') {
action(store.dispatch, store.getState);
} else {
next(action);
}
}
//直接返回这个dispatchAndThunk,统一在middleware中调用
return dispatchAndThunk;
}
function patchLogging(store) {
let next = store.dispatch;
function dispatchAndLoggin(action) {
console.log('dispatch前---dispatch action', action);
next(addAction(5));
console.log('dispatch前---dispatch action', store.getState());
}
//直接返回这个dispatchAndLoggin,统一在middleware中调用
return dispatchAndLoggin;
}
function applyMiddlewares(...middlewares){
middlewares.forEach(middleware => {
//为了保证middleware是个纯函数,所有在此处进行dispatch的一个负值操作
store.dispatch = middleware(store);
});
//如果要保证applyMiddlewares是一个纯函数,也可以在此处return store
}
applyMiddlewares(patchThunk ,patchLogging)
Array.prototype.reduce(reducer, ?initialValue)的作用非常相似
目前我们已经将不同的状态处理拆分到不同的reducer中,但是依然有弊端:
目前我们合并的方式是通过每次调用reducer函数自己来返回一个新的对象。
事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并:
function reducer(state = {}, action) {
return {
aboutInfo: aboutReducer(state.aboutInfo, action),
homeInfo: homeReducer(state.homeInfo, action)
}
}
替换为: const reducer = combineReducers({
aboutInfo: aboutReducer,
homeInfo: homeReducer
});
运行依然ok