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" />