MV*的理解
1、概念
在计算机编程领域,MV*(也称为 MVC、MVP、MVVM 等)是一种用于组织和设计应用程序结构的模式。这些模式旨在实现应用程序的解耦、可维护性和可扩展性。MV 代表着 Model-View-(表示控制器或视图模型等其他组件)的缩写,其中可以根据具体的模式而变化
2、MVC(Model-View-Controller):
模型(Model):负责处理应用程序的数据逻辑,通常包含数据的获取、处理和存储。 视图(View):负责展示数据给用户,通常是用户界面的组件。 控制器(Controller):负责处理用户输入,并根据输入更新模型和视图。它充当了模型和视图之间的中介者。
3、MVP(Model-View-Presenter):
模型(Model):同样负责处理应用程序的数据逻辑,与 MVC 中的模型类似。 视图(View):负责展示数据给用户,但通常比 MVC 中的视图更为被动,不直接处理用户输入。 主持人(Presenter):充当了控制器的角色,处理用户输入并根据输入更新模型和视图。与控制器不同的是,它更紧密地与视图交互,可以通过接口直接与视图进行交互。
4、MVVM(Model-View-ViewModel):
模型(Model):同样负责处理应用程序的数据逻辑,与 MVC 和 MVP 中的模型类似。 视图(View):负责展示数据给用户,通常是用户界面的组件。 视图模型(ViewModel):是连接视图和模型的中介者。它从模型中获取数据,并将数据转换成视图所需的格式,同时处理用户输入,并将用户操作传递给模型。视图模型使得视图与模型的交互解耦,让视图能够更专注于展示数据
声明式及与传统 DOM 开发对比
1、什么叫声明式开发
声明式编程是一种编程范型,与命令式编程相对立。它描述目标性质,让计算机明白目标,而非流程。声明式编程不用告诉电脑问题领域,从而避免随之而来的副作用。而指令式编程专指需要用算法来明确的指出每一步该怎么做。
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
2、与传统 DOM 开发对比
在 div 中写入你好 vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>
document.getElementById("app").innerHTML = "你好vue";
</script>
</body>
</html>
编写第一个 vue 代码并实现 hello word
<template>
<div>
{{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: "你好 vue",
};
},
};
</script>
Vue 基础语法
vue 语法分为选项式 API(Option api)和组合式 api(Composition Api),我们以选项式 Api 入门
基本构成
template、script、style 三部分构成。template 可以理解成编写 html 的地方,script 编写逻辑 js 的地方,style 编写样式的地方
Vue 的插值表达式
概念
vue 中,使用{{}}双花括号,在 html 标签的 '内容区域' 中表现数据,这个技术称为插值表达式。
表达式:变量、常量、算术运算符、比较运算符、逻辑运算符、三元运算符等等。我们通过把{{}}里面的内容称作组件的状态
Vue
<template>
<div>
{{ message }}
</div>
</template>
如何使用定义状态并在 template 中显示
<template>
<div>
<!-- 花括号中可以直接展示这个状态 -->
{{ message }}
</div>
</template>
<script>
/**
* 在script中使用export default 导出一个对象
* 在对象里面定义一个函数 data
* 在 data 函数里面return 一个 对象
* 在 return 的这个对象里面 可以直接定义当前组件的状态
*/
export default {
data() {
return {
// 定义的message状态
message: "你好 vue",
};
},
};
</script>
如何展示当前的这个页面(组件)
在入口文件 main.js 中引入
import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
/**
* 把引入的组件直接放到createApp中
*/
createApp(App).mount("#app");
条件渲染
v-if
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 的时候被渲染。可以和正常使用 if else 嵌套(不推荐)
<template>
<div>
<div v-if="isShow">出现吗</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
};
},
};
</script>
v-show
v-show 和 v-if 的用法几乎一致(v-show 没有嵌套),不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 display 属性
<template>
<div>
<div v-show="isShow">显示吗</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
};
},
};
</script>
v-if 与 v-show 如何选择
- v-if 因为每次都会彻底删除 DOM 或者 不渲染 DOM, 所以适用于展示隐藏切换不频繁的时候
- v-if 的值如果为 false, 那么 DOM 直接不加载
- v-show 只是修改元素 DOM 的 display 样式, 所以一般适用于展示隐藏切换频繁的时候
- v-show 的值如果为 false, 那么 DOM 还会加载 只不过 display 的值为 none
Vue 属性绑定
属性指的是 Html 元素的属性,例如 a 标签的 title 就是一个属性!
- vue 如何动态绑定属性
- Vue 绑定属性一般是在属性名后面加 : 也就是 v-bind(不推荐使用 v-bind),当使用 : 绑定 html 元素的属性后,属性就相当于参数,属性值为预期值,一般在实际应用中,: 绑定的属性值都是通过计算得来 或者 是一个通过判断时刻变化的值。该操作也叫动态属性在组件传值中经常使用。
<template>
<div>
<a :href="url">百度</a>
</div>
</template>
<script>
export default {
data() {
return {
url: "https://www.baidu.com",
};
},
};
</script>
Vue 处理样式
内联
<template>
<div style="color:'red'">红色</div>
</template>
在 style 中编写
- 创建 xxx.css 文件
.red {
color: red;
}
- 在文件中引入
<template>
<div class="red">红色</div>
</template>
<script>
import "./learn-style.css";
</script>
样式污染
- 产生原因
- Vue 最终编译打包后都在一个 html 页面中,如果在两个组件中取一样类名分别引用在自身,那么后者会覆盖前者。默认情况下,只要导入了组件,不管组件有没有显示在页面中,组件的样式就会生效。也就是说并没有自己的局部作用域
- 解决思路
- 手动处理 (起不同的类名, 但项目大了之后就会导致类名很乱, 不利于团队协作)
- CSS IN JS: 以 js 的方式处理 css (推荐)
- CSS IN JS
- CSS IN JS 是使用 JS 编写 CSS 的统称, 用来解决 CSS 样式冲突, 覆盖等问题
- CSS IN JS 的具体实现有 50 多种
- React 常用:
CSS Modules、styled-components - Vue 常用:
<style scoped> 、css modules
- React 常用:
- 推荐使用:
<style scoped>(脚手架自动继承, 并且非常简单)
Scoped CSS
- 基本使用
在 style 标签上使用 scoped,当 <style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。
<template>
<div class="ex">hi</div>
</template>
<style scoped>
.ex {
color: red;
}
</style>
- 原理解析
- 每个 Vue 文件都将对应一个唯一的 id, 该 id 根据文件路径名和内容 hash 生成, 通过组合形成 scopeId
- 编译 template 标签时, 会为每个标签添加了当前的 scopeId
<div class="demo">test</div> // 会被编译成: <div class="demo" data-v-12e4e11e>test</div>
- 编译 style 标签时, 会根据当前组件的 scopeId 通过属性选择器和组合选择器输出样式
.demo { color: red; } // 会被编译成: .demo[data-v-12e4e11e] { color: red; }
-
深层原理
- vue-loader 通过生成 哈希 ID,根据 type 的不同, 调用不同的 loader 将 哈希 ID 分别注入到 DOM 和属性选择器中。实现 CSS 局部作用域的效果。CSS Scoped 可以算作为 Vue 定制的一个处理原生 CSS 作用域的解决方案
-
混用本地和全局样式
- 可以直接创建一个全局的 css 文件, 在入口文件处引入, 或者在单个组件内使用不加 scoped 的 style
<style> /* 全局样式 */ </style> <style scoped> /* 本地样式 */ </style> -
样式穿透
- 如果你的引入了第三方库,如果你想修改第三方库的样式,直接通过 dom 查找,修改样式是没有效果的。那么可以使用以下属性
>>>/deep/::v-deep
<style lang="scss" scoped> .box-card { >>> .el-card__body { padding: 10px; } } </style> <style lang="scss" scoped> .box-card { /deep/.el-card__body { padding: 10px; } } </style> <style lang="scss" scoped> .box-card { ::v-deep.el-card__body { padding: 10px; } } </style> - 如果你的引入了第三方库,如果你想修改第三方库的样式,直接通过 dom 查找,修改样式是没有效果的。那么可以使用以下属性
-
注意
- 通过 v-html 创建的 DOM 内容不受 scoped 样式影响,但是你仍然可以通过深度作用选择器来为他们设置样式
- Scoped 样式不能代替 class。考虑到浏览器渲染各种 CSS 选择器的方式,当
p { color: red }是 scoped 时 (即与特性选择器组合使用时) 会慢很多倍。如果你使用 class 或者 id 取而代之,比如.example { color: red },性能影响就会消除
Vue 中的事件
定义事件
Vue 元素的事件处理和 DOM 元素的很相似, 但是语法有一些区别 使用
@修饰符(v-on:的缩写) + 事件名的方式给 DOM 添加事件后面跟方法名, 方法名可以直接加括号(@click='add(可以书写实参)') 里面进行传参, 对应的事件处理函数必须在 methods 对象中定义
<template>
<div>
<!-- 在button上定义点击事件 -->
<button @click="hello('传入的参数')">你好</button>
</div>
</template>
<script>
export default {
/**
* methods 在vue定义 方法的属性对象
* 所有的方法都必须在methods里面定义
*/
methods: {
hello(msg) {
console.log("事件触发啦哈哈哈");
console.log(msg);
},
},
};
</script>
事件修饰符
为了更好的处理时间, Vue3 提供了一些便利的事件修饰符, 事件修饰符可以用于改变默认事件行为, 限制事件触发条件等 如:
.stop,.prevent,.capture,.self,.once......
-
.stop
- 阻止事件冒泡, 即停止事件在父元素中的传播
<template> <div class="box" @click="handle2"> <div class="box2" @click="handle"></div> </div> </template> <script> export default { methods: { handle() { console.log("触发"); }, handle2() { console.log("冒泡"); }, }, }; </script>
-
.prevent
- 阻止事件的默认行为, 如提交表单或点击链接后的页面跳转
<template> <!-- 只触发点击事件,不触发跳转 --> <a href="https://www.baidu.com" @click.prevent="handle">百度</a> </template> <script> export default { methods: { handle() { console.log("触发"); }, }, }; </script> -
.once
- 只触发一次事件处理方法, 之后解绑事件
<template> <button @click.once="handle">点击一次就失效</button> </template> <script> export default { methods: { handle() { console.log("触发"); }, }, }; </script>
event 对象
- 默认传入获取 event
<template>
<!--
如果事件什么都不传、并且不写()
那么事件处理函数会默认接收到event对象
-->
<button @click="handle">点击</button>
</template>
<script>
export default {
methods: {
handle(event) {
console.log(event);
},
},
};
</script>
- 携带其他参数获取 event
<template>
<!--
在 template 里面使用 $event 获取当前事件的 event 对象
-->
<button @click="handle('第一个参数', $event)">点击</button>
</template>
<script>
export default {
methods: {
handle(msg, event) {
console.log(event);
},
},
};
</script>
在函数内使用 this 获取当前 Vue 上下文
- 可以直接使用 this.xx 使用 data 里定义的状态,或者使用 this.xx()调用 methods 里面定义的其他函数
- 注意:this 指向问题
<template>
<button @click="handle">点击</button>
</template>
<script>
export default {
data() {
return {
num: 1,
};
},
methods: {
handle() {
console.log(this.num);
this.handle2();
},
handle2() {
console.log("第二个方法");
},
},
};
</script>
Vue 定义组件状态
概念
存放当前组件状态的地方,组件所有的状态数据都可以放到 data 里面存储,在 data 里定义的数据具备响应式。在组件中 data 只能是函数。
定义状态并驱动视图 (点击按钮 num + 1)
<template>
<div>
{{ num }}
<button @click="handle">num+1</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 1,
};
},
methods: {
handle() {
// 可以直接使用this拿到当前组件的状态并修改,视图会自动重新渲染
this.num += 1;
},
},
};
</script>
数据修改是同步还是异步
- 思考以下代码会打印什么内容 (同步更新数据, 异步更新 dom)
<template>
<div>
<button id="btn" @click="handle">{{ num }}</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 1,
};
},
methods: {
handle() {
this.num += 1;
console.log("num的值", this.num);
console.log(
"按钮里的数据",
document.getElementById("btn").innerHTML
);
},
},
};
</script>
- nextTick
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
<template>
<div>
<button id="btn" @click="handle">{{ num }}</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 1,
};
},
methods: {
async handle() {
this.num += 1;
console.log("num的值", this.num);
await this.$nextTick();
console.log(
"按钮里的数据",
document.getElementById("btn").innerHTML
);
},
},
};
</script>
- 原理
- Vue 是异步执行 dom 更新的,一旦观察到数据变化,Vue 就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个 watcher 被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和 DOM 操作。而在下一个事件循环时,Vue 会清空队列,并进行必要的 DOM 更新。
- 当你设置 this.num = '新的数据',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的 DOM 更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。
- 为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback)/this.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用
- 使用场景
- Vue 生命周期的 created() 钩子函数进行的 DOM 操作一定要放在 Vue.nextTick() 的回调函数中
- 当项目中你想在改变 DOM 元素的数据后基于新的 DOM 做点什么,对新 DOM 一系列的 js 操作都需要放进 Vue.nextTick() 的回调函数中
Vue 渲染列表 + key 的作用
列表渲染
Vue 中使用 v-for 指令进行列表渲染
<template>
<div>
<!-- item 代表 当前循环的每一项 -->
<!-- index 代表 当前循环的下标-->
<!-- 注意:必须要加key-->
<div v-for="(item, index) in arr" :key="index">{{ item }}</div>
</div>
</template>
<script>
export default {
data() {
return {
arr: [1, 2, 3, 4],
};
},
};
</script>
为什么循环的时候需要加 key
- 作用
- key 的作用主要是为了高效的更新虚拟 DOM,提高渲染性能。
- key 属性可以避免数据混乱的情况出现。
- 原理
- vue 实现了一套虚拟 DOM,使我们可以不直接操作 DOM 元素只操作数据,就可以重新渲染页面,而隐藏在背后的原理是高效的 Diff 算法
- 当页面数据发生变化时,Diff 算法只会比较同一层级的节点;
- 如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点后面的子节点;如果节点类型相同,则会重新设置该节点属性,从而实现节点更新
- 使用 key 给每个节点做一个唯一标识,Diff 算法就可以正确识别此节点,"就地更新"找到正确的位置插入新的节点
- 注意
- key 的值只能是字符串或数字类型
- key 的值必须具有唯一性(即:key 的值不能重复)
- 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
- 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性,实际项目中如果没有 id,推荐使用 index)
- 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)
为什么 index 不能当作 key
- 因为 index 只是代表数组中的位置
- 当我们删除了数组中位置靠前或者中间的某一个元素
- 此时会出现数组塌陷的情况, 也就是被删除的元素后的所有元素的下标都发生了变化
- 如果 key 值发生变化, 那么页面的标签就一定会被重新渲染
- 所以我们只是删除了某一个对象, 但是会导致这个对象后续的所有标签重新渲染
<template>
<div>
<div v-for="(item, index) in infos" :key="index">
{{ item.name }} --- <button @click="del(index)">删除</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
infos: [
{
name: "张三",
age: 18,
},
{
name: "李四",
age: 20,
},
{
name: "王五",
age: 21,
},
],
};
},
methods: {
del(index) {
this.infos.splice(index, 1);
},
},
};
</script>