Vue.js进阶
自定义指令
除了默认设置的核心指令( v-model 和 v-show ), Vue 也允许注册自定义指令。
关于指令,我们可以总结下面几点:
- 指令是写在 HTML 属性地方的.
<input v-model='name' type='text' />
- 指令都是以
v-
开头的. - 指令表达式的右边一般也可以跟值
v-if = false
问题:我们需要一个指令,写在某个HTML表单元素上,然后让它在被加载到DOM中时,自动获取焦点
.
1 2 3 4
| <div id="app"> <p>页面载入时,input 元素自动获取焦点:</p> <input type="text" v-focus> </div>
|
注册一个全局自定义指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
Vue.directive('focus', { inserted: function (el) { el.focus() } })
new Vue({ el: '#app' })
|
注册一个局部指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| new Vue({ el: '#app', directives: { focus: { inserted: function (el) { el.focus() } } } })
|
directive
全局:Vue.directive('自定义指令名、不加v-',{ 参数 })
局部:directives:{ 自定义指令名、不加v-:{ 参数} }
参数: 钩子函数
钩子函数
指令定义函数提供了几个钩子函数(可选):
bind
: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。inserted
: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。update
: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。componentUpdated
: 被绑定元素所在模板完成一次更新周期时调用。unbind
: 只调用一次, 指令与元素解绑时调用。
钩子函数的参数
钩子函数的参数有:
el
: 指令所绑定的元素,可以用来直接操作 DOM 。binding
: 一个对象,包含以下属性:name
: 指令名,不包括 v-
前缀。value
: 指令的绑定值, 例如: v-my-directive="1 + 1"
, value 的值是 2
。oldValue
: 指令绑定的前一个值,仅在 update
和 componentUpdated
钩子中可用。无论值是否改变都可用。expression
: 绑定值的表达式或变量名。 例如 v-my-directive="1 + 1"
, expression 的值是 "1 + 1"
。arg
: 传给指令的参数。例如 v-my-directive:foo
, arg 的值是 "foo"
。modifiers
: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar
, 修饰符对象 modifiers 的值是 { foo: true, bar: true }
。
vnode
: Vue 编译生成的虚拟节点。oldVnode
: 上一个虚拟节点,仅在 update
和 componentUpdated
钩子中可用。
Vue组件化
WEB中的组件其实就是页面组成的一部分,好比是电脑中的每一个原件(如硬盘、键盘、鼠标),它是一个具有独立的逻辑和功能或页面,同时又能根据规定的接口规则进行相互融合,变成一个完整的应用。
全局组件和局部组件
全局组件
- 基本语法:
const xxx = Vue.extend({ template:'' }) Vue.component('xxx',xxx)
- 语法糖:
Vue.component('xxx',{ template: '' })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <div id="app"> <zykj></zykj> </div> <script>
const myCon = Vue.extend({ template: '<h1>自定义组件!</h1>' })
Vue.component('zykj',myCon);
Vue.component('zykj', { template: '<h1>自定义组件!</h1>' })
new Vue({ el: '#app' }) </script>
|
局部组件
- 基本语法:
const xxx = Vue.extend({ template:'' }) components:{ 'xxx' : xxx }
- 语法糖:
components: { 'xxx': { template: '' } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <div id="app"> <zykj></zykj> </div> <script>
const zykj = Vue.extend({ template: '<h1>自定义组件!</h1>' })
new Vue({ el: '#app', components: { 'zykj': zykj, 'myCon' : { template : ` <div> <h2>Hello myCon</h2> </div> ` } } }) </script>
|
模板抽离
由于template
模板书写太麻烦,这里提供几种方法
script text/x-template
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <div id="app"> <zykj></zykj> </div>
<script type="text/x-template" id="myCon"> <div> <h1>我是模板里的</h1> </div> </script>
<script> Vue.component("zykj",{ template: "#myCon" }) var vm = new Vue({ el: '#app', components:{
} }); </script>
|
<template></template>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div id="app"> <zykj></zykj> </div>
<template id="myCon"> <div> <h1>我是模板里的</h1> </div> </template>
<script> Vue.component("zykj",{ template: "#myCon" }) var vm = new Vue({ el: '#app', components:{
} }); </script>
|
父组件和子组件
子组件就是在父组件的components
上定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <div id="app"> <my-con2></my-con> </div>
<script>
const myCon1 = Vue.extend({ template:` <div> <h1>Hello myCon1!</h1> </div> ` })
const myCon2 = Vue.extend({ template:` <div> <h1>Hello myCon2!</h1> <my-con1></my-con1> </div> `, components:{ 'myCon1':myCon1 } })
var vm = new Vue({ el: '#app', components:{ 'myCon2' : myCon2 } }); </script>
|
ref和$refs
访问子组件实例或子元素
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref
这个 attribute 为子组件赋予一个 ID 引用。例如:
1
| <base-input ref="usernameInput"></base-input>
|
现在在你已经定义了这个 ref
的组件里,你可以使用:
1
| this.$refs.usernameInput
|
来访问这个 <base-input>
实例,以便不时之需。比如程序化地从一个父级组件聚焦这个输入框。在刚才那个例子中,该 <base-input>
组件也可以使用一个类似的 ref
提供对内部这个指定元素的访问,例如:
甚至可以通过其父级组件定义方法:
1 2 3 4 5 6
| methods: { focus: function () { this.$refs.input.focus() } }
|
这样就允许父级组件通过下面的代码聚焦 <base-input>
里的输入框:
1
| this.$refs.usernameInput.focus()
|
当 ref
和 v-for
一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。
$refs
只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs
。
组件数据存放问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <div id="app"> <zykj></zykj> </div>
<template id="myCon"> <div> <h1>{{ title }}</h1> <h1>我是模板里的</h1> </div> </template>
<script> Vue.component("zykj",{ template: "#myCon", data(){ return{ title:"我是title" } } }) var vm = new Vue({ el: '#app', components:{
} }); </script>
|
为什么返回的 data data(){ return {} }
为什么组件data必须是函数 自己总结一下:组件data是函数避免了数据共用的问题、通过函数中返回对象会重新开辟空间。。。
组件通信
父组件向子组件传值
使用props
属性。
props主要用于父组件传递数据给子组件,是你可以在组件上注册一些自定义特性。当一个值传递给一个prop特性的时候,它就变成了那个组件实例的一个属性。这样在子组件就可以使用该该值。请注意:所有的prop都使得期父子prop之间形成了一个单向下行绑定,即父级prop的更新会向下流动到子组件,但是反过来就不行,子组件不能改变父组件的状态。
每次父组件发生更新时,子组件中所有的prop都会被刷新为最新的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <div id="app"> <zykj v-bind:ctitle="title"></zykj> </div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<template id="myCon"> <div> // ctitle 的内容就是 v-bind:ctitle绑定的 title <h1 >{{ ctitle }}</h1> <h1>我是模板里的</h1> </div> </template>
<script> const zykj = { template:"#myCon", props: ['ctitle'] }
var vm = new Vue({ el: '#app', data:{ title: "Hello World" }, components:{ 'zykj':zykj }
}); <script/>
|
prop 验证
能判断的所有种类(也就是 type 值)有:String, Number, Boolean, Function, Object, Array, Symbol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| Vue.component('myComponent', { props: { 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 value > 10 } } } })
|
props对象里的 xXXX 绑定属性不支持驼峰命名 需要改成 x-xxx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!-- 需要改成x-xxx --> <zykj v-bind:c-title="title"></zykj>
<template id="myCon"> <!-- 别忘了一定要加一个根元素 --> <div> <!-- 这里可以使用驼峰命名 --> <h1>{{ cTitle }}</h1> <h1>我是模板里的</h1> </div> </template>
const zykj = { template:"#myCon", //自定义组件中传入的 ctitle 可以给子组件使用 //数组形式 props: { // 1.通过驼峰命名的 xXxx cTitle:{ type: String } } }
|
子组件向父组件传值
一般情况下、子组件要向父控件传入数据时、就要使用自定义事件
流程:
- 子组件中通过
$emit
触发事件 - 父组件中通过
v-on
来监听子组件事件
$emit( eventName, […args] )
- eventName:这是一个事件名,会绑定一个方法。当组件触发事件后,将调用这个方法。
- …args:附加参数,会被抛出,由上述绑定的方法接收使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <div id="app"> <zykj @btncl="btnc"></zykj> </div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<template id="myCon"> <div> <button v-for="item in items" @click="btnclick(item)">{{ item.name }}</button> </div> </template>
<script>
const zykj = { template:"#myCon", data(){ return { items:[ {id:"1",name:"明日方舟"}, {id:"2",name:"碧蓝航线"}, {id:"3",name:"Miku"}, {id:"4",name:"zykj"}, ] } }, methods:{ btnclick(item){ this.$emit('btncl',item) } } }
var vm = new Vue({ el: '#app', data:{ title: "Hello World" }, components:{ 'zykj':zykj }, methods:{ btnc(item){ console.log("btnc",item); } }
});
|
父子组件通信(案例)
需求:父组件通过props向子组件传入数据、并且子组件可以修改父组件的数据,
问题:要避免子组件直接修改父组件的数据、可以根据父组件传入的数据给子组件创建一个data或者computed
方法一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| <body> <div id="app"> <cpn :number1='num1' :number2='num2' @num1change="num1Change" @num2change="num2Change"></cpn>
<h2>父组件{{num1}}</h2> <input type="text" v-model="num1" > <h2>父组件{{num2}}</h2> <input type="text" v-model="num2">
</div>
<template id="cpn"> <div> <h2>{{number1}}</h2> <h2>{{dnumber1}}</h2> <input type="text" :value="dnumber1" @input="num1input"> <h2>{{number2}}</h2> <input type="text" :value="dnumber2" @input="num2input"> </div> </template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const cpn = { template: "#cpn", data() { return { dnumber1:this.number1, dnumber2:this.number2 } }, props:{ number1:[Number,String], number2:[Number,String], }, methods: { num1input(event){ this.dnumber1 = event.target.value this.$emit('num1change',this.dnumber1) }, num2input(event){ this.dnumber2 = event.target.value this.$emit('num2change',this.dnumber2) } }, }; const app = new Vue({ el: "#app", data() { return { num1:1, num2:2, } }, methods: { num1Change(value){ this.num1=value }, num2Change(value){ this.num1=value } }, components: { cpn }, }) </script> </body>
|
方法二:watch 侦听属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| <body> <div id="app">
<cpn :number1='num1' :number2='num2' @num1change="num1Change" @num2change="num2Change"></cpn>
<h2>父组件{{num1}}</h2> <input type="text" v-model="num1" > <h2>父组件{{num2}}</h2> <input type="text" v-model="num2">
</div>
<template id="cpn"> <div> <h2>{{number1}}</h2> <input type="text" v-model="dnumber1"> <h2>{{number2}}</h2> <input type="text" v-model="dnumber2"> </div> </template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const cpn = { template: "#cpn", data() { return { dnumber1:this.number1, dnumber2:this.number2 } }, props:{ number1:[Number,String], number2:[Number,String], }, watch: { dnumber1(newValue){ this.dnumber1 = newValue * 100 this.$emit('num1change',newValue) }, dnumber2(newValue){ this.dnumber1 = newValue * 100 this.$emit('num2change',newValue) } }, }; const app = new Vue({ el: "#app", data() { return { num1:1, num2:2, } }, methods: { num1Change(value){ this.num1=value }, num2Change(value){ this.num1=value } }, components: { cpn }, }) </script> </body>
|
组件访问方式
父组件访问子组件
$childnet
:查找当前组件的直接子组件,可以遍历全部子组件, 需要注意 $childnet
并不保证顺序,也不是响应式的。refs
: 查找命名子组件、子组件添加ref='xxx'
属性、通过 this.$refs.xxx
获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <div id="app"> <cpn></cpn> <cpn></cpn> <cpn ref="aaa"></cpn> <button @click="btnClick" >按钮</button> </div> <template id="cpn"> <div> 我是子组件 </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const cpn = { template: "#cpn", data() { return { name:"我是子组件的name" } }, methods: { showMessage(){ console.log("showMessage"); } }, }; const app = new Vue({ el: "#app", data() { return { message:"hello" } }, methods: { btnClick(){ console.log(this.$refs.aaa.name); } }, components: { cpn }, }) </script>
|
子组件访问父组件
$panett
: panett 访问得到的是它最近一级的父组件$root
: 访问得到的是根父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <body> <div id="app"> <cpn></cpn> <cpn></cpn> <cpn ref="aaa"></cpn> </div>
<template id="cpn"> <div> 子组件消息:{{message}} <button @click="btnClick" >子组件按钮</button> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const cpn = { template: "#cpn", data() { return { message:"我是子组件的name" } }, methods: { btnClick(){ console.log("子组件按钮被点击") this.message = this.$panett.message console.log(this.$root) console.log(this.$root.message) } }, }; const app = new Vue({ el: "#app", data() { return { message:"我是父组件消息" } }, methods: {
}, components: { cpn }, }) </script> </body>
|
插槽
基本使用
插槽(Slot)是Vue提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性。
- 使用
<slot></slot>
加了个slot标签,在父组件中,可以在父组件内写标签替换子组件标签中的slot - 插槽可以使用默认值,slot里的
<button>button</button>
就是插槽的默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <div id="app"> <cpn></cpn> <cpn> <span style="color:red;">这是插槽内容222</span> </cpn> <cpn> <i style="color:red;">这是插槽内容333</i> </cpn> <cpn></cpn> </div>
<template id="cpn"> <div> <div> {{message}} </div> <slot><button>button</button></slot> </div> </template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const cpn = { template: "#cpn", data() { return { message: "我是子组件" } }, } const app = new Vue({ el: "#app", data() { return { message: "我是父组件消息" } }, components: { cpn }, }) </script>
|
具名插槽
具名插槽,就是可以让插槽按指定的顺序填充,而没有具名的插槽是按照你填充的顺序排列的,而具名插槽可以自定义排列。
当有多个slot的时候、在子组件里面写上标签会将说有的slot替换
- 通过
<slot name="xxx"></slot>
给插槽取名字 - 需要添加的标签的添加
slot="xxx"
xxx为name的xxx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <div id="app"> <cpn> <span>没具名</span> <span slot="left">这是左边具名插槽</span> <template v-slot:center>这是中间具名插槽</template> <template #right>这是右边具名插槽</template> </cpn> </div>
<template id="cpn"> <div> <slot name="left">左边</slot> <slot name="center">中间</slot> <slot name="right">右边</slot> <slot>没有具名的插槽</slot> </div> </template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const cpn = { template: "#cpn", data() { return { message: "我是子组件" } }, } const app = new Vue({ el: "#app", data() { return { message: "我是父组件消息" } }, components: { cpn }, }) </script>
|
编译作用域
组件都有自己的作用域,自己组件的作用在自己组件内
- 在父组件中的子组件标签上使用的父组件的、子组件使用的子组件的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <div id="app"> <cpn v-show="isShow"></cpn> </div>
<template id="cpn"> <div> <h2>我是子组件</h2> <p>哈哈哈</p> <button v-show="isShow"></button> </div> </template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const cpn = { template: "#cpn", data() { return { isShwo:false } }, } const app = new Vue({ el: "#app", data() { return { message: "我是父组件消息", isShow:true } }, components: { cpn }, }) </script>
|
作用域插槽
父组件在用子组件来填充插槽的时候 有时候需要用到子组件里面插槽的数据 .
子组件文件插槽上带的数据 在父组件的子组件标签里 让一个标签 带有 slot-scope="xxx"
去接收
步骤:
- 在子组件模板中的插槽
slot
绑定子组件中data中的数据命名为xxx1
- 父组件中的子组件里通过
<template></template>
的属性 slot-scope="xxx2"
接收数据、通过 xxx2.xxx1
获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <div id="app"> <zykj> <template slot-scope="slot"> <span>{{ slot.data.join(" - ") }}</span> </template> </zykj>
<zykj></zykj> </div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<template id="myCon"> <div> <slot :data="pLanguage"> <ul> <li v-for="item in pLanguage">{{item}}</li> </ul> </slot> </div> </template>
<script> const zykj = { template: "#myCon", data() { return { pLanguage: ['JavaScript','Python','Java'] } } }
var vm = new Vue({ el: '#app', data: { title: "Hello World" }, components: { 'zykj': zykj }, methods: {
}
}); </script>
|