对于封装react组件的一些思考

因为近期在涉及到封装组件的时候遇到了一些问题,因而我认真地了解了一下react封装组件过程当中应该要涉及和思考到的一些问题,写了下来。(如下主要是针对UI组件,因为水平有限不保证内容正确性,仅仅是一些我的的思考)css

1、什么是组件

组件能够将UI切分红一些的独立的、可复用的部件,这样就只需专一于构建每个单独的部件。node

所谓组件,即封装起来的具备独立功能的UI部件。react

在 React 中,一切皆是组件,所以理解组件的工做流与核心尤其重要。es6

且react中有多种建立组件的方式和各类各样的组件概念,所以在设计组件的时候应该使用哪一种组件的建立方式且应该设计一个怎样的组件都值得深刻思考。数组

那么在React里面一个组件应该有什么特征呢?在react中认为组件应该具备以下特征:

  1. 可组合(Composeable):一个组件易于和其它组件一块儿使用,或者嵌套在另外一个组件内部。若是一个组件内部建立了另外一个组件,那么说父组件拥有(own)它建立的子组件,经过这个特性,一个复杂的UI能够拆分红多个简单的UI组件;
  2. 可重用(Reusable):每一个组件都是具备独立功能的,它能够被使用在多个UI场景;
  3. 可维护(Maintainable):每一个小的组件仅仅包含自身的逻辑,更容易被理解和维护;

2、一个设计良好的组件应该有什么特性?

(一)高内聚、低耦合

咱们常常谈一个设计良好的系统应该是高内聚低耦合的,那么其实我认为一个好的组件也应该是具备高内聚低耦合的特性。bash

那么咱们应该要怎么去作到使一个组件实现高内聚低耦合的特色呢?

  1. 高内聚:将逻辑紧密相关的内容放在一个组件内。
    React能够将展现内容的JSX、定义行为的JavaScript代码、甚至定义样式的css,
    均可以放在一个JavaScript文件中,所以React天生具备高内聚的特色。
  2. 低耦合:不一样组件之间的依赖关系要尽可能弱化。
    也就是每一个组件要尽可能独立,
    一个组件不该该掌握着其余组件的细节,
    而是要尽可能作到对其余组件了解不多,甚至是一无所知。

为何须要实现低耦合呢?

由于低耦合会带来如下的好处:异步

  1. 在系统中的局部改变不会影响到其余地方
  2. 任何组件均可以被替代品取代
  3. 系统之间的组件能够复用
  4. 能够轻易测试独立的组件,提升了应用的测试代码覆盖率

而高耦合的组件间会很容易出现一个问题,
就是没法或者很艰难去修改一个大量依赖其余组件的组件,
甚至只是改一个用来传递数据的字段都会致使大量的修改。函数

(二)隐藏内部结构

一个封装良好的组件应该是要隐藏其内部结构的,并经过一组 props来提供控制其行为的途径。性能

隐藏内部结构是必须的。内部结构或实现细节不该该能被其余组件知道或关联。测试

React 组件能够是函数式的,也能够是基于类的,能够定义实例方法、设置 refs、维护 state或是使用生命周期方法。而这些实现细节被封装在组件自身中,其余组件不该该窥见其中的任何细节。

基于此特色来设计的组件对其余组件的依赖是极低的,带来的是低耦合的特色和好处。

(三)职责单一

我认为组件应该要符合单一职责原则,
一个组件应该尽可能只负责一件事情,而且把这件事情作好,
由于我以为一个组件若是负责处理的事情过多,
在修改其中一件事情的时候颇有可能也会影响到它负责的其余事情,且不利于维护和复用。

3、在封装一个组件的时候应该先思考什么?

  1. 这个组件应该是作什么的
  2. 这个组件应该至少须要知道那些信息
  3. 这个组件会反馈什么东西

在设计一个组件的时候咱们不该该仅限于实现当前的需求,
设计出一个只适用于单一项目的组件,而是应该是一个能够适应大部分同种需求的通用组件。
因此咱们在碰到一个需求的时候应该首先对需求进行抽象,而不是看到设计稿就撸着袖子上。

例如碰到一个轮播图组件的需求的时候,咱们拆分如下这个需求,能够获得:

(1) 这个组件要作什么:

  1. 能够展现多张图片
  2. 能够向左向右翻页,或者是能够是上下翻页
  3. PageControl的状态会根据图片的滚动而相应改变 还有可能有一些隐藏的需求,相似于:
  4. 应该支持左右两侧或者上下无限循环滚动
  5. 能够选择的是否自动轮播
  6. 支持手动滑动切换图片
  7. 图片有点击事件,能够点击来进行相关的事件反应

(2)这个组件至少应该知道什么信息

一个好的组件应该是要像存在魔法同样,只须要极其少数的参数和条件就能够获得指望的效果。就像这个轮播图组件同样,组件应该至少知道的信息有:

  1. 图片的url地址数组
  2. 当图片不存在时候的占位图

其余能够知道也能够不知道的信息能够有:

  1. 是否开启自动轮播,默认是开启或者不开启

  2. 图片滚动是左右仍是上下,默认是左右

    等等 ....................................

(3)这个组件会反馈什么

一个可用的轮播图效果

4、组件的通讯

父组件向封装好的子组件通讯一般是经过props

做为组件的输入,props的值应该最好是js基本类型 (如 string、number、boolean)
可是props能够传入的不只仅只是这些,它但是一个神奇的东西,它能够传入包括:

  1. js基本类型(如 string、number、boolean)
<Message text="Hello world!" modal={false} />;  
复制代码
  1. 对象
<Message
  data={{ 
  thexAxis:  thexAxis ,     
  lineData : lineData
   }} 
  />
复制代码
  1. 数组
<MoviesList items={['Batman Begins', 'Blade Runner']} />  
复制代码
  1. 做为事件处理和异步操做时,能够指定为函数:
<Message type="text" onChange={handleChange} />  
复制代码
  1. prop 甚至能够是一个组件构造器。组件可被用来处理其余组件的实例化:
function If({ Component, condition }) {  
 return condition ? <Component /> : null;
  }
<If condition={false} component={LazyComponent} />  
复制代码

为避免破坏封装,要谨慎对待 props 传递的细节。
父组件对子组件设置 props 时,也不该该暴露自身的结构。
好比,把整个组件实例或 refs 当成 props 传递之类的神奇操做。

访问全局变量是另外一个对封装形成负面影响的问题。

咱们能够经过 proptypes来对传入的数据进行类型限制。

5、react中建立组件的方法

react建立组件有三种方法,分别是:

  1. function式无状态组件
  2. es5方式React.createClass组件
  3. es6方式extends React.Component

而目前react推荐ES5方式和ES6方式建立组件的写法中推荐的是ES6的写法,因此这里就不对ES5的写法进行讨论了。

React.Component

React.Component是以ES6的形式来建立React组件,也是如今React官方推荐的建立组件的方式,
其和React.createClass建立的组件同样,也是建立有状态的组件。

相比React.createClass方式,React.Component带来了诸多语法上的改进

1.import

ES6使用import方式替代ES5的require方式来导入模块,其中import { }能够直接从模块中导入变量名,此种写法更加简洁直观。

2.初始化 state

在ES6的语法规则中,React的组件使用的类继承的方式来实现,去掉了ES5的getInitialState的hook函数,state的初始化则放在constructor构造函数中声明。

引伸内容:

如何正肯定义State

React把组件当作一个状态机。经过与用户的交互,实现不一样状态,而后渲染UI,让用户界面和数据保持一致。 组件的任何UI改变,均可以从State的变化中反映出来; State中的全部状态都用于反映UI的变化,不该有多余状态。

那么什么样的变量应该作为组件的State呢:

  1. 能够经过props从父组件中获取的变量不该该作为组件State。
  2. 这个变量若是在组件的整个生命周期中都保持不变就不该该做为组件State。
  3. 经过其余状态(State)或者属性(Props)计算获得的变量不该该做为组件State。
  4. 没有在组件的render方法中使用的变量不用于UI的渲染,那么这个变量不该该做为组件的State。这种状况下,这个变量更适合定义为组件的一个普通属性。

function和class建立组件的区别

React内部是经过调用组件的定义来获取被渲染的节点,而对于不一样的组件定义方式,其获取节点的步骤也不同。以下:

//function方式定义
function Example() {
  return <div>this is a div</div>;
}

const node = Example(props);

// 类方式定义
class Example extends React.Component {
  render() {
    return <div>this is a div</div>;
  }
}

const instance = new Example(props);
const node = instance.render();
复制代码

在这里,函数直接调用,类则须要先实例化再去调用实例化对象上的render方法;

若是将类按照普通函数去调用则会报错

6、Component 和 PureComponent

由于这方面没有详细去了解过,因此也只是粗浅总结一下其区别:

PureComponent除了提供了一个具备浅比较的shouldComponentUpdate方法,
PureComponent和Component基本上彻底相同。
当组件更新时,若是组件的 props 和 state 都没发生改变, render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提高性能的目的。

若是咱们想用PureComponent去代替Component的时候不须要去作太多的事情,
仅仅是把Component改为PureComponent便可。 可是咱们并不是能够在全部地方都用PureComponent去代替Component,
具体仍是要按照实际状况来选择,由于了解不深就不在此处详谈了。

7、有状态组件和无状态组件

无状态组件更多的是用来定义模板,接收来自父组件props传递过来的数据,
使用{props.xxx}的表达式把props塞到模板里面。
无状态组件应该保持模板的纯粹性,以便于组件复用,因此一般UI组件应该是无状态组件。
相似于:

var Header = (props) = (
        <div>{props.xxx}</div>
   );
复制代码

而有状态组件一般是用来处理定义交互逻辑和业务数据
(使用{this.state.xxx}的表达式把业务数据挂载到容器组件的实例上(有状态组件也能够叫作容器组件,无状态组件也能够叫作展现组件),
而后传递props到展现组件,展现组件接收到props,把props塞到模板里面。
相似于:

class Home extends React.Component {
  constructor(props) {
      super(props);
      };
   render() {
      return (
         <Header/> 
      )
   }
}
复制代码

8、高阶组件

高阶组件给个人感受相似于高阶函数,都是接受一个东西的输入, 而后再给输入的东西添加新的特性做为一个新的东西输出, 看起来相似于装饰器模式的实现。 可是由于目前为止没有写太高阶组件,因此就不在这里讨论了。

相关文章
相关标签/搜索