为什么 React 推崇 HOC 和组合的方式,而不是继承的方式来扩展组件?

React 组件本身是 class 的写法,为什么不直接用继承的方式给组件添加行为,而推荐使用 HOC 和组合的方式?总感觉是 OOP 和 FP 混着用,有点别扭。
关注者
85
被浏览
3951

OOP和FP并不矛盾,所以混着用没毛病,很多基于FP思想的库也需要OOP来搭建。

为什么React推崇HOC和组合的方式,我的理解是React希望组件是按照最小可用的思想来进行封装的,理想的说,就是一个组件只做一件的事情,且把它做好,DRY。在OOP原则,这叫单一职责原则。如果要对组件增强,首先应该先思路这个增强的组件需要用到哪些功能,这些功能由哪些组件提供,然后把这些组件组合起来

D => f(A, B, C)

D中A相关的功能交由D内部的A来负责,D中B相关的功能交由D内部的B来负责,D仅仅负责维护A,B,C的关系,另外也可以额外提供增加项,实现组件的增强。

继承有什么不好,注意,React只是推荐,但没限制。其实用继承来扩展组件也没问题,而且也存在这样的场景。比如:有一个按钮组件,仅仅是对<button />进行一个包装,我们且叫它Button,可是,按照产品需求,很多地方的按钮都是带着一个icon的,我们需要提供一个IconButton。这是时候,就可以通过继承来扩展,同时组合另外一个独立的组件,我们且叫它Icon,显示icon的功能交给Icon组件来做,原来按钮的功能继续延续着。对于这种同类型组件的扩展,我认为用继承的方式是没关系的,灵活性,复用性还在。

但是,用继承的方式扩展前,要先思考,新组件是否与被继承的组件是不是同一类型的,同一类职责的。如果是,可以继承,如果不是,那么就用组合。怎么定义同一类呢,回到上面的Button的例子,所谓同一类,就是说,我直接用IconButton直接替换掉Button,不去改动其他代码,页面依然可以正常渲染,功能可以正常使用,就可以认为是同一类的,在OOP中,这叫做里氏替换原则

继承会带来什么问题,以我的实践经验,过渡使用继承,虽然给编码带来便利,但容易导致代码失控,组件膨胀,降低组件的复用性。比如:有一个列表组件,叫它ListView吧,可以上下滚动显示一个item集,突然有一天需求变了,PM说,我要这个ListView能像iOS那样有个回弹效果。好,用继承对这个ListView进行扩展,加入了回弹效果,任务closed。第二天PM找上门来了,希望所有上下滚动的地方都可以支持回弹效果,这时候就懵逼啦,怎么办?把ListView中回弹效果的代码copy一遍?这就和DRY原则相悖了不是,而且有可能受到其他地方代码的影响,处理回弹效果略有不同,要是有一天PM希望对这个回弹效果做升级,那就有得改啦。应对这种场景,最好的办法是啥?用组合,封装一个带回弹效果的Scroller,ListView看成是Scroller和item容器组件的组合,其他地方需要要用到滚动的,直接套一个Scroller,以后不管回弹效果怎么变,我只要维护这个Scroller就好了。当然,最理想的,把回弹效果也做成一个组件SpringBackEffect,从Scroller分离出来,这样,需要用回弹效果的地方就加上SpringBackEffect组件就好了,这就是为什么组合优先于继承的原因。

页面简单的时候,组合也好,继承也罢,可维护就好,能够快速的响应需求迭代就好,用什么方式实现到无所谓。但如果是一个大项目,页面用到很多组件,或者是团队多人共同维护的话,就要考虑协作中可能存在的矛盾,然后通过一定约束来闭坑。组合的方式是可以保证组件具有充分的复用性,灵活度,遵守DRY原则的其中一种实践

以上是我个人观点,如有不足和错误的地方,欢迎斧正。