React专题:组件

React专题:组件

本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出
作者 GitHub repo 阅读完整 的专题文章
作者 个人博客 获得无与伦比的阅读体验

刀耕火种时期的前端,HTML描述页面结构,CSS描述样式,JavaScript描述功能。它们彼此是分离的。

然而这种方式却满足不了开发者对代码复用的需求。

近几年各大前端框架做了很多探索,其中组件化就是最璀璨的成果之一。

一个组件就是一个功能模块,所有的前端元素都封装在组件内部,对外只暴露有限的接口。这样开发者拿来就能用,通过接口与组件交互而不必知道组件的内部细节。

而React是前端框架里面组件化思想贯彻的最彻底的。

class组件和函数组件

在React中,构造一个组件既可以用class类,也可以仅用一个普通函数。

两者有什么区别呢?

class类不仅允许内部状态的存在,还有完整的生命周期钩子。

这让一个组件变的有生命力而且可以管理。

代价就是它的性能稍逊。

还有一点,class类组件必须继承React内置的组件类。因为那些生命周期钩子都是继承自React内置的组件类。

那你说,我不用生命周期钩子,可不可以不继承呢?

不可以,因为你必须要用生命周期钩子。

在一个class类组件中,render方法是必须的,没有了它就不可能返回UI,也就不能称其为一个组件。而render方法就是生命周期钩子之一。

函数组件没有办法实例化,除了一些逻辑判断之外,它的功能只是返回UI。

所以函数组件常常被称为无状态组件。

然而这正是React强大之处,它构建组件可以如此随意,只需要一个普通函数返回一段JSX元素。在React中,有的时候函数和组件的界限会非常模糊。

有一种设计模式把React组件分为容器组件和展示组件,容器组件管理数据、状态和业务逻辑,展示组件仅仅负责接收props展示UI。

所以函数组件的使命肯定是做好展示组件咯。

那么在React中函数和组件的区别到底在哪?

组件必须返回一段JSX元素,class类组件和函数组件都一样。

Component和PureComponent

前面说到class类组件有完整的生命周期钩子。这些生命周期钩子是从哪来的呢?毕竟class类组件就是原生的class类写法。

其实React内置了一个Component类,生命周期钩子都是从它这里来的,麻烦的地方就是每次都要继承。

PureComponent类又是干嘛用的?

凭名字猜测,它是一个纯纯的组件类。

怎么个纯法子?

它自动帮开发者做了一些优化工作,使得组件看起来更加纯粹。

而组件性能优化的主要手段就是通过shouldComponentUpdate生命周期钩子。

我们来看看它是如何自动优化的。

从下面的例子看,React专门有一个方法来判断组件该不该更新。如果typeof instance.shouldComponentUpdate === 'function',那这就是一个继承了Component类的组件,直接执行shouldComponentUpdate,返回true则更新,返回false则不更新。

如果ctor.prototype.isPureReactComponent,那这就是一个继承了PureComponent类的组件,这时React会将oldProps和newProps做一层浅比较,同时将oldStatenewState做一层浅比较,只要有一个浅比较不相等,则返回true更新,否则返回false不更新。

shallowEqual又干了什么呢?

  • 首先用Object.is判断是否相等(React自己写的polyfill)。
  • 如果Object.is判断不相等,再作引用类型的比较,比较属性key的长度,比较属性key的一一对应,比较属性value的引用。

总的来说,它只做了一层比较,所以才叫做浅比较。

聪明的你肯定要问了:为什么不递归呢(并发射一个傲娇脸)?

因为递归的深比较非常耗费性能。PureComponent类只是帮开发者适度优化性能,它还是要找到成本与收益的平衡点的。

PureComponent类有其正确打开方式

例如像数组这样的数据结构,在不改变引用的情况下,使用数组的方法操作数组,然后再setState该数组,组件是不会更新的。

你说不对呀,老数组和新数组虽然是同一个引用,但是长度不一样了,浅比较是能识别出来的呀。

我们以上面的例子来看,pop方法会修改老数组,所以此时老数组和新数组是一模一样的,引用一样,长度一样。

React拿着这两份数据做浅比较,肯定返回true。

这就是会误导开发者的地方。

正确的做法是永远不修改原数据,生成新数据时依赖于原数据的浅拷贝,避免新数据和老数据指向同一个引用。

当然还有一种情况,采用传入时定义的方式给子组件传递props。

结果就是子组件做props的浅比较时永远返回false,因为每次的值都是重新定义的,绝非同一个引用。

PureComponent类就失去它的意义了。

总之,要想使用PureComponent类,得时刻盯紧引用数据类型。

在继承了PureComponent类的组件里写shouldComponentUpdate生命周期钩子会怎么样?

小伙子果然骨骼清奇。

原则上,React并不推荐这种写法,并且在开发环境下会打印一个警告。

你要么偷个懒让React帮你做优化,要么自己做优化,这么干不是赤裸裸的挑衅么!

不过React早就预测到你会这么干的,所以它只能随你的脾气,只要组件里定义了shouldComponentUpdate生命周期钩子,PureComponent类的自动优化就不再起作用了。

组件复用

话说代码复用还真不是因为开发者懒。

假如一个蓝色卡片组件,分别有十个地方要用到。而且这个开发者异常勤奋,把这段代码小心翼翼的复制到这十个地方。看起来大功告成了。但是有一天,产品经理说:“我希望卡片背景换成柠檬色,再删减掉一些信息。”

那么问题来了:该开发者是应该砍死产品经理还是应该考虑代码复用?

React世界中一切都是组件,组件思想的根本诉求又是什么呢?当然是代码复用。一个组件就好像一个集装箱,我不用知道里面装的什么货物,我只用知道它能上我的货轮。

集装箱是商业世界的伟大发明,组件也是前端世界的伟大发明。

React的代码复用都是以组件为单位的。

组件四海为家

最普通的组件复用的方式就是直接使用组件。

一个<Card />组件,我可以将它放在任何地方。它对外可以提供一些接口,以保证填充所在上下文要求的内容。开发者可以很方便的一处修改,处处生效。

高阶组件

首先我们回忆一下,什么是高阶函数?

定义非常简单:一个函数,它的参数是函数,或者它的返回值是函数。

基本就是鸡吃鸡或者鸡生鸡的意思。

那么高阶组件就好理解了。它的定义是:一个函数,传入一个组件,并且返回一个组件。

区别就是,高阶函数只要满足一个条件,高阶组件要满足两个条件。

高阶组件既是函数,也是组件,因为在React中一个函数只要返回JSX元素就是组件。但这个组件有点特殊,因为它不是用来堆砌UI的,而是一个组件装饰工厂。
话不多说,我们直接来看一下react-redux中的connect方法(极度简化的伪代码)。

connect方法是用来将redux中的state作为props传给组件的。这就是一个典型的高阶组件,当然它还在外面套了一层函数,为了传值。

不使用connect方法的组件是这样导出的:export default App;

使用connect方法的组件是这样导出的:export default connect(mapState, mapDispatch)(App);

开发者首先传参执行connect,然后再传组件执行返回的函数,导出的组件就多了一些属性。

可以看出,这种类型的高阶组件主要是为了给目标组件传递一些props。

说高阶组件是一个组件装饰工厂是有道理的,因为我们可以用装饰器的写法来部署高阶组件。

下面是connect方法的装饰器写法。

另一种类型的高阶组件更加巧妙,它使用到了继承的特性。

返回的组件可以获得传入组件的所有属性和方法,如果复用组件需要对传入组件做一些侵入性比较强的改动,那么这种方式非常具有灵活性。

理解高阶组件的精髓是什么呢?我认为是理解复用的到底是什么。

假如我们造一个组件,然后用到不同的地方,那直接用就好了,组件本身就是可复用的。

关键在于组件之外的东西。我们需要重复的给一个组件传入props,或者我们需要重复的给组件增强一些功能。这些都可以理解为组件的装饰,高阶组件其实解决的是组件装饰的复用。

大多数时候,使用高阶组件的场景都有更简单的替代方案。

高阶组件之所以这么流行,是因为React生态系统中很多库都有高阶组件的身影,它们或许是为了提供一个优雅的API,或许是需要处理复杂的场景,或许是...炫技。
如果哪天你复用组件时产生了瓶颈,不妨来看看老朋友高阶组件,它有多强大完全取决于你。

忘了告诉你们,高阶组件简称HOC,高阶组件的第一种类型叫属性代理,第二种类型叫反向继承。不过让这些概念都见鬼去吧,它只会让初学者望而却步。

render props

高阶组件就是传入一个组件,然后返回一个组件。

那我能不能不通过函数的参数传递组件,比如说通过props传递呢?

这是一个绝好的思路。

它有一个名字叫render props

当然,render props也需要一个用来复用的容器。通过给这个容器传递render属性,它可以渲染不同的组件。

属性名是无所谓的,render或者rander都可以,关键它的值得是一个组件,或者说无状态组件。

react-router中的withRouter方法就是用的render props

评论 ( 0 )
最新评论
暂无评论

赶紧努力消灭 0 回复