blogsome
blogsome copied to clipboard
React组件状态朝花夕拾
原文发于我的博客:https://github.com/hwen/blogsome/issues/5
最近从vue
转到react
,遇到了一个问题。
场景:
如图,有一个组件A
,它接受一个从父级页面传过来的 dataList。而父级页面X
里的 dataList 是 redux store 的数据。dataList 的数据会通过一个接口获取返回,也就是 dataList 是异步获取的。
组件A
需要根据 dataList 来动态展示数据,用户与组件A
交互后,变化的数据,希望回传给页面X
。
保证组件的纯净
大家可以看看 ElementUI
和 Ant Design React
的源码,可以发现子组件跟父组件的数据交互,都是通过回调事件(vue 里是 $emit),将更改后的数据返回给父组件。例如,antd
的 timepicker
组件,通过组件传进来的 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
回头来说,如果我们直接把各种组件都直接 connenct
到 redux store
,整件事的确就变得简单很多了。但这样组件A
,组件B
,组件xxx就变得不再纯洁了,它们依赖于应用的顶层状态(redux store),脱离了这个应用,它们是无法使用的。
而反过来看 ElementUI
和 Ant Design React
的组件不会跟任何应用状态有耦合,才使得它的组件可以被用到各个系统。
似乎走到死胡同?
我们再来看看 redux 的思想,
在 Redux Store 中管理关系数据或嵌套数据的推荐做法是将这一部分视为数据库,并且将数据按范式化存储。
也就是说,redux是建议我们将组件直接connect到redux store的。因为是数据库嘛,所以你需要什么数据就查什么数据,你需要做更改就改数据库的。
事实上这种做法是非常有好处的。像上面“产生的问题”部分提到的问题就可以迎刃而解。而且,对于大型应用,有很多组件嵌套组件,里面再嵌套,再嵌套,这种多重嵌套的情况。像这种情况,如果你不用redux,难道要一层一层把更改的数据一层层回调上去??
想想就可怕好吧。这也就是为什么redux这种状态管理库出现的必要。
那么,所有组件都connect到redux,这么,组件复用性就相对很差了。
所以这两者之间需要权衡利弊。
结论是
如果是底层的组件,抽象层度较高的组件,应该用类似 antd 方式来实现这个组件。那么什么情况下,需要封装这类组件?
判断的基点在于这个组件是否跟业务的关联性,如果跟业务没什么关联,而且复用场景多,就很合适用 antd 的方式来封装了。
除此之外,其他应该都 connect 到 redux,统一来管理状态。