前言
自从 2013 年 React
开源以来,对于 React
组件的探讨层出不穷。一些组件类型在倒退中逐步淘汰,而另一些组件类型和组件设计模式逐步积淀下来,并演变为约定成俗的为 React
应用程序规范。本文将对 React
中组件类型进行一个梳理总结。旨在让 React
开发者清晰地理解 React
组件类型。读完本文后,你该当可能从 React
程序与 React
技术文章中分辨出不同类型的 React
组件,并能在设计 React
利用时抉择适合类型的组件。
咱们将在下文由浅入深的介绍以下 React
组件类型:
- 受控组件与非受控组件
- 托管组价与非托管组件
- 状态组件与无状态组件
- 容器组件与展现组件
- 高阶组件与渲染属性
看到一堆组件名称,刚上手 React
的同学可能霎时就头大了。其实,察看这些组件名称会发现它们是一对对的,了解一个组件即了解了另一个组件。另一方面,有一部分的组件并不罕用,理解一下只是为了查漏补缺。上面来别离讲一下这几种组件类型:
一、受控组件与非受控组件
首先,厘清一个概念。React 中受控组件与非受控组件是针对表单元素而言。
1. 受控组件
在 HTML 中,像
<input>
<textarea>
<select>
这类表单元素会维持本身状态,并依据用户输出进行更新。但在 React 中,可变的状态通常保留在组件的状态属性state
中,并且只能通过setState()
办法更新。相应的,其值由 React 管制的输出表单元素称为“受控组件”。
上述这两句话是 React 官网文档对于受控组件的介绍,咱们先来看看 React
中的受控组件长什么样子
上方代码中,input
元素数据依赖 state.userName
。每次按下键盘都会触发 onChange
事件去更新 state
,state
更新触发 input
的 value
值变动。有一点须要留神的是:在受控组件中,如果没有给表单元素绑定 onChange
事件。将会收到 React
的正告。此时,输入框除了默认值是无奈输出任何值的。
有人看到这可能会纳闷,这不就是最一般的 React
组件书写形式吗?没错,抛开表单元素受控组件这个概念,大家对这种写书形式也都司空见惯了,实质上都是通过 React
自上而下的单向数据流,通过 state
来更新 UI。
总结 React
受控组件的特色:
- 特指表单元素。
- 表单元素数据依赖状态。
- 受控组件必须要在表单元素上应用
onChange
事件来绑定对应的事件。 - 表单元素的批改会实时映射到状态值上。
- 受控组件只有继承
React.Component
才会有状态。
2. 非受控组件
与受控组件正相反,非受控组件即不受状态的管制,通过虚构 DOM 来获取数据。
总结 Reac 非受控组件的特色:
- 特指表单元素。
- 表单元素数据存储在
DOM
中。 - 通过虚构
DOM
的形式获取表单元素数据。
3. 特点与区别
受控组件与非受控组件的特点
- 均特指表单元素
受控组件与非受控组件的区别
- 数据存储形式不同,受控组件的数据存储在
state
中,非受控组件的数据存储在DOM
中。 - 扭转数据形式不同,受控组件通过事件回调扭转
state
数据,非受控组件通过DOM
扭转数据。 - 获取数据形式同步。受控组件通过拜访
state
获取数据,非受控组件通过拜访虚构DOM
获取数据。
置信通过上述代码示例,大家曾经齐全了解什么是受控组件与非受控组件了。在 React
程序开发过程中,大家借助于 ant-desing
、element-ui
等优良的 UI 组件库,间接编写受控组件与非受控组件的业务场景并不多。对这两种组件类型,大家了解基本概念即可。
二、托管组件与非托管组件
随着 React 程序性能的减少,开发者会遇到一个问题。随着组件数量减少,组件之间如何通信?React 官网给出的解决办法: 状态晋升(Lifting State Up)。将多个子组件的状态提取到独特的父级组件中,而子组件自身不具备状态。
1. 托管组件
托管组件,顾名思义,将数据委托给其它组件管制。上面的例子中 <InputLeft />
与 <InputRight />
组件自身不解决用户输出的数据,而是将数据托管给父组件 <Client />
下面的例子中 <InputLeft />
与 <InputRight />
自身不解决用户输入的数据,而是委托给父组件 <Client />
来解决。在没有应用 Mobx
、Redux
等状态治理的 React
我的项目中。咱们通常会采纳状态晋升 (lifting-state-up) 的形式,把所有的数据委托给一个雷同的父组件,由父组件来控制数据与逻辑。这种把数据托管给其它组件管制的组件,称之为托管组件。
此时,回忆 ant-design
的<Input />
组件是如何设计的,其 Input
组件既能够作为托管组件也能够作为非托管组件,如果存在 Input.props.value
则把该组件作为托管组件,如果没有 props.value
则作为非托管组件。咱们来 j 简略实现一遍 Antd.Input
的设计
2. 非托管组件
非托管组件的概念比拟基蛋,即组件通过领有状态解决本身的数据。上面有个输入框组件<BaseInput/>
,该组件监听用户的输出,并存储在本身的 state
,通过 state
扭转来变动 UI。这也是 React
最根本的组件模式。
3. 特点与区别
特点:受控组件与非受控组件以外的组件均是托管组件或非托管组件
区别:托管组件将数据托管给其它组件的状态管制。而非托管组件通过本身状态自行治理。
三、状态组件与无状态组件
- 无状态组件(Stateless Component)
在下面中的托管组件中,咱们理解到一个组件能够不须要领有本人的状态,而将本人须要数据托管给其它的组件。React
程序中,无状态组件的特色比拟显著,概念也比较简单,没有本身的状态的组件都能够称之为无状态组件。
无状态组件有什么用途呢,咱们将在下文的展现组件中介绍。而无状态组件通常由两种写法:
1.1 无状态类组件 Stateless Class Component: SCC
export default class StatelessClassComponent extends React.Component{render() {return( <div> 用户名: <input type=”text” value={this.props.value} onChange={this.props.onChange} /> </div> ) } }
1.2 无状态函数组件 Stateless Function Component:SFC
const StatelessFunctionComponent = (props) => (<div> 用户名: <input type=”text” value={props.value} onChange={props.onChange} /> </div> ); export default StatelessFunctionComponent
下面这两种组件均是无状态组件,SCC
与 SFC
这两种无状态组件类型的区别:
- 在
SCC
中依然能够拜访React
的生命周期,如ComponentWillMount
、ComponentDidMount
等 - 在
SFC
中无法访问React
的申明周期 SFC
在渲染性能上要显著高于SCC
组件。
在目前的 React
利用中,无状态组件更多采纳第二种写法 SFC
。但在一些对生命周期钩子有非凡需要的场景中,你同样能够应用第一种无状态组件 SCC
。
2. 状态组件
与无状态组件绝对应,状态组件会带有 state 用以解决业务逻辑、UI 逻辑、数据处理等。通常还会调用 React 的生命周期函数,用以在非凡的时刻管制状态。
class StatefulComponent extends Component {constructor(props) {super(props); this.state = {// 定义状态} } componentWillMount() { //do something} componentDidMount() { //do something} … // 其余生命周期 render() { return ( //render); } }
- 状态组件与无状态组件区别
- 在组件写法上,两者区别为组件自身是否具备状态。
- 在业务场景上,状态组件、无状态组件与托管组件、非托管组件相似。状态组件通常作为托管组件,治理数据、解决业务逻辑与 UI 逻辑,而无状态组件往往是非托管组件,将本身的 UI 逻辑委托给状态组件来解决。
四、高阶组件与渲染属性
上述介绍的三类 React 中最根本的组件类型。在 React 应用程序的倒退与设计历程中,为了进步组件的可复用性,社区与官网逐步提出了一些组件设计模式,咱们在本文中也将之纳入组件类型中。
1. 高阶组件(HOC)
高阶组件(HOC)是 React 中用于重用组件逻辑的高级技术。HOC 自身不是 React API 的一部分。它们是从 React 构思实质中浮现进去的一种模式。具体来说,高阶组件是一个函数,可能承受一个组件并返回一个新的组件。
上述是 React 官网对高阶组件的形容,对于如何编写高阶组件以及注意事项,官网给出了具体阐明,在本文中咱们不再介绍。咱们以一个简略的高阶组件例子,来剖析高阶组件的特点
如果,咱们有一个需要在鼠标挪动时,在界面上显示鼠标的坐标值。而且,这个性能可能会用到多个组件中,甚至不同的页面中。象征界面上显示数据坐标值的需要是固定的,但 UI 组件显示的形式可能是变动的。
这个例子中通过 showMouse 装璜的组件,会具备一个能力,领有鼠标的坐标数据。并且组件会领有一个新的 props.tag
与 props.mouse
。此时的 props.mouse
相当于 <WrapperComponent />
组件将数据托管给了 <Wrapper />
组件,而且 <WarppedComponent />
组件的 props
将齐全由 <Warpper />
组件管制,而不对外开放。因为高阶组件的低侵入性,showMouse 这个函数装璜过的组件都会领有数据坐标数据的能力。
值得一提的是,与高阶组件相匹配的另外一个杀手锏是 ES7 的装璜器模式,应用装璜器模式来搭配高阶组价可极大进步代码的可浏览性与高可复用性,最闻名的当属 Mobx
的 @observer
装璜器。
2. 渲染属性(Render Props)
术语 Render Props 是指一种技术,用于应用一个值为函数的 prop 在 React 组件之间的代码共享。
如果,咱们有一个需要在鼠标挪动时,在界面上显示鼠标的坐标值。而且,这个性能可能会用到多个组件中,甚至不同的页面中。象征界面上显示数据坐标值的需要是固定的,但 UI 组件显示的形式可能是变动的。
在 <MouseTrail />
组件中,其会追踪鼠标坐标地位。但没有决定如何显示这个坐标值。反而是将如何展现 UI 交给了调用者来管制。在前端开发工作中,UI 是高频次变动的。业务逻辑与业务数据反而是绝对稳固的。这种将展数据展现形式交由调用者来管制的形式,极大进步了 UI 的局部的高扩展性。在一些定制零碎的需要场景中,往往会内置几种零碎交互方式、UI 格调,而这种组件则是非常适合的组件类型。
值得一提的是,该组件类型的官网名称是 Render Props
,但并不意味着你肯定要通过一个 props.render
来实现。该组件的核心思想是:由调用者决定如何展现组件。现有的 React
程序中,该组件类型还有一种变体,写法如下:
- Render as Child
React
中的 this.props.children
API 自身也是个函数,因而,上述例子中应用 this.props.children
办法,同样将 UI 的展现形式交由了调用者来决定。
3. 特点与区别
高阶函数的特点:
- 接管一个组件,并返回一个组件
- 代理
props
,如上述例子中,<WrappedComponent/>
组件的props
齐全由 <Wrapper />
组件来管制。 - 低侵入性。
<showMouse />
函数为组件赋予了<mouse />
组件的数据,而并没有影响组件外部的逻辑。 - 领有反向继承能力。
渲染属性的特点:
- 自身不决定数据展现形式,将数据展现形式齐全交由调用者决定。
- 无侵入性。上述高阶函数的例子,因为组件被赋予
props.mouse
。因而组件在调用时不能再领有props.mouse
,因而,咱们说高阶函数是低侵入性,而不是无侵入性。但渲染属性回调形参的形式,决定了其不会有任何侵入性。
高阶函数与渲染属性的独特特点
- 均有解决逻辑复用的作用。
- 均通过逻辑拆散、组件拆分,组合调用的形式,实现代码复用,逻辑复用。
高阶函数与渲染属性的区别
- 实现形式的区别。高阶函数通过柯里化函数实现,渲染属性通过回调函数来实现。
- 调用形式的不同。高级函数通过函数调用、装璜器模式调用。渲染属性通过回调函数重写实现。
五、展现组件与容器组件
到目前为止,上述四种类型组件,能够从写法上、特定元素类型上,显著辨别进去。上面咱们要介绍两种组件,它们没有什特定的写法,更着重于组件设计模式的概念。与上述高阶组件与渲染回调类似的是,它们有类似的设计思路:逻辑拆散与组件复用。
咱们晓得 React 组件化的主题思想: 组件高可复用性 — 将一个页面拆成一堆独立、可复用的组件,并且通过自上而下的单向数据流的模式将这些组件串联起来。但在理论的开发过程中,大家意识到 React 的组件不仅仅是 UI 展现,往往会牵扯大量的业务逻辑、UI 逻辑、业务数据及状态。比方咱们须要解决数据申请,须要解决点击事件、扭转事件、UI 状态等等。这么一坨业务逻辑与 UI 操作逻辑放在组件里。不仅逻辑简单的组件代码简短,难以开发与保护,而且这些组件很难复用到其它的中央去。那怎么办呢?
将轻便的组件 拆分 开来,将通用的业务逻辑与 UI 逻辑 提取 进去!将组件拆分为职责繁多的业务逻辑组件、UI 逻辑组件、UI 展现组件等。再将适合粒度的组件 组合 成一个残缺的性能点。
将业务逻辑与 UI 组件拆分开来,齐全没有必要把业务逻辑跟 UI 展现混淆在一起。什么意思呢?咱们用代码示例阐明一下,一个简化版 TodoList 的例子。
下面这个组件很简略,加载数据,显示为一个列表。点击时弹出点击项的名称。咱们将上述组件拆分为两局部,一部分加载数据,一部分负责 UI 展现
对上述 <Todo />
组件来说,其自身是一个获取数据、存储数据的容器组件,获取数据的 url 可能会变动,存储的数据接口可能会变动。但它对展现组件 <List />
没有什么要求。
对上述 <List />
组件来说,即使是换一个有利用场景,只有给定的 props
上有同样是数据结构的 list
,它就肯定能运行,而且点击事件也失常运行。<List />
组件对使用者只有数据源的要求,而无其它的要求,增大了组合的可能性。
此时,上述两个组件的拆分,尽管晋升了一些复用的能力,但复用的可能性并不大。比方 <Todo />
这个组件,在理论的业务中,它作为一个复用组件依然是鸡肋般的存在。不过没关系,咱们先有有一个拆分与组合的概念,上面通过一个略微简单的例子,来进一步论述容器组件与展现组件。
1. 容器组件(Container Component)
前端有一句俗语:所有从数据中来,所有向数据中去。任何一个零碎绕不过申请数据这个需要。上述的 <Todo />
组件中有申请数据、存储数据,及响应 UI 的能力。咱们设计一个具备同样能力的容器组件。
上述 <SomePage />
即为一个容器组件,咱们将申请数据的逻辑、所有 UI 响应事件、数据存储封装在内,而其不解决 UI 展现的问题,也不具备任何 DOM 标签。实际上借助 <Mobx />
、<Redux />
等状态管理器,上述代码能够将数据存储与批改逻辑,拆分到状态管理文件中,不过本文篇幅优先,咱们不在这里介绍这些状态管理器。在一个组件中调用 SomePage
代码调用如下
此时来看咱们的 SomePage
组件,它有以下能力
- 主动申请数据。
- 存储数据。
- 响应 UI 事件。
- 不关怀如何展现的问题。
总结咱们的容器组件:解决所有与展现不相干的逻辑。在上述代码开端,咱们看到一个 <UIComponent/>
组件,它正是咱们要在下文介绍的展现组件
2. 展现组件(Presentational Component)
通过上方的示例代码,咱们曾经的 <Warpper />
组件曾经与 <SomePage />
组件曾经解决了申请数据的业务逻辑、存储数据、响应 UI 事件、解决 UI 逻辑等性能。咱们还剩下展现数据等工作没做,毕竟没有界面的前端还叫前端吗~
上述 <UIComponent />
组件代码如下,此时咱们采纳了无状态组件 SFC
当年星宿老怪率众围攻少林寺搜查易筋经,方丈让虚竹打探一下院子里有多少人马?虚竹回到:方丈,两个人。方丈大喜,冲动的来到窗前,筹备看看是哪两个不要命的来了,后果却看到了千军万马。方丈不解的问虚竹:对方来了这么多人,你为什么说只来了两个?虚竹答复:是两个啊,方丈。一敌一我。
回忆一下,任何的界面是不是只关怀两个问题:显示什么?操作了什么?
对一个 UI 组件来说,要思考的事件只有两点,划重点敲黑板,只有两点:输出与输入。
如果我有一个按钮,只须要关怀按钮显示什么文字的问题(先不思考款式问题)?文字是哪来的?内部输出进来的。
如果我还是只有一个按钮,你会关怀按钮被点击时,要怎么告诉内部(绑定的 click
事件),至于要告诉内部的内容,要晓得 click
事件的参数是由你本人确定的,换句话说是这个按钮组件决定了传什么参数,也就是输入什么。
有了上述输出与输出的概念后,如果我的组件不必关怀怎么去加载数据,怎么变动数据,而且这个组件输出固定数据,就显示固定 UI,操作固定 UI,就对外输入固定值。比方上述的 <UIComponent />
,那么这个组件就非常适合移植。依据开闭准则:对批改敞开,对扩大凋谢。
- 如果咱们此时须要更换 UI(前端更换 UI 成果,太常见的需要了),只须要在调用 <
App />
时替换<UIComponent />
组件即可,而不须要批改<SomePage />
与loadData
中的其它代码。 - 如果咱们此时须要更改申请,只须要在调用
<SomePage />
替换申请的地址即可。而不须要批改<UIComponent />
组件。
此时,咱们回忆蚂蚁金服的 ant-design
,咱们会发现其组件,内聚了 UI 交互逻辑、明确申明所须要的数据结构,并且所有的组件全副遵循这个规定。使得咱们在应用ant-design
的React
我的项目能够通过组合其组件,极其疾速的搭建界面。
3. 特点与区别
展现组件(Presentational Component)
- 只关注页面的展现成果。
- 不关怀数据怎么加载与怎么响应事件。
- 只能通过
props
的形式接收数据和触发响应事件。 - 对其它组件没有其余依赖关系。
- 对数据结构有要求,固定输出固定输入。
- 通常是无状态组件,举荐应用
SFC
。
容器组件(Container Component)
- 外部封装了业务逻辑与 UI 逻辑。
- 提供数据和行为给展现组件或其它容器组件。
- 往往是有状态组件,因为它们偏向于作为数据源。
- 外部能够蕴含容器组件和展现组件,但通常没有任何本人的
DOM
标记,除了一些包装div
,并且从不具备任何款式。
值得注意的是,本文中的容器组件,均指数据容器、业务逻辑容器、UI 逻辑容器等,而非相似 B ootstrap
中 UI 容器的概念。
结语
如前文所述,React
中的罕用组件分为五大类类型:
- 受控组件与非受控组件
- 托管组件与非托管组件
- 状态组件与无状态组件
- 高阶组件与渲染属性
- 展现组件与容器组件
前三类组件类型,为 React
的根本组件类型。后两类组件类型,为 React
倒退中积淀下的组件设计模式。其目标在于使得组件达到高可复用性。在梳理过 React
利用开发中的罕用组件类型后,开发者在接手 React
我的项目中或在设计 React
程序时,依据业务需要,抉择应用适合的组件类型。
本文完~