Vue 2 续集
这里介绍老版本 vue 相关的知识点, 主要包括:指令, 组件通信(传值), 内置组件
指令
Vue 中的指令
-
v-on 给标签绑定函数,可以缩写为@,例如绑定一个点击函数 函数必须写在 methods 里面
- .stop - 调用 event.stopPropagation()。 阻止默认事件
- .prevent - 调用 event.preventDefault()。阻止默认行为
- .native - 监听组件根元素的原生事件
- .self - 只当在 event.target 是当前元素自身时触发处理函数, 即事件不是从内部元素触发的
- .once - 只第一次起作用
- .capture - 内部元素触发的事件先在此处理,然后才交由内部元素进行处理
.{keyCode | keyAlias}
- 只当事件是从特定键触发时才触发回调。- .left - (2.2.0) 只当点击鼠标左键时触发。
- .right - (2.2.0) 只当点击鼠标右键时触发。
- .middle - (2.2.0) 只当点击鼠标中键时触发。
- .passive - (2.3.0) 以
{ passive: true }
模式添加侦听器
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发, -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
<!-- 缩写 -->
<div @scroll.passive="onScroll">...</div>
<!-- 这个 .passive 修饰符尤其能够提升移动端的性能。 --> -
v-bind 动态绑定 作用: 及时对页面的数据进行更改, 可以简写成( :xxx )冒号
- .prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。
- .camel - (2.1.0+) 将 kebab-case attribute 名转换为 camelCase。
- .sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器
v-bind 也可以使用动态 attribute 绑定, 还有在绑定 style、class 时, 可以使用键值对的形式进行绑定
<!-- 缩写 -->
<img :src="imgSrc" />
<!-- 使用动态 attribute -->
<button v-bind:[key]="value"></button>
<!-- style, data 见下方-->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="[styleObject, { color: activeColor }]"></div>
<!-- class, data 见下方 -->
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
<div :class="[activeClass, errorClass]"></div>
<div :class="[{ active: isActive }, errorClass]"></div>data: {
isActive: true,
hasError: false,
activeClass: 'active',
errorClass: 'text-danger',
// style
activeColor: 'red',
fontSize: 30,
styleObject: {
color: 'red',
fontSize: '13px'
}
} -
v-model
- .lazy - 取代 input 监听 change 事件
- .number - 输入字符串转为有效的数字
- .trim - 输入首尾空格过滤
<input v-model="message" placeholder="edit me" />
<p>Message is: {{ message }}</p>
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" /> -
v-slot: 缩写为#, 组件插槽, 详细请看 → 这里
<!-- 具名插槽 -->
<base-layout>
<template v-slot:header> Header content </template>
Default slot content
<template v-slot:footer> Footer content </template>
</base-layout>
<!-- 接收 prop 的具名插槽 -->
<infinite-scroll>
<template v-slot:item="slotProps">
<div class="item">{{ slotProps.item.text }}</div>
</template>
</infinite-scroll>
<!-- 接收 prop 的默认插槽,使用了解构 -->
<mouse-position v-slot="{ x, y }"> Mouse position: {{ x }}, {{ y }} </mouse-position>
<!-- 动态插槽名 dynamicSlotName=XXX -->
<template v-slot:[dynamicSlotName]> ... </template> -
v-for 根据数组的个数, 循环数组元素的同时还生成所在的标签
<!-- 既然使用了循环,那么Key是没跑了 -->
<div v-for="item in items" :key="item.id">{{ item.text }}</div>
<!-- 使用数组索引,对象的键名 -->
<div v-for="(item, index) in items" :key="item.id"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, name, index) in object"></div> -
v-show 显示内容
-
v-if 显示与隐藏
-
v-else 必须和 v-if 连用 不能单独使用 否则报错
<div v-if="Math.random() > 0.5">Now you see me</div>
<div v-else>Now you don't</div> -
同理, 还有
v-else-if
-
v-text 解析文本
<span v-text="msg"></span>
<!-- 和下面的一样 -->
<span>{{msg}}</span> -
v-html 解析 html 标签
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>data: {
rawHtml: '<span style="color: red">This should be red.</span>';
} -
v-cloak
- 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性。
- 使用 css(display:'none')配合 v-cloak 可以解决网速慢时页面展示出
{{xxx}}
的问题。 - 主要是解决,页面初次加载出现
{{xxx}}
的问题
-
v-pre
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
自定义指令
vue 中指令集很强大,自然脱不开自定义了, vue 提供了 directive
api, 来自定义指令
<script>
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
// 局部注册也是可以的
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
</script>
<template>
<input v-focus />
</template>
钩子函数:指令定义对象提供钩子函数
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
注:上面的钩子函数,在 vue3 中改为了 vue 的生命周期钩子
指令钩子函数会被传入以下参数:
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding:一个对象,包含以下 property:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为
{ foo: true, bar: true }
。
- vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。(vue 3 改为了 prevNode, 仅在 beforUpdate 和 updated 中可用)
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive("pin", {
bind: function (el, binding, vnode) {
el.style.position = "fixed";
// 通过arg 获取指令参数
let s = binding.arg == "left" ? "left" : "top";
el.style[s] = binding.value + "px";
},
});
new Vue({
el: "#dynamicexample",
data: function () {
return {
// 自定义指令名称
direction: "left",
};
},
});
使用场景
- 普通 DOM 元素进行底层操作的时候,可以使用自定义指令
- 自定义指令是用来操作 DOM 的。
使用场景例子:
- 鼠标聚焦,下拉菜单,共同动画
- 自定义指令实现图片懒加载
- 自定义指令集成第三方插件
v-if v-show v-html 原理
- v-if 会调用 addIfCondition 方法,生成 vnode 的时候会忽略对应节点,render 的时候就不会渲染;
- v-show 会生成 vnode,render 的时候也会渲染成真实节点,只是在 render 过程中会在节点的属性中修改 show 属性值,也就是常说的 display;
- v-html 会先移除节点下的所有节点,调用 html 方法,通过 addProp 添加 innerHTML 属性,归根结底还是设置 innerHTML 为 v-html 的值。
v-show 和 v-if 的区别
上面解释了 两者的原理, 可以理解为
- 相同点:为 true 的时候, 都是显示, 否则都为隐藏
- 不同点:v-if 就是直接操作 dom 是否渲染, v-show 则为控制 dom 的显示隐藏 display
所以,当遇到需要频繁切换显示隐藏的时候,使用 v-show 更好,不需要不断的去操作 vdom 不然开销会很大;那如果不需要频繁切换,那使用 v-if 更合适
v-for 和 v-if 在一起使用?
官方文档中这么写到:不推荐同时使用 v-if 和 v-for。
在 vue 2 中,当 v-if 和 v-for 同时存在于一个元素上的时候,v-for 会首先被执行。
在 vue 3 中,当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执 行。
v-model 是如何实现的
v-model 就是数据双向绑定,主要用于表单和组件之间的数据双向绑定。
主要实现原理就是:
- v-bind 绑定一个 value 属性
- 然后,通过 v-on 指令给元素绑定 input 事件
综上所述,v-model 就是 v-bind 和 v-on 监听 input 事件的结合。
<!-- 等于 -->
<input :value="vlaue" @input="value = $event.target.value" />
<!-- 父组件 -->
<TextInput :value="vlaue" @input="value = $event.target.value" />
<!-- 子组件 TextInput -->
<input :value="vlaue" @input="textChange" />
methods: {
textChange(e) {
$emit('input', e.target.value);
}
}
v-model 和.sync 的对比
两者都是语法糖, 都可以实现父子组件中的数据的双向通信。
v-model 上面已经总结了,相当于 v-bind 和 v-on 监听 input 时间的结合体。这里还要提一点就是一个组件只能绑定一个 v-modal .sync 是 v-bind 的拓展,常用于 父子组件通信,例如: 父组件 :propName.sync 子组件中 @update:propName 的模式来替代事件触发 .sync 相当于指令的拓展,在一个组件上是可以绑定多个的,因为 v-bind 是可以有多个的
直接上例子:
<!-- 子组件 -->
<template>
<div class="child">
{{money}}
<!-- 我要花掉 50 -->
<button @click="$emit('update:money', money-50)">
<span>花钱</span>
</button>
<!-- 我要去挣 50 -->
<button @click="$emit('update:earn', money+50)">
<span>挣钱</span>
</button>
</div>
</template>
<script>
export default {
props: ["money"],
};
</script>
<!-- 父组件 -->
<Child :money="total" @update:money="payment" @update:earn="earnmoney" />
<script>
export default {
data: ["total"],
methods: {
payment: function (payed) {
this.data.total = payed;
},
earnmoney: function (earn) {
this.data.total = earn;
},
},
};
</script>
<!-- .sync 替代 -->
<Child :money.sync="total" />
内置组件
component
渲染一个“元组件”为动态组件。依 is 的值,来决定哪个组件被渲染。
<!-- 动态组件由 vm 实例的 `componentId` property 控制 -->
<component :is="componentId"></component>
<!-- 也能够渲染注册过的组件或 prop 传入的组件 -->
<component :is="$options.components.child"></component>
<ul>
<!-- 自己写的组件 -->
<li is="my-component"></li>
</ul>
<!-- 组件会在 `件名` 改变时改变 -->
<component :is="组件名变量"></component>
可以看到例子中的 is,这个是干啥用的呢?
is 实现动态组件
解决 html 模板的限制
比如 ul 里面嵌套 li 的写法是 html 语法的固定写法,如果想在 ul 里面嵌套自己的组件,但是 html 在渲染 dom 的时候,组件对 ul 来说并不是有效的 dom。
动态组件(组件切换) componentName 可以是在本页面已经注册的局部组件名和全局组件名, 也可以是一个组件的选项对象。当控制 componentName 改变时就可以动态切换选择组件。
<template>
<button v-for="tab in tabs" :key="tab" :class="['tab-button', { active: currentTab === tab }]" v-on:click="currentTab = tab">
{{ tab }} //渲染按钮文字
</button>
<component :is="currentTabComponent"></component>
</template>
<script>
Vue.component("tab-home", {
template: "<div>Home component</div>",
});
Vue.component("tab-posts", {
template: "<div>Posts component</div>",
});
Vue.component("tab-archive", {
template: "<div>Archive component</div>",
});
new Vue({
el: "#dynamic-component-demo",
data: {
currentTab: "Home",
tabs: ["Home", "Posts", "Archive"],
},
computed: {
currentTabComponent: function () {
return "tab-" + this.currentTab.toLowerCase();
},
},
});
</script>
上面的例子可以看到,当 tab 切换时,currentTab 发生变化,然后 computed 通过 currentTabComponent 计算,返回不同的结果返回给 is, 然后 component 通过 is 的结果动态渲染不同的 tab-xxx 组件。
transtion
<transition>
元素作为单个元素/组件的过渡效果。<transition>
只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中。
用于在 Vue 插入、更新或者移除 DOM 时, 提供多种不同方式的应用过渡、动画效果。
- :duration="1000" 可以设置动画的时长
- 可以通过 appear attribute 设置节点在初始渲染的过渡
- mode 动画模式
- in-out:新元素先进行过渡,完成之后当前元素过渡离开。
- out-in:当前元素先进行过渡,完成之后新元素过渡进入。
<!-- 简单元素 -->
<transition>
<div v-if="ok">toggled content</div>
</transition>
<!-- 动态组件 -->
<transition name="slide-fade" mode="out-in" appear :duration="1000">
<component :is="view"></component>
</transition>
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all 0.3s ease;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
过渡的类名
过渡的类名 在进入/离开的过渡中,会有 6 个 class 切换。
-
v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
-
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
-
v-enter-to:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
-
v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
-
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
-
v-leave-to:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>
,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">
,那么 v-enter
会替换为 my-transition-enter
。
另外这里的过度效果,可以搭配一些动画库,例如:animate.css 或者 Velocity js 动画库
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="example-3">
<button @click="show = !show">Toggle render</button>
<transition name="custom-classes-transition" enter-active-class="animated tada" leave-active-class="animated bounceOutRight">
<p v-if="show">hello</p>
</transition>
</div>
<script>
new Vue({
el: "#example-3",
data: {
show: true,
},
});
</script>
<!-- velocity 动画 -->
<script>
new Vue({
el: "#example-4",
data: {
show: false,
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0;
el.style.transformOrigin = "left";
},
enter: function (el, done) {
Velocity(el, { opacity: 1, fontSize: "1.4em" }, { duration: 300 });
Velocity(el, { fontSize: "1em" }, { complete: done });
},
leave: function (el, done) {
Velocity(el, { translateX: "15px", rotateZ: "50deg" }, { duration: 600 });
Velocity(el, { rotateZ: "100deg" }, { loop: 2 });
Velocity(
el,
{
rotateZ: "45deg",
translateY: "30px",
translateX: "30px",
opacity: 0,
},
{ complete: done }
);
},
},
});
</script>
如果你想监听动画,那么 vue 也提供了动画的一些钩子
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
// ...
methods: {
// --------
// 进入中
// --------
beforeEnter: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// 离开时
// --------
beforeLeave: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}
另外还有很多炫酷的动画,可以参考官网 -> 点击查看
transition-group
<transition-group>
用于给列表统一设置过渡动画。
keep-alive
主要用于保留组件状态或避免组件重新渲染。也就是组件缓存,类似于 react 中的 memo 的作用
主要参数:
include
属性用于指定哪些组件会被缓存,具有多种设置方式。exclude
属性用于指定哪些组 件不会被缓存。max
属性用于设置最大缓存个数。
使用
-
基本的组件缓存
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 逗号分隔字符串, 可以使用 v-bind -->
<keep-alive :include="a,b" :exclude="c,d">
<component :is="view"></component>
</keep-alive>
<!-- 最多缓存十个组件 -->
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive> -
搭配路由使用,缓存打开的页面,需要搭配路由的 meta 信息的 keepAlive 属性
-
清除缓存组件,例如: 在组件跳转之前使用后置路由守卫 beferRouterLeave 设置 from.meta.keepAlive 为 true 或者 false
钩子函数
在总结 vue 生命周期时,有两个特殊的生命周期,activated
和 deactivated
就是在激活 keep-alive 组件时,和 keep-alive 组件停用时。
使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。
所以生命周期的执行顺序就是:... -> created -> ... -> activated -> ... -> deactivated -> ...
使用场景
- 列表页跳转到详情页,返回后仍显示原有信息
- 路由中设置
- 视图组件缓存时
源码
// src/core/components/keep-alive.js
export default {
name: "keep-alive", //设置一个组件名为keep-alive
abstract: true, // 判断当前组件虚拟dom是否渲染成真实dom, 也就是抽象组件,本身不会渲染成dom元素,也不会出现在父组件链中
props: {
// 定义了keep-alive组件支持的全部参数
include: patternTypes, // 缓存白名单
exclude: patternTypes, // 缓存黑名单
max: [String, Number], // 缓存的组件
},
created() {
this.cache = Object.create(null); // 缓存虚拟dom
this.keys = []; // 缓存的虚拟dom的键集合
},
destroyed() {
for (const key in this.cache) {
// 删除所有的缓存
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted() {
// 实时监听黑白名单的变动
this.$watch("include", (val) => {
pruneCache(this, (name) => matched(val, name));
});
this.$watch("exclude", (val) => {
pruneCache(this, (name) => !matches(val, name));
});
},
render() {
// 省略...
},
};
slot
- name - string,用于命名插槽。
<slot>
元素作为组件模板之中的内容分发插槽。<slot>
元素自身将被替换。
slot 是什么
slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。
通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理
通过 slot 插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用
比如布局组件、表格列、下拉选、弹框显示内容等
分类
-
默认插槽 子组件用
<slot>
标签来确定渲染的位置,标签里面可以放 DOM 结构,当父组件使用的时候没有往插槽传入内容,标签内 DOM 结构就会显示在页面父组件在使用的时候,直接在子组件的标签内写入内容即可
-
具名插槽 子组件用 name 属性来表示插槽的名字,不传为默认插槽
父组件中在使用时在默认插槽的基础上加上 slot 属性,值为子组件插槽 name 属性 值
-
作用域插槽 子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件 v-slot 接受的对象上
父组件中在使用时通过 v-slot:(简写:#)获取子组件的信息,在内容中使用
总结
v-slot
属性只能在<template>
上使用,但在只有默认插槽时可以在组件标签上使用- 默认插槽名为
default
,可以省略default
直接写v-slot
v-slot
缩写为#
时不能不写参数,写成#default
- 可以通过解构获取
v-slot={user}
,还可以重命名v-slot="{user: newName}"
和定义默认值v-slot="{user = '默认值'}"
组件通信/ 组件传值
props / $emit 父子传递
父组件通过 props 向子组件传递数据,子组件通过$emit 和父组件通信
-
- 父组件向子组件传值 props 只能是父组件向子组件进行传值,props 使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。 props 可以显示定义一个或一个以上的数据,对于接收的数 据,可以是各种数据类型,同样也可以传递一个函数。 props 属性名规则:若在 props 中使用驼峰形式,模板中需要使用短横线的形式
-
- 子组件通过 $emit 向父组件传值 $emit 绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过 v-on 监听并接收参数。
依赖注入 provide / inject(父子、祖孙)
这种方式就是 Vue 中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。
provide / inject 是 Vue 提供的两个钩子,和 data、methods 是同级的。并且 provide 的书写形式和 data 一样。
- provide 钩子用来发送数据或方法
- inject 钩子用来接收数据或方法
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: "bar",
},
// ...
};
// 子组件注入 'foo'
var Child = {
inject: ["foo"],
created() {
console.log(this.foo); // => "bar"
},
// ...
};
ref / $refs (父子,兄弟)
ref : 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。
这种方式也是实现兄弟组件之间的通信。子组件 1 通过 this.$emit通知父组件调用函数,父组件的函数里用this.$refs 拿到子组件 2 的方法,这样就实现兄弟组件之间的通信。
<template>
<!-- 父组件 -->
<base-input ref="usernameInput"></base-input>
</template>
<script>
export default {
mounted: function () {
this.$refs.usernameInput.focus();
},
};
</script>
<template>
<!-- 子组件 -->
<input ref="input" />
</template>
<script>
export default {
methods: {
// 用来从父级组件聚焦输入框
focus: function () {
this.$refs.input.focus();
},
},
};
</script>
$parent / $children (父子)
- 使用$parent 可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
- 使用$children可以让组件访问子组件的实例,但是,$children 并不能保证顺序,并且访问的数据也不是响应式的。
- $children 的值是数组,而$parent 是个对象
- 在根组件#app 上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent 得到的是 undefined,而在最底 层的子组件拿$children 是个空数组
- 通过$parent访问到的是上一级父组件的实例,可以使用$root 来访问根组件的实例
$attrs / $listeners (祖孙)
在父子组件传递时,通常使用 props/ $emit 来实现,祖孙也可以这么做,一层一层去实现,就没有 更简单的方法了吗?
答案是肯定有,就是要讲的 $attrs/ $listeners 来实现跨代通信 在讲这两个实例之前,先要说 inheritAttrs, 这个属性默认值是 true,主要作用是默认情况下,不将 props 作为 attribute 绑定, 也就是只会继承父组件除 props 之外的所有属性,设置为 false 之后,这些默认行为将会被去掉。而通过实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。
inheritAttrs 不管 true 还是 false,是不会影响 class 和 style 的
- $attrs:继承所有的父组件属性(除了 prop 传递的属性、class 和 style ),一般用在子组件的子元素上
- $listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)
下面举个例子:
<!-- 父组件(home.vue) -->
<template>
<div>
<aa test1="1" test2="2" @abc="abc"></aa>
</div>
</template>
<script>
import aa from "./aa.vue";
export default {
name: "Home",
components: {
aa,
},
methods: {
abc(value) {
console.log("value", value); // 监听到来自孙子组件
},
},
};
</script>
<!-- 子组件(aa.vue) -->
<template>
<div class="aa">
<bb v-bind="$attrs" v-on="$listeners"></bb>
</div>
</template>
<script>
import bb from "./bb.vue";
export default {
inheritAttrs: false, //默认值为true,为true时,会显示下面红圈标记内容
components: {
bb,
},
};
</script>
<!-- 孙子组件(bb.vue) -->
<template>
<div class="bb">
{{$attrs.test2}}
<div @click="btn">btn</div>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 默认值为true,为true时,会显示下面红圈标记内容
created() {
console.log(this.$attrs); // {test1: "1", test2: "2"}
console.log(this.$listeners); // {abc: ƒ}
},
methods: {
btn() {
this.$emit("abc", "来自孙子组件"); // 触发父组件监听(home.vue),把数据传给home.vue组件
},
},
};
</script>