看文档不如看源码? - React-Redux 源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: react-redux 的源码不太适合阅读,本身常用的 API 不多,只是 Provider 和 connect,而且都很好理解,这源码没必要还是不要看了,太绕了一点都不享受 🤦‍♂️。

网络异常,图片无法展示
|

前排提示:

文件结构

老规矩,先看下文件结构(去掉一些不太重要的文件)。

├── components
│   ├── Context.ts
│   ├── Provider.tsx
│   └── connect.tsx
├── connect
├── exports.ts
├── hooks
│   ├── useDispatch.ts
│   ├── useReduxContext.ts
│   ├── useSelector.ts
│   └── useStore.ts
├── index.ts
├── next.ts
├── types.ts
└── utils
复制代码

说实话看源码前我一直以为 react-redux 会比 redux 代码量更小,失算了。🤦‍♂️

源码分析

核心源码

不同于 redux,这里导出包裹两部分,一个是 index.ts 中导出的 batch(就是 react-dom/react-native 中的 unstable_batchedUpdates),一个是 exports.ts 中的导出。

export {
    Provider,
    ReactReduxContext,
    connect,
    useDispatch,
    useSelector,
    useStore,
    createDispatchHook,
    createSelectorHook,
    createStoreHook,
    shallowEqual
};
复制代码

其中 ReactReduxContext 就是 Provider 用到的默认的 ContextshallowEqual 就是一个浅比较函数。

首先我们看下 subscription,因为 Providerconnect 中都有使用。代码我就不贴了,贴下定义:

function createSubscription(store: any, parentSub?: Subscription): Subscription;
const subscription: Subscription = {
    addNestedSub,
    notifyNestedSubs,
    handleChangeWrapper,
    isSubscribed,
    trySubscribe,
    tryUnsubscribe,
    getListeners: () => listenerCollection
};
复制代码

源码中 listenerCollection 也叫 listeners,为了避免混淆,下面称为 listenerCollection

  • parentSub 可以认为是最顶层的 subscription 实例
  • trySubscribe 会调用 addNestedSub 订阅 handleChangeWrapper,其实就是将 subscription.onStateChange 添加到 parentSubstore 的订阅中,然后调用 createListenerCollection 创建 listenerCollectiononStateChange 需要手动添加,后面可以看到。
  • listenerCollection中有以下几个方法
  • subscribe 添加订阅,这里的 listener 为链表结构
  • clear 清空所有的订阅
  • notify 调用 batch 触发所有的 listener
  • getlistener 转换为数组返回
  • tryUnsubscribe 会调用 clear
  • notifyNestedSubs 调用 notify
  • addNestedSub(listener) 调用 trySubscribe 并添加 listener

说实话逻辑有点绕,我们再看下 Provider,正好看下 subscription 的使用

function Provider<A extends Action = AnyAction>({ store, context, children, serverState }: ProviderProps<A>) {
    const contextValue = useMemo(() => {
        const subscription = createSubscription(store);
        return {
            store,
            subscription,
            getServerState: serverState ? () => serverState : undefined
        };
    }, [store, serverState]);
    const previousState = useMemo(() => store.getState(), [store]);
    useIsomorphicLayoutEffect(() => {
        const { subscription } = contextValue;
        subscription.onStateChange = subscription.notifyNestedSubs;
        subscription.trySubscribe();
        if (previousState !== store.getState()) {
            subscription.notifyNestedSubs();
        }
        return () => {
            subscription.tryUnsubscribe();
            subscription.onStateChange = undefined;
        };
    }, [contextValue, previousState]);
    const Context = context || ReactReduxContext;
    return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}
复制代码

可以看到 Provider 除了 store 外还接受 contextserverStatecontext 用于自定义 context

这里还用到 useIsomorphicLayoutEffect,该 hook 可以认为就是 useLayoutEffect,只是因为在 ssr 时使用 useLayoutEffect 会报错,所以在服务端替换为 useEffect。这里主要用来处理 subscription 的注册和取消。

export const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
复制代码

可以看到这里 subscriptiononStateChange 就是 notifyNestedSubs,所以在被包裹的组件中调用 notifyNestedSubs 时会触发此处的此处 subscriptionnotifyNestedSubs,从而将发布向上传递。讲真这里的代码是真的绕,感觉可以重新梳理一遍,比如可以保存 parentSub 然后调用其方法来将发布向上传递。

serverState 则是在 SSR 时客户端 hydration 时使用。这里结合文档看下场景:

const preloadedState = window.__PRELOADED_STATE__;
const clientStore = configureStore({
    reducer: rootReducer,
    preloadedState
});
hydrateRoot(
    document.getElementById('root'),
    <Provider store={clientStore} serverState={preloadedState}>
        <App />
    </Provider>
);
复制代码

服务端将初始化的 state 写入 window下,hydrate 时会将该 state 传入 serverState,从而避免注水后 state 不一致的问题。

再看下 contextValue,其中除了刚刚讲到的 getServerState,还有传入的 storesubscriptionstore 就是 reduxcreateStore 创建的 store

再看下 connect 部分,居然有七八百行,离谱。先看下 ts 定义:

function connect<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}, State = unknown>(
    mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, State>,
    mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
    mergeProps?: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
    {
        pure,
        areStatesEqual = strictEqual,
        areOwnPropsEqual = shallowEqual,
        areStatePropsEqual = shallowEqual,
        areMergedPropsEqual = shallowEqual,
        forwardRef = false,
        context = ReactReduxContext
    }: ConnectOptions<unknown, unknown, unknown, unknown> = {}
): unknown;
复制代码

看下几个参数:

  • mapStateToPropsstorestate 转换为 props
  • mapDispatchToPropsdispatch 转换为 props
  • mergeProps 将上述的 props 和原有的 props 进行合并
  • 下面几个 equal 函数默认是 strictEqual 用于比较各种 props,特殊情况可以做一些性能优化或精细比较
  • 可以看到这里还有 context 参数,由于 Provider 可以接受自定义 context,如果不在此处声明会导致 connect 中的 context 丢失。

返回值则是一个 hoc 方法:wrapWithConnectconnect 的主要逻辑也都在其中。

connect 上方的代码主要是处理 mapStateToPropsmapDispatchToPropsmergeProps 几个函数,并通过 mapStateToProps 来确定 shouldHandleStateChanges,用于判定 state 变更时是否需要处理。

下面从 wrapWithConnect 提出几个重要部分代码看下具体逻辑:

const displayName = `Connect(${wrappedComponentName})`;
const selectorFactoryOptions: SelectorFactoryOptions;
function ConnectFunction<TOwnProps>(props: InternalConnectProps & TOwnProps);
let Connect = React.memo(ConnectFunction);
Connect.WrappedComponent = WrappedComponent;
Connect.displayName = ConnectFunction.displayName = displayName;
if (forwardRef) {
    Connect = React.forwardRef(function forwardConnectRef(props, ref) {
        return <Connect {...props} reactReduxForwardedRef={ref} />;
    });
}
return hoistStatics(Connect, WrappedComponent);
复制代码

此处的 WrappedComponenthoc 中传入的原始组件。首先是处理 displayName ,然后到 connect.ts:518 处的 ConnectFunction 便是 hoc 后返回的新组件了,紧接着就是 forwardRef的处理,displayName 的挂载和 hoistStatics 进行静态变量的提升。

这里说下静态变量的提升,主要是为了避免 connect 后组件的静态变量丢失的情况,如一些早期常用的一些 defaultPropspropTypes 等。

重点看下 ConnectFunction,此处代码较多,且比较复杂,就不贴了,我大概梳理下逻辑:

  1. 该组件中会创建自己的 subscription
  2. 该组件会包裹 Context 并将 subscription 覆盖向下传递
  3. 其中会判定 store 的来源做一些区分处理

简单说就是通过 connect 可以将顶部的 store 配合 mapStateToProps 等转换为 props,并且其中每个 connect 都会包裹 Context,并且将 subscription 覆盖向下传递,实现了类似事件的冒泡机制。

其它部分

其它还包括 useDispatchuseReduxContextuseStore 源码比较简单,useSelector 现挖个坑,后面有时间再补。

总结

从上面可以看出,react-redux 的源码不太适合阅读,本身常用的 API 不多,只是 Providerconnect,而且都很好理解,这源码没必要还是不要看了,太绕了一点都不享受 🤦‍♂️。

相关文章
|
8天前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
45 17
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
14天前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
41 8
|
15天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
20天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
50 12
|
1月前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
1月前
|
自然语言处理 并行计算 数据可视化
免费开源法律文档比对工具:技术解析与应用
这款免费开源的法律文档比对工具,利用先进的文本分析和自然语言处理技术,实现高效、精准的文档比对。核心功能包括文本差异检测、多格式支持、语义分析、批量处理及用户友好的可视化界面,广泛适用于法律行业的各类场景。
|
25天前
|
前端开发 JavaScript
React Hooks 深入解析
React Hooks 深入解析
23 0
|
7月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
381 0
|
7月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
130 0

推荐镜像

更多