44 条回复  ·  1009 次点击
superedlimited 小成 2024-4-15 19:20:48
react 是种哲学,学习 declarative ui ,一定要先摒弃 imperative ui 的 get value 、set text 等等思想。数据在 react 中是单向流动的,就像水一样,如果上游发生了变化,那么自然而然,下流必然发生变化。
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 实例,一个组件更新,一个组件监听,相当于一个小型的受限的广播系统,会比全局的广播好一些。
monster1priest 小成 2024-4-15 22:58:23
1. useContext 共享全局变量
2. useImperativeHandle 层层上传,获取 Grid 对象
3. callback 层层向下传

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

初学前端的话,我推荐你使用第一种 useContext ,先试一试,推荐去找个教程看看,可以把 Context 部分抽象成一个 Provider 组件。
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
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 内部总是有关联的,试论哪种解构方式都无法脱离耦合,只是高低的区别而已。
12345
返回顶部