# 组件化的定义和使用

# 组件基础

组件是可复用的 Vue 实例,且带有一个名字,它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。

# 基本示例

// 定义一个名为 button-counter 的新组件 Vue.component('button-counter', { data: function () { return { count: 0 } },
template: '
<button v-on:click="count++">You clicked me {{ count }} times.</button>
' })

# 组件的复用

​ 组件可以进行任意次数的复用,每使用一次组件,就会有一个它的新实例被创建,每一个组件实例都有自己的作用域。

一个组件的 data 选项必须是一个函数,通过闭包返回数据,保证了数据不会被全局变量污染,因此每个实例可以维护一份被返回对象的独立的拷贝,注意如果是复杂数据类型,依然保持浅拷贝的特性。

# 组件的组织

通常一个应用会以一个嵌套的组件树的形式来组织,组件中又包含了子组件,为了能在模板中使用,这些组件必须先注册。

这里有两种组件的注册类型:全局注册和局部注册。全局组件是通过 Vue.component 注册的。

# 通过 Prop 向子组件传递数据

Prop 是你可以在组件上注册的一些自定义 attribute,当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property

Vue.component('blog-post', { props: ['title'], template: '
<h3>{{ title }}</h3>
' })

# 单个根元素

每个组件必须有且只有一个根元素,通常将模板的内容包裹在一个父元素内

# 监听子组件事件

  1. 使用事件抛出一个值

有的时候用一个事件来抛出一个特定的值是非常有用的。 例如我们可能想让<blog-post>组件决定它的文办要放大多少。这时可以使用$emit的第二个参数来提供这个值,父组件在监听这个事件的时候,通过$event访问到被抛出的这个值,或者将这个事件处理函数放在方法里,通过参数来传递

<button v-on:click="$emit('enlarge-text', 0.1)">Enlarge text</button>
<blog-post ... v-on:enlarge-text="postFontSize += $event"></blog-post>
  1. 在组件上使用 v-model

自定义事件也可用于创建支持 v-model 的自定义输入组件

<input v-model="searchText" />

等价于:

<input v-bind:value="searchText" v-on:input="searchText = $event.target.value" />

写成组件时:

Vue.component('custom-input', { props: ['value'], template: `
<input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" />
` })

这里有两点,1.将 value 属性绑定到 value 的 prop 上,2.在 input 事件被触发时,将新的值通过自定义的 input 事件抛出。

# 通过插槽分发内容

<alert-box>Something bad happened.</alert-box>
Vue.component("alert-box", {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
});

和 HTML 元素一样,我们经常需要向一个组件传递内容 如你所见,我们只要在需要的地方加入插槽就行了——就这么简单! 其中自定义组件<alert-box>标签中的内容就填充在<slot>标签中,能被正常渲染到了

# 动态组件

通过在 Vue 的 component 元素上加一个特殊的is attribute 来实现

# 组件注册

# 组件名

在注册组件的时候,我们始终需要给他一个名字,这个组件名就是 Vue.component 的第一个参数。

组件名写法:一般使用 kebab-case

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

# 全局注册

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

# 局部注册

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

如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

import ComponentA from "./ComponentA.vue";

export default {
  components: {
    ComponentA
  }
  // ...
};

# 模块系统

  1. 在模块系统中局部注册

推荐创建一个 component 目录,并把乜咯组件放置在其各自的文件中,然后在局部注册前导入每个想使用的组件

// ConponentB.Vue

import ComponentA from "./ComponentA";
import ComponentC from "./ComponentC";

export default {
  components: {
    ComponentA,
    ComponentC
  }
  // ...
};
  1. 基础组件的自动化全局注册

如果使用了 webpack,那么就可使用request.context只全局注册一些非常通用的基础组件。

//src/main.js
import Vue from "vue";
import upperFirst from "lodash/upperFirst";
import camelCase from "lodash/camelCase";

const requireComponent = require.context(
  // 其组件目录的相对路径
  "./components",
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式
  /[A-Z]\w+\.(vue|js)$/
);

requireComponent.keys().forEach((fileName) => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName);
  // 获取组件的 PascalCase 命名
  const componentName = upperFirst(
    camelCase(
      // 获取和目录深度无关的文件名
      fileName
        .split("/")
        .pop()
        .replace(/\.\w+$/, "")
    )
  );

  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  );
});

注意全局注册的行为必须在根 Vue 实例创建之前发生