hwen / blogsome

something right here
https://github.com/hwen/blogsome/issues
39 stars 7 forks source link

React组件状态朝花夕拾 #5

Open hwen opened 6 years ago

hwen commented 6 years ago

原文发于我的博客:https://github.com/hwen/blogsome/issues/5

最近从vue转到react,遇到了一个问题。

场景:

react-1

如图,有一个组件A,它接受一个从父级页面传过来的 dataList。而父级页面X里的 dataList 是 redux store 的数据。dataList 的数据会通过一个接口获取返回,也就是 dataList 是异步获取的。

组件A需要根据 dataList 来动态展示数据,用户与组件A交互后,变化的数据,希望回传给页面X

保证组件的纯净

大家可以看看 ElementUIAnt Design React 的源码,可以发现子组件跟父组件的数据交互,都是通过回调事件(vue 里是 $emit),将更改后的数据返回给父组件。例如,antdtimepicker 组件,通过组件传进来的 onChange 事件,获取到所选择的时间。

这样我们所封装的组件就是纯净的,没跟父组件耦合,可以用在任何地方。

考虑到上面的场景,用 antd 的方式来实现,就是图中的方式的方案1

1.页面X通过props把dataList和回调事件传到onChange传到组件A,这里页面X的dataList,是 connect 到 redux store 的。 2.组件A通过 componentWillReceiveProps(17+ 用getDerivedStateFromProps)将 props dataList 同步到自己的 state 里。因为组件A不能直接更改props。 3.组件A交互后,更新自己的 state,更新展示,然后调用传进来的回调事件,并把更改后的回调数据作为参数 4.页面X获得更改后的数据

产生的问题

目前为止看起来还行。不过,假设页面X里面,还有一个组件B组件B也需要dataList作为展示,所以当dataList的数据发生改变时,它的UI也要同步改变。

为了要将组件A产生的更改也反馈到其他组件,所以我们就需要将组件A回调回来的数据,通过 action 来更新 redux store 了。那么,

5.页面X获得数据后,通过某个 action 更新了 redux store 的数据,这样组件B也得到了更新

问题在于,页面X更新了 redux store 后,更新会重复反馈到了组件A组件A又经过componentWillReceiveProps获取到了新的props。但其实组件A现在的state已经是新的了,所以事实上并不会真正更改。

那么,既然这样,我还不如直接将组件A,和组件B 直接 connect 到 redux store,这样页面X压根就不用传什么数据到组件A组件B了。组件A直接暗箱操作即可。

不再纯洁的组件A

回头来说,如果我们直接把各种组件都直接 connenctredux store,整件事的确就变得简单很多了。但这样组件A组件B,组件xxx就变得不再纯洁了,它们依赖于应用的顶层状态(redux store),脱离了这个应用,它们是无法使用的。

而反过来看 ElementUIAnt Design React 的组件不会跟任何应用状态有耦合,才使得它的组件可以被用到各个系统。

似乎走到死胡同?

我们再来看看 redux 的思想,

在 Redux Store 中管理关系数据或嵌套数据的推荐做法是将这一部分视为数据库,并且将数据按范式化存储。

也就是说,redux是建议我们将组件直接connect到redux store的。因为是数据库嘛,所以你需要什么数据就查什么数据,你需要做更改就改数据库的。

事实上这种做法是非常有好处的。像上面“产生的问题”部分提到的问题就可以迎刃而解。而且,对于大型应用,有很多组件嵌套组件,里面再嵌套,再嵌套,这种多重嵌套的情况。像这种情况,如果你不用redux,难道要一层一层把更改的数据一层层回调上去??

想想就可怕好吧。这也就是为什么redux这种状态管理库出现的必要。

那么,所有组件都connect到redux,这么,组件复用性就相对很差了。

所以这两者之间需要权衡利弊。

结论是

如果是底层的组件,抽象层度较高的组件,应该用类似 antd 方式来实现这个组件。那么什么情况下,需要封装这类组件?

判断的基点在于这个组件是否跟业务的关联性,如果跟业务没什么关联,而且复用场景多,就很合适用 antd 的方式来封装了。

除此之外,其他应该都 connect 到 redux,统一来管理状态。