自定义属性props
prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。
props: {
type: {
type: String,
validator: (value) => {
return ['success', 'warning', 'danger'].includes(value)
},
default: () => 'success'
}
}
inheritAttrs与$attrs
inheritAttrs
这是2.4.0 新增的一个API,默认情况下父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。可通过设置 inheritAttrs 为 false,这些默认行为将会被去掉。注意:这个选项不影响 class 和 style 绑定。
-
父组件
<Child1 :title="title" :message="message"></Child1>
-
子组件
<template> <div> <div>子组件1</div> <Child2 v-bind="$attrs"></Child2> </div> </template> <script> import Child2 from './child2.vue' export default { components: { Child2 }, // inheritAttrs: false, props: ['title'], created () { console.log(this.$attrs) } } </script>
-
效果
-
效果分析
们看到:组件内未被注册的属性将作为普通html元素属性被渲染,如果想让属性能够向下传递,即使prop组件没有被使用,你也需要在组件上注册。这样做会使组件预期功能变得模糊不清,同时也难以维护组件的DRY。在Vue2.4.0,可以在组件定义中添加inheritAttrs:false,组件将不会把未被注册的props呈现为普通的HTML属性。但是在组件里我们可以通过其$attrs可以获取到没有使用的注册属性,如果需要,我们在这也可以往下继续传递。
5.设置了inheritAttrs: false效果
6. 没有申明的属性,默认挂载在组件的根元素上,可以使用inheritAttrs去除掉
$attrs
-
多级组件嵌套:
例如:有一个页面由父组件,子组件,孙子组件构成
-
如果attrs被绑定在子组件上后,我们就可以在孙子组件里获取
this.$attrs
值。这个{{$attrs}}
值是父组件中传递下来的props
(除了子组件中props
声明的)。 -
案例
父组件:
<template> <div> hello world <Child1 :title="title" :message="message"></Child1> </div> </template>
子组件:
<div> <div>子组件1</div> <Child2 v-bind="$attrs"></Child2> </div>
孙子组件:
<template> <div>模板2 ---- {{$attrs.message}}</div> </template> <script> export default { created () { console.log(this.$attrs) } } </script>
单向数据流
vue是单向数据流,子组件中不允许直接更改props传递过来的数据,如果需要更改,通过两种方式间接更改
- 操作data
- 通过计算属性
<template>
<div>
<div>子组件1</div>
<button @click="handleClick1">通过data改变传递的props值---{{currentType}}</button>
<button>通过computed改变传递的props值---{{currentMessage}}</button>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: () => ''
},
message: {
type: String,
default: () => ''
}
},
computed: { // 通过计算属性的方式间接处理props传递过来的值
currentMessage () {
return this.message + '...'
}
},
data () {
return {
currentType: this.type // 通过data间接处理props传递过来的值
}
},
methods: {
handleClick1 () {
this.currentType = '修改了标题'
}
}
}
</script>
以上两种方法虽可以在子组件间接修改props的值,但如果子组件想修改数据并且同步更新到父组件,却无济于事。在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』,此时就推荐以下这两种方法:
-
使用.sync
-
将父组件中的数据包装成对象传递给子组件
父组件:
<template> <div> 父组件 --- {{title}} --- {{arr}} --- {{oldValue.value}} <Child1 :title.sync="title" :arr.sync="arr" :oldValue="oldValue"></Child1> </div> </template> <script> import Child1 from './child1.vue' export default { components: { Child1 }, data () { return { title: '标题', arr: ['book', 'people'], oldValue: { value: '原始值' } } } } </script>
子组件:
<template> <div> <div>子组件1</div> <button @click="$emit('update:title', '标题被更改了')">通过“双向数据绑定”的形式更改props值</button> <button @click="arr.push('工程师')">在子组件中改变数组</button> <button @click="oldValue.value = '1234'">通过包装对象(数组的)的形式更改props传递的值</button> </div> </template> <script> export default { props: { title: { type: String, default: () => '' }, arr: { type: Array, default: () => [] }, oldValue: { type: [Array, Object, String], default: () => '' } }, data () { return {} }, methods: {} } </script>
事件修饰符
经典案例:Vue事件分为普通事件和修饰符事件,给自定义组件原生的 click 事件
错误写法:<Child1 @click="handleClick"></Child1>
,这里的 @click 是自定义事件 click,并不是原生事件 click。
正确写法:<Child1 @click.native="handleClick"></Child1>
表单修饰符
- lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 。你可以添加 lazy 修饰符,从而转变为使用 change事件进行同步。适用于输入完所有内容后,光标离开才更新视图的场景。 - trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符: - number
自动将用户的输入值转为数值类型
事件修饰符
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
样式穿透
使用 >>> 或 /deep/ 解决
<style scoped>
.box /deep/ h2 {
color: red;
}
.box >>> h4 {
color: blue;
}
</style>
watch高阶使用
立即执行
watch 是在监听属性改变时才会触发,有些时候,我们希望在组件创建后 watch 能够立即执行
可能想到的的方法就是在 create 生命周期中调用一次,但这样的写法不优雅,我们可以使用这样的方法
export default {
data () {
return {
name: 'JOe'
}
},
watch: {
name: {
handler: 'printData',
immediate: true // 立即执行
}
},
methods: {
printData () {
console.log(this.name)
}
}
}
深度监听
在监听对象时,对象内部的属性被改变时无法触发 watch ,我们可以为其设置深度监听
export default {
data () {
return {
user: {
name: '李四',
age: 23
}
}
},
watch: {
user: {
handler: (val) => {
console.log(val) // 使用deep才能打印出更新后的值,否则监听的值不会变化
},
deep: true
}
}
}
事件参数$event
<template>
<div class="box">
<input type="text" @change="handleChange" />
<input type="text" @change="handleChange2('hello', $event)">
</div>
</template>
<script>
export default {
data () {
return {}
},
methods: {
handleChange (e) {
console.log(e.target.value)
},
handleChange2 (msg, e) {
console.log(msg, e.target.value)
}
}
}
</script>
监听组件生命周期(2种方式)
- 通常我们监听组件生命周期会使用 $emit ,父组件接收事件来进行通知
- 用 @hook 即可监听组件生命周期,组件内无需做任何改变
子组件:
export default {
beforeCreate () {
this.$emit('listenChildCreate')
}
}
父组件:
<template>
<div class="box">
<button @click="show = !show">点击</button>
<Child v-if="show" @hook:mounted="listenChildMounted" @hook:beforeDestroy="listenChildDestroy" @listenChildCreate="listenChildCreate"></Child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
},
data () {
return {
show: true
}
},
methods: {
listenChildMounted () {
console.log('子组件挂载了')
},
listenChildDestroy () {
console.log('监听到子组件销毁')
},
listenChildCreate () {
console.log('监听到子组件创建')
}
}
}
</script>
程序化的事件侦听器
经典案例:在页面挂载时定义计时器,需要在页面销毁时清除定时器。这看起来没什么问题。但仔细一看 this.timer 唯一的作用只是为了能够在 beforeDestroy 内取到计时器序号,除此之外没有任何用处。
export default {
mounted() {
this.timer = setInterval(() => {
console.log(Date.now())
}, 1000)
},
beforeDestroy() {
clearInterval(this.timer)
}
}
如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
我们可以通过 $on 或 $once 监听页面生命周期销毁来解决这个问题:
export default {
mounted() {
this.creatInterval('hello')
this.creatInterval('world')
},
creatInterval(msg) {
let timer = setInterval(() => {
console.log(msg)
}, 1000)
this.$once('hook:beforeDestroy', function() {
clearInterval(timer)
})
}
}
使用这个方法后,即使我们同时创建多个计时器,也不影响效果。因为它们会在页面销毁后程序化的自主清除。