第二节: react-redux库、redux-thunk库、devtools工具的使用 、reducer模块划分

一. react-redux库

1. 背景

 在仅仅导入【redux】库的情况下,代码非常冗余,每个组件中都需要:

 (1). 需要在componentDidMount生命周期中subscribe订阅, store中的数据修改时候,给页面state中的数据进行修改赋值

 (2). 默认构造函数中需要给 state中进行从store中获取数据进行赋值

详见:home.jsx   profile.jsx

 

2. 说明

 虽然我们之前已经实现了connect、Provider这些帮助我们完成连接redux、react的辅助工具,但是实际上redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效

  【npm install react-redux】

 实现的功能:

  (1). 将state里的数据映射到props里,通过 this.props.xxx 获取, 简化了componentDidMount中subscribe订阅 和 constructor中的赋值。

  (2). 将自定义方法映射到props里,通过 this.props.xxx 调用方法。

 

3. 实操

 (1). 安装 【npm install react-redux】

 (2). index 中的配置  <Provider> 包裹, 并给store属性赋值: store={store}

import store from "./store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
	<Provider store={store}>
		<App />
	</Provider>
);

 (3). 在ZAbout组件中:

      A. 编写 mapStateToProps方法 和 mapDispatchToProps,用于将state里“需要的”数据和自定义的方法映射到props里

      B. 调用connect方法进行执行 export default connect(mapStateToProps, mapDispatchToProps)(ZAbout);

PS:剖析connect方法:

   connect是一个高阶函数,接收两个函数作为参数,connect(xxx,xxx)调用后的返回值还是一个函数。

   这个函数是一个高阶组件 connect(xxx,xxx)(newComponent),接收一个组件作为参数, 返回值还是一个组件

补充

   高阶函数:接收函数作为参数,返回值也是一个函数

   高阶组件:高阶组件本身是一个函数,接收一个组件作为参数,返回值也是一个组件

 (4). 在render中通过 this.props里拿到counter进行赋值显示即可

 (5). 自定义方法进行 加减counter,通过this.props.xxx 调用 mapDispatchToProps 里映射的方法来修改 store里的counter  

核心代码分享:

查看代码
 import React, { PureComponent } from "react";
import { subNumAction, addNumAction } from "../store/actionCreators";
import { connect } from "react-redux";

export class ZAbout extends PureComponent {
	subNumber(num) {
		this.props.subNumber(num);
	}
	addNumber(num) {
		this.props.addNumber(num);
	}

	render() {
		const { counter, banners, recommends } = this.props;
		return (
			<div>
				<h3>ZAbout Counter:{counter}</h3>
				<div>
					<button onClick={() => this.subNumber(1)}>-1</button>
					<button onClick={() => this.subNumber(5)}>-5</button>
					<button onClick={() => this.subNumber(8)}>-8</button>
					<button onClick={() => this.addNumber(1)}>+1</button>
					<button onClick={() => this.addNumber(5)}>+5</button>
					<button onClick={() => this.addNumber(8)}>+8</button>
				</div>
				<div className="banner">
					<h2>轮播图数据:</h2>
					<ul>
						{banners?.map((item, index) => (
							<li key={index}>{item.title}</li>
						))}
					</ul>
				</div>
				<div className="recommend">
					<h2>推荐数据:</h2>
					<ul>
						{recommends?.map((item, index) => (
							<li key={index}>{item.title}</li>
						))}
					</ul>
				</div>
			</div>
		);
	}
}

//1. 将state里的数据映射到props里的封装
const mapStateToProps = state => ({
	counter: state.counter,
	// 下面的banners 和 recommends 都是zCategory1中获取后赋值到store里的
	banners: state.banners,
	recommends: state.recommends,
});

//2. 将下面定义的的方法映射到props里的封装
const mapDispatchToProps = dispatch => ({
	addNumber(num) {
		dispatch(addNumAction(num));
	},
	subNumber(num) {
		dispatch(subNumAction(num));
	},
});

//3. 执行
/* 
   connect是一个高阶函数,接收两个函数作为参数,connect(xxx,xxx)调用后的返回值还是一个函数。
   这个函数是一个高阶组件 connect(xxx,xxx)(newComponent),接收一个组件作为参数, 返回值还是一个组件
   高阶函数:接收函数作为参数,返回值也是一个函数
   高阶组件:高阶组件本身是一个函数,接收一个组件作为参数,返回值也是一个组件
*/
export default connect(mapStateToProps, mapDispatchToProps)(ZAbout);

 

二. redux-thunk库

1. 背景--组件中的异步操作

  真实开发中,redux中保存的很多数据可能来自服务器,我们需要进行异步的请求,再将数据保存到redux中,网络请求可以在class组件的componentDidMount中发送,如下图01:

1.1  实操-zCateGory1组件中

  (1). 编写 mapStateToProps方法 和 mapDispatchToProps,用于将state里“需要的”数据和自定义的方法映射到props里

  (2). 在componentDidMount发送异步请求获取数据,修改store里的数据banners和recommends数据

  (3). 修改后zAbout组件中就可以通过mapStateToProps映射拿到store里的banners和recommends了

1.2 缺陷

  我们必须将网络请求的异步代码放到组件的生命周期中来完成;

  事实上,网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其也交给redux来管理

代码分享:

查看代码
 import React, { PureComponent } from "react";
import {
	changeBannersAction,
	changeRecommendsAction,
} from "../store/actionCreators";
import { connect } from "react-redux";
import axios from "axios";
// import store from "../store";

export class ZCategory1 extends PureComponent {
	async componentDidMount() {
		let { data } = await axios.get("http://xxx:8000/home/multidata");

		const banners = data.data.banner.list;
		const recommends = data.data.recommend.list;

		// 修改store里的数据
		this.props.changeBanners(banners);
		this.props.changeRecommends(recommends);

		// console.log(store.getState());
	}

	render() {
		const { counter } = this.props;
		return (
			<div>
				<h3>ZCategory1 Counter:{counter}</h3>
			</div>
		);
	}
}

//1. 将state里的数据映射到props里的封装
const mapStateToProps = state => ({
	counter: state.counter,
});

//2. 将下面生命的方法映射到props里的封装
const mapDispatchToProps = dispatch => ({
	changeBanners(data) {
		dispatch(changeBannersAction(data));
	},
	changeRecommends(data) {
		dispatch(changeRecommendsAction(data));
	},
});

//3. 执行
export default connect(mapStateToProps, mapDispatchToProps)(ZCategory1);

 

2.  redux-thunk说明

(1). redux中如何可以进行异步的操作呢?

  使用中间件, redux也引入了中间件(Middleware)的概念:

  这个中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码;比如日志记录、调用异步接口、添加代码调试功能等等

  官网推荐:使用【redux-thunk】来实现网络请求中间件

  详见图02

(2). redux-thunk 如何做到让我们可以发送异步的请求呢?

  默认情况下的dispatch(action),action需要是一个JavaScript的对象,eg dispatch({type:"xxx",num:10})

  redux-thunk可以让dispatch接收一个函数,即action可以是一个函数;该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数;

  A. dispatch函数用于我们之后再次派发action;

  B. getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态, 即:可以用来获取store里的数据;

 

3. 实操

详见:ZCategory2

(1). 安装 【npm install redux-thunk】

(2). 配置中间件

  在创建store时传入应用了middleware的enhance函数,通过applyMiddleware来结合多个Middleware, 返回一个enhancer; 将enhancer作为第二个参数传入到createStore中

  详见 store/index.js

import thunk from 'redux-thunk';
import reducer from './reducer';
import { applyMiddleware, createStore, compose } from 'redux';

// 1. 最原始的默认写法
// const store = createStore(reducer);

// 2. redux-thunk的使用
// 用于实现派发一个函数  dispatch(function)
 const store = createStore(reducer, applyMiddleware(thunk));

export default store;

(3). actionCreators中配置

  编写 fetchHomeMultidataAction方法,返回的也是一个函数,函数有两个参数:dispatch 和 getState

(4) ZCategory2组件

  编写 mapStateToProps用来映射counter

  编写 mapDispatchToProps 用来映射调用fetchHomeMultidataAction的方法

  在 componentDidMount 中通过 this.props.fecthHomeMultidata() 调用即可

(5). App中注释掉 ZCategory1组件,引入ZCategory1,测试获取数据,在zAbout组件中使用

核心代码分享:

查看代码
 import React, { PureComponent } from "react";
import { fetchHomeMultidataAction } from "../store/actionCreators";
import { connect } from "react-redux";

export class ZCategory2 extends PureComponent {
	async componentDidMount() {
		this.props.fetchHomeMultidata();
	}

	render() {
		const { counter } = this.props;
		return (
			<div>
				<h3>ZCategory2 Counter:{counter}</h3>
			</div>
		);
	}
}

//1. 将state里的数据映射到props里的封装
const mapStateToProps = state => ({
	counter: state.counter,
});

//2. 将下面声明的方法映射到props里的封装
const mapDispatchToProps = dispatch => ({
	fetchHomeMultidata() {
		dispatch(fetchHomeMultidataAction());
	},
});

//3. 执行
export default connect(mapStateToProps, mapDispatchToProps)(ZCategory2);

 

三. devtools工具的使用

1. 在对应的浏览器中安装相关的插件(比如Chrome/Edge浏览器扩展商店中搜索Redux DevTools即可);

 2. 在redux中继承devtools的中间件;

    详见store/index (特别注意下面的 ?. 可选链的使用,否则在没有安装插件的浏览器上无法使用哦)

    const composeEnhancers =  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?.({ trace: true }) || compose;

    const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));

import thunk from 'redux-thunk';
import reducer from './reducer';
import { applyMiddleware, createStore, compose } from 'redux';

// 1. 最原始的默认写法
// const store = createStore(reducer);

// 2. redux-thunk的使用
// 用于实现派发一个函数  dispatch(function)
// const store = createStore(reducer, applyMiddleware(thunk));

// 3. redux-devtools的使用 (生产环境建议注释掉,不要把数据暴露出来)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?.({ trace: true }) || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));

export default store;

 3. 调试图详见 03

 

四. reducer模块

1. 背景

 当前这个reducer既有处理counter的代码,又有处理home页面的数据;

 后续counter相关的状态或home相关的状态会进一步变得更加复杂;

 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;

 如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。

 

 2. reducer拆分

 先抽取一个对counter处理的reducer;

 再抽取一个对home处理的reducer;

 将它们合并起来;

3. 文件拆分

 虽然已经放到不同的函数了,但是这些函数的处理依然是在同一个文件中,代码非常的混乱;

 另外关于reducer中用到的constant、action等我们也依然是在同一个文件中;

 所以在store文件夹下拆分出来两个文件,count 和 home文件夹, 这两个文件夹下的index文件改为 对外导出reducer 和 actionCreators

import reducer from "./reducer";

// 对外导出reducer (默认导出的形式)
export default reducer;

// 对外导出所有的actionCreators
export * from "./actionCreators";

 4. 整合

 redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并:

 那么combineReducers是如何实现的呢?

 (1). 事实上,它也是将我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);

 (2). 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state;

 (3). 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;

  详见:store/index

import thunk from 'redux-thunk';
import { applyMiddleware, createStore, compose, combineReducers } from 'redux';
// 导入子reducer
import counterReducer from './count';
import homeReducer from './home';
// 合并reducer
const combineReducer = combineReducers({
	childCount: counterReducer,
	childHome: homeReducer,
});

// 1. 最原始的默认写法
// const store = createStore(combineReducer);

// 2. redux-thunk的使用
// 用于实现派发一个函数  dispatch(function)
// const store = createStore(combineReducer, applyMiddleware(thunk));

// 3. redux-devtools的使用 (生产环境建议注释掉,不要把数据暴露出来)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?.({ trace: true }) || compose;
const store = createStore(combineReducer, composeEnhancers(applyMiddleware(thunk)));

export default store;

  其它代码的改造: 调用子reducer中state里的数据的时候,需要加上子名称

  store.getState().childCount.counter

  state.childCount.counter

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2023-05-05 08:47  Yaopengfei  阅读(148)  评论(1编辑  收藏  举报