请教 react-redux 中 connect 的实现

2022-02-19 10:14:02 +08:00
 FaiChou

两年没写代码, 之前用过 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} />
}

2093 次点击
所在节点    React
11 条回复
FaiChou
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
2022-02-19 11:20:41 +08:00
https://react-redux.js.org/api/connect#connect-returns

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

```
@connect(mapStateToProps, mapDispatchtoProps...)
class MyComponent extends PureComponent {
render () {}
}
```
maplelin
2022-02-19 16:57:47 +08:00
@nondanee #3 这个肯定不是,只是 HOC 的用法就是这样,connect 其实可以理解成返回一个函数式的 HOC ,说白了里面闭包的部分就是一个组件了,而不是单纯的 function ,可以用 connect 返回的那个 function 包装出多个不同 UI 的组件,比单纯的 return component 复用性高不少
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
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
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
2022-02-19 23:54:50 +08:00
@FaiChou #7 没有 Provider 就拿不到 store 啊,store 是用户创建的,redux 不知道在哪里。就算我的那个简单实现,useSelector 和 useDispatch 的实现里面也是通过 useContext 拿到 store ,再用 useSyncExternalStore 监听 store 的
FaiChou
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
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
2022-02-24 16:35:58 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/834972

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX