最近在学 react,遇到一个问题请教一下,假如组件都是用 function 定义的,如果页面的结构是这样的:

![component]( https://i.imgur.com/ASSaPMs.png)

我想在 toolbar 中的一个 button 点击事件中获取 Grid 控件的值,该用什么方法实现呢。
按照以往非组件化的思路应该是直接获取到这个 grid 对象,在调用对应的 getValue 方法就可以了。

虽然组件支持传递回调函数,在 grid 内部,state 变化时调用回调函数,但是这样的话回调函数就要在 page 里面从 layout 传到 grid ,感觉这种思路太不符合直觉了,因为 layout 和 some 组件根本就不应改有 callback 的 prop ,如果 some 下面有 5 个组件,那就要传递 5 个 callback ,太可怕了吧,并且多加一个组件就要去修改 some 的代码也是不对的。

后段 coder 最近在学前端,没用 react 做过项目,不知道遇到这种情况改怎么解决,有什么好的方法或者 lib 能够优雅的解决这个问题么。
举报· 1013 次点击
登录 注册 站外分享
44 条回复  
shunia 小成 2024-4-16 11:42:29
这图都画的这么详细了,一眼看过去就应该知道接下来只能再在外面加一个数据层来进行数据管理啊?毕竟组件和组件之间的关系已经是完备的了。

用 context 确实会限制数据只能在 react 组件之间流转,但是你也没意识到一个问题,当你选择了用 react 去实现一个组件,它就已经不再是一个”通用“组件了,它本身就只能在 react 框架里流转。

如果你能把 Grid 实现为一个独立的组件,那你就必然要为它设计 API 用来向外传递数据,也就意味着你使用 Grid 的地方一定要能捕获 Grid 的输出并且把它集成到你的 App 的数据流里(如果你需要的话),也就意味着你的 App 必须要有一个数据层。此时使用 context 是完全合理的。

如果你还是觉得生理不适,必须要使用一个无依赖的数据层,react 生态里很多这种东西,比如 zustand ,但是我觉得增加的额外心智负担其实不如使用 context 。

最后再说一下那个鼓吹 Angular 的,包括说 service+rxjs 的,其实和 context 是一个性质的东西甚至更为不如。react 好歹数据层的生命周期管理的让人毫无心智负担,rxjs 则难以管理而且还要显式的销毁,否则会引发内存泄漏。service+rxjs 又完全不独立,和代码是紧密关联的,也并不符合 OP 的要求。就试问一下使用 Angular 实现和使用 React 实现,代码路径有什么区别?独立一个状态管理类,所有组件从中直接或者间接进行引用,还能有其他方法?而且早期的 Angular 没有 Inject 注解,甚至要在构造函数里传递 service 实例(至今官方示例代码依然优先采用这种写法),一旦遇到需要继承的情况就构造函数爆炸,蠢的很。而且直到最新的 standalone 组件之前,所有组件和 service 之类的都要显式的声明依赖和引用关系,傻的爆炸。组件编译出来默认 Shadow Dom alike 无法直接外部干涉,还需要每个组件显式声明不采用 Shadow Dom ,无语到让人喊娘。

最最后再提一下,脱藕 html/dom 和脱藕 React 组件不是一码事,不要想着希望自己最终产生的 html/dom 是脱藕的,就期盼着 JS/TS 代码内部也完全脱藕,不存在这种可能性呀,毕竟你的 App 内部总是有关联的,试论哪种解构方式都无法脱离耦合,只是高低的区别而已。
Lesenelir 小成 2024-4-15 23:51:07
@hahaFck 我帮他回答吧。

1. 是的。用一个单独以 .ts 结尾的文件定义 atoms ,每一个 atom 本质都是一个对象。如果你组件树只有一个顶层的 Provider ,我觉得是可以理解为全局的单例。

2. 首先在 useEffect 去通过 url 来请求数据本身就不是一个很好的 pattern 。其次,你最后的理解是错的。如果你在 Grid 组件中 使用了 useAtom(dataAtom) 后,请求并更新了数据,Page 是否更新取决于 「 Page 组件是否使用了你当前的 atom ,即 Page 组件中是否有 useAtom(dataAtom) useAtomValue(dataAtom) useSetAtom(dataAtom) 中的任意一个」。如果 Page 组件中使用了你的 atom ,则会触发你 Page 组件的 re-render ,而又因为你的 Page 组件在顶层,所以你 Page 组件下的所有子组件都会 re-render 。但如果你的 Page 组件中没有使用这个 atom ,那就不会触发 Page 组件 re-render 。具体的原因你可以看: https://jotai.org/docs/guides/core-internals#first-fersion  

3. useAtom 本质是一个自定义 hooks ,它内部有 useState 进行处理。你可以理解为 data 是 grid 里的 data ,但也只是值是相同的,因为它其实是属于组件内的 state 。你要知道共享的只是 状态值,但状态是没有共享的,还是由组件内部维护的。

如果你对 jotai 感兴趣,可以我看的一篇博客: https://lesenelir.me/posts/jotai
monster1priest 小成 2024-4-15 22:58:23
1. useContext 共享全局变量
2. useImperativeHandle 层层上传,获取 Grid 对象
3. callback 层层向下传

React 由于历史包袱的原因,在一些语言设计上是有问题的,一般会更推荐使用现代状态管理库,比如 Redux ,Jotai 等等。

初学前端的话,我推荐你使用第一种 useContext ,先试一试,推荐去找个教程看看,可以把 Context 部分抽象成一个 Provider 组件。
jinliming2 小成 2024-4-15 20:15:03
子组件获取祖先组件的数据,这个逻辑没有问题,props/context 往下传都行。子组件存在的前提肯定是所有祖先组件都挂载存在了。
但是祖先组件获取子组件的数据、兄弟组件之间获取数据,这个有一个问题是,你要获取数据的那个组件可能不存在。
而你现在这个问题,综合了获取兄弟组件、子组件数据的情况。
你提到了“按照以往非组件化的思路应该是直接获取到这个 grid 对象,在调用对应的 getValue 方法就可以了”,这个在 React 中对应的就是 ref ,ref 上暴露 getValue 方法就是你说的这个了。函数组件没有对象实例可以用 React 提供的 forwardRef 。但是你也得要考虑一个事情是,这个实例引用变量存放在哪里,怎么去获取。因为这个 ref 的持有人默认只有挂载这个子组件的那个父组件,你又得要想办法把这个 ref 传给祖先、兄弟。
所以,这个 ref 的最佳存放位置就是共同祖先上,然后通过 context 往下传。但既然到了共同祖先这一步,那么就不要存 ref 了嘛,直接存数据就好?这就是楼上提到的数据存在共同祖先上,然后下面用 context 来读写数据。
祖先上不管是存数据还是存 ref 都是有自己的实际场景的。如果要调用对应组件的 API ,就还是得存 ref 。
然后另一个方案,全局状态,这个实际上也是把数据存在祖先上,只不过是存在根祖先上,子孙组件通过封装过的 context 读写数据。

不管是用全局状态还是自己写 context ,本质上都是数据存在祖先上,你在读取的时候不需要关心目标组件是否已经挂载存在,没挂载存在的话,你读到的就是个默认值。

或者楼上也有提到全局的通知广播,但这个一旦滥用就不好控制了。React 18 里有个 API ,useSyncExternalStore ,实际上也可以实现跨组件的共享,因为本质上数据是脱离 React 存在的,一个 store 实例,一个组件更新,一个组件监听,相当于一个小型的受限的广播系统,会比全局的广播好一些。
superedlimited 小成 2024-4-15 19:20:48
react 是种哲学,学习 declarative ui ,一定要先摒弃 imperative ui 的 get value 、set text 等等思想。数据在 react 中是单向流动的,就像水一样,如果上游发生了变化,那么自然而然,下流必然发生变化。
madao199 小成 2024-4-15 19:10:16
可以用 ref useimperativehandle 把组件的状态暴露出来 向下渗透 ref 也能拿到值
ivslyyy 小成 2024-4-15 18:08:30
自定义一个 hooks ,在其中用 useState ,
可以在其他组件中使用 hook ,
RRRSSS 小成 2024-4-15 17:19:04
一定不要一层一层传,根本难以维护,更没有意义,这么多层写出来的一定是冗余代码。

想要在 Grid 和 Page 之间状态共享,使用 zustand / jotai / redux 这种全局状态库就行了,写出来代码都差不多(这里以 jotai 为例):

// 定义 atom
export const dataAtom = atom('')

// Grid ,想要使用 `data` 或 设置 `data` 值都可以
const [data, setDataA] = useAtom(dataAtom)

// 同理在 Page 也是一样的:
const [data, setDataA] = useAtom(dataAtom)

这样写,省去了一层一层的 props ,代码简单多了。更重要的是:你只要知道你在组件里需要什么 atom ,然后使用就行了,没有心智负担。hook 本来就是干这事的。

另外,这样的需求,不建议使用 context 。实际上,我在任何情况下都不建议别人使用 context 来做业务代码,context 最常见的是场景其实是封装组件。
ZGame 初学 2024-4-15 16:55:29
是我的话我可能会提 5 个 gridRef 出来, gridRef.getRow?.[0],比较符合直觉
myl0204 小成 2024-4-15 16:37:36
很多人说到了,状态提升。

“我想在 toolbar 中的一个 button 点击事件中获取 Grid 控件的值”

可以了解下受控组件的概念: https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components
12345下一页
返回顶部