prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。
props: {
type: {
type: String,
validator: (value) => {
return ['success', 'warning', 'danger'].includes(value)
},
default: () => 'success'
}
}
这是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被绑定在子组件上后,我们就可以在孙子组件里获取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传递过来的数据,如果需要更改,通过两种方式间接更改
<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 事件
错误写法:
,这里的 @click 是自定义事件 click,并不是原生事件 click。
正确写法:
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
使用 >>> 或 /deep/ 解决
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
}
}
}
<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>
子组件:
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)
})
}
}
使用这个方法后,即使我们同时创建多个计时器,也不影响效果。因为它们会在页面销毁后程序化的自主清除。