likes
comments
collection
share

Redux相关笔记之二

作者站长头像
站长
· 阅读数 58

前言

上文我们了解了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;

效果:

Redux相关笔记之二

结尾

本文的内容到此就结束了,本文以上文Counter组件为例,引出了useSelector、Provider以及useDispatch的原理与实现。

最后,希望这篇文章能对大家有所帮助,另外,觉得本文不错的话,请不要吝啬手中的赞哦🌹🌹🌹

点击查看完整代码

转载自:https://juejin.cn/post/7368372944584376361
评论
请登录