【Vue】详解Vue组件系统

目录

 

正文

回到顶部

 

回到顶部

Vue 渲染的两大基础方式

new 一个 Vue 的实例

这个我们一般会使用在挂载根节点这一初始化操作上

new Vue({
  el: '#app'
})

 

注册组件并使用

通过 Vue.component()去注册一个组件,你就可以全局地使用它了,具体体现在每个被 new 的 Vue

实例 / 注册组件, 的 template 选项属性或者对应的 DOM 模板中, 去直接使用

 

回到顶部

注册组件

全局注册

例如,放在通过 new 创建的 Vue 实例当中

Vue.component('my-component', {
  template: '<p> 我是被全局注册的组件 </p>'
})
/*
  Vue.component(组件名称 [ 字符串], 组件对象 )
*/

new Vue({
el:
'#app',
template:
'<my-component></my-component>'
})

 

demo:

 

 

又例如,放在另外一个组件中

Vue.component('my-component', {
  template: '<p> 我是被全局注册的组件 </p>'
})

Vue.component('other-component', {
template:
'<div> 我是另一个全局组件:<my-component></my-component></div>'
})

new Vue({
el:
'#app',
template:
'<other-component></other-component>'
})

 

 

 

局部注册

const child = {
  template: '<p> 我是局部注册的组件 </p>'
}
/*
   通过 components 选项属性进行局部注册:
   components: {组件名称 [ 字符串]: 组件对象
  }
*/
new Vue({
  el: '#app',
template:
'<my-component></my-component>', components: { 'my-component': child } })

demo:

 

 

通过组件组合(嵌套),构建大型的应用:

const child = {
  template: '<p> 我是 child 组件 </p>'
}

const father = {
template:
'<p> 我是 father 组件,我包含了:<child-component></child-component></p>',
components: {
'child-component': child
}
}

const grandFather = {
template:
'<p> 我是 grandFather 组件,我包含了:<father-component></father-component></p>',
components: {
'father-component': father
}
}
new Vue({
el:
'#app',
template:
'<my-component></my-component>',
components: {
'my-component': grandFather
}
})

 

demo:

 

通过 new 创建 Vue 实例,  全局注册组件,局部注册组件三者的使用频率(场景)

1.new  Vue(),  尽管在 Vue 官方文档上在相当多的例子中使用到了创建 Vue 实例这个操作,实际上它的使用可能并没有你想象的那么平凡,在很多时候,它可能就只在挂载根实例的时候使用到

 

【这段话给写 react 框架的人看】

对 new Vue()做个最简单的描述!:在使用上类似于 ReactDOM.render()... 对,就是那个一开始你撸文档的时候觉得好像很重要,但最后发现在整个 APP 中就只使用了一次的那个顶层 API ....

 

2. 全局注册组件的使用也不太频繁,首先来说,如果大量使用全局注册的话,当然容易产生组件的命名冲突,这就意味着你在构建大型组件的时候,你不应该选择用全局注册构建具体的细颗粒度的组件(实际上即使是小型应用也不推荐啦 ~~~)

那么全局注册组件会在哪里使用到呢?

2.1 有许多可全局复用的公共 UI 组件,你可能希望通过 Vue.component({...}) 的方式全局注册它

2.2 可以很简单地添加第三方 UI 框架

【对比】大凡使用过一些 UI 框架的人,都知道一般情况下,使用这些 UI 组件的方式就是为元素添加类,像这样:

 

<div class='UI 框架中定义的类名'></div>

而在 Vue 中,你可以通过直接使用组件名称去使用,就和 react 相关的 UI 框架一样

 

3. 大多数时候我们通过组件组合的方式构建页面的时候,运用的是局部注册,就像上文所提及的那样

 

 

【注意点】

1. 注册组件必须发生在根实例初始化前

2.data 是函数!

 

回到顶部

Vue 中的 props 数据流

【写给 react 学习者们看的】这跟 react 中设计非常类似,连名称都相同,所以学过 react 的同学看这里应该会很轻松吧 ~~

 

这里要用到 Vue 的一个选项属性——props;

通过在注册组件中声明需要使用的 props,然后通过 props 中与模板中传入的对应的属性名,去取用这传入的值

例子:

model 部分:

Vue.component('my-component', {
  props: ['name', 'birthTime'],
  template: '<p> 我叫:{{name}} 我出生于:{{birthTime}}</p>',
  created: function () {
    console.log('在 created 钩子函数中被调用')
    console.log('我叫:', this.name)
    console.log('我出生于:', this.birthTime)}})

new Vue({
el:
'#app'
})

 

HTML 部分:

<div id='app'>
   <my-component name="彭湖湾" birth-time="1997 -06 - 06"></my-component>
<div id='app'>

 

demo:

 

 

你在注册组件的时候通过 props 选项声明了要取用的多个 prop:

props: ['name', 'birthTime'],

 

然后在模板中通过属性传值的方式进行数据的注入:

<my-component name="彭湖湾" birth-time="1997 -06 - 06"></my-component>

 

再然后我们就可以在注册组件的模板中使用到 props 选项中声明的值了:

template: '<p> 我叫:{{name}} 我出生于:{{birthTime}}</p>'

 

这里要注意几个点:

props 取值的方式

1. 如果是在注册组件的模板内部,直接通过 prop 的名称取就 OK 了,例如

template: '<p> 我叫:{{name}} 我出生于:{{birthTime}}</p>'

 

2. 如果在注册组件的其他地方,用 this.prop 的方式取用,例如

console.log('我叫:', this.name)

 

props 内写的是驼峰命名法,为什么在 HTML(模板)中又用了短横线命名法?

(camelCased VS kebab-case)

 

首先我们知道,Vue 组件的模板可以放在两个地方:

1. Vue 组件的 template 选项属性中,作为模板字符串

2. 放在 index.html 中,作为 HTML

 

这里的问题在于,HTML 特性是不区分大小写的

所以在 Vue 注册组件中通用的驼峰命名法,显然不适用于 HTML 中的 Vue 模板,所以

在 HTML 中写入 props 属性,必须写短横线命名法(就是把原来 props 属性中的每个 prop 大写换成小写,并且在前面加个“-”)

 

总结:

1. 在 template 选项属性中,可以写驼峰命名法,也可以写短横线命名法

2. 在 HTML(模板)中,只能写短横线命名法,不能写驼峰

 

下面我就来证明以上两点:

对 1

Vue.component('my-component', {
  props: ['name', 'birthTime'],
  template: '<p> 我叫:{{name}} 我出生于:{{birthTime}}</p>',
  created: function () {
    console.log('在 created 钩子函数中被调用')
    console.log('我叫:', this.name)
    console.log('我出生于:', this.birthTime)}})
new Vue({
  el: '#app',
  template: '<my-component name=" 彭湖湾 "birthTime="1997 -06 - 06"></my-component>'
})

 

demo:

 

 

name 和 birthTime 都正常显示,这说明在 template 模板字符串中,是可以写驼峰的

(请注意到一点:name 既符合驼峰写法也符合短横线写法,而 birthTime 只符合驼峰写法)

 

JS 部分

Vue.component('my-component', {
  props: ['name', 'birthTime'],
  template: '<p> 我叫:{{name}} 我出生于:{{birthTime}}</p>',
  created: function () {
    console.log('在 created 钩子函数中被调用')
    console.log('我叫:', this.name)
    console.log('我出生于:', this.birthTime)}})

new Vue({
el:
'#app'
})

 

HTML(模板)部分

<div id='app'>
       <my-component name="彭湖湾" birthTime="1997 -06 - 06"></my-component>
</div>

  

demo:

 

 

这里有个有趣的现象:name 对应的值可以正常地显示,但!birthTime 不能

这是因为上文提到的:

name 既符合驼峰写法也符合短横线写法,而 birthTime 只符合驼峰写法,不符合 HTML 要求的短横线写法

 

使用 v-bind 的必要性:props 不绑定的前提下, 只能被作为字符串解析

Vue.component('my-component', {
  props: ['number'],
  template: '<p> 检测 number 的类型 </p>',
  created: function () {
    console.log(typeof this.number)}})

new Vue({
el:
'#app',
template:
'<my-component number="1"></my-component>'
})

 

demo:

 

 

 

number 被检测为字符串这表明在不加 v-bind 绑定的情况下,props 接受到的都是字符串,(注:如果被作为 javacript,”1“会被解析为 Number 的 1,而” ‘1’ “才会被解析为 String 的 1)

 

没错,仅仅这一点就会让我们非常为难,所以,我们需要使用 v-bind:

当使用 v-bind 的时候, 在模板中 props 将会被作为 javascript 解析

Vue.component('my-component', {
  props: ['number'],
  template: '<p> 检测 number 的类型 </p>',
  created: function () {
    console.log(typeof this.number)}})

new Vue({
el:
'#app',
template:
'<my-component v-bind:number="1"></my-component>'
})

 

demo:

 

 

这可能拓展我们对 v-bind 的认知

1. 用 v-bind 一般是为了做数据的动态绑定

2. 有时 v-bind 并不为了实现点 1,只是纯粹为了让字符串内的内容被当作 JS 解析罢了

 

回到顶部

Vue 的自定义事件

自定义事件是我非常喜欢的 Vue 的一大特性!!! 看文档的第一眼我就对它情有独钟(虽然那一天离现在也就几天而已的时间。。。)

 

先展示代码和 demo:

Vue.component('button-counter', {
  template: '<button v-on:click="increment">{{counter}}</button>',
  data: function () {
    return {
      counter: 0
    }
  },

methods: {
increment: function () {
this.counter += 1
this.$emit('increment-event')
}
}
})

new Vue({
el:
'#app',
data: {
totalCounter:
0
},

methods: {
total_increment: function () {
this.totalCounter += 1
}
}
})

 

模板 HTML 部分:

<div id='app'>
      <button>{{totalCounter}}</button>
      </br>
      <button-counter v-on:increment-event='total_increment'></button-counter>
      <button-counter v-on:increment-event='total_increment'></button-counter>
</div>

 

demo:

下面两个按钮是两个相同的子组件,并和上面那个按钮共同组成了父组件。

当点击任意一个子组件的按钮,使其加 1,都会使得父组件 +1, 最终:父组件的数值 = 子组件的数值之和

点击下方左边 button

 

 点击下方右边 button

 

 

 

 

自定义事件的原理

通过 $emit(event) 触发一个自定义事件

然后通过 $on(event,callback) 去执行对应的 callback(回调函数)

(两个 event 是字符串,且必须名称相同)

但 $on 不能在父组件中监听子组件抛出的事件,所以我们要做到这一点,可以在父组件的模板中使用到子组件的时候,直接用 v-on 绑定 (和 $on 作用效果一致) 就像上面那样:

<button-counter v-on:increment-event='total_increment'></button-counter>

 

这样一来,自定义事件的雏形就变得和原生事件一样了 

即使这样,上面的代码可能还是有些难理解,我认为比较重要的是这一段:

 

increment: function () {
      this.counter += 1
      this.$emit('increment-event') }

 

因为我们对于事件的运用主要是:利用事件和函数绑定,从而在事件触发的时候能执行相印的函数

所以! 对于自定义事件,我们要解决的问题就是,“这个事件在什么时候被触发” 在上面的代码中,触发事件的时间(执行 this.$emit('increment-event') 的时间)

就恰恰是执行 this.counter += 1 的时候

 

 

自定义事件的作用

对此,我主要从两点阐述我的观点:(非官方文档内容,自己思考的,觉得不对的可以指出):

自定义事件的作用 1 ——“重新定义”了事件监听机制的范围

 

MDN 是这样描述 DOM 事件的:“DOM 事件被发送以通知代码已发生的有趣的事。每个事件都由基于 Event 接口的一个对象表示”

在我看来:当你使用事件的时候,你可能试图做这样一件事情: 在某一个特定的时间节点(或场景)做某个操作,例如调用一个函数。 而定位这个“时间节点”或“场景”的,就是事件。而我们对事件最喜欢做的事情,就是把事件和某个函数给绑定起来

 但我们可能一直都忽略了一个认知:我们认知范围内的事件,好像只有原生事件呀?例如 click(点击),focus(聚焦),keydown(按键)

 

我们认知内的事件,难道只有这些个固定的范围吗?点击是事件,按下键盘按钮是事件。那么,我们能不能人为地定义一个事件呢? 例如上面的,我们通过代码处理,让 "某个数据增加 1" 也作为一个事件,从而去触发一个函数呢?

这,就是自定义事件的目的和魅力

 

自定义事件的作用 2 ——使得父子组件权责明确

就让我们看一下 父组件的这个模板吧,在这里,我们发现:

1. 父组件不知道子组件究竟做了什么(increment 事件触发前的处理),同时也无需关心

2. 父组件只要完成它的任务:在 increment 事件触发的时候执行对应的函数就足够了

对子组件反是

 

所以,从这个角度上说,自定义事件使得父子组件“权责明确”

 

【注意】官方文档的示例可能容易制造这样一种错觉:自定义事件是以原生事件(如 click)为基础的, 但实际上并不是这样。

虽然自定义事件和原生事件息息相关,但自定义事件并不以原生事件的触发为基础的

 

回到顶部

Slot 的使用

当你试图使用 slot 的时候,你可能试图做这样一件事情:

用父组件动态地控制子组件的显示的内容

Vue.component('son-component', {
  template: '<div><slot></slot></div>'
})

new Vue({
el:
'#app'
})

模板 HTML:

<div id='app'>
      <p> 这是 slot 的内容 </p>
      <son-component>
        <p> 你好,我是 slot</p>
      </son-component>
</div>

 

demo:

 

【写给 react 的同学看的】你可以把 slot 看作是个 3.0 版本的 props.children

 

通俗的理解

在父组件模板中使用子组件的时候,如果在子组件里面嵌套了 HTML 的内容,它会以”props“的方式传递给子组件的模板,并被子组件中的 slot 接受,例如:

 

【一个不太专业的说法】

<son-component>
   <p> 你好,我是 slot</p>
</son-component>

等同于

<son-component  slot = '<p> 你好,我是 slot</p>'></son-component>

 

具名 slot

为了使增强 slot 的用法,使父组件能够更加灵活地控制子组件,Vue 引入了具名 slot

通过 name 属性,可以把在父组件中控制子组件同时渲染不同的 slot

Vue.component('son-component', {
  template: '<div><slot name="h1"></slot><slot name="button"></slot><slot name="a"></slot></div>'
})

new Vue({
el:
'#app'
})

 

HTML 模板部分:

<div id='app'>
  &lt;son-component&gt;

    &lt;h1 slot='h1' &gt;我是标题&lt;/h1&gt;

    &lt;button slot='button'&gt;我是按钮&lt;/button&gt;

    &lt;a href='#' slot='a'&gt;我是链接&lt;/a&gt;

  &lt;/son-component&gt;

</div>

 demo:

 

【注意事项】

1. 实际上,我觉得我这篇文章的语言有些过于罗嗦(其实很为难,因为说多了怕罗嗦——”太长不看“),说少了又怕不能完整地表达自己的意思,这是我权衡后的所做的结果

2. 文中很多只为知识点服务,跟实际的项目构建存在很大差异,例如我把很多模板都放在 template 选项中,而实际上我们会使用 Vue 单文件组件来实现这些

【完】