Redux相关笔记之二
前言
上文我们了解了redux,那么本文就开始聊聊react-redux。
react-redux正如其名,基于react和redux的,其实就是结合两者然后提供一些方法,我们常使用的方法如useSelector、useDispatch、Provider等,在类式组件中也有类似的方法,像是connect、,mapState等,不过现在基本都是函数式组件了,所以今天的重点便是在这两个hooks以及Provider组件的原理与实现。
react-redux
在使用react-redux之前,可还记得我们是如何使用redux的?
我们回顾一下上文的Counter组件。
function Counter() {
const forceUpdate = useState({})[1];
useEffect(() => {
const unsubscribe = store.subscribe(() => {
forceUpdate({});
});
return () => {
unsubscribe();
}
}, []);
const number = store.getState().counter.number;
// ......
}
在Counter组件中,我们需要使用store中的number状态,而且要做到当number变化的时候视图需要更新,所以我们便在useEffect中订阅了这个状态。
重点就在于订阅了,每一个用到store状态的组件都需要手动写一遍订阅,这太麻烦了。
为了更好的使用,我们就想到将这个订阅的代码抽象成hook(命名为useSelector),于是便有了以下这样的代码:
import { useState, useEffect } from 'react';
import store from 'xxx'; // xxx为store的暴露文件路径,根据实际情况写
export function useSelector(selector) {
const state = store.getState();
const selectedState = selector(state);
const forceUpdate = useState({})[1];
useEffect(() => {
const unsubscribe = store.subscribe(() => {
forceUpdate({});
});
return () => {
unsubscribe();
}
}, []);
return selectedState;
}
ok,这样这个hook已经可以工作了,不过这代码有点多,而且这个forceUpdate方法看着感觉很蠢,所以react就提供了useSyncExternalStore这个钩子,内部维护了一个forceUpdate方法,那么这个useSelector就能简化一下。
import { useSyncExternalStore } from 'react';
import store from 'xxx'; // xxx为store的暴露文件路径,根据实际情况写
export function useSelector(selector) {
const selectedState = useSyncExternalStore(store.subscribe, () =>
selector(store.getState())
);
return selectedState;
}
ok,这样看起来代码就简洁多了。接着,我们在思考一下,这样的useSelector其实只是于个人用的,因为我们使用的store是直接通过路径引入的,而react-redux自然要做多一层封装,使得每一个api所使用的store都是同一个来源,不会因为当前文件路径变化而导致store读取不到等问题,因此也就引出了Provider组件,Provider接收store,api所使用的store都是Provider接收的那个store,既保证了来源的一致性也保证了取store方式的一致性。
既然react-redux称这个组件为Provider,那其实也说明了这个组件是基于react的context去做的。
首先,我们单独创建一个文件暴露context容器。
import { createContext } from "react";
export const ReactReduxContext = createContext(null);
然后完成Provider组件。
import PropsType from 'prop-types'
import { ReactReduxContext } from "./reactReduxContext";
function Provider(props) {
return (
<ReactReduxContext.Provider value={{ store: props.store }}>
{props.children}
</ReactReduxContext.Provider>
);
}
Provider.propTypes = {
children: PropsType.node.isRequired,
store: PropsType.objectOf(PropsType.any).isRequired,
};
export default Provider;
Provider组件很简单,就是套一层context的Provider而已,这一层Provider的value保存着store。
有了Provider组件后,我们需要在最外层的组件上套上。
import { Provider } from '@/react-redux'; // 这里表示自己实现的react-redux
import store from './store';
import Counter from '@/components/counter';
export default function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
ok,store的获取方式变更之后,我们的useSelector再改一下。
import { useContext, useSyncExternalStore } from "react";
import { ReactReduxContext } from "../reactReduxContext";
export function useSelector(selector) {
const { store } = useContext(ReactReduxContext);
const selectedState = useSyncExternalStore(store.subscribe, () =>
selector(store.getState())
);
return selectedState;
}
ok,这样Provider和useSelector都完整了,接下来到修改状态的方法了,也就是useDispatch。
实际上useDispatch并没有做太多的事情,就是一个简单的转换,将store的dispatch暴露出去而已,这样组件在使用dispatch也不需要手动引入store了。
import { useContext } from "react";
import { ReactReduxContext } from "../reactReduxContext";
export const useDispatch = () => {
const { store } = useContext(ReactReduxContext);
return store.dispatch;
};
到这,我们主要的三个方法都写完了,那么Counter组件就能使用起来了。
import { useSelector, useBoundDispatch } from '@/react-redux';
import { ADD, MINUS } from '@/store/actionTypes';
function Counter() {
const counter = useSelector((state) => state.counter);
const dispatch = useDispatch();
const add = () => dispatch({ type: ADD });
const minus = () => dispatch({ type: MINUS });
return (
<div>
<div>{counter.number}</div>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>
)
}
export default Counter;
效果:
结尾
本文的内容到此就结束了,本文以上文Counter组件为例,引出了useSelector、Provider以及useDispatch的原理与实现。
最后,希望这篇文章能对大家有所帮助,另外,觉得本文不错的话,请不要吝啬手中的赞哦🌹🌹🌹
转载自:https://juejin.cn/post/7368372944584376361