redux工程化的第一种方式:拆分和合并reducer。
新的总reducer
。新的总reducer
生成一个store公共状态容器。/src/Theme.js
,用于创建上下文对象并在根组件/src/index.jsx
中引入。将公共容器放在根组件的上下文对象中。把大的reducer拆分成多个reducer。最后通过combineReducers把各个reducer合成一个总reducer。最后createStore根据总reducer得到公共状态容器。
state = {
vote:{
//...
},
task:{
//...
}
}
store.getState()//得到类似于上方的东西。所以还要取具体模块,再解析出一个公共状态的具体公共状态。
上方就是redux工程化的第一种方式:reducer的拆分和合并。
redux工程化的第二种方式:action-types。对派发行为标识的统一管理-即宏管理。
新建一个文件,存放所有的行为标识。
/src/store/action-types.js
// 各个模块下,需要派发的判断的行为标识,都在这个文件中进行统一的管理。
// 可以保证,标识是不会冲突的。因为变量名和变量值保持一致,如果冲突了,那么变量名冲突就会报错了。
export const VOTE_SUP = "VOTE_SUP";
export const VOTE_OPP = "VOTE_OPP";
export const TASK_QUERY_LIST = "TASK_QUERY_LIST";
独立的reducer根据统一标识符进行逻辑处理。
/src/store/reducers/voteReducer.js
import * as AT from "../action-types"; //导入所有统一管理的行为标识。
// import * as AT from '../action-types'
let initial = {
title: "React不是很难的!",
supNum: 10,
oppNum: 5,
};
export default function voteReducer(state = initial, action) {
let { type } = action;
// 基于统一管理的标识进行判断,而且写代码时有代码提示。
switch (type) {
case AT.VOTE_SUP:
state.supNum++;
break;
case AT.VOTE_OPP:
state.oppNum++;
break;
default:
}
return state;
}
/src/store/reducers/taskReducer.js
import * as AT from "../action-types";
let initial = {
title: "TASK OA 任务管理系统",
list: [],
};
export default function taskReducer(state = initial, action) {
let { type } = action;
switch (type) {
case AT.TASK_QUERY_LIST:
// ...
break;
default:
}
return state;
}
业务组件调用时,dispatch中用到的行为对象中type类似使用统一标识符。
/src/views/VoteFooter.jsx
import React, { useContext } from "react";
import { Button } from "antd";
import Theme from "@/Theme";
import * as AT from '@/store/action-types'//组件中派发的时候,也是用统一管理的标识进行派发。
const VoteFooter = function VoteFooter() {
const { store } = useContext(Theme);
return (
<div className="footer-box">
<Button
type="primary"
onClick={() => {
store.dispatch({
type: AT.VOTE_SUP,
});
}}
>
支持
</Button>
<Button
type="primary"
danger
onClick={() => {
store.dispatch({
type: AT.VOTE_OPP,
});
}}
>
反对
</Button>
</div>
);
};
export default VoteFooter;
redux工程化的第三种方式:actionCreators。对派发的action行为对象进行统一管理-分模块管理。
单纯这样看,本次工程化一点用都没有,而且还让代码写起来更麻烦。
实际步骤:
新建多个独立action文件
,内部返回一个独立action对象
。
独立action对象
是内部存放多个返回具体reducer所需行为对象的函数
。
代码:
/src/store/actions/voteAction.js
import * as AT from "../action-types";
//一个对象中包含多个方法,每个方法执行返回的就是dispatch时需要传递的action对象。
const voteAction = {
support() {
return {
type: AT.VOTE_SUP,
};
},
oppose() {
return {
type: AT.VOTE_OPP,
};
},
};
export default voteAction;
代码:
/src/store/actions/taskAction.js
import * as AT from "../action-types";
//一个对象中包含多个方法,每个方法执行返回的就是dispatch时需要传递的action对象。
const taskAction = {
queryTaskList() {
return {
type: AT.TASK_QUERY_LIST,
};
},
};
export default taskAction;
将多个独立action对象
组合成一个总action对象
,并导出。
/src/store/actions/index.js
/* // 把各个模块的action管理对象,合并为一个总的对象
action = {
vote: {
support() {
return {
type: AT.VOTE_SUP,
};
},
oppose() {
return {
type: AT.VOTE_OPP,
};
},
},
task: {
//...
},
};
*/
import voteAction from "./voteAction";
import taskAction from "./taskAction";
const action = {
vote: voteAction,
task: taskAction,
};
export default action;
业务组件调用时,dispatch中用到的行为对象是由总action对象
中包含的某个独立action文件
的某个返回具体reducer所需行为对象的函数
被调用时所生成的。
/src/views/VoteFooter.jsx
import React, { useContext } from "react";
import { Button } from "antd";
import Theme from "@/Theme";
import action from "@/store/actions";
const VoteFooter = function VoteFooter() {
const { store } = useContext(Theme);
return (
<div className="footer-box">
<Button
type="primary"
onClick={() => {
store.dispatch(action.vote.support());
}}
>
支持
</Button>
<Button
type="primary"
danger
onClick={() => {
store.dispatch(action.vote.oppose());
}}
>
反对
</Button>
</div>
);
};
export default VoteFooter;
通过上方的代码应用和分析,发现本次工程化的方式,一点用都没有,而且写起来更复杂了。
redux中间件
实现异步派发,效果上类似于vuex中的actions异步派发管理。作用:
基于connect函数操作:
优化前:
代码:
import VoteStyle from "./VoteStyle";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
import React, { useContext } from "react";
import Theme from "@/Theme";
import useForceUpdate from "@/useForceUpdate";
// import { connect } from "react-redux";
const Vote = function Vote(props) {
const { store } = useContext(Theme);
let { supNum, oppNum, title } = store.getState().vote;
useForceUpdate(store);
// let { title, supNum, oppNum } = props;
return (
{title}
{supNum + oppNum}
);
};
export default Vote;
优化后:
代码:
import VoteStyle from "./VoteStyle";
import VoteMain from "./VoteMain";
import VoteFooter from "./VoteFooter";
import { connect } from "react-redux";
const Vote = function Vote(props) {
let { title, supNum, oppNum } = props;
return (
{title}
{supNum + oppNum}
);
};
export default connect((state) => {
return state.vote;
})(Vote);
// import React, { useContext } from "react";
// import Theme from "@/Theme";
// import useForceUpdate from "@/useForceUpdate";
import { connect } from "react-redux";
const VoteMain = function VoteMain(props) {
// const { store } = useContext(Theme);
// let { supNum, oppNum } = store.getState().vote;
// useForceUpdate(store);
let { supNum, oppNum } = props;
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
};
// export default VoteMain;
export default connect((state) => state.vote)(VoteMain);
connect:react-redux提供的高阶函数。
获取基于Provider放在上下文中的store对象。
把让组件更新的办法自动加入到事件池中。
语法:
export default connect(
[mapStateToProps],
[mapDispatchToProps],
)(需要渲染的组件)
函数
。
connect(state=>{
//state:总的状态。
return {
//返回对象中的信息,最后会作为属性传递给组件。
//...
}
})
null
/undefined
。
函数。
(dispatch)=>{
//dispatch:store.dispatch 用来派发任务的方法。
return {
//返回对象中的信息,会基于属性传递给组件。
support(){
return dispatch(action.vote.support())//{ type: AT.VOTE_SUP, }
}
}
}
actionCreator对象:
actionCreator对象:一个对象中包含好多方法,每个方法执行,老师返回需要派发的action对象。
connect内部,自动基于bindActionCreators方法,把actionCreator对象,变成mapDispatchToProps这样的函数模式。
// action.vote 「actionCreator对象:对象中包含好多方法,每个方法执行,都是返回需要派发的action对象」
//假设传入的是action.vote,而action.vote的样式为下方那样:
{
support() {
return {
type: AT.VOTE_SUP
}
},
oppose() {
return {
type: AT.VOTE_OPP
}
}
}
// 基于 bindActionCreators(action.vote,dispatch) 处理后:
{
support(){
return dispatch(action.vote.support())
},
oppose(){
return dispatch(action.vote.oppose())
}
}
null/undefined。
函数:
import React from "react";
import { Button } from "antd";
import { connect } from "react-redux";
import action from "@/store/actions";
const VoteFooter = function VoteFooter(props) {
let { support, oppose } = props;
return (
);
};
export default connect(null, (dispatch) => {
return {
support() {
console.log("support()");
return dispatch(action.vote.support());
},
oppose() {
console.log("oppose()");
return dispatch(action.vote.oppose());
},
};
})(VoteFooter);
actionCreators对象格式
。actionCreator对象:
import React from "react"
import { Button } from 'antd'
import { connect } from '@/myreactredux'
import action from "@/store/actions"
const VoteFooter = function VoteFooter(props) {
let { support, oppose } = props
return
}
export default connect(null, action.vote)(VoteFooter)
除了基于 connect 函数「适用于所有类型的组件」可以做这个事情,react-redux还提供了一些Hook函数「只适用于函数组件」,也可以实现类似的效果!
常用的:useSelector, useStore, useDispatch。
useSelector: 是react-redux提供的自定义Hook函数,只能在函数组件中运用。
获取基于Provider放在上下文中的store对象。
把让组件更新的办法自动加入到事件池中。
useSelector语法
let xxx = useSelector((state) => {
//必须传递一个函数。
//state是公共容器中的总状态。
return {
//返回的对象中包含我们需要的状态信息,整体赋值给外面的xxx。
a:state.vote.supNum,
b:state.task.title,
//....
}
})
//xxx.a --> state.vote.supNum
//xxx.b --> state.task.title
import { useSelector,useStore } from "react-redux";//
const VoteMain = function VoteMain() {
let xxx = useSelector((state) => {
console.log('state',state);
return {
a:state.vote.supNum,
b:state.task.list,
}
})
console.log(xxx);
console.log('useStore()',useStore());
let { supNum, oppNum } = useSelector((state) => state.vote);
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
};
export default VoteMain;
useStore: 把上下文中的store对象获取到。
import { useSelector,useStore } from "react-redux";//
const VoteMain = function VoteMain() {
console.log('useStore()',useStore());
let { supNum, oppNum } = useSelector((state) => state.vote);
return (
支持人数:{supNum} 人
反对人数:{oppNum} 人
);
};
export default VoteMain;
useDispatch: 得到store对象的dispatch()方法。
import React from "react";
import { Button } from "antd";
import { useDispatch } from "react-redux";
import action from "@/store/actions";
const VoteFooter = function VoteFooter() {
const dispatch = useDispatch();
return (
);
};
export default VoteFooter;
/src/views/VoteFooter.jsx
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, but instead received: '${kindOf(
actionCreators
)}'. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
我们讲的,其默认向事件池中加入让组件更新的办法「无需我们自己增加」;方法确实加了,但是加的方法有特殊处理!!
而我们在讲 combineReducers 源码的时候发现,如果我们的reducer这样写:
export default function taskReducer(state = initial, action) {
let { type } = action
switch (type) {
//...
default:
}
return state
}
所以我们在写reducer的时候,进来的第一件事情,就是把 传递的原有的 state 进行拷贝!
export default function taskReducer(state = initial, action) {
state = { ...state };
let { type } = action
switch (type) {
//...
default:
}
return state
}
react-redux源码可在node_modules里查看:/node_modules/react-redux/dist/react-redux.js
。
/src/myreactredux.jsx
import { createContext, useContext, useEffect, useState } from "react";
import { bindActionCreators } from "redux";
import PT from "prop-types";
import _ from "@/assets/utils";
/* 创建上下文对象 */
const ThemeContext = createContext();
/* Provider:把基于属性传递进来的store对象,放在上下文中 */
export const Provider = function Provider(props) {
let { store, children } = props;
return (
{children}
);
};
Provider.propTypes = {
store: PT.object.isRequired,
};
/* 提供三个自定义Hook函数 */
export const useStore = function useStore() {
let { store } = useContext(ThemeContext);
return store;
};
export const useDispatch = function useDispatch() {
const store = useStore();
return store.dispatch;
};
export const useSelector = function useSelector(callback) {
if (typeof callback !== "function")
throw new TypeError(`useSelector 中的 callback 必须是一个函数`);
// 获取总状态
const store = useStore(),
state = store.getState();
// 向redux事件池中,注入让组件更新的办法
let [, setRandom] = useState(+new Date());
useEffect(() => {
let unsubscribe = store.subscribe(() => {
// 其内部在让组件更新之前做了优化:对比新老状态(浅比较),如果不一样,才让组件更新
setRandom(+new Date());
});
return () => unsubscribe();
}, []);
// 把传递的 callback 函数执行,传递总状态,接收返回的对象(包含需要用的状态)
let result = callback(state);
if (!_.isPlainObject(result))
throw new TypeError(`callback 执行,必须返回的一个对象`);
return result;
};
/* 核心方法:connect */
export const connect = function connect(mapStateToProps, mapDispatchToProps) {
// 处理参数为空的情况
if (mapStateToProps == null) {
mapStateToProps = () => {
return {};
};
}
if (mapDispatchToProps == null) {
mapDispatchToProps = () => {
return {};
};
}
return function (Component) {
// Component:最终需要渲染的组件
// 还需要返回一个供外面调用的组件「HOC:Higher-Order Components 」
return function HOC(props) {
// 传递给HOC组件的属性,其实也是要传递给Vote的
let attrs = {
...props,
};
// 处理 mapStateToProps「直接用 useSelector 处理即可,其内部把事件池的操作都处理了」
let state = useSelector(mapStateToProps);
Object.assign(attrs, state);
// 处理 mapDispatchToProps
let temp = {};
let dispatch = useDispatch();
if (typeof mapDispatchToProps === "function") {
// 是函数的情况:把函数执行,返回的信息就是需要基于属性传递给组件的
temp = mapDispatchToProps(dispatch);
} else if (_.isPlainObject(mapDispatchToProps)) {
// 是对象的情况:说明其实 actionCreators 对象,我们需要基于 bindActionCreators 处理
temp = bindActionCreators(mapDispatchToProps, dispatch);
}
if (!_.isPlainObject(temp))
throw new TypeError(`mapDispatchToProps 处理的结果需要是一个对象`);
Object.assign(attrs, temp);
// 但是最终要渲染的还是之前传递的组件(例如:Vote),并且把 mapStateToProps&mapDispatchToProps 等处理后的结果,通过属性传递给这个组件
return ;
};
};
};
通过高级函数-即闭包函数返回的组件。
// @HOC
class Index extends React.Component{
say(){
const { name } = this.props
console.log(name)
}
render(){
return hello,world
}
}
function HOC(Component) {
return class wrapComponent extends React.Component{
constructor(){
super()
this.state={
name:'alien'
}
}
render=()=>
}
}
HOC(Index)//会返回一个wrapComponent组件,但wrapComponent组件实际渲染的是传入的Index组件。不过wrapComponent组件内部中的Index组件有了wrapComponent组件实例上的state实例状态及父组件调用wrapComponent组件时传给wrapComponent组件的props。