V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
FaiChou
V2EX  ›  React

请教 react-redux 中 connect 的实现

  •  
  •   FaiChou ·
    FaiChou · 2022-02-19 10:14:02 +08:00 · 2099 次点击
    这是一个创建于 1009 天前的主题,其中的信息可能已经有所发展或是发生改变。

    两年没写代码, 之前用过 React, 但没(经常)用 hooks.

    最近看了下文档, 遇到几个问题, 比如, 外部的数据(redux)变化如何让组件 rerender?

    如果是我去实现 connect, 我会写成一个 HOC, 添加一个 listener 监听 state 变化, 当发生变化, 手动调用 forceUpdate.

    但看了下现在 connect 源码, 没太看懂, 看到是用 Context 实现的, 但具体逻辑有几个问题:

    1 具体实现方式

    如果是 Context 实现, 那外层应该是一个 Context.Provider, connect 应该是对 Consumer 的封装, 但发现还是用 Provider 的封装. 一个项目成千上万的小组件使用了 connect, 那就会有成千上万个 Provider, 逻辑岂不很混乱?

    2 为什么不写成 connect(Component, mapStateToProps, mapDispatchtoProps)?

    现在的实现(pseudocode):

    function connect(mapStateToProps, mapDispatchtoProps...) {
      // some logic1
      return function(Component) {
    	// some logic2
    	return <Component {...props} />
      }
    }
    

    使用的时候需要 connect(mapState, mapProps)(Component).

    为什么不实现成这样(pseudocode):

    function connect(Component, mapStateToProps, mapDispatchtoProps...) {
      // some logic with mapState and mapDispatch
      return <Component {...props} />
    }
    
    
    11 条回复    2022-02-24 16:35:58 +08:00
    FaiChou
        1
    FaiChou  
    OP
       2022-02-19 10:20:35 +08:00
    const compose = (...funcs) =>
    funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg)

    问题 2, 难道是为了 functional programming ?
    7anshuai
        2
    7anshuai  
       2022-02-19 11:20:41 +08:00   ❤️ 2
    https://react-redux.js.org/api/connect#connect-returns

    connect() 返回一个 HOC ,HOC 可以重复使用
    nondanee
        3
    nondanee  
       2022-02-19 13:31:11 +08:00   ❤️ 2
    可能是为了方便用装饰器?

    ```
    @connect(mapStateToProps, mapDispatchtoProps...)
    class MyComponent extends PureComponent {
    render () {}
    }
    ```
    maplelin
        4
    maplelin  
       2022-02-19 16:57:47 +08:00
    @nondanee #3 这个肯定不是,只是 HOC 的用法就是这样,connect 其实可以理解成返回一个函数式的 HOC ,说白了里面闭包的部分就是一个组件了,而不是单纯的 function ,可以用 connect 返回的那个 function 包装出多个不同 UI 的组件,比单纯的 return component 复用性高不少
    otakustay
        5
    otakustay  
       2022-02-19 17:03:49 +08:00
    理论上来说,connect(mapState, mapDispatch)的结果应该是一个可复用的东西,比如:

    const connectCurrentUser = connect(state => state.session.currentUser);
    const connectItemList = connect(
    state => state.items.all,
    dispatch => ({reload: () => dispatch('RELOAD_ITEMS')})
    );

    然后需要用当前用户的地方,就 connectCurrentUser(MyComponent)就行

    但实践中,不知怎么着,就几乎没人把 connect 返回的那个函数复用,最终就有了楼主这样不理解 connect 设计的人了
    otakustay
        6
    otakustay  
       2022-02-19 17:06:00 +08:00
    关于实现,beta 版现在应该是用 useSyncExternalStore 实现 useSelector 和 useDispatch 了,大概十来行就够用了。然后 connect 是可以基于 useSelector 和 useDispatch 二次封装的:

    const connect = (mapState, mapDispatch) => {
    return ComponentIn => props => {
    const state = useSelector(mapState);
    const dispatch = useDispatch();
    const methods = mapDispatch(dispatch);
    return <ComponentIn {...props} {...state} {...methods} />;
    };
    }
    FaiChou
        7
    FaiChou  
    OP
       2022-02-19 22:33:50 +08:00
    @otakustay 这种简单实现我也懂, 我是不明白源码中为何要多套一层 <ContextToUse.Provider>:

    Provider:
    const Context = React.createContext(null);
    function Provider(store, children) {
    return <Context.Provider value={contextValue}>{childeren}</Context.Provider>
    }


    const connect = (mapState, mapDispatch) => {
    return ComponentIn => props => {
    const state = useSelector(mapState);
    const dispatch = useDispatch();
    const methods = mapDispatch(dispatch);
    return (
    <ContextToUse.Provider value={overriddenContextValue}>
    <ComponentIn {...props} {...state} {...methods} />;
    </ContextToUse.Provider>
    );
    };
    }
    otakustay
        8
    otakustay  
       2022-02-19 23:54:50 +08:00
    @FaiChou #7 没有 Provider 就拿不到 store 啊,store 是用户创建的,redux 不知道在哪里。就算我的那个简单实现,useSelector 和 useDispatch 的实现里面也是通过 useContext 拿到 store ,再用 useSyncExternalStore 监听 store 的
    FaiChou
        9
    FaiChou  
    OP
       2022-02-20 10:33:20 +08:00
    @otakustay 你还是没有看源码,「没有 Provider 就拿不到 store 」是的, 所以 react-redux 在最外层的 Provider 使用了 <Context.Provider> :

    https://github.com/reduxjs/react-redux/blob/master/src/components/Provider.tsx

    但是在 connect 中, 它 wrap 的组件外再套了一层 <ContextToUse.Provider> , 难道不是应该套一层 <Consumer> 吗?
    FaiChou
        10
    FaiChou  
    OP
       2022-02-20 10:50:00 +08:00
    @otakustay 我明白了. 原来并不是 store 改变 <Provider>下面所有的 tree 都需要 update:

    > Because each connected component should only re-render when its nearest connected ancestor component has rendered.

    https://github.com/reduxjs/react-redux/discussions/1875#discussioncomment-2210998
    aec4d
        11
    aec4d  
       2022-02-24 16:35:58 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5287 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 01:29 · PVG 09:29 · LAX 17:29 · JFK 20:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.