组件的概念
组件是可复用的 Vue 实例,包含html、css、js三部分。
组件树
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
组件创建
局部组件
<body>
<div id="app">
<!--3. 使用组件 -->
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
// App组件
// 1.创建组件
//组件是可复用的vue实例
const App = {
// 一个组件就是一个对象
//template里写html结构
template: `
<div>
<h3>{{msg}}</h3>
<button @click='handleClick'>按钮</button>
</div>
`,
data(){
// 在组件中data必须是一个函数,返回一个对象,便于维护数据
return {
msg: '我是App组件'
}
},
methods:{
handleClick(){
this.msg = '局部组件'
}
},
computed:{
}
};
//vue就是一个根组件
const vm = new Vue({
el: "#app",
data: {
},
//2. 挂载组件
components: {
App
}
});
</script>
</body>
全局组件
Vue.compontent('组件名',{配置});
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
//1.创建全局组件,第一个参数是组件名,第二个是配置
//只要创建了全局组件可以在任意地方(template)使用
Vue.component('myHeader',{
template:`
<div>我是导航组件</div>
`
});
Vue.component('myAside',{
template:`
<div>我是侧边栏组件</div>
`
});
const myContent = {
data(){
return{
}
},
template:`
<div>我是内容组件</div>
`
}
const App = {
// 2.使用全局组件
template: `
<div>
<myHeader></myHeader>
<div>
<myAside/>
<myContent></myContent>
</div>
</div>
`,
components:{
myContent
}
};
const vm = new Vue({
el: "#app",
data: {
},
components: {
App
}
});
</script>
</body>
注意事项
组件命名
定义组件名有两种方式
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>。
使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说<my-component-name> 和 <MyComponentName>都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
组件通信
父传子
通过props来进行通信:
- 在
子组件中声明props接收在父组件挂载的属性 - 可以在子组件的template中任意使用
- 在
父组件中绑定自定义的属性
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('Appson',{
template:`
<div>
<h3>我是App的子组件</h3>
<h4>{{sonData}}</h4>
</div>`,
//接收父组件传值
props:['sonData']
})
const App = {
template:`
<div>
<Appson :sonData = 'msg'></Appson>
</div>`,
components:{
},
data(){
return {
msg:'我是父组件传进来的值'
}
}
};
const vm = new Vue({
el: "#app",
data: {
},
components: {
App
}
});
</script>
</body>
子传父
- 在
父组件中,绑定自定义的事件 - 在子组件中 ,触发原生的事件
- 在事件函数中通过this.$emit触发自定义的事件
- 自定义事件执行
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('Appson',{
template:`
<div>
<h3>我是App的子组件</h3>
<!--step2:在子组件sonData中,触发原生的input事件 -->
<input type='text'@input ='handleInp'/>
</div>`,
methods:{
handleInp(e){
const val = e.target.value;
//step3:在事件函数handleInp中通过this.$emit触发自定义的事件
this.$emit('inpHandle',val);
},
}
});
const App = {
template:`
<div>
<div class="father">数据:{{newVal}}</div>
<!--step1:在父组件App中,绑定自定义事件inpuHandle -->
<Appson @inpHandle='input' ></Appson>
</div>`,
data(){
return {
newVal:''
}
},
methods:{
//step4:自定义inpuHandle事件执行
input(newVal){
this.newVal = newVal;
}
}
};
const vm = new Vue({
el: "#app",
components: {
App
}
});
</script>
</body>
平行组件通信
通过中央事件总线
const bus = new Vue();
//bus.$on()绑定事件
//bus.$emit()触发事件
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('A',{
template:`
<button @click = 'handleClick'>加入购物车</button>
`,
methods:{
handleClick(){
bus.$emit('add',1);
}
}
});
// 中央事件总线 bus
const bus = new Vue;
Vue.component('B',{
template:`
<div>{{count}}</div>
`,
data(){
return{
count:0
}
},
created(){
// $on 绑定事件
bus.$on('add',(n)=>{
this.count += n;
});
// $emit 触发事件
}
});
const App = {
template:`
<div>
<A></A>
<B></B>
</div>`,
};
const vm = new Vue({
el: "#app",
components: {
App
}
});
</script>
</body>
其他通信方式
通过provide、inject,无论组件嵌套多深,都能实现。
- 父组件 provide提供变量
- 子组件 inject接收变量
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('A',{
template:`
<div>
<B></B>
</div>
`,
created(){
console.log(this.$parent);//App组件
console.log(this.$parent.title);//grandfather
}
});
Vue.component('B',{
template:`
<div>{{msg}}</div>
`,
inject:['msg'],
created(){
console.log(this.msg);
}
});
const App = {
template:`
<div>
<A></A>
</div>`,
data(){
return{
title:'grandfaher'
}
},
provide(){
return {
msg:'grandfather'
}
}
};
const vm = new Vue({
el: "#app",
components: {
App
},
});
</script>
</body>
插槽
匿名插槽
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('Mbtn',{
template:`
<button>
<slot></slot>
</button>
`
});
const App = {
template:`
<div>
<Mbtn>登录</Mbtn>
<Mbtn>注册</Mbtn>
</div>`,
data(){
return{
title:'grandfaher'
}
},
};
const vm = new Vue({
el: "#app",
components: {
App
},
});
</script>
</body>
具名插槽
只要匹配到slot标签的name值,模板template中的内容就会被插入到槽中。
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('Mbtn',{
template:`
<button>
<slot name="login"></slot>
<slot name="submit"></slot>
<slot name="reset"></slot>
</button>
`
});
const App = {
template:`
<div>
<Mbtn>
<template slot="login">
<a href='#'>登录</a>
</template>
</Mbtn>
<Mbtn>
<template slot="submit">
注册
</template>
</Mbtn>
<Mbtn>
<template slot="reset">
重置
</template>
</Mbtn>
</div>`,
data(){
return{
title:'grandfaher'
}
},
};
const vm = new Vue({
el: "#app",
components: {
App
},
});
</script>
</body>
作用域插槽
针对项目需求使用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const todoList = {
data(){
return{
}
},
props:{
todos:Array,
defaultValue:[]
},
template:`
<ul>
<li v-for='item in todos' :key='item.id'>
<slot :itemValue='item'>
</slot>
{{item.title}}
</li>
</ul>
`
}
const App = {
data(){
return{
todoList:[
{
title:'vue基础',
isComplate:true,
id:1
},
{
title:'vue组件化',
isComplate:false,
id:2
},
{
title:'vue全家桶',
isComplate:false,
id:3
},
{
title:'vue实战',
isComplate:false,
id:4
},
]
}
},
components:{
todoList
},
template:`
<todoList :todos='todoList'>
<template v-slot="data">
<input type="checkbox" v-model="data.itemValue.isComplate"/>
</template>
</todoList>
`
};
const vm = new Vue({
el: "#app",
components: {
App
},
});
</script>
</body>
</html>
生命周期
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- activated
- deactivated
- beforeDestroy
- destroyed
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.active{
color: red;
}
</style>
</head>
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('Test',{
data(){
return{
msg:'john',
isActive:false
}
},
template:`
<div>
<button @click = 'handleClick'>修改</button>
<h3 :class={active:isActive}>{{msg}}</h3>
</div>
`,
methods:{
handleClick(){
this.msg = '改变了';
this.isActive = true;
}
},
beforeCreate(){
console.log('组件创建之前',this.$data);
},
created(){
//此时发送ajax,请求后端的数据
console.log('组件创建完成',this.$data);
},
beforeMount(){
// 即将挂载
console.log('dom挂载之前',document.getElementById('app'));
},
mounted(){
// 发送ajax
console.log('dom挂载完成',document.getElementById('app'));
},
beforeUpdate(){
console.log('更新之前的dom',document.getElementById('app').innerHTML);
},
updated(){
console.log('更新之后的dom',document.getElementById('app').innerHTML);
},
beforeDestroy(){
console.log('销毁之前');
},
destroyed(){
console.log('销毁完成');
},
activated(){
console.log('组件被激活了');
},
deactivated(){
console.log('组件被停用了');
}
});
const App = {
data(){
return{
isShow:true
}
},
template:`
<div>
<keep-alive>
<Test v-if='isShow'></Test>
</keep-alive>
<button @click = 'handleDestory'>改变生死</button>
</div>
`,
methods:{
handleDestory(){
this.isShow = !this.isShow;
}
}
};
const vm = new Vue({
el: "#app",
components: {
App
}
});
</script>
</body>
</html>
异步组件加载
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
</style>
</head>
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="module">
const App = {
data(){
return{
isShow:false
}
},
components:{
Test:()=> import('./Test.js')
},
template:`
<div>
<button @click='asyncLoad'>异步加载</button>
<Test v-if='isShow'></Test>
</div>
`,
methods:{
asyncLoad(){
this.isShow = !this.isShow
}
}
};
const vm = new Vue({
el: "#app",
components: {
App
}
});
</script>
</body>
</html>
Test.js
export default {
data(){
return{
msg:'john'
}
},
template:`
<div>
<h3>{{msg}}</h3>
</div> `
};
refs的使用
访问子组件实例或子元素
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 这个 attribute 为子组件赋予一个 ID 引用。
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const Test = {
data(){
return{
msg:'john'
}
},
template:`
<div>
<h3>{{msg}}</h3>
</div>
`
};
const App = {
components:{
Test
},
mounted(){
//如果给标签添加ref,获取的就是真实的dom节点
console.log(this.$refs.btn);
//加载页面自动获取焦点
this.$refs.input.focus();
//如果给组件添加ref,获取的就是该组件
console.log(this.$refs.test);
},
template:`
<div>
<Test ref='test'></Test>
<input type="text"ref="input"/>
<button ref='btn'>改变生死</button>
</div>
`,
};
const vm = new Vue({
el: "#app",
components: {
App
}
});
</script>
</body>
nextTick的使用
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
<body>
<div id="app">
<h3>{{message}}</h3>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const vm = new Vue({
el:'#app',
data:{
message:'john'
},
});
vm.message = 'new message';
// console.log(vm.$el.textContent);//john
Vue.nextTick(()=>{
console.log(vm.$el.textContent);//message
});
</script>
</body>
应用
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
<div id="app">
<App></App>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
/* 需求
在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,
然后我们在接口一返回数据就展示了这个浮层组件,展示的同时,
上报一些数据给后台(这些数据是父组件从接口拿的),
这个时候,神奇的事情发生了,虽然拿到了数据,但是浮层出现的时候,
这些数据还未更新到组件上去,上报失败*/
const Pop = {
// 浮层组件
data(){
return{
isShow:false
}
},
template:`
<div v-if = 'isShow'>
{{name}}
</div>
`,
methods:{
show(){
this.isShow = true;//浮层组件展示
console.log(this.name);
}
},
props:{
name:{
type:String,
default:''
}
}
};
const App = {
data(){
return{
name:''
}
},
template:`
<div>
<pop ref="pop" :name='name'></pop>
</div>
`,
components:{
Pop
},
created(){
// 模拟异步请求
setTimeout(()=>{
// 数据更新
this.name = 'john';
this.$nextTick(()=>{
this.$refs.pop.show();
})
},1000);
}
}
const vm = new Vue({
el:'#app',
data:{
},
components:{
App
}
});
</script>
</body>
</html>
对象变更检测注意事项
vue不能检测对象属性的添加与删除,如果想要操作,就可以用vue提供的set()方法。
Vue.set(obj,key,val)
<body>
<div id="app">
<h3>{{user}}</h3>
<button @click = 'addAge'>添加</button>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const vm = new Vue({
el:'#app',
data:{
user:{},
},
created(){
setTimeout(()=>{
this.user={
name:'john',
}
},1250)
},
methods: {
addAge() {
// this.user['age']=18
// this.user.age = 18;
// this.$set是Vue.set的别名
this.$set(this.user,'age',18);
/* // 也可以通过Object.assign()添加多个响应式属性
this.user = Object.assign({},this.user,{
age:18,
phone:1234567890
}) */
}
},
});
</script>
</body>
mixin混入偷懒技术
mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。
Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
作用
来分发vue组件中的可复用功能
<body>
<div id="app">
<h3>{{msg}}</h3>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const myMixin = {
data(){
return{
msg:"123"
}
},
created(){
this.sayHello()
},
methods:{
sayHello(){
console.log('hello mixin');
}
}
}
const vm = new Vue({
el:'#app',
data:{
title:'john'
},
created(){
console.log(111);
},
mixins:[myMixin]
});
</script>
</body>
应用
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
<div id="app">
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
/* 全局的mixin
Vue.mixin({
})
注意事项
每个组件实例创建时,他都会被调用
*/
const toggleShow = {
data(){
return{
isShow:false
}
},
methods:{
toggleShow(){
this.isShow = !this.isShow;
}
}
}
const Modal = {
template:`
<div v-if='isShow'>
<h3>模态框组件</h3>
</div>
`,
// 局部的mixins
mixins:[toggleShow]
};
const Prompt = {
template:`
<div v-if='isShow'>
<h2>提示框组件</h2>
</div>
`,
mixins:[toggleShow]
};
const vm = new Vue({
el:'#app',
data:{
},
components:{
Modal,
Prompt
},
template:`
<div>
<button @click ='handleModal'>模态框</button>
<button @click ='handlePrompt'>提示框</button>
<Modal ref="modal"></Modal>
<Prompt ref='hint'></Prompt>
</div>
`,
methods:{
handleModal(){
this.$refs.modal.toggleShow();
},
handlePrompt(){
this.$refs.hint.toggleShow();
}
}
});
</script>
</body>
</html>