自定义指令

如何自定义指令? 其实关于这个问题官方文档上已经有了很好的示例的,我们先来温故一下。 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。举个聚焦输入框的例子,如下: 当页面加载时,该元素将获得焦点 (注意: autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能: // 注册一个全局自定义指令 `v-focus` Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus() } }) 如果想注册局部指令,组件中也接受一个 directives 的选项: directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } } } 然后你可以在模板中任何元素上使用新的 v-focus property,如下: <input v-focus> 指令内部提供的钩子函数 一个指令定义对象可以提供如下几个钩子函数 (均为可选): bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 inserted: 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 update: 所在组件的 VNode 更新时调用,**但是可能发生在其子 VNode 更新之前。**指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。 componentUpdated: 指令所在组件的 VNode 及其子VNode全部更新后调用。 unbind: 只调用一次,指令与元素解绑时调用。 钩子函数参数 指令钩子函数会被传入以下参数: ...

October 5, 2024

vue事件处理

一、DOM 事件流 想必看过《红宝书》的前端 er 对标题都不会特别陌生,我们来看看定义:当一个 HTML 元素产生一个事件时,该事件会在树形结构的 DOM 上面沿着元素节点路径进行传播,事件所经过的路径结点都会收到该事件,这个传播过程可称为 DOM 事件流。 二、DOM 事件流模型 DOM 事件流分为 捕获型事件流 和 冒泡型事件流。两种事件流分别对应三阶段 DOM 事件流模型中的捕获阶段和冒泡阶段: 捕获阶段:事件从最外面的祖先节点依次传递到最里面的后代节点 目标阶段:真正的目标节点正在处理事件的阶段 冒泡阶段:事件又从最里面的后代节点逐层传出到最外面的祖先节点 三、Vue 之事件处理 Vue 中提供了事件绑定的语法糖,我们可以很简单地在标签中直接使用 @click="handleClick($event)" 就可以绑定点击事件。 而且在 Vue 里面,Vue 的事件触发默认为冒泡过程监听,意思就是上面 @click="handleClick($event)" 中点击事件的执行是冒泡过程触发的。 四、Vue 之事件修饰符 4.1 .capture 捕获监听器 <div @click="log(1)" @click.capture="log(1)" style="background-color: #00f"> <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f"> <div @click="log(3)" @click.capture="log(3)" style="background-color: #ccf"> <div @click="log(4)" @click.capture="log(4)" style="background-color: #fff"> 点击这里 </div> </div> </div> </div> 控制台打印结果为:1 2 3 4 4 3 2 1 ...

October 5, 2024

v-for

在 Vue.js 开发中,v-for 是我们用来遍历数组、对象或其他数据结构的核心指令。然而,很多开发者可能忽略了其中的一个细微差别:in 和 of。这两个关键字看似相似,但却有各自不同的使用场景和适用数据类型。理解它们的区别,可以帮助我们编写更简洁、高效的代码。 本文将详细探讨 v-for 中的 in 和 of,并讲解它们各自的适用场景,帮助你在 Vue.js 项目中做出最佳选择。 什么是 v-for? v-for 是 Vue.js 提供的用于遍历数据的指令,它可以帮助你快速渲染列表、表格等动态生成的内容。在使用 v-for 时,我们通常需要遍历一个数据集合,并将每一项绑定到 DOM 元素上。 in 和 of 的概念 v-for 中的 in 和 of 是遍历数据时的两个常用关键字,它们的主要区别在于:in 更通用,可以遍历数组、对象和整数范围;而 of 专注于遍历可迭代对象(如数组、字符串、Set、Map 等) 。 接下来,我们深入探讨它们的具体用法及差异。 1. in:适用于数组、对象、整数范围 1.1 遍历数组 in 是 Vue.js 中默认推荐的方式,用于遍历数组中的元素。你可以轻松地用 in 处理数组中的每个值。 <ul> <li v-for="item in items">{{ item }}</li> </ul> 假设 items = ['apple', 'banana', 'cherry'],输出结果为: <li>apple</li> <li>banana</li> <li>cherry</li> 1.2 遍历对象 当你需要遍历对象的键和值时,in 也非常方便。通过 (value, key) 语法,你可以轻松获取对象的键和值。 ...

October 5, 2024

v-if/v-show

v-show原理 export const vShow: ObjectDirective<VShowElement> = { beforeMount(el, { value }, { transition }) { el._vod = el.style.display === 'none' ? '' : el.style.display if (transition && value) { transition.beforeEnter(el) } else { setDisplay(el, value) } }, mounted(el, { value }, { transition }) { if (transition && value) { transition.enter(el) } }, updated(el, { value, oldValue }, { transition }) { // ... }, beforeUnmount(el, { value }) { setDisplay(el, value) } } 有transition就执行transition,没有transition就直接设置display。不管初始条件是什么,元素总是会被渲染。 v-if原理 export const transformIf = createStructuralDirectiveTransform( /^(if|else|else-if)$/, (node, dir, context) => { return processIf(node, dir, context, (ifNode, branch, isRoot) => { // ... return () => { if (isRoot) { ifNode.codegenNode = createCodegenNodeForBranch( branch, key, context ) as IfConditionalExpression } else { // attach this branch's codegen node to the v-if root. const parentCondition = getParentCondition(ifNode.codegenNode!) parentCondition.alternate = createCodegenNodeForBranch( branch, key + ifNode.branches.length - 1, context ) } } }) } ) 返回一个node节点,render函数通过表达式的值来决定是否生成DOM ...

October 4, 2024

插槽

一、插槽的几种使用方式 1. 普通插槽 举例来说,这里有一个 <FancyButton> 组件,可以像这样使用: <FancyButton> Click me! <!-- 插槽内容 --> </FancyButton> 而 <FancyButton> 的模板是这样的: <button class="fancy-btn"> <slot></slot> <!-- 插槽出口 --> </button> <slot> 元素是一个 插槽出口 (slot outlet),标示了父元素提供的 插槽内容 (slot content) 将在哪里被渲染。 最终渲染出的 DOM 是这样: <button class="fancy-btn">Click me!</button> 2. 具名插槽 有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个 <BaseLayout> 组件中,有如下模板: <div class="container"> <header> <!-- 标题内容放这里 --> </header> <main> <!-- 主要内容放这里 --> </main> <footer> <!-- 底部内容放这里 --> </footer> </div> 对于这种场景, <slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容: ...

October 4, 2024

组件通信

Vue3 组件通信方式 props $emit expose / ref $attrs v-model provide / inject Vuex mitt Vue3 通信使用写法 1. props 用 props 传数据给子组件有两种方法,如下 方法一,setup() 方法写法 // Parent.vue 传送 <child :msg1="msg1" :msg2="msg2"></child> <script> import child from "./child.vue" import { ref, reactive } from "vue" export default { data(){ return { msg1:"这是传级子组件的信息1" } }, setup(){ // 创建一个响应式数据 // 写法一 适用于基础类型 ref 还有其他用处,下面章节有介绍 const msg2 = ref("这是传级子组件的信息2") // 写法二 适用于复杂类型,如数组、对象 const msg2 = reactive(["这是传级子组件的信息2"]) return { msg2 } } } </script> // Child.vue 接收 <script> export default { props: ["msg1", "msg2"],// 如果这行不写,下面就接收不到 setup(props) { console.log(props) // { msg1:"这是传给子组件的信息1", msg2:"这是传给子组件的信息2" } }, } </script> 方法二,setup 语法糖 ...

October 4, 2024

生命周期

vue3生命周期 在Vue中,生命周期指的是Vue实例从创建到销毁的过程。在这个过程中,Vue实例会经历一系列的阶段,包括数据观测、模板编译、挂载DOM、更新DOM、卸载等等。在这些阶段,Vue提供了一些生命周期钩子函数,让我们可以在特定的时机执行某些代码。 为什么需要生命周期 生命周期钩子函数为我们提供了在Vue实例生命周期不同阶段执行代码的能力。这对于非响应式的操作非常有用,例如直接操作DOM、添加事件监听器或者创建定时器等。这些操作如果在数据变化时执行可能会导致不必要的性能开销,而生命周期钩子函数让我们可以更合理地安排这些操作的执行时机。一些常用的库,例如react,webpack都提供了生命周期的钩子,方便开发者理解整个运行流程。 Vue3 有哪些生命周期,分别在什么时候调用? Vue3的生命周期钩子函数主要包括: beforeCreate: 创建实例之前调用,此时实例的数据观测、事件等还未初始化。 created: 创建实例后调用,此时实例的数据观测、事件等已经初始化完成。 beforeMount: 挂载实例之前调用,此时模板已经编译完成,但是还未挂载到DOM。 mounted: 挂载实例后调用,此时实例已经挂载到DOM,可以进行DOM操作。 beforeUpdate: 数据更新之前调用,此时可以进行状态的最后更改。 updated: 数据更新后调用,此时DOM已经完成更新,可以进行DOM操作。 beforeUnmount: 卸载实例之前调用,此时实例仍然完全可用。 unmounted: 卸载实例后调用,此时实例已经完全卸载。 生命周期类型的枚举位于packages/runtime-core/src/enums.ts目录下: export const enum LifecycleHooks { BEFORE_CREATE = 'bc', // 创建之前 CREATED = 'c', // 创建 BEFORE_MOUNT = 'bm', // 挂载之前 MOUNTED = 'm', // 挂载 BEFORE_UPDATE = 'bu', // 更新之前 UPDATED = 'u', // 更新 BEFORE_UNMOUNT = 'bum', // 卸载之前 UNMOUNTED = 'um', // 卸载之后 DEACTIVATED = 'da', // 被 keep-alive 缓存的组件停用时调用。 ACTIVATED = 'a', // 被 keep-alive 缓存的组件激活时调用。 RENDER_TRIGGERED = 'rtg', // 依赖被收集时调用 RENDER_TRACKED = 'rtc', // 依赖被触发时调用 ERROR_CAPTURED = 'ec', // 错误捕获 SERVER_PREFETCH = 'sp' } 上面生命周期可以用一张图来表示: 选项式和组合式生命周期的区别 在Vue3中,我们可以使用选项式API和组合式API来定义组件。这两种API对应的生命周期钩子函数有一些区别。 选项式API的生命周期钩子函数定义在组件选项对象的methods中,例如: export default { methods: { created() { // do something } } } 组合式API的生命周期钩子函数定义在setup函数中,需要使用import从vue中引入,例如: import { onMounted } from 'vue'; ​ export default { setup() { onMounted(() => { // do something }); } } 需要注意的是,在组合式API中没有beforecate和created钩子,因为他们本身被setup这个函数替代了,直接把相关逻辑写在setup中就行了 ...

October 4, 2024

methods

Methods 的初始化 在 Vue.js 中,每个组件实例(vm)都会有一个 methods 属性,这个属性包含了组件中定义的所有方法。这些方法需要被初始化到实例上,以便可以在模板和生命周期钩子中被调用。初始化过程如下: function initMethods(vm, methods) { for (var key in methods) { vm[key] = methods[key] == null ? noop : bind(methods[key], vm); } } 这段代码遍历 methods 对象中的每个属性,并将其复制到 Vue 实例(vm)上。如果方法不存在(methods[key] == null),则赋值为一个空函数(noop),以避免对 undefined 的调用。否则,使用 bind 方法将每个方法的 this 绑定到 Vue 实例上。 Methods 作用域的固定 在 JavaScript 中,函数的 this 值是在函数被调用时确定的,而不是在定义时。这可能导致在 Vue 实例的方法中 this 并不指向实例本身,尤其是在方法被传递给其他函数或作为回调函数时。Vue 使用 bind 来解决这个问题,确保 this 总是指向 Vue 实例。 原生和兼容性 bind 实现 Vue.js 提供了两种 bind 的实现:原生的和兼容性的。兼容性实现是为了兼容那些不支持 Function.prototype.bind 的旧浏览器。 function polyfillBind(fn, ctx) { function boundFn(a) { var l = arguments.length; return l ? (l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a)) : fn.call(ctx); } boundFn._length = fn.length; return boundFn; } function nativeBind(fn, ctx) { return fn.bind(ctx); } var bind = Function.prototype.bind ? nativeBind : polyfillBind; polyfillBind 函数创建了一个新的函数 boundFn,它在调用时使用 call 或 apply 将 fn 的 this 绑定到 ctx 上。 nativeBind 函数直接使用原生的 bind 方法。 bind 变量根据浏览器是否支持原生 bind 来选择使用哪种实现。 使用 bind 的好处 使用 bind 后,我们可以在组件的方法中直接使用 this 来访问实例的属性和方法,而不用担心 this 的指向问题。这使得代码更加简洁和安全。 ...

October 4, 2024

侦听器watch

一、 watch API 我们先看看 watch的使用例子,watch 有多种用法,可以接收多种类型参数: import { reactive, watch } from 'vue' // 1. 传入getter函数 const state = reactive({ count: 0 }) watch(() => state.count, (count, prevCount) => { // 当 state.count 更新,会触发此回调函数 }) // 2. 传入reactive对象 watch(state, (count, prevCount) => { // 当 state.count 更新,会触发此回调函数 }) // 3. 传入ref对象 const stateRef = ref(0) watch(stateRef, (count, prevCount) => { // 当 stateRef.value 更新,会触发此回调函数 }) // 4.监听多个数据源,回调函数接受两个数组,分别对应来源数组中的新值和旧值: watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ }) 从上面的例子可以看到,对于 watch,它能接收的第一个参数类型非常多。 你可以传入一个ref对象、一个响应式对象、一个 getter 函数、甚至是一个数组。 我们在上一篇中知道了computed对象其内部是借助了 effect函数 创建了一个 reactiveEffect函数,在访问computed对象的值时,执行其 runner函数 求值。 ...

October 4, 2024

计算属性

前言 Vue 的计算属性(computed)是 Vue.js 中非常重要的一个概念。它允许你在 Vue 组件中定义一些计算逻辑,这些逻辑会依赖于 Vue 的响应式数据,并且具有缓存机制,只有在依赖数据发生改变时才会重新计算,从而提高了性能。 计算属性的主要作用包括: 数据转换 数据过滤 响应式依赖 缓存机制 简化模板 在本篇文章中,我们将详细探寻如何利用计算属性优化 Vue 组件的数据处理和渲染逻辑,深入了解 Vue 计算属性的精髓。 一. 认识计算属性 1. 什么是计算属性 Vue.js 中的计算属性(computed properties)是 Vue 实例中非常重要的一个特性。它们用于对 Vue 实例的数据进行动态计算,且具有缓存机制,只有在相关依赖发生改变时才会重新计算。这种特性使得计算属性非常适合用于处理模板中的逻辑。 2. 计算属性的定义 计算属性是在 Vue 实例的computed选项中定义的,可以是一个函数或具有get和set方法的对象。函数形式的计算属性会在调用时被执行,而对象形式的计算属性则可以提供自定义的get和set方法。 computed: { // 函数形式的计算属性 reversedMessage: function () { return this.message.split('').reverse().join(''); }, // 对象形式的计算属性 fullName: { get: function () { return this.firstName + ' ' + this.lastName; }, set: function (newValue) { var names = newValue.split(' '); this.firstName = names[0]; this.lastName = names[names.length - 1]; } } } 3. 计算属性的依赖 计算属性可以依赖于 Vue 实例的响应式数据。当依赖数据发生改变时,相关的计算属性会重新计算。 ...

October 4, 2024