组件化开发之如何封装组件

我心飞翔 分类:javascript

自从React,Vue等前端框架在市面上大量使用之后,组件化开发逐渐成为了前端主流开发方式,今天我就在这里给大家分享一下在我们平时的开发中我们自己应该如何去封装组件。主要从以下三个方面给大家讲解:

  • 什么是组件
  • 为什么要拆分组件
  • 如何拆分组件

什么是组件

对功能、ui样式的封装,一个功能或者一个ui样式就是一个组件,导航栏,banner,页脚等等这些功能、样式都是组件。
不同的框架对组件的分类有些许差别,比如有些人对react的组件分为函数式组件、无状态组件和有状态组件、展示型组件和容器型组件等;有些人vue对组件分为全局组件、局部组件、函数式(无状态)组件、普通(有状态)组件、动态组件、普通(非动态)组件、异步组件、普通(非异步)组件、递归组件、普通(非递归)组件。但本质上都是大同小异。鉴于我们目前使用的都是vue框架,所有今天的分享也主要是针对vue的组件进行分析。

1.按组件注册方式分类:vue将组件分为全局组件和局部组件

全局组件:使用Vue.component全局注册的组件(在我们的项目中一般情况下是在utils文件中进行统一注册的)

Vue.component("my-component-name", {
  /****/
});
 

clipboard.png

局部组件:在使用的页面通过components单独引入(,一般情况下推荐使用按需引入的方式引入组件)

clipboard (1).png

2.按组件有无自己的状态分类:可以分为函数式(无状态)组件和普通(无状态)组件

函数式:用jsx语法编写的html或者template 标签上加 functional。一般是无状态 (没有响应式数据)的组件可以注册成函数式组件(它没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数,所以渲染开销也低很多,在一定程度上能提高一些性能)
jsx 的语法:

<script>
  import OtherComponent from "./other";
  export default {
    name: "FunctionComponent",
    functional: true,
    components: {
      OtherComponent
    },
    props: {
      title: {
        type: String,
        required: true
      }
    },
    render(h, { props }) {
      const { title } = props;
      return (
        <div>
          <p> 我是函数式组件</p>
          <p>props {title}</p>
          <OtherComponent otherInfo={title} title="我是函数式组件的其他组件" />
        </div>
      );
    }
  };
</script>
 

template 标签标记 functional:

<template functional>
  <div>
    {{ props.title }}
    <p>
      otherInfo {{ props.otherInfo }}
    </p>
  </div>
</template>
<script>
  export default {
    functional: true,
    props: {
      title: {
        default: "",
        type: String
      },
      otherInfo: {
        default: "",
        type: String
      }
    }
  };
</script>
 

3.按组件是否动态分类:可以分为动态组件和普通(非动态)组件
动态组件:通过component 标签加is属性(属性值为引入的组件名称)引入的组件

<template>
  <div>
    <component :is="currentTabComponent"></component>
  </div> 
</template>
export default {
  components: {
    DynamicComponent1
  },
  data() {
    return {
      currentTabComponent: "DynamicComponent1"
    };
  }
};

 

一般是需要很多组件之间切换的情况下可以使用动态组件,例如下面的代码就可以优化为动态组件:(el-tab-pane部分代码基本上是重复的知识少量数据不一样,这种时候就可以将el-tab-pane里面的数据在data中定义成一个数组型的变量,然后循环el-tab-pane,里面的组件可以更换成自定义组件)
clipboard.png
优化后:(如果组件切换比较频繁的话,可以在components标签外面加上keep-alive标签将已失活的组件缓存起来,以便下次切换组件时不需要从新创建、挂载)

<template>
  <el-tabs v-model="activeTab">
    <el-tab-pane v-for="(item) in tabList" :key="item.id" :label="item.name" :name="item.id">
      <component :is="item.components"></component>
    </el-tab-pane>
  </el-tabs>
</template>
<script>
import page1 from "@/views/example/page1";
import page2 from "@/views/example/page2";
import page3 from "@/views/example/page3";
export default {
  name: "tab",
  data() {
    return {
      activeTab: '1',
      tabList: [
        { id: '1', name: "页面1", components: page1 },
        { id: '2', name: "页面2", components: page2 },
        { id: '3', name: "页面3", components: page3 }
      ]
    };
  },
};
</script>
 

4.按组件是否异步分类:可以分为异步组件和普通(非异步)组件
异步组件:按需加载组件,在需要组件的时候再去加载组件(这样可以让首屏部分代码块优先加载,加快首屏渲染速度,像目前我们项目中使用的路由懒加载,以及局部这种方式引入的组件() => import('./my-async-component')等都是异步组件)

异步组件引入的方式:

a.通过工厂函数

Vue.component('async-example', function (resolve, reject) {
  resolve({
    template: '<div>hello vue !</div>'
  })
})
 

b.通过webpack 的 代码切割 功能一起配合使用:

Vue.component('async-wepack-example', function (resolve) {
  // require会告诉webpack将你的代码切割成多个包,然后通过ajax加载    
  require(['./my-async-component'], resolve)
})
 

c.通过 import()

Vue.component('async-wepack-example', () => import('./my-async-component'))
 
局部组件注册异步组件(这种方式为目前我们项目中常用的方式)
new Vue({
  components: {
    myComponent: () => import('./my-async-component')
  }
})
 
高级异步组件
const asyncComponent = () => ({
  component:import('./my-async-component.vue'),//需要加载的组件
  delay: 200, // 展示加载时组件的延时时间,默认200毫秒
  error: errorComponent, // 加载失败显示组件
  loading: loadingComponent, // 加载时使用组件
  timeout: 2000 // 超时时间,默认 Infinity(组件加载也超时后则使用加载失败时使用的组件。)
})
 

5.按组件是否循环引用分类:可以分为递归组件和普通(非递归)组件

递归组件:组件可以在它们自己的模板中调用自身(必须要定义name组件的值,一般用于树组件,侧边栏路由组件等)

<template>
  <div>
    <ul>
      <li v-for="item in list" :key="item.name">
        <span>{{ item.name }}</span>
        <recursive-component v-if="item.children" :list="item.children" />
      </li>
    </ul>
  </div>
</template>
<script>
  export default {
    name: "RecursiveComponent",
    props: {
      list: {
        default: () => [],
        type: Array
      }
    }
  };
</script>
 

6.在一般我们的开发中,组件又分为ui展示类组件(着重ui样式的展示,比如我们常用的element-ui之类的)、功能组件(不注重样式,只对功能进行封装)、业务组件等。

为什么要拆分组件

1.提高开发效率

在组件化开发之前,我们每个页面都是单独的,如果在开发一个页面的时候遇到了曾今开发过类似的部分,只能复制粘贴到当前页面,然后进行一些改动,有时参数、变量之类的丢失,页面或许还会报错,还要花费大量的时候去排查问题,组件化之后,类似的部分我们只需要引入组件即可,无需重复开发,一定程度上也减少了代码量,极大提高项目的编译速度

2.方便重复使用

一个组件可在多个地方使用

3.简化调试步骤

一个页面出现问题时,可以优先定位到某个模块,然后直接定位到某个组件,无需看完整个页面的所有代码然后排查问题

4.提升整个项目的可维护性

页面都由组件组成,模块与模块之间的耦合度降低,删除修改某个模块的功能时仅需直接修改组件

5.便于协同开发

每个组件都把对应的业务功能收敛在一个工程里,彼此互不打扰。 在多人团队里,每个人只负责自己的业务模块,对业务功能的增删改查,都只限定在自己的这个业务模块里,不会影响其他人的业务。

如何拆分组件

1.保证单一职责。

一个组件“只做好一件事”,只做好最基本的事,留出可组合的口子。尽量把更多功能外包出去。

2.开放封闭原则。

对扩展开放,对修改封闭。有新的业务需求时,首先考虑如何扩展,单个组件只实现最单纯的功能,更多通过扩展实现,而不是修改组件。

3.单个组件文件最好不超过200或400kb

追求短小精悍,有利于调试,缩小排错范围

4.避免函数有太多的参数。入口处检查参数的有效性,出口处检查返回的正确性

避免别人使用组件时传参有误,造成很多无法预料的报错。

5.松耦合,封装的组件不要依赖太多其他的组件

当所有问题都是通过堆砌一堆开源代码解決时,一是会存在冗余,二是很难调试,当其中一个模块有问题时可能会导致整个组件无法使用,复杂的依赖关系,也会发生版本冲突之类的事情。如果要依赖组件或库,要依赖稳定的也就是不经常変的。

6.无副作用:不依赖、不修改外部变量,内部操作不影响其它组件

在保证输入/输出不变的情况,可以安全的被替换。

7.提炼精华,一些无关紧要的东西,比如数据获取,数据整理或事件处理逻辑,理想情况下应该将其移入外部 js 或者放在父组件中去进行处理,组件内只负责ui展示或者功能的实现。

8.合理组件化

将大块代码变成松散耦合且可复用的组件确实是有很多优点,但是并不是所有的页面结构都需要被抽离成组件,也不是所有的逻辑部分都需要被抽出到组件外部。我们在实际进行组件抽离工作的时候,需要考虑到不要过度的组件化。那么我们在决定是否要抽离组件的时候可以根据以下几点来判断:
a.是否有足够的页面结构/逻辑来保证它?如果它只是几行代码,那么最终可能会创建更多的代码来分隔它。

b.代码重复(或可能重复)? 如果某些东西只使用一次,并且服务于一个不太可能在其他地方使用的特定用例,那么将它嵌入其中可能会更好。

c.性能是否会受到影响?更改 state/props 会导致组件重新渲染,当发生这种情况时,你需要的是 只是重新去渲染经过 diff 之后得到的相关元素节点。在较大的、关联很紧密的组件中,你可能会发现状态更改会导致在不需要它的许多地方重新呈现,这时应用的性能就可能会开始受到影响。

回复

我来回复
  • 暂无回复内容