两年没写代码, 之前用过 React, 但没(经常)用 hooks.
最近看了下文档, 遇到几个问题, 比如, 外部的数据(redux)变化如何让组件 rerender?
如果是我去实现 connect, 我会写成一个 HOC, 添加一个 listener 监听 state 变化, 当发生变化, 手动调用 forceUpdate
.
但看了下现在 connect 源码, 没太看懂, 看到是用 Context 实现的, 但具体逻辑有几个问题:
如果是 Context
实现, 那外层应该是一个 Context.Provider
, connect
应该是对 Consumer
的封装, 但发现还是用 Provider 的封装. 一个项目成千上万的小组件使用了 connect, 那就会有成千上万个 Provider
, 逻辑岂不很混乱?
现在的实现(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} />
}
1
FaiChou OP const compose = (...funcs) =>
funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg) 问题 2, 难道是为了 functional programming ? |
2
7anshuai 2022-02-19 11:20:41 +08:00 2
|
3
nondanee 2022-02-19 13:31:11 +08:00 2
可能是为了方便用装饰器?
``` @connect(mapStateToProps, mapDispatchtoProps...) class MyComponent extends PureComponent { render () {} } ``` |
4
maplelin 2022-02-19 16:57:47 +08:00
@nondanee #3 这个肯定不是,只是 HOC 的用法就是这样,connect 其实可以理解成返回一个函数式的 HOC ,说白了里面闭包的部分就是一个组件了,而不是单纯的 function ,可以用 connect 返回的那个 function 包装出多个不同 UI 的组件,比单纯的 return component 复用性高不少
|
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 设计的人了 |
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} />; }; } |
7
FaiChou OP @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> ); }; } |
8
otakustay 2022-02-19 23:54:50 +08:00
@FaiChou #7 没有 Provider 就拿不到 store 啊,store 是用户创建的,redux 不知道在哪里。就算我的那个简单实现,useSelector 和 useDispatch 的实现里面也是通过 useContext 拿到 store ,再用 useSyncExternalStore 监听 store 的
|
9
FaiChou OP @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> 吗? |
10
FaiChou OP @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 |
11
aec4d 2022-02-24 16:35:58 +08:00
|