• RxJS 实战: 基于 BehaviorSubject 实现状态管理 & 结合 React


    RxJS 实战: 基于 BehaviorSubject 实现状态管理 & 结合 React

    完整代码示例

    https://github.com/superfreeeee/Blog-code/tree/main/front_end/others/rxjs/rxjs_state_management_react

    基于 BehaviorSubject 定义状态

    上一篇我们提到 RxJS 里面的 BehaviorSubject 非常适合作为一个响应式对象来帮助我们进行状态管理。今天我们来尝试手写一下基于 React 框架的封装

    • /src/store/state.ts

    一开始我们先来看我们可以利用 BehaviorSubject 做出哪些常用的状态

    基础状态

    export const nameSubject = new BehaviorSubject<string>('crystal');
    export const ageSubject = new BehaviorSubject<number>(15);
    
    • 1
    • 2

    导出量

    也称为计算状态,类似 Vue 里面的 computed

    export const maxAgeSubject = ageSubject.pipe(
      map((age) => (age >= 18 ? 18 : age)),
    );
    
    • 1
    • 2
    • 3

    依赖多个状态的导出量

    export const idCardSubject: Observable<IIDCard> = combineLatest({
      name: nameSubject,
      age: ageSubject,
    });
    
    • 1
    • 2
    • 3
    • 4

    使用 RxJS 的 combineLatest,会自动将两个 Observable 对象合并成一个新的 Observable

    自定义 Hook

    第一个当然少不了的就是 useState,几乎每个与 React 对接的外部状态管理都希望尽量贴合 React 原生 state 的写法

    useSubjectState(使用 useState 的函数签名)

    export const useSubjectState = <T>(
      subject: Subject<T>,
      defaultValue: T,
    ): [T, (value: T) => void] => {
      const [state, setState] = useState(defaultValue);
    
      useEffect(() => {
        const subscription = subject.subscribe((value) => {
          setState(value);
        });
        return () => {
          subscription.unsubscribe();
        };
      }, [subject]);
    
      const setSubject = useCallback((value: T) => {
        subject.next(value);
      }, []);
    
      return [state, setSubject];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    利用 useEffect 去 subscribe,然后将状态同步到该组件;setState 则是用 next 来实现,非常简单

    下面我们也可以学习 Recoil 的语法,再进一步将状态与 setState 隔离开来,如此一来如果我们只需要 setState 的场景下,就不会因为 state 状态的改变而产生重渲染

    useSubjectValue(对照 recoil 的 useRecoilValue)

    export const useSubjectValue = <T>(
      observable: Observable<T>,
      defaultValue: T,
    ): T => {
      const [state, setState] = useState(defaultValue);
    
      useEffect(() => {
        const subscription = observable.subscribe((value) => {
          setState(value);
        });
        return () => {
          subscription.unsubscribe();
        };
      }, [observable]);
    
      return state;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    useSetSubjectState(对照 recoil 的 useSetRecoilState)

    export const useSetSubjectState = <T>(
      subject: Subject<T>,
    ): ((value: T) => void) => {
      const setSubject = useCallback((value: T) => {
        subject.next(value);
      }, []);
    
      return setSubject;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    再者,我们可以进一步在调用之前进行绑定,自动生成与某一个 subject 相关的 Hooks

    createUseSubjectStateHooks:自动生成与指定 Subject 绑定的上述三种钩子

    export const createUseSubjectStateHooks = <T>(
      subject: Subject<T>,
    ): UseSubjectStateHooks<T> => {
      const useSubjectValue = (defaultValue: T): T => {
        const [state, setState] = useState(defaultValue);
    
        useEffect(() => {
          const subscription = subject.subscribe((value) => {
            setState(value);
          });
          return () => {
            subscription.unsubscribe();
          };
        }, [subject]);
    
        return state;
      };
    
      const useSetSubjectState = (): ((value: T) => void) => {
        const setSubject = useCallback((value: T) => {
          subject.next(value);
        }, []);
    
        return setSubject;
      };
    
      const useSubjectState = (defaultValue: T): [T, (value: T) => void] => {
        const state = useSubjectValue(defaultValue);
        const setState = useSetSubjectState();
        return [state, setState];
      };
    
      return {
        useSubjectValue,
        useSetSubjectState,
        useSubjectState,
      };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    除了直接使用状态之外,我们也可以构建一个专门监听 Subject 变化的回调函数

    第一种你可以写成一个 state,然后再配合 useEffect 实现,但是这样一来每次 Subject 变化都会调用 setState 引发重渲染;然而我们只是想用来调用 callback,我们其实完全可以跳过状态的绑定,直接使用原生的 subject 来调用回调即可

    useSubjectSideEffect

    export const useSubjectSideEffect = <T>(
      observable: Observable<T>,
      onSubjectChange: (value: T) => void,
    ) => {
      const cbRef = useRef(onSubjectChange);
      cbRef.current = onSubjectChange;
    
      useEffect(() => {
        const subscription = observable.subscribe((value) => {
          cbRef.current(value);
        });
        return () => {
          subscription.unsubscribe();
        };
      }, [observable]);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    利用 useRef 固定 callback 的引用,我们就可以在 subscribe 的时候去调用最新的回调就可以了

    React-RxJS

    实际上,RxJS 的作者们早就做了类似的工作,封装的还更好,将状态先从 Observable 利用 state 进行转换统一类型,然后再使用 useStateObservable API 接入状态

    传送门:React-RxJS

    参考链接

  • 相关阅读:
    《编译原理》实验二句法分析器
    【网页前端】CSS进阶综合案例
    力扣刷题学习SQL篇——1-5 修改(变更性别——使用判断if/case when)
    369-HI-R-M-0-0-0-E 数字化转型如何改变DCS和SCADA
    C++入门1
    11. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 实现健康检查
    实用:AE/PR 视频交换格式哪家强?
    论文分享|Arxiv2024‘复旦|如何让LLM说不?
    (附源码)计算机毕业设计SSM基于的楼盘销售系统的设计与实现
    String类
  • 原文地址:https://blog.csdn.net/weixin_44691608/article/details/127679008