跳到主要内容

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 生命周期时,有两个特殊的生命周期,activateddeactivated 就是在激活 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 和父组件通信

    1. 父组件向子组件传值 props 只能是父组件向子组件进行传值,props 使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。 props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。 props 属性名规则:若在 props 中使用驼峰形式,模板中需要使用短横线的形式
    1. 子组件通过 $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>

eventBus 事件总线($emit / $on)(任意组件通信)

信息

事件总线,就是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所有组件都可以上下平行地通知其他组件。主要是采取的是发布订阅模式

直接上代码:

Vue.prototype.$eventBus=new Vue()

// 在需要发送消息 的组件中调用
this.$eventBus.$emit('testEmit', 'test')

// 在需要使用的地方
this.$eventBus.$on('testEmit', this.testEmit);
methods: {
testEmit(params) {
console.log(params); // test
}
}

从上面例子看到,$on 监听了 「testEmit」, 如果我离开了该组件,这个监听还是在的,所以就要求我们,在离开组件时,要把当前组件中的监听取消掉。

beforDestory() {
this.$eventBus.$off('testEmit')
}

子组件可以直接改变父组件的数据吗?

答案肯定是不可以。 子组件不可以直接改变父组件的数据。 这样做主要是为了维护父子组件的单向数据流。 每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中发出警告。

Vue 提倡单向数据流,即父级 props 的更新会流向子组件,但是反过来则不行。这是为了防止意外的改变父组件状态,使得应用的数据流变得难以理解,导致数据流混乱。如果破坏了单向数据流,当应用复杂时,debug 的成本会非常高。 只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

实例方法 - 数据

$set

$set()是 Vue 提供的一个全局方法,用于给对象动态添加属性,以便触发视图更新。

// 其中,target是需要添加属性的对象,key是要添加的属性名,value是要添加的属性值。
Vue.set(target, key, value);

为什么需要 $set()? 我们知道,vue 是通过双向绑定来实现数据驱动视图,当数据更新,视图就会随之更新,这个前提是已经声明好了响应式数据,那么如果我们需要动态添加属性或者删除属性,这时候就需要用到 $set()

原理:

  • 如果目标是数组,直接使用数组的 splice 方法触发响应式;
  • 如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)

$watch

观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。回调函数得到的参数为新值和旧值。表达式只接受简单的键路径。对于更复杂的表达式,用一个函数取代。

  • 参数:

    • 第一个参数为 绑定需要 watch 的表达式或者函数

    • callback 监听的 表达式或者函数 结果发生变化,需要执行的东西

    • [options] 可选配置

      • deep 为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。
      vm.$watch("someObject", callback, {
      deep: true,
      });
      vm.someObject.nestedValue = 123;
      • immediate 在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调
      vm.$watch("a", callback, {
      immediate: true,
      });
      // 立即以 `a` 的当前值触发回调
      // 但是不可以在第一次触发回调中取消监听, 可以先校验取消的可用性
      var unwatch = vm.$watch(
      "value",
      function () {
      doSomething();
      if (unwatch) {
      unwatch();
      }
      },
      { immediate: true }
      );
  • 返回值: unwatch

    var unwatch = vm.$watch("a", cb);
    // 之后取消观察
    unwatch();

$delete

这是全局 Vue.delete 的别名。 删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制,但是你应该很少会使用它。