react-redux基础使用
react-redux最大的特点就是:让redux的操作在react项目中更简单一些,主要是在radux在组件中应用的时候更方便一些。
- react-redux内部自己创建了上下文对象,并且我们可以把store放在上下文中,在组件中使用的时候,无需我们自己再获取上下文中的store了,它可以帮我们获取到,我们直接使用。
此时我们不再需要原src文件夹下的ThemeContext.js文件
index.jsx:
...
/* REDUX */
import store from './store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<Vote />
</Provider>
</ConfigProvider>
);
- 在组件中,我们想获取公共状态信息进行绑定等,无需自己基于上下文对象获取store,也无需自己再基于getState获取公共状态,直接基于 react-redux 提供的 connect 函数处理即可;而且也不需要我们手动把让组件更新的方法放在事件池中了,react-redux内部帮我们处理了。
connect(mapStateToProps,mapDispatchToProps)(我们要渲染的组件)
mapStateToProps:可以获取到redux中的公共状态,把需要的信息作为属性传递给组件
connect(state=>{
// state:存储redux容器中,所有模块的公共状态信息
// 返回对象中的信息,就是要作为属性,传递给组件的信息
return {
supNum: state.vote.supNum,
info: state.personal.info
};
})(Vote);
Vote.jsx:
...
import { connect } from 'react-redux';
const Vote = function Vote(props) {
let { supNum, oppNum } = props;
return <div className="vote-box">
<div className="header">
<h2 className="title">React 52lkk</h2>
<span className="num">{supNum + oppNum}</span>
</div>
<VoteMain />
<VoteFooter />
</div>;
};
export default connect(state => state.vote)(Vote);
// 两种写法一个意思,都是把redux中vote板块中的所有状态作为属性传递给组件
/* export default connect(state => {
return {
supNum: state.vote.supNum,
oppNum: state.vote.oppNum,
num: state.vote.num
}
})(Vote); */
voteMain.jsx:
...
import { connect } from 'react-redux';
class VoteMain extends React.Component {
render() {
let { supNum, oppNum } = this.props;
return <div className="main">
<p>支持人数:{supNum}人</p>
<p>反对人数:{oppNum}人</p>
</div>;
}
}
export default connect(state => state.vote)(VoteMain);
mapDispatchToProps:把需要派发的任务当做属性传递给组件
connect(
null,
dispatch=>{
// dispatch:store.dispatch 派发任务的方法
// 返回对象中的信息,会作为属性传递给组件
return {
...
};
}
)(Vote);
VoteFooter.jsx:
...
import action from "../store/actions";
import { connect } from 'react-redux';
const VoteFooter = function VoteFooter(props) {
let { support, oppose } = props;
return <div className="footer">
<Button type="primary" onClick={support}>支持</Button>
<Button type="primary" danger onClick={oppose}>反对</Button>
</div >;
};
// 标准写法的替换写法,
export default connect(
null,
action.vote
)(VoteFooter);
// mapDispatchToProps的标准写法,返回相关的方法,作为属性传递给组件;
// 组件内部执行方法的时候,基于dispatch完成任务的派发;
// 派发的行为对象,基于action中封装的操作获取。
/* export default connect(
null,
dispatch => {
return {
support() {
dispatch(action.vote.support());
},
oppose() {
dispatch(action.vote.oppose());
}
};
}
)(VoteFooter); */
验证:给bindActionCreators传入一个actionCreators和dispatch派发方法,bindActionCreators()会把actionCreators变为标准mapDispatchToProps要求的形式。
...
import action from "../store/actions";
import { connect } from 'react-redux';
import { bindActionCreators } from "redux";
...
export default connect(
null,
dispatch => {
console.log(bindActionCreators(action.vote, dispatch));
return {
support() { },
oppose() { }
}
}
)(VoteFooter);
console.log(String(bindActionCreators(action.vote, dispatch).support));
bindActionCreators源码解析:
总结: 真实项目当中,redux和react-redux配合处理,redux中按照工程化操作进行三步处理(reducer统一管理/派发的行为标识的统一管理/派发的行为对象的统一管理);通过react-redux在各组件中使用redux,首先根组件中通过react-redux提供的Provider将store放到根组件当中,在各个组件当中通过react-redux提供的connect函数将状态和派发的方法当作属性传递给组件,其中派发的方法可以直接使用actionCreators,connect内部的bindActionCreators方法会将actionCreators变为mapDispatchToProps需要的标准格式;并且也不再需要自己将让组件更新的方法添加到事件池当中去了,各组件中也不需要自己手动从上下文中获取store,进而通过store获取状态、派发任务了。
redux和react-redux归纳梳理
redux工程化处理:
- 把reducer/状态按照模块进行划分和管理;把所有模块的reducer合并为一个
- 每一次任务派发,都会把所有模块的reducer,依次去执行,派发时候传递的行为对象(行为标识)是统一的,所以我们要保证:各个模块之间,派发的行为标识它的唯一性 ===> 派发行为标识的统一管理
- 创建 actionCreator 对象,按模块管理我们需要派发的行为对象
在组件中使用的时候,如果使用的是 redux :
- 我们需要创建上下文对象,基于其 Provider 把创建的store放在根组件的上下文信息中
- 后代组件需要基于上下文对象,获取到上下文中的store
- 需要用到公共状态的组件
- store.getState() 获取公共状态
- store.subscribe(让组件更新的函数) 放在事件池中
- 需要派发的组件
- store.dispatch(actionCreator)
react-redux就是帮助我们简化redux在组件中的应用
- 提供的Provider组件,可以自己在内部创建上下文对象,把store放在根组件的上下文中
- 提供的connect函数,在函数内部,可以获取到上下文中的store,然后快速地把公共状态,以及需要派发的操作,基于属性传递给组件
connect(mapStateToProps,mapDispatchToProps)(渲染的组件)
我们涉及的原理和源码:
redux- createStore
- combineReducers
- bindActionCreators
- …
redux在设计上存在一些不足
-
我们基于getState获取的公共状态,是直接和redux中的公共状态,共用相同的堆地址,这样导致,是可以直接修改公共状态信息的
源码示例:
优化思路:我们应该在返回的时候,对状态做深拷贝处理【返回值和redux中的状态不应该是相同的堆内存】 -
我们会把让组件更新的办法,放在事件池中,当公共状态改变,会通知事件池中的所有方法执行。此操作在放置方法的时候,没有办法设置状态的依赖,这样后期不论哪个状态被修改,事件池中所有的方法都要执行(相关的组件都要进行更新)
源码示例:
优化思路:我们在向事件池中加入方法的时候,把依赖的信息也设置了,在每一次执行reducer修改状态之前,把之前的状态存储一份到「 prev」,修改后的最新状态获取到「next」。通知事件池中方法执行的时候,拿出来的某个方法是否会执行,取决于prev和next中此方法依赖的状态是否改变。
真实项目中,如果都这样去优化这个操作,每一次事件池中方法执行,也会有一套计算的逻辑(多多少少消耗一些性能)。
而往往,我们配合react-router操作的时候,虽然按照原有的操作逻辑,不论什么状态改变,事件池中的方法都会触发执行,但是react-router会让很多组件释放掉,只展示当前模块的组件「SPA」,这样即便组件更新的方法执行,但是因为组件都释放了,所以也不会产生太大的影响「而且我们还可以在组件释放的时候,把对应更新的方法,从事件池中移除掉」 -
所有reducer的合并,其实不是代码的合并,而是创建一个总的reducer出来,每一次派发,都是让总的reducer执行,而在这里,会把每个模块的reducer都完完整整执行一遍「即便中间已经发现匹配的逻辑了,也会继续把其它模块中的reducer执行」
源码示例:
优化思路:在某个模块的reducer中,如果派发的行为标识有匹配了「因为行为标识是统一管理的,所以遇到匹配的,说明后面不可能再匹配了」,则停止执行后面的reducer。
react-redux源码解读
较完整功能源码实现:src下新建myReactRedux.js
import React, { createContext, useContext, useEffect, useState, useMemo } from "react"
import { bindActionCreators } from 'redux'
const ThemeContext = createContext()
/* Provider:把传递进来的store放在根组件的上下文中 */
export function Provider(props) {
let { store, children } = props
return <ThemeContext.Provider value={{ store }}>
{children}
</ThemeContext.Provider>
}
/* connect:获取上下文中的store,然后把公共状态、要派发的方法等,都基于属性传递给需要渲染的组件;把让组件更新的方法放在redux事件池中 */
export function connect(mapStateToProps, mapDispatchToProps) {
//处理默认值
if (!mapStateToProps) {
mapStateToProps = () => {
//不写则什么都不给组件传递
return {}
}
}
if (!mapDispatchToProps) {
mapDispatchToProps = (dispatch) => {
//不写则把dispatch方法传递给组件
return { dispatch }
}
}
return function curring(Component) {
//Component:最终要渲染的组件
//HOC:我们最后基于export default导出去的组件
return function HOC(props) {
//我们需要获取上下文中的store
let { store } = useContext(ThemeContext),
{ getState, subscribe, dispatch } = store
//向事件池中加入让组件更新的办法
let [, forceUpdate] = useState(0)
useEffect(() => {
let unsubscribe = subscribe(() => {
forceUpdate(+new Date())
})
return () => {
//组件释放的时候执行:把放在事件池中的函数移除掉
unsubscribe()
}
}, [])
//把mapStateToProps/mapDispatchToProps,把执行的返回值,作为属性传递给组件
let state = getState(),
stateProps= useMemo(() => {
return mapStateToProps(state)
}, [state])
let dispatchProps = {}
if (typeof mapDispatchToProps === 'function') {
//是函数直接执行即可
dispatchProps = mapDispatchToProps(dispatch)
} else {
//是actionCreator对象,需要经过bindActionCreator处理
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch)
}
return <Component {...props} {...stateProps} {...dispatchProps} />
}
}
}
解读:
Provider:把传递进来的store放在根组件的上下文中。
import React, { createContext } from "react";
const ThemeContext = createContext()
export function Provider(props) {
let { store, children } = props
return <ThemeContext.Provider
value={{ store }}>
{children}
</ThemeContext.Provider>
}
connect:获取上下文中的store,然后把公共状态、要派发的方法等,都基于属性传递给需要渲染的组件;把让组件更新的方法放在redux事件池中。
Component是我们最终要渲染的组件【Vote】,HOC是我们最后基于export default导出的组件【高阶组件】:
export function connect(mapStateToProps, mapDispatchToProps) {
// 柯里化函数:大函数返回小函数
return function curring(Component) {
return function HOC() {
return <Component {...props} />
}
}
}
对应关系:
处理默认值:
if (!mapStateToProps) {
mapStateToProps = () => {
// 不写则什么都不给组件传递
return {}
}
}
if (!mapDispatchToProps) {
mapDispatchToProps = (dispatch) => {
// 不写则把dispatch方法传递给组件
return { dispatch }
}
}
高阶组件中获取上下文中的store,将让组件更新的方法添加到事件池中:
import React, { createContext, useContext, useState, useEffect } from "react";
function HOC(props) {
let { store } = useContext(ThemeContext),
{ getState, subscribe, dispatch } = store
let [, forceUpdate] = useState(0)
useEffect(() => {
let unsubscribe = subscribe(() => {
forceUpdate(+new Date())
})
// useEffect()中的callback执行完成之后还可以返回一个函数,这个函数会在组件释放的时候执行:把放在事件池中的函数移除掉
return () => {
unsubscribe()
}
}, [])
return < Component {...props} />
}
把mapStateToProps/mapDiapatchToProps执行的返回值作为属性传递给组件:
import { bindActionCreators } from "redux";
...
let state = getState(),
stateProps = mapStateToProps(state)
let dispatchProps = {}
if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(dispatch)
} else {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch)
}
return < Component {...props} {...stateProps} {...dispatchProps} />
优化:做个计算缓存
import { useMemo } from "react";
stateProps = useMemo(() => {
return mapStateToProps(state)
}, [state])