当前位置: 首页 > article >正文

Vue 系列之:基础知识

什么是 MVVM

MVVM(Model-View-ViewModel)一种软件设计模式,旨在将应用程序的数据模型(Model)与视图层(View)分离,并通过 ViewModel 来实现它们之间的通信。降低了代码的耦合度。

Model 代表数据模型,是应用程序中用于处理数据的部分。在 Vue.js 中,Model 通常指的是组件的 data 函数返回的对象,或者 Vue 实例的 data 属性。

View 是用户界面,是用户与应用程序进行交互的部分。在 Vue.js 中,View 通常指的是 Vue 组件的模板(template),它使用声明式的语法来描述如何渲染数据。Vue 的模板语法允许开发者以声明的方式将 DOM 绑定到底层 Vue 实例的数据上。

ViewModel 是 MVVM 模式的核心,它作为 View 和 Model 之间的桥梁,负责将 Model 的数据同步到 View 显示出来,以及将用户对 View 的操作反映到 Model 上。

Vue 实例通过其响应式系统(基于 ES6 的 Proxy 或 Object.defineProperty 实现)来监听 Model 的变化,并自动更新 DOM(View)

Vue 钩子函数

钩子函数(hook)是在系统在进行消息的传递处理时候利用钩子机制截取消息,可以对一些特定的消息进行处理。

为什么要叫钩子?

钩子就是用来挂的嘛,在线程执行的过程中,我们想要在步骤1和步骤2直接挂一个函数,什么东西可以挂呢?钩子!所以就有钩子函数 这个叫法了。
在这里插入图片描述

Vue 八大生命周期钩子函数:

Vue2Vue3调用时间
beforeCreatesetupvue 实例初始化之前调用
createdsetupvue 实例初始化之后调用
beforeMountonBeforeMount挂载到 DOM 树之前调用
mountedonMounted挂载到 DOM 树之后调用
beforeUpdateonBeforeUpdate数据更新之前调用
updatedonUpdated数据更新之后调用
beforeDestroyonBeforeUnmountvue实例销毁之前调用
destroyedonUnmountedvue实例销毁之后调用

Vue.js 官方文档

Vue 实例

每个 Vue 应用都是通过用 Vue 构造函数创建一个新的 Vue 实例开始的

// vue2
var vm = new Vue({
  // 选项
})
// vue3
import { createApp } from 'vue'

const app = createApp({
  /* 根组件选项 */
})

Vue 实例和 Vue 生命周期有什么关系?

在 Vue 中,当创建一个组件或 Vue 实例时,它会经历一系列初始化步骤,这就是所谓的生命周期。

Vue 生命周期描述了 Vue 实例从创建到销毁的整个过程。

Vue2 实例方法

Vue3 应用实例

Vue3 组件实例

Vue.prototype

用途:

  • Vue.prototype 是 Vue 构造函数的原型对象,用于向所有 Vue 实例添加共享的方法和属性。

  • 通过在 Vue.prototype 上定义方法或属性,可以确保这些方法或属性在所有 Vue 实例中都是可用的。

用法:

  • 添加全局方法:Vue.prototype.$myMethod = function() { ... };

  • 添加全局属性:Vue.prototype.$myProperty = 'some value';

为什么要以 $ 符号开头?

这是 Vue 中的一个简单约定,为了避免和组件中定义的数据、方法、计算属性产生冲突。

Vue.prototype 加一个变量,只是给每个组件加了一个属性,这个属性的值并不具有全局性。

例如:

// main.js
Vue.prototype.$test = "测试"

//test1.vue
mounted() {
    this.$test = "哈哈"
    console.log(this.$test) // 哈哈
    this.$router.push("/test2")
}

//test2.vue
mounted() {
    console.log(this.$test) // 测试
}

如果要实现全局变量的功能,需要把属性变为引用类型

// main.js
Vue.prototype.$test = { name: "测试" }

//test1.vue
mounted() {
    this.$test.name= "哈哈"
    console.log(this.$test) // 哈哈
    this.$router.push("/test2")
}

//test2.vue
mounted() {
    console.log(this.$test) // 哈哈
}

created 和 mounted 区别

  • created:在模板渲染成 html 前调用,通常初始化某些属性值,然后再渲染成视图。

  • mounted:在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。

    例如 chart.js 的使用 var ctx = document.getElementById(id),这个就一定要等 html 渲染完成后才可以完成,这就要用 mounted

computed 和 watch 区别

computed 计算属性:

  • 只有当计算属性所依赖的响应式数据发生变化时,计算属性才会重新求值。如果多次访问计算属性,但依赖项没有变化,那么它将返回之前的计算结果,而不是重新计算。

  • 在模板中可以直接使用计算属性

  • 计算属性总是返回一个值

<div>{{test}}</div>

data() {
	return {
		name: '张三',
		age: 10
	}
},
computed: {
	test() {
		return `姓名${this.name},年龄${this.age}`;
	}
}

watch 监听器:

  • watch 中的回调函数可以执行异步操作,而 computed 则不可以。

  • 没有缓存,每次触发时重新计算

  • 接收两个参数(newValue, oldValue)

data() {
	return {
		user: {
			name: "张三",
			age: 10,
		},
	};
},
watch: {
	user: {
		handler(newVal, oldVal) {
			console.log("用户信息发生变化:", newVal);
		},
		deep: true, // 深度监听
		immediate: true, // 立即触发
	},
}

总结:

  • 当多个属性通过计算影响一个属性的时候,建议用 computed

  • 当一个值发生变化之后,会引起一系列的操作,这种情况就适合用 watch

computed 如何传参

<div :title="text('123')"></div>

computed: {
    text(){
        return function (params) {
            console.log(params) // 123
            return params
        }
    }
}

执行顺序

父子组件生命周期执行顺序

  • 页面初始化时:

    父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted-> 父 mounted

  • 页面销毁时:

    父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed-> 父 destroyed

页面跳转的生命周期执行顺序

  • 旧页面跳转到新页面:

    新页面 created > 新 beforeMount > 旧 beforeDestroy > 旧 destroyed > 新 mounted

computed 、watch、created 、mounted 的先后顺序

  • immediate [ɪˈmiːdiət] 为 false 时: created => computed => mounted => watch

  • immediate 为 true 时: watch =>created=> computed => mounted

$nextTick

原理

Vue 采用异步更新策略,当你修改响应式数据时,Vue 不会立即更新 DOM,而是将这些 DOM 更新操作放入一个队列中,等到下一个事件循环时,再批量执行这些更新操作。$nextTick 的作用就是将回调函数(即传递给 $nextTick 的函数)添加到这个队列的末尾,确保在 DOM 更新完成后再执行回调。

批量的意思是指:当有多个 DOM 更新操作时,Vue 不会立即逐个地去更新 DOM,而是将这些更新操作收集起来,在下次事件循环时一次性处理,而不是循环依次更新。

在 Vue 里,数据是响应式的。当一个响应式数据发生变化时,与之绑定的 DOM 就需要更新。要是每次数据变化都立即更新 DOM,会带来大量的重排和重绘操作,严重影响性能。重排和重绘是浏览器渲染页面时开销较大的操作,频繁进行会导致页面卡顿。

JavaScript 的事件循环机制

实现细节

Vue 会根据不同的环境,选择不同的异步执行方法,优先顺序如下:

  • Promise.then:现代浏览器支持的异步方法,优先使用。

  • MutationObserver:用于监听 DOM 变化的 API,在不支持 Promise 的环境中使用。

  • setImmediate:IE 浏览器和 Node.js 环境支持的异步方法。

  • setTimeout:作为最后的兜底方案,所有环境都支持。

使用场景

  • 当你修改了数据后,需要立即操作更新后的 DOM 元素时,必须使用 $nextTick。

  • 当你修改了数据后,需要立即获取更新后的组件状态(如计算属性、子组件的状态等),必须使用 $nextTick。

  • 当你使用 v-if 或 v-for 动态渲染 DOM 元素后,需要立即操作这些元素时,必须使用 $nextTick。

  • 当你在 watch 监听器中监听数据变化,并需要操作 DOM 时,必须使用 $nextTick 确保 DOM 已经更新。

总结起来就是在操作执行完成时需要理解操作 DOM 或子组件的,都要使用 $nextTick

@click.native

  • @click:主要用于监听 Vue 组件内部自定义的点击事件,适用于普通 HTML 元素和自定义组件。当用于自定义组件时,需要在组件内部通过 $emit 触发自定义的 click 事件,外部才能监听到。

  • @click.native:用于监听原生 DOM 元素的点击事件,即使是在自定义组件上使用,它监听的也是组件根元素的原生点击事件,而不是自定义事件。对于普通 HTML 元素,@click.native 和 @click 效果相同。

意思就是:如果你想在组件内的子组件上绑定一个点击事件,如果你使用的是 @click,如果你的子组件内部没有通过 $emit 触发自定义的 click 事件,那么 @click 无效。

例如:

<!--子组件-->
<template>
    <div class="children">
        <div class="my-class">子组件内容</div>
    </div>
</template>

<!--父组件-->
<template>
  <div id="app">
    <Children @click="handleClick"></Children>
  </div>
</template>
<script>
import Children from "./Children.vue";
export default {
  components: { Children },
  methods: {
    handleClick(e) {
      console.log(e);
    }
  }
}
</script>

此时 handleClick 是不会触发的,除非把子组件改成:

<template>
    <div class="children" @click="handleChildClick">
        <div class="my-class">子组件内容</div>
    </div>
</template>

<script>
export default {
    methods: {
        handleChildClick() {
            // 通过 $emit 触发父组件的 click 事件
            this.$emit('click', '这是子组件触发的事件');
        }
    }
}
</script>

但是如果你使用的是 @click.native 就不会有这个问题,因为它监听的是组件根元素的原生点击事件,而不是自定义事件。

再举个例子加深理解:

<el-card @click="handleClick1"></el-card>
<el-card @click.native="handleClick2"></el-card>

<script>
methods: {
    handleClick1() {
        // 不会触发,因为 element-ui 的 el-card 组件没有定义 click 事件
    },
    handleClick2() {
        // 可以触发,因为是原生点击事件
    }
}
</script>

你以为你已经掌握了全部吗?不,对于 Vue2 来说,确实是这样,但是对于 Vue3 来说,情况又有不同:

<!--子组件-->
<template>
    <div class="children">
        <div class="my-class">子组件内容</div>
    </div>
</template>

<!--父组件-->
<template>
  <div id="app">
    <Children @click="handleClick"></Children>
  </div>
</template>
<script setup>
import Children from "./Children.vue";
const handleClick = e => {
  console.log(e);
}
</script>

发现也可以触发!为什么?

因为在 Vue 3 里,当父组件向子组件传递事件监听器时,如果子组件没有通过 emits 选项显式声明这些事件,那么这些事件监听器会被自动绑定到子组件的根元素上,这就是所谓的事件继承特性

上面的例子就是因为子组件没有显式定义 emits 选项,父组件中的 @click 会被当作原生 DOM 事件绑定到子组件的根元素上。

@click.stop

阻止事件冒泡,即阻止事件向父级元素传递。

<div id="app">
    <div v-on:click="parentClick">
        <button v-on:click="childClick">阻止单击事件继续传递</button>
    </div>
</div>

<script>
var app = new Vue({
    el: "#app",
    data: {
        name: "Vue.js"
    },
    methods: {
        parentClick: function () {
            alert("parentClick");
        },
        childClick: function () {
            alert("childClick");
        },
    }
});
</script>

<!--点击按钮,将会先弹出 "childClick", 再弹出 "parentClick"-->
<div id="app">
    <div v-on:click="parentClick">
        <button v-on:click.stop="childClick">阻止单击事件继续传播</button>
    </div>
</div>

<script>
var app = new Vue({
    el: "#app",
    data: {
        name: "Vue.js"
    },
    methods: {
        parentClick: function () {
            alert("parentClick");
        },
        childClick: function () {
            alert("childClick");
        },
    }
});
</script>

<!--点击按钮,只弹出 "childClick"-->

使用场景:点击子元素区域的的时候,不触发父级元素的点击事件。

@click.prevent

阻止事件的默认行为。

<!--点击链接时,阻止 a 标签跳转,仅执行函数 test4-->
<a href="http://www.baidu.com" @click.prevent="test4">百度一下</a>   

<!--点击按钮时,阻止表单默认提交行为 action,仅执行函数 test5-->
<form action="/xxx" @submit.prevent="test5">   
    <input type="submit" value="注册">
</form>

@keyup.enter

按键修饰符。

<!--按下键盘 enter 健时,执行方法 test7-->
<input type="text" @keyup.enter="test7">

directive 自定义指令

什么是自定义指令?

vue2.js 官方链接

vue3.js 官方链接

Vue 的自定义指令允许你对 DOM 进行低级别的操作。自定义指令通过 Vue 的全局方法 Vue.directive() 或组件的 directives 选项来注册。注册之后,你可以在 Vue 模板中通过 v- 前缀加上指令名来使用这个指令。

低级别操作是指直接通过 JavaScript 对 DOM 树中节点的进行增删改查,以及对节点的属性、样式、事件等进行操作。

全局自定义指令

Vue2:

// 在main.js中
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

Vue3:

const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
})

局部组件指令

Vue2:

created() {},
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

Vue3:

<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。

<template>
  <input v-focus />
</template>

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

或:

<template>
  <input v-focus />
</template>

<script>
export default {
  setup() {
    /*...*/
  },
  directives: {
    // 在模板中启用 v-focus
    focus: {
      /* ... */
    }
  }
}
</script>

钩子函数

Vue2:

函数描述
bind只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
componentUpdated指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind只调用一次,指令与元素解绑时调用。

Vue3:

函数描述
created在绑定元素的 attribute 前或事件监听器应用前调用
beforeMount在元素被插入到 DOM 前调用
mounted在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
beforeUpdate绑定元素的父组件更新前调用
updated在绑定元素的父组件及他自己的所有子节点都更新后调用
beforeUnmount绑定元素的父组件卸载前调用
unmounted绑定元素的父组件卸载后调用

钩子函数参数:

  • el:指令绑定到的元素。这可以用于直接操作 DOM。

  • binding:一个对象,包含指令值(value)、旧的指令值(oldValue)、参数(arg)等等

  • vnode

  • oldVnode

原理

当 Vue 组件的模板被编译时,Vue 会识别出模板中所有的 v- 前缀指令,这会包含 Vue 的内置指令和自定义指令。对于自定义指令,Vue 会根据注册的钩子函数生成相应的指令对象,这些对象包含了指令的名称、参数、表达式等信息。然后会将这些信息存储在之后生成的 VNode 对象中。最后在生成真实 DOM 的时候,Vue 会在相应的 DOM 元素上调用自定义指令的钩子函数。

v-model 的原理

v-model 是 Vue.js 中的一个语法糖,它内部实际上是使用了 v-bind (:)和 v-on (@)两个指令。

  • v-bind:实现数据到 DOM 的绑定

    • 修改 data 中的属性值会体现在页面上

    • 原理主要是基于 Vue 的响应式系统,当数据变化时触发相应的 setter,来触发渲染过程,以更新 DOM

  • v-on:实现 DOM 到数据的绑定

    • 在页面上修改的属性值会作用到 data 中

    • 原理是监听 DOM 元素上指定的事件,当事件发生时,执行相应的 JavaScript 代码或方法,从而更新属性值。

Vue 中使用 CSS 变量

<template>
    <!-- 方式一:
     在根或父或当前节点使用 --varName 来定义变量 
     然后当前节点使用 var(--varName) 来使用变量 -->
    <div class="parent">

        <!-- 方式二:可以在 data 中定义变量,在 template 中直接使用 -->
        <span :style="{ color: myColor1 }">parent</span>

        <!-- 方式三:data 中定义变量,css 中使用 v-bind 绑定变量 -->
        <div class="child1">child1</div>

        <!-- 方式四:通过 style module -->
        <div :class="classModule.child2">child2</div>

        <!-- 方式五:不好理解,不知道这样写的意义在哪里 -->
        <div class="child3" :style="{ '--my-color': myColor3 }">child3</div>
    </div>
</template>

<script lang="ts" setup>
const myColor1 = 'red'; // 方式二
const myColor2 = 'yellow'; // 方式三
const myColor3 = 'blue'; // 方式五
</script>

<style>
.parent {
    /* 方式一 */
    --parent-bg-color: skyblue;

    /* 方式一 */
    background-color: var(--parent-bg-color);
}

.child1 {
    /* 方式三 */
    color: v-bind(myColor2);
}

.child3 {
    /* 方式五 */
    color: var(--my-color);
}
</style>

<style module="classModule">
/* 方式四 */
.child2 {
    color: green;
}
</style>

Vue 项目结构

文件名注释
build项目构建(webpack)相关代码
config项目配置
dev.env.js开发环境变量
prod.env.js生产环境变量
test.env.js测试环境变量
index.js项目配置文件
package.jsonnpm 包配置文件,npm 脚本
src日常开发主要在这个文件夹
asset放置静态资源,会被 webpack 构建
main.js项目入口文件
components公共组件
App.vue根组件
pages页面文件
api接口文件
router路由文件
static纯静态资源,不会被 webpack 构建
index.html首页入口文件
README.md项目的说明文档
dist打包之后的文件

http://www.kler.cn/a/568968.html

相关文章:

  • 重构MVC
  • 一次连接,可能会多次创建socket???
  • 心智模式与企业瓶颈突破
  • 基于 Ray 构建的机器学习平台
  • MATLAB的msgbox函数使用教程(一)
  • Java 泛型(Generics)详解与使用
  • FPGA开发,使用Deepseek V3还是R1(2):V3和R1的区别
  • git命令学习记录
  • IMX6Ull学习笔记1:汇编点亮LED灯
  • 【智能音频新风尚】智能音频眼镜+FPC,打造极致听觉享受!【新立电子】
  • python多线程之Event机制笔记
  • Qt 中 **QGraphicsView** 框架的总结
  • openssl下aes128算法gcm模式加解密运算实例
  • 基于java,SpringBoot和Vue流浪动物救助领养管理系统设计
  • Qt中应用程序框架的体系说明 及应用程序类QApplication类深度解析与应用分析
  • ZK Rollup
  • JMeter 不同协议测试最佳实践汇总
  • 深入讨论C语言的可能抽象:部分对设计模式的思考
  • Maven 与持续集成(CI)/ 持续部署(CD)(一)
  • 小红的字母游戏(A组)