2021年,重学Vue.js之组件篇

2,788 阅读7分钟

组件概念

组件是一段可复用的、利于维护的、方便组合的代码段。在 Vue 中组件实际就是一个 Vue 的实例。把一些相对独立的模块封装成组件非常有利于代码的复用以及维护。

组件注册

  • 组件命名

组件名称将来会被用来放在模板中类似html标签的形式使用,那么其命名最好是不要同原生标签冲突或者是在未来可能会冲突。所以官方推荐的命名方式为字母全小写且必须包含一个连字符,例如:

Vue.component("v-component-name",{
    //...
    template:`
        <div>
        //....
        </div>
    `
});
  • 全局注册

有的组件我们期望在 web 到处使用,这个时候就需要用到全局注册,方便一次编写,随处使用。

Vue.component('component-a', { /* ... */ })
  • 局部注册

有的时候我们可能这个组件只会在某些具体的业务中使用,而不希望全局都注册这个组件。这个时候就可以像下面这样局部注册了。

var ComponentA = {
  /* ... */
};
new Vue({
  el: "#app",
  components: {
    "component-a": ComponentA,
  },
});

组件生命周期

上面说了组件是 Vue 的一个实例,那么在这个 Vue 实例的创建与销毁的过程中,Vue 内部帮我们实现了一系列的钩子函数,能够在某些时期自动执行。

  • 创建阶段

    beforeCreate

    在这个函数执行前做一些初始化的事情,例如一些实例的默认属性和事件的初始化,但是这个时候涉及到状态相关的还没有初始化,也就是说这个函数里面你不能调用实例的props、methods、data、computed、watch

    created

    在执行完 beforeCreate 后,Vue 会马上给我们初始化状态 方法等。通常你可以在这个方法里面拿到传递的 props 、 data 等相关状态属性或者方法

  • 挂载阶段

    beforeMount

    在执行挂载到页面上之前会对你的option 选项分析,判断有没有el或者template,最后生成render函数

    mounted

    真正的挂载到页面上生成真实 DOM,这个方法里面,你可以很方便的拿到你想要操作的 dom

  • 更新阶段

    beforeUpdate

    当你模版中依赖的数据发生改变,但是虚拟 Dom 还没有重新渲染和打补丁

    updated

    当虚拟 Dom 完成了重新渲染,界面发生了改变的时候

  • 销毁阶段

    beforeDestroy

    组件被销毁之前调用,这个时候组件还是可用的,这里适合清除一些定时器任务或者 window 的事件绑定

    destroyed

    所有的事件监听器会被移除,所有的子实例也会被销毁

组件属性 Prop

原生html标签会有各种属性来实现其某种功能,组件可以看作是自定义标签,那么它也应该有一些由外部传入的属性 Props来实现一些功能,通常在组件使用的地方通过静态或者数据绑定的方式将prop传入组件内部, 然后组件内部通过一个props的属性来接收这些传入的值。例如:

<div id="app">
  <my-component title="123" color="red" userName="userName"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
  Vue.component("my-component", {
    props: ["title", "color", "username"],
    template: `
      <p> {{title}} --- {{ color }} --- {{username}}</p>
    `,
  });
  const app = new Vue({
    el: "#app",
  });
</script>

这里仅需要注意的是在绑定属性时候的名称推荐使用 小写 或者 短横线分割命名,这里主要是因为浏览器对属性名是不区分大小写的。 任何类型的值都可以通过属性的方式传递给子组件

  • 单向数据流

在组件化开发中,有一个规范就是遵循数据单向流动,意思就是父组件的数据传递给子组件,子组件应该只读,而不应该尝试去更改,虽然可能 更改也能达到目的,但是不推荐。如果你真的想用,你可以将props赋值给data或者computed。但是需要注意的是引用类型的问题,所以通常不推荐子组件更改props

  • props 验证

props作为数据传递给子组件,作为一个严格的程序员,我们有时候需要对数据做类型或者一些值的判定,这样也能使组件变得更加健壮。props除了是一个数组,还可以是一个对象,对每个传入的属性做类型上的检查,一个标准的属性检查格式为:

 props: {
    // 检测类型 + 其他验证
    age: {
      type: Number,  // String、Number、Boolean、Array、Object、Date、Function、Symbol
      default: 0, // any
      required: true, // boolean
      validator: function (value) {
        // todo
      }
    }
  }

当传入的值不符合 验证的时候,控制台会将警告抛出!

  • 没有被 props 接收的属性

有的时候你可能会在组件使用的地方写一些自定义的属性,或者是写了一些错误的属性,这些属性并没有在组件内部通过props接收。 默认情况下,这些属性将会继承到你组件的最外层根标签上。有时候可能会造成一些错误。例如:

// 使用
<my-input class="def" type="button" data-hello="hello"></my-input>;

// 声明组件
Vue.component("my-input", {
  template: `
    <input class="abc" type="text" placeholder="请输入"/>
  `,
  inheritAttrs: false, // 不继承,也就是说上面使用的属性不会继承到input标签上,如果开启的话那么就会破坏标签了。
  mounted() {
    console.log(this.$attrs);
  },
});

需要注意的是,这些不被 prop 接收的属性会存在于组件实例的$attrs 上。另外 class & style的属性会自己做合并

组件自定义事件

自定义事件实际上是在自定义组件上绑定一个你自己定义的事件,然后组件在某些时刻,通过消费的形式触发这些事件。

// 绑定事件
<my-component v-on:my-event="doSomething"></my-component>;

// 消费事件

this.$emit("my-event", ...args);

关于自定义事件 请参考文档 自定义事件

动态组件和异步组件

  • 动态组件

动态组件顾名思义就是组件可能在未来某种情况,变成另外一个组件,如果我们不通过动态组件的话,可能你需要自己去控制组件的显示与隐藏。比如我们把页面划分为10个区块, 但是这10个区块也就是10个组件,但是这10个组件可能根据时间日期不同,排列组合不一样。像这种功能怎么办呢?假设有一个接口,能按条件返回给你每天应该显示那些组件

{
  code:0,
  data:[ 'component-a','component-b' ]
}

那么你可以 使用循环的方式,使用Vue提供的动态组件来实现,component是Vue提供给我们的内置组件

<component v-bind:is="item" v-for="item of componentList"></component>

有的时候你可能需要动态切换一些组件,但是每次切换都会使原来那个组件重新渲染。这时候你可能需要使用keep-alive组件。

  • 异步组件

异步组件是一种只有当真正使用到组件的时候才会去加载组件所需要的资源,这样可以更好的做代码的分割和性能优化,加快首屏速度; Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。实际上定义组件除了传递一个对象外,第二个参数还可以是一个函数。


Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

还可以这样声明组件:

Vue.component(
  'async-webpack-example',
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

或者局部声明

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

另外时候,可能你这个方法需要请求接口,并且处理一些异常,此时你可能需要定义下异常处理的问题:


const AsyncComponent = () => ({

  // todo something

  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

详细参考:动态组件 & 异步组件

插槽

组件自定义v-model和插槽

组件通信

组件通信是组件非常重要的一部分,后面会总结一些常见的组件通信方法文章