概述
Vue.js是一个构建数据驱动的 web 界面的渐进式框架。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。核心是一个响应的数据绑定系统。
可见组件化在vue.js中的重要作用。组件可以扩展 HTML 元素,封装可重用的代码。
1.注册组件
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
1.1全局注册
也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
Vue.component('my-component-name', { /* ... */ })
Vue.component 的参数:
- 组件的名字,可以使用‘kebab-case’的方法(my-component-name),或是驼峰命名(MyComponentName),但调用的时候建议使用kebab-case方法(my-component-name)
- 组件的具体细节,因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
实例
<div id="app">
<!-- 调用组件,可以多次调用 -->
<vue-demo></vue-demo>
<vue-demo></vue-demo>
</div>
<script>
//注册全局组件
Vue.component('vue-demo',{
data() {
return {
msg: 'vue-demo'
}
},
template: '<div>这是组件:{{msg}}</div>'
})
//new Vue 创建的 Vue 根实例
new Vue({
el: '#app',
})
</script>
1.2局部注册
我们通常使用webpack 构建vue系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。
我们定义component-a.vue
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
data() {
return {
msg: 'this is component A'
};
}
};
</script>
<style scoped>
</style>
在B.vue中引用,别的可以在引用B
<template>
<div>
<component-a></component-a>
</div>
</template>
<script>
import componentA from '../components/test';
export default {
data() {
return {
};
},
components: {
componentA: componentA
}
};
</script>
<style scoped>
</style>
可以看到
- import componentA from ‘./components/component-a.vue’; 将组件引入
- components: { } 将组件定义为 componentA
- 定义componentA为组件名,通过 component-a 这样的驼峰使用组件
1.3 组件data 必须是一个函数
定义一个Vue实例
new Vue({
el: '#app',
data:{
msg: '这是vue实例'
}
})
组件
data: function () {
return {
msg: '这是vue组件'
}
}
这样可以保证实例可以维护一份被返回对象的独立的拷贝。
我们使用vue-cli等配置webpack构建项目的时候,其实只有一个vue实例在main.js中定义,data使用的是对象的方式,而其他的vue页面其实都是组件,data使用的都是函数的方式
1.4 单个根元素
组件模板最终应该只有一个包含元素
template元素中只有(也必须有一个包裹元素,这里是div)
<template>
<div>
<p>{{msg}}</p>
<p>{{num}}</p>
</div>
</template>
错误示范:
<template>
<p>{{msg}}</p>
<p>{{num}}</p>
</template>
1.5使用子组件的注意事项
有些 HTML 元素,诸如ul、ol、table 和 select,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 li、tr和 option,只能出现在其它某些特定的元素内部。
这就使得如我们子组件是有div包裹的:
<table>
<component-a></component-a>
</table>
将不会显示
我们应该采用下面的调用方式
<table>
<tr is="blog-post-row"></tr>
</table>
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:template: ‘…’)
- 单文件组件 (.vue)
- script type=”text/x-template”
这就使得如我们采用的是类似vue-cli这样的配置webpack的架构,就可以不用考虑这个问题了。
2.父子组件之间通信
因为我们通常使用webpack配合局部定义组件的方式更多,所以这里的主要讲解webpack配合局部定义组件,全局的差不了太多
2.1 父组件向子组件通信
2.1.1 利用props定义需要传递的参数
子组件
<template>
<div>
<p>{{num}}</p>
</div>
</template>
<script>
export default {
props: ['num'],
data() {
return {
};
}
};
</script>
父组件
<component-a num="this is num"></component-a>
结果:
可以看到显示了父组件传过来的 this is num 字符串
2.1.2 props大小写
子组件中定义参数使用驼峰,父组件传参使用kebab-case
子组件:
export default {
props: ['compontMsg'],
};
父组件
<component-a compont-msg="this is messsage"></component-a>
2.1.3 静态的和动态的 Prop
传递静态的值,也就是说传递的就是字符串”this is num”
<component-a num="this is num"></component-a>
传递动态的值
以v-bind:开头定义的参数,会以js解析 ,person.num不再是字符串,而是JavaScript 表达式
<component-a v-bind:num="person.num"></component-a>
2.1.3.1 传递数字
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。不加‘v-bind’就变成字符串42了-->
<component-a v-bind:likes="42"></component-a>
<!-- 用一个变量进行动态赋值。-->
<component-a v-bind:likes="post.likes"></component-a>
2.1.3.2 传递一个布尔值
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<component-a favorited></component-a>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<component-a v-bind:favorited="false">
<!-- 用一个变量进行动态赋值。-->
<component-a v-bind:favorited="post.currentUserFavorited">
2.1.3.3 传入一个数组
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<component-a v-bind:comment-ids="[234, 266, 273]"></component-a>
<!-- 用一个变量进行动态赋值。-->
<component-a v-bind:comment-ids="post.commentIds"></component-a>
2.1.3.4 传入一个对象
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<component-a v-bind:comments="{ id: 1, title: 'My Journey with Vue' }"></component-a>
<!-- 用一个变量进行动态赋值。-->
<component-a v-bind:obj="person"></component-a>
2.1.3.5 传入一个对象的所有属性
如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)。
对象 person
{
name: 'demo',
age: 12
}
使用模板
<component-a v-bind="person"></component-a>
等价于:
<component-a v-bind:name="person.name" v-bind:age="person.age"></component-a>
2.1.4 子组件更改父组件传的参数
- 参数只能由 父组件 ====> 子组件,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
- 每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。(这意味着你不应该在一个子组件内部改变
prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。)
但是有的时候我们确实需要更改
方法1:赋值给data中别的变量
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
方法2:利用计算属性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
2.1.5 Prop 验证
我们可以设置参数的类型,type 可以是下列原生构造函数中的一个:
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
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
}
}
}
})
额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。
例如,给定下列现成的构造函数:
function Person (name, age) {
this.name = name;
this.age = age
}
验证父组件传的参数是不是Person的实例
Vue.component('components-a', {
props: {
author: Person
}
})
2.1.2非 Prop 的特性
一个非 prop 特性是指传向一个组件,但是该组件并没有相应 prop 定义的特性。
因为显式定义的 prop 适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的特性,而这些特性会被添加到这个组件的根元素上。
像在html标签中添加data-开头的自定义属性一样,给自定义组件添加任意的属性。而不仅限于data-*形式,这样做的话,Vue会把这个属性放在自定义组件的根元素上。一个自定义组件的模板只能有一个根元素。
例如:
<div id="app">
<my-component success="yes"></my-component2>
</div>
<script>
Vue.component('my-component', {
template: `<div success="no">子组件原有的特性被覆盖了</div>`
})
new Vue({
el: '#app'
})
</script>
结果:
组件中div success属性值变为’yes‘
只有class 和 style 特性会稍微智能一些,父组件传递的值和子组件的值会被合并起来,其他的都会被父组件传递的值覆盖。
2.2 子组件影响父组件
其实就是通过事件向父组件发送消息
- 通过 $on(eventName, eventHandler) 侦听一个事件
- 通过 $once(eventName, eventHandler) 一次性侦听一个事件
- 通过 $off(eventName, eventHandler) 停止侦听一个事件
父组件
<template>
<div>
<component-a compont-msg="this is messsage" @addfn="add"></component-a>
<p>{{number}}</p>
</div>
</template>
<script>
import componentA from '../components/test';
export default {
data() {
return {
number: 0
};
},
methods: {
add(a) {
this.number++;
console.log(a);
}
},
components: {
componentA: componentA
}
};
</script>
子组件
<template>
<div>
<p @click='go'>{{compontMsg}}</p>
</div>
</template>
<script>
export default {
props: ['compontMsg'],
methods: {
go() {
this.$emit('addfn',10);
}
}
};
</script>
这样一来 点击子组件的p标签,相当于调用父组件的add函数
如上所示,共分为以下步骤:
1.子组件在自己的方法中将自定义事件以及需要发出的数据通过以下代码发送出去
this.$emit('addfn',10);
- 第一个参数是自定义事件的名字
- 第二个参数是依次想要发送出去的数据
2.父组件利用v-on为事件绑定处理器
<component-a compont-msg="this is messsage" v-on:addfn="add"></component-a>
这样就可以调用父组件的methods方法中add方法了,并将10传入
注意:
在使用v-on绑定事件处理方法时,不应该传进任何参数,而是直接写v-on:addfn=”add”,不然,子组件暴露出来的数据就无法获取到了
推荐始终使用 kebab-case 的事件名
例如 @add-event: 不是@addEvent
2.3 父子组件的v-model实现双向数据绑定
2.3.1 探究v-model
v-model可以对表单控件实现数据的双向绑定,它的原理就是利用了绑定属性和事件来实现的。
比如input控件。不使用v-model,可以这样实现数据的双向绑定:
<div id="app">
<input type="text" v-bind:value="text" v-on:input="changeValue($event.target.value)">
{{text}}
</div>
<script>
new Vue({
el: '#app',
data: {
text: 'text'
},
methods: {
changeValue (value) {
this.text = value
}
}
})
</script>
上面的代码同样实现了数据的双向绑定。其本质就是:
- 把input的value特性绑定到Vue实例的属性text上,text改变,input中的内容也会改变
- 然后把表单的input事件处理函数设置为Vue实例的一个方法,这个方法会根据输入参数改变Vue中text`的值
- 相应的,在input中输入内容时,触发了input事件,把event.target.value传给这个方法,最后就实现了改变绑定的数据的效果。
而v-model就是上面这种方式的语法糖,也就是把上面的写法封装了一下,方便我们使用。
2.3.2 v-model实现父子组件双向数据绑定
父组件
<template>
<div>
<component-a v-model='fatherValue'></component-a>
<p>{{fatherValue}}</p>
</div>
</template>
<script>
import componentA from '../components/test';
export default {
data() {
return {
fatherValue: 'this is value'
};
},
components: {
componentA: componentA
}
};
</script>
子组件
<template>
<div>
<input type="text" :value="value" @input="go($event.target.value)">
</div>
</template>
<script>
export default {
props: ['value'],
methods: {
go(value) {
this.$emit('input', value);
}
}
};
</script>
结果:
步骤:
- 父组件
<component-a v-model='fatherValue'></component-a>
其实就是
<component-a :value='fatherValue' @input="fatherValue = $event"></component-a>
也就是向子元素传递参数 value(这个参数名是固定的,不能改变), 参数值为父组件定义的 fatherValue
父组件定义input事件,触发时将父组件的定义的 fatherValue 重新赋值
- 子组件
子组件通过 props: [‘value’],接受参数 ‘value‘,
<input type="text" :value="value" @input="go($event.target.value)">
将子组件的input的value赋值父组件传过来的参数值,并在触发input事件时去调用父组件的input事件,并将子组件input的值传给父组件,将父组件的fatherValue赋值
2.4绑定原生事件
如果想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on
<base-input v-on:focus.native="onFocus"></base-input>
2.5 .sync 修饰符
上面已经讲到了,可以实现父子组件的通信, 我们建议在定义父组件的方法时采用‘update:my-prop-name’ 的模式
父组件:
<template>
<div>
<component-a :number="fNumber" @update:number="fNumber=$event"></component-a>
<!-- 可以简写为:-->
<!-- <component-a :number.sync="fNumber"></component-a> -->
<p>{{fNumber}}</p>
</div>
</template>
<script>
import componentA from '../components/test';
export default {
data() {
return {
fNumber: 1
};
},
components: {
componentA: componentA
}
};
</script>
子组件
<template>
<div>
<button @click="go">{{number}}</button>
</div>
</template>
<script>
export default {
props: ['number'],
data() {
return {
monery: this.number
};
},
methods: {
go() {
this.monery++;
this.$emit('update:number', this.monery);
}
}
};
</script>
实现了 每次点击子组件的按钮,就可以传给父组件一个 +1 值。实现了数据的双向绑定
并且将以下形式的
<component-a :number="fNumber" @update:number="fNumber=$event"></component-a>
简写为
<component-a :number.sync="fNumber"></component-a>
3.向子组件插入内容
在使用子组件时,直接插入内容是无法显示的
<component-a >this is hcd</component-a>
这是就用到了插槽—solt
3.1 单个slot
slot相当于子组件设置了一个地方,如果在调用它的时候,往它的开闭标签之间放了东西,那么它就把这些东西放到slot中。
- 当子组件中没有slot时,父组件放在子组件标签内的东西将被丢弃;
- 子组件的slot标签内可以放置内容,当父组件没有放置内容在子组件标签内时,slot中的内容会渲染出来;
- 当父组件在子组件标签内放置了内容时,slot中的内容被丢弃
子组件的模板:
<div>
<h2>我是子组件的标题</h2>
<slot>
默认的显示内容
</slot>
</div>
父组件模板:
<div>
<h1>我是父组件的标题</h1>
<my-component>
<p>这是一些初始内容</p>
</my-component>
</div>
渲染结果:
<div>
<h1>我是父组件的标题</h1>
<div>
<h2>我是子组件的标题</h2>
<p>这是一些初始内容</p>
</div>
</div>
3.2 具名slot
- 每个元素的slot属性分配一个代表slot的名字
- 子组件可以有一个匿名的slot,当分发的多余内容找不到对应的slot时,就会进入这里面
- 如果子组件没有匿名的slot,当分发的多余内容找不到对应的slot时,就会被丢弃
子组件:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
父组件
<component-a>
<h1 slot="header">这里可能是一个页面标题</h1>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
<p slot="footer">这里有一些联系信息</p>
</component-a>
渲染结果
<div class="container">
<header>
<h1>这里可能是一个页面标题</h1>
</header>
<main>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
</main>
<footer>
<p>这里有一些联系信息</p>
</footer>
</div>
3.3 作用域插槽
作用域插槽也是一个插槽slot,但是他可以把数据传递给到父组件的特定元素内,然后父组件决定如何渲染这些数据。
子组件:(本来是显示name)
<template>
<div>
<ul>
<li v-for="(obj,index) of persons" :key="index">
<slot :object="obj">{{obj.name}}</slot>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
persons: [
{
name: 'a',
age: 12
},
{
name: 'b',
age: 13
}
]
};
}
};
</script>
父组件(让子组件不再显示name,而是显示age)
<template>
<div>
<component-a>
<template scope="props">
<span>
{{props.object.age}}
</span>
</template>
</component-a>
</div>
</template>
scope特性的值,就代表了所有子组件传过来的数据组成的对象。相当于
props: {
object:{
name:-- ,
age:--
}
}
4.动态组件 & 异步组件
4.1动态组件
其实就是一个父组件引入多个子组件,并在多个子组件间切换
父组件:
<template>
<div>
<select v-model="currentComponent">
<option value="componentA">componentA</option>
<option value="componentB">componentB</option>
</select>
<component :is="currentComponent"></component>
</div>
</template>
<script>
import componentA from '../components/test';
import componentB from './name';
export default {
data() {
return {
currentComponent: 'componentA'
};
},
components: {
componentA: componentA,
componentB: componentB
}
};
</script>
动态地绑定到它的 is 特性,is属性值currentComponent对应不同的组件。
4.1.1 避免组件重新渲染
上面的代码,会使得每一次切换都需要重新渲染,这并不是我们想要的
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
4.2异步获取组件
父组件获取子组件,同步:
import componentA from '../components/test';
import componentB from './name';
export default {
components: {
componentA: componentA,
componentB: componentB
}
};
异步:
只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会被触发,且会把结果缓存起来供未来重渲染
export default {
components: {
componentA: () => import('../components/test'),
componentB: () => import('./name')
}
};
5.组件常用知识点
5.1 组件访问根vue实例
// Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
}
methods: {
baz: function () { /* ... */ }
}
})
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
注意这是访问根Vue实例,不一定是父组件,例如vue-cli就一个Vue实例
5.2 子组件访问父组件
// 获取父组件的数据
this.$parent.foo
// 写入父组件的数据
this.$parent.foo = 2
// 访问父组件的计算属性
this.$parent.bar
// 调用父组件的方法
this.$parent.baz()
//调用父组件的父组件
this.$parent.$parent
5.3 父组件访问子组件
首先给这个子组件唯一表示 这里用hcd表示
<component ref="hcd"></component>
// 获取父组件的数据
this.$refs.hcd.foo
// 写入子组件数据
this.$refs.hcd.foo = 2
// 访问子组件的计算属性
this.$refs.hcd.bar
// 调用子组件的方法
this.$refs.hcd.baz()
5.4组件之间相互引用
简单来说就是 A引用B B也有引用了A
如果按照同步的做法是会报错的,我们可以采用异步的做法
components: {
componentA: () => import('./componentA')
},
components: {
componentB: () => import('./componentB')
},
5.5 通过 v-once 创建低开销的静态组件
渲染普通的 HTML 元素在 Vue 中是非常快速的,但有的时候你可能有一个组件,这个组件包含了大量静态内容。在这种情况下,你可以在根元素上添加 v-once 特性以确保这些内容只计算一次然后缓存起来,就像这样:
Vue.component('terms-of-service', {
template: `
<div v-once>
<h1>Terms of Service</h1>
... a lot of static content ...
</div>
`
})
注意 :
一旦这样做了就会导致组件为什么无法正确更新,所以谨慎使用
最后
以上就是靓丽向日葵为你收集整理的Vue组件1.注册组件2.父子组件之间通信3.向子组件插入内容4.动态组件 & 异步组件5.组件常用知识点的全部内容,希望文章能够帮你解决Vue组件1.注册组件2.父子组件之间通信3.向子组件插入内容4.动态组件 & 异步组件5.组件常用知识点所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复