Skip to content

组件

什么是组件

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。

所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。

理解组件

简单理解,组件其实就是一个独立的 HTML,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像 iframe,它都是在页面中引入之后展现另一个页面的内容,但实际上它与 iframe 又完全不同,iframe 是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。

javascript
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为什么使用组件

举个简单的列子,最近我的项目中有一个日历模块,多个页面都要用这个日历,而每个页面的日历都存在一些差别,如果不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。

而使用组件呢?一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制即可。

javascript
<calendar></calendar>

我可以通过给 calendar 传递值实现在本页面对日历的控制,让它满足我这个页面的某些单独需求。

有人会问,你 calendar 标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar 就是我自定义的元素,它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。

组件name

在组件中设置name,这样在vue的调试工具中就不会全部显示index组件,而是设置的name,方便找到对应的组件。

vue
<template>

</template>

<script lang="ts">
export default {
    name:"组件名称"
}
</script>

组件注册

注册组件

我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过 Vue.component 来注册一个全局组件

Javascript
// 全局注册
Vue.component(componentName, {
    //选项
})

// 局部注册
new Vue({
    el:"#app",
    components:{
        componentName:{
            template:`
            	<div>
            		<p></p>
            		<p></p>
            		<p></p>
            	</div>
            `
        }
    }
})

对于自定义组件的命名,Vue.js 不强制遵循 W3C 规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。

组件名大小写

定义组件名的方式有两种:

使用 kebab-case

javascript
Vue.component('my-component-name', { /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

使用 PascalCase

javascript
Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name><MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

javascript
1- 如果组件名字小写,在视图中小写即可实现渲染
2- 如果组件名字首字母大写,其它小写,那么视图中可以实现渲染(首字母可大写,也可小写)
3- 如果使用驼峰命名,在视图中渲染需要用-进行分割多个单词。
// 建议:
形式一:首字母大写
形式二:全部小写,单词之间用-分割 //(建议 官方写法)
形式三:定义组件采用大驼峰命名,视图中渲染需要全部小写,岮峰之间使用-

组件的选项

  • 与创建Vue示例时的选项相同(除了一些根级特有的选项)
  • 一个组件的 data 选项必须是一个函数 (每个组件实例具有自己的作用域,组件复用不会互相影响)
javascript
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

组件的使用

html
<div id="components-demo">
  <button-counter></button-counter>
</div>
javascript
new Vue({ el: '#components-demo' })

组件复用

你可以将组件进行任意次数的复用:

html
<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

这里有一个 Vue 组件的示例:

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

组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用:

javascript
<div id="components-demo">
  <button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

组件data必须是一个函数

当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:

data: {
  count: 0
}

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:

javascript
// 函数必须返回单例模式的对象,可以让组件中的数据独立
data: function () {
  return {
    count: 0
  }
}

如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例

单个根元素

每个组件必须只有一个根元素

你可以将模板的内容包裹在一个父元素内

javascript
<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>

组件模板

每个组件模板必须只有一个根元素

模板的形式:

  • template 选项指定字符串 (模板字符串)

    javascript
    // 使用字符串 进行拼接
    new Vue({
        el:"#app",
        data:{},
        template:"<div>"+"<p>{{a}},{{b}}</p>"+""+"</div>"
    })
    
    // 或 使用模版字符串 进行拼接
    new Vue({
        el:"#app",
        data:{},
        template:`
        	<div>
        		<p>{{a}}</p>
    			<p>{{b}}</p>
        	</div>
        `
    })
  • 单文件组件(.vue)

  • 内联模板 (不推荐)

    html
    <my-component inline-template>
      <div>
        <p>These are compiled as the component's own template.</p>
        <p>Not parent's transclusion content.</p>
      </div>
    </my-component>
  • x-templates

    Html
    // 指定id为tp的script标签包裹的内容为模板的内容
    <script type="text/x-template" id="hello-world-template">
      <p>Hello hello hello</p>
    </script>
    javascript
    // 全局组件
    //
    Vue.component('hello-world', {
      template: '#hello-world-template'
    })
    
    // 局部组件
    // 一旦指定了模板,那么会将模板的内容替换掉el所指定的挂载元素。
    new Vue({
        el:"#app",
        data:{
            a:1,
            b:2
        },
        methods:{},
        filters:{},
        computed:{},
        // 指定id为tp的script标签包裹的内容为模板的内容
        template:"#hello-world-template"
    })
  • template标签

    javascript
    <template id="hello-world-template">
      <p>Hello hello hello</p>
    </template>
    javascript
    // 全局组件
    Vue.component('hello-world', {
      template: '#hello-world-template'
    })
    
    // 局部组件
    // 一旦指定了模板,那么会将模板的内容替换掉el所指定的挂载元素。
    new Vue({
        el:"#app",
        data:{
            a:1,
            b:2
        },
        methods:{},
        filters:{},
        computed:{},
        // 指定id为tp的script标签包裹的内容为模板的内容
        template:"#hello-world-template"
    })

全局组件

使用 Vue.component()注册的组件都是全局组件

这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。比如:

全局组件的注册要写在Vue实例化的前边

javascript
Vue.component('my-component-name', {
  // ... options ...
})
javascript
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
html
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

全局对象的推荐写法

javascript
// index.html
import components from "./components/index.js";
 new Vue({
     
 })
javascript
// index.js
import One from "./One.js";
import Two from "./Two.js";
// 暴露出去的是一个对象,对象中的属性即是全局组件的配置信息,属性的名字即是组件的名字。
export default {
	One,
	Two
}

局部组件

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

javascript
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { 
	template: `
        <div>
            <h3>Hanser</h3>
        </div>
	`
}

然后在 components 选项中定义你想要使用的组件:

javascript
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。

注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentAComponentB 中可用,则你需要这样写:

javascript
var ComponentA = { /* ... */ }

// 局部组件的嵌套使用
var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

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

javascript
import ComponentA from './ComponentA.vue'

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

在对象中放一个类似 ComponentA 的变量名其实是 ComponentA: ComponentA 的缩写

模块系统

推荐创建一个 components 目录,并将每个组件放置在其各自的文件中。

然后你需要在局部注册之前导入每个你想使用的组件。例如,在一个假设的 ComponentB.jsComponentB.vue 文件中:

javascript
import ComponentA from './ComponentA.js'
import ComponentC from './ComponentC.js'

export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}
javascript
// ComponentA.js
import PageList from "./PageList.js";
// 评论区域模版
const template = (`
	<div class="takeComment">
		<textarea name="textarea" class="takeTextField" id="tijiaoText"></textarea>
		<div class="takeSbmComment">
			<input type="button" class="inputs" value="" />
		</div>
	</div>
`)
// 组件的嵌套
const components = {
    PageList
}
export default{
    template,
    components
}

现在 ComponentAComponentC 都可以在 ComponentB 的模板中使用了。

基础组件的自动化全局注册

可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。

所以会导致很多组件里都会有一个包含基础组件的长列表:

javascript
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'

export default {
  components: {
    BaseButton,
    BaseIcon,
    BaseInput
  }
}

而只是用于模板中的一小部分:

html
<BaseInput
  v-model="searchText"
  @keydown.enter="search"
/>
<BaseButton @click="search">
  <BaseIcon name="search"/>
</BaseButton>

如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js) 中全局导入基础组件的示例代码:

javascript
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // 其组件目录的相对路径
  './components',
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式
  /Base[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 实例 (通过 new Vue) 创建之前发生

Prop

组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。

每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。

image-20240529104046408

Vue实例与组件实例

给Vue.prototype设置属性,一般自定义属性前边加$

javascript
Vue.prototype.$useName = "zhangsan";

组件中的vc(VueComponent函数返回的)对象在Vue.prototype的原型链上

Vue.prototype对象中有什么属性,那么组件的实例vc就拥有什么属性

javascript
vc也是通过New Vue生成的
所以vc.__proto__.__proto__ 不等于 Vue.prototype
vm.__proto__ == Vue.prototype
javascript
// 给Vue.prototype设置属性useName 
    Vue.prototype.$useName = "zhangsan";
    // 创建Vue实例vm
	const vm = new Vue({
		el:'#app',
        methods:{
            fun(){
                console.log(this === vm);// true this是通过Vue构造函数 实例化出来的 vm
                console.log(this.__proto__ === Vue.prototype);// true
                console.log(vm.__proto__ === Vue.prototype);// true
                console.log(this.$useName)  // zhangsan
                console.log(this.__proto__.$useName)    // zhangsan
            },
        },
javascript
components:{
// Hanser组件
Hanser:{
   // Vue.prototype对象中有什么属性,那么组件的实例vc就拥有什么属性
   // 组件中的vc实例在Vue.prototype的原型链上
   template:(`<button @click="fun2">Click Hanser</button>`),
   methods:{
     fun2(){
       // this是通过VueComponent函数 创建出来的 vc 
       console.log(this); 
       console.log(this.__proto__); //Vue{}的对象
       // false 组件中vc.__proto__与 vm 不是同一个对象
       console.log(this.__proto__ === vm); 
       //true => 组件中的实例在Vue.prototype的原型链上  
       console.log(this.__proto__.__proto__ === Vue.prototype);    
       console.log(this.$useName)      // zhangsan
       console.log(this.__proto__.__proto__.$useName) // zhangsan
      },
   },
 },
}

通过Prop向子组件传递数据

javascript
父向子组件传递数据通过属性传值 子组件设置props选项,父元素通过给子组件的props['属性名']中的属性名传值。
子组件不可以直接使用传递过来的属性,需要对其通过props进行设置
一旦子元素在props中指定接收的属性名后,那么函数,过滤器等不允许与设置的属性名相同

Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。为了给博文组件传递一个标题,我们可以用一个 props 选项将其包含在该组件可接受的 prop 列表中:

静态传值

在子组件中声明 prop,然后添加一个 message

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

一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。

我们能够在组件实例中访问这个值,然后直接传入值就可以在子组件中使用 message,就像访问 data 中的值一样

一个 prop 被注册之后,就可以像这样把数据作为一个自定义 attribute 传递进来:

Html
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

动态传值

prop 可以通过 v-bind 动态赋值,例如

javascript
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
  v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
vue
<template> 
<Child :userName="userName" :age="age" hobby="1"/>
<!--  上面使用子组件的语法糖,效果相同,一种简写  -->
<Child v-bind="{userName,age,hobby:'1'}" />
</template> 
<script>
import Child from "@/components/Child";
export default {
    name: "App",
    components: {Child},
    data(){
        return {
            userName:"zhangsan",
            age:10
        }
    }
}
</script>

在HTML中这样定义

javascript
<div id="app">
    <span>aqua的体重是:{{aquaWeight}}</span>

    <!-- 子组件中的属性名 = "父实例中的data属性名" -->
	<hanser 
    :hanser-weight="hanserWeight1" 
    :hanserpassword="hanserPassword1" 
    hanser-age = "12" 
    :hanser-sex = "hanserSex"
    ></hanser>
</div>

组件中

javascript
// 全局组件
const vc = Vue.component("hanser",{
    // 自定义子组件标签中的属性名(JS中使用小驼峰,HTML中使用小写- ) 
    props:['hanserWeight','hanserpassword','hanserAge','hanserSex'],
    // hanser标签模版
    // 模版中使用属性名就相当于使用父实例的data属性
    template:(`
                <div>
                    <span>Hanser的体重是:{{hanserWeight}}</span> <br>
                    <span>Hanser的密码是:{{hanserpassword}}</span>  <br>
                    <span>Hanser的年龄是:{{hanserAge}}</span>  <br>
                    <span>Hanser的性别是:{{hanserSex}}</span>  <br>
                </div>
        `),
    methods:{
        fun2(){

        },
    },        
})

Vue实例中

javascript
// 创建Vue实例vm
const vm = new Vue({
    el:'#app',
    data:{
        aquaWeight:44.5,
        hanserWeight1:47.4,
        hanserPassword1:"123123",
        hanserSex:"女"
    },
    methods:{
        fun(){

        },
    },

})

在一个典型的应用中,你可能在 data 里有一个博文的数组

javascript
new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }
})

并想要为每篇博文渲染一个组件:

javascript
<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
></blog-post>

传入各种类型的数据

javascript
在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。
传入一个数字
传入一个布尔值
传入一个数组
传入一个对象

注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

Prop 的大小写

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM (JS)中的模板时,camelCase (驼峰命名法) 的 prop 名时,视图上(HTML)的属性需要使用其等价的 kebab-case (短横线分隔命名) 命名

传入一个对象的所有属性

html
<blog-post v-bind="post"></blog-post>

等价于

html
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

看起来当组件变得越来越复杂的时候,我们的博文不只需要标题和内容,还需要发布日期、评论等等。为每个相关的信息定义一个 prop 会变得很麻烦:

javascript
<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
  v-bind:content="post.content"
  v-bind:publishedAt="post.publishedAt"
  v-bind:comments="post.comments"
></blog-post>

重构一下这个 <blog-post> 组件了,让它变成接受一个单独的 post prop:

javascript
<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:post="post"
></blog-post>
javascript
Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <div v-html="post.content"></div>
    </div>
  `
})
javascript
new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }
})

把post作为一个对象放入props选项中

prop初始值

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件不能直接修改传来的值(引入的方式))

javascript
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

Prop验证

我们可以为组件的 prop 指定验证要求

javascript
Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 匹配任何类型)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组且一定会从一个工厂函数返回默认值 //
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 datacomputed 等) 在 defaultvalidator 函数中是不可用的。

类型列表:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
  • 自定义的构造函数

额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

javascript
function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

你可以使用:

javascript
Vue.component('blog-post', {
  props: {
    author: Person
  }
})

来验证 author prop 的值是否是通过 new Person 创建的。

非 Prop 的 Attribute

javascript
详见 Vue官网

父向子传值

通过父向子组件传递一个方法函数,然后子组件调用这个函数达到视图改变

javascript
<div id="app">
	<button @click="changeIsShow(!isShow)">显示与隐藏</button>
	<app v-show="isShow" :change-is-show="changeIsShow"></app>
</div>
javascript
new Vue({
    el:"#app",
    data:{
        isShow:true
    },
    methods:{
        changeIsShow(isShow){
            this.isShow = isShow;
        }
    },
    components:{
        App:{
            props:{
                // props验证
                changeIsShow:Function
            },
            data(){
                return {
                    bol:true
                }
            },
            template:(`
					<div style="width:600px;height:300px;background:skyblue;">
						<button @click="changeIsShow(false)">关闭</button>
					</div>
				`)
        }
    }
})

插槽 slot

通过插槽分发内容

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

·Something bad happened.· 会替换掉 slot标签

模板中多个插槽

组件模板

html
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

调用组件

html
<base-layout>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <p slot="footer">Here's some contact info</p>
</base-layout>

插槽默认内容

vue
<button type="submit">
  <slot>Submit</slot>
</button>

动态组件&异步组件

实现动态组件

在不同组件之间进行动态切换 Vue提供的内置组件component,

参数is的表达式是**"组件名称""组件名称"**是谁,这个组件component就代表谁。

html
<component is="组件名" class="tab"></component>

// 绑定实例的属性
<component :is="arr[index]" class="tab"></component>
new Vue({
	data:{
        arr:['12','23']
        index:1
	}
})

实现选项卡案例

在动态组件上使用 keep-alive

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

主要用于保留组件状态或避免重新渲染

html
<!-- 基本 -->
<keep-alive>
  <component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</keep-alive>

绑定组件选项对象

动态组件可以绑定 组件选项对象(有component属性的对象),而不是已注册组件名的实例

javascript
var tabs = [
  {
    name: 'Home', 
    component: { 
      template: '<div>Home component</div>' 
    }
  },
  {
    name: 'Posts',
    component: {
      template: '<div>Posts component</div>'
    }
  },
  {
    name: 'Archive',
    component: {
      template: '<div>Archive component</div>',
    }
  }
]

new Vue({
  el: '#dynamic-component-demo',
  data: {
      tabs: tabs,
    currentTab: tabs[0]
  }
})
<component
    v-bind:is="currentTab.component"
    class="tab"
 >
</component>

处理边界情况

组件的其他特性

解析 DOM 模板时的注意事项

有些 HTML 元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。

html
<table>
  <blog-post-row></blog-post-row>
</table>

上面的写法,渲染效果会不甚理想,可以采用以下写法

html
<table>
  <tr is="blog-post-row"></tr>
</table>

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:

  • 字符串 (例如:template: '...')
  • 单文件组件 (.vue)
  • <script type="text/x-template">

Prop的一些问题

Prop的属性名问题

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

如果你使用字符串模板,那么这个限制就不存在了。

非Prop属性

组件上定义的非Prop属性 会传递到 组件模板的根元素上

class 和 style 特性会非常智能,即两边的值会被合并起来

对prop重新赋值

子组件中,对prop重新赋值,会报警告

组件事件的相关问题

将原生事件绑定到组件

想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符

html
<base-input v-on:focus.native="onFocus"></base-input>

.sync 修饰符

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”

推荐以 update:my-prop-name 的模式触发事件

javascript
//子组件中
this.$emit('update:title', newTitle)
<!-- 上级组件 模板中 -->
<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

以上写法可以换成下列写法

html
<text-document v-bind:title.sync="doc.title"></text-document>