Vue 是一个 自底向上 逐层应用、注重 视图模块 、快捷构建 前端应用 的 渐进式框架 !
随着项目规模的增大、业务流程的复杂度提升,现在主流的项目开发方式主要是前后端分离模式
鉴于上面的开发要求,使用 Vue 框架的 优势 :
最基础的单个 .html 网页作为一个独立的应用:
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initialscale=1.0">
<title>第一个Vue应用title>
head>
<body>
<div id="app">
<h2>msg1: {{ message }} h2>
div>
<div>
<h2>msg2: {{ message }}h2>
div>
<script src="./vue.js">script>
<script>
// 1.创建一个Vue实例
const app = new Vue({
// 2.el(element缩写):将实例挂载到id=app的div上,表示将实例关键到那个标签上
el: "#app",
// 3.data:声明使用的数据,表示实例上需要使用的数据
data: {
message: "hello vue!"
}
})
script>
body>
html>
Vue 项目中最核心的就是一个 和页面中某个标签节点挂载的 实例对象
学习之前,先了解 Vue 实例的常见选项:
选项 | 描述 | 备注 |
---|---|---|
el | 挂载页面节点的选项 | 必须要有的,不进行挂载的话数据就不会被渲染 |
data | 声明实例中的数据的选项 | |
methods | 声明实例中的函数的选项 | |
watch | 声明实例中监听器/侦听器的选项,监控变量数据 |
在 watch 中添加一个与监听数据同名 的函数 |
computed | 声明实例中计算属性 的选项,用于自动运算 |
|
filters | 声明实例中过滤器的选项,用于条件过滤 | {{需要过滤的数据:filters添加的过滤函数}} |
components | 声明实例中子组件的选项,用于视图模块复用 | 引入——>声明 |
created() | 生命周期选项,表示实例创建 | |
mounted() | 生命周期选项,表示 DOM 挂载 | |
distroyed() | 生命周期选项,表示组件卸载 | |
directives | 注册自定义指令,同components一样,只不过一个是注册组件,一个是注册指令 | |
mixins | 组件中添加局部混入模块 | |
… | … | … |
vue
基础语法 Vue 应用开发中,每个网页视图中都会使用到的基础操作语法
基础语法:项目开发中使用非常频繁的语法,面试的时候需要有条理的、精确的进行描述的技术点
Vue 中通过固定的操作语法: Mustach 语法、插值表达式,用于渲染展示数据
基础语法:
{{ 表达式 }}
应用场景:
// 1、输出变量数据
{{ message }}
// 2、支持JS内置函数调用
{{ message.toUpperCase() }}
// 3、支持自定义函数的调用
{{ fn(message) }}
// 4、支持基础运算
{{ "消息:" + message}}
// 5、支持三元表达式
{{ editId ? '编辑数据' : '新增数据' }}
{{ age > 18 ? '成年': '未成年' }}
...
推荐/适用场景:
// 1、变量数据渲染输出【频繁】
{{ message }}
// 2、执行自定义函数
{{ fn(message )}}
// 3、三元表达式
{{ editId ? '编辑数据': '新增数据' }}
// 4、其他函数调用执行、表达式运算:不推荐在插值表达式中执行
// Vue中推荐将数据的运算/操作过程,放在实例的函数中进行,而不是放在插值表达式进行
// 方便后期代码功能维护
Vue 指令 directive ,描述了页面中的一些简单 DOM 操作的封装,通过指令语法完成基础 DOM 操作
作用:添加到标签属性中,以普通文本方式,渲染输出表达式结果数据
语法:
用途:
// 1、变量数据的输出[推荐]
// 2、执行基础运算
注意:
输出的数据中如果包含标签字符,如
,输出时会将标签当成普通文本进行输出 作用:添加到标签属性中,以富文本的方式,解释并渲染输出富文本数据
语法:
用途:
// 1、普通数据输出[推荐]
// 2、支持基础运算
注意:
作用:声明在标签属性中,用于一次性加载数据
语法:
{{ message }} // 使用v-once修饰之后,数据只被渲染一次,数据被修改后,显示页不变
注意:
作用:条件渲染指令,添加到标签的属性中,属性值是一个返回布尔值的表达式,用于条件判断是否显示指定的标签
语法:
需要展示的数据
注意:
作用:条件渲染指令,添加到标签的属性上,通过属性值表达式返回的布尔值,判断是否加载指定的标签
语法:
// 1.基础语法,条件判断是否加载指定标签
需要展示的数据
// 2.多条件语法,条件判断是否加载对应的标签
尊敬的用户您好,欢迎访问本系统
登录注册
// 3.多分支条件语法
角色:管理员
角色:会员
角色:游客
注意:
v-if 条件渲染指令,是通过 DOM 加载方式显式或者隐藏指定的标签
页面中需要频繁切换显示和隐藏,建议使用 v-show
页面中不需要频繁切换显示/隐藏状态的,建议使用 v-if
面试题:请描述 v-show 和 v-if 的区别?
一、应用场景
v-if:当组件中某块内容只会显示或者只会隐藏,不会因为页面上的操作再次改变显示状态时。
v-show:当组件中某块内容显示隐藏是可以变化时。
二、两者实现显示隐藏的方式
v-if:动态的向dom树中
添加或者删除
dom元素 v-show:通过设置dom元素的
display
属性 三、编译条件
v-if:惰性的,如果初始条件为假,则什么也不做,只有在条件第一次变为真时,才开始编译
v-show:在任何条件下都会被编译
四、v-if解决问题的场景
页签切换,返回等情况,出现表格或者图表渲染问题,可以考虑v-if去解决
作用:列表渲染指令,用于循环遍历序列数据
语法:
- {{ item }}
- {{ index }} {{ item }}
- {{ index }} {{ item }}
注意:
备注:为什么 v-for 和 v-if 不建议同时使用?
在处于同一节点的时候, v-for 优先级比 v-if 高。这意味着 v-if 将分别重复运行于每个 v-for 循环中。即先运行 v-for 的循环,然后在每一个 v-for 的循环中,再进行 v-if 的条件对比。所以,
不推荐v-if和v-for同时使用
作用:属性动态绑定指令,让标签的属性值可以是一个动态表达式的结果数据,增强页面功能
语法:
// 1.基础语法
// 2.快捷语法:属性前面添加一个冒号,优化v-bind语法
// 3.优化语法:绑定多个数据
注意:
v-bind 几种操作语法都需要熟练
作用:给标签添加事件的指令
语法:
// 1. 事件绑定完整语法
添加了事件的标签
// 2. 快捷语法
添加了事件的标签
注意:
v-on 指令绑定事件的标准语法、快捷语法都需要掌握
标准事件操作中和其他系统修饰符、按键修饰符的混合使用语法
面试题: v-on 指令是否可以绑定多个相同事件? v-on 指令是否支持未来元素?
// 绑定多个事件
盒子//可以正常执行不同方法盒子//绑定多个相同事件,只执行最后一个方法盒子//这样绑定无效 // 快速给所在标签同时绑定多个事件和相应处理函数不支持绑定未来元素
修饰符:
作用:用于表单元素上,绑定操作数据
语法:
// 1. username变量就可以和input输入框中的数据实现双向绑定
注意事项:
v-model 指令可以实现变量和输入框中的数据双向绑定,是一个需要掌握的指令
底层可以通过 @input 事件结合 :value 动态属性,实现 v-model
面试题:请使用事件和属性赋值的方式,解释说明 v-model 执行原理?
项目实际开发中, Vue 内置的指令不一定满足项目功能的需要, Vue 单独预留了自定义指令开发 API ,可以让用户根据项目的实际需要定义自定义指令,完成定制化的 DOM 操作:
语法:
// 全局指令(推荐、常用)
Vue.directive('指令名称', {
bind: ()=> { // 最常用:只会执行一次,第一次绑定到标签/元素节点上时调用
...
},
inserted: () => { // 绑定的节点,插入到父节点时调用
...
},
update: () => { // 组件节点数据更新时调用
...
}
})
// 局部指令:只能被用于当前组件中
export default {
directives: {
// 声明局部指令
指令名称: function() {
bind: ()=> { // 最常用:只会执行一次,第一次绑定到标签/元素节点上时调用
...
},
inserted: () => { // 绑定的节点,插入到父节点时调用
...
},
update: () => { // 组件节点数据更新时调用
...
}
}
}
}
注意:
项目开发中,自定义指令的使用不可避免,标准项目结构中包含一个directive/ 文件夹用于存放 全局指令
Vue 项目网页视图中的样式,可以通过常规 css 样式直接处理,项目中如果希望样式可以跟随数据状态的变化而产生对应的页面效果,就需要结合 Vue 特性完成样式动态操作
样式会提前在 css 中声明完成,提供 class 名称给标签元素使用:
对象操作语法(掌握):
// css class: box、active
// isBox变量true,给div设置box样式;否则不设置样式
// isActive变量true,给div设置active样式;否则不设置样式
需要操作动态样式的内容
数组操作语法(了解):
// css class: box、active
data() {
return {
_box: 'box',
_active: 'active'
}
}
// 展示两个样式叠加的效果
需要操作动态样式的内容
需要操作动态样式的内容
--------
// 展示其中一个样式的效果
需要操作动态样式的内容
行内样式在网页开发中优先级较高,有些时候设置动态样式时需要行内样式支持
基础语法:对象语法
// color: css样式名称
// fontColor:变量,存储具体样式值
// 通过控制fontColor变量中的数据,动态切换对应标签中字体颜色
需要动态管理样式的内容
扩展语法:样式数据包含在 data 选项对象中处理
需要动态管理样式的内容
---- data选项
data() {
return {
divStyle: {
color: 'blue',
fontSize: '22px',
backgrounColor: '#ef1e1f'
}
}
}
v-on 指令,进行单个或者多个事件绑定的操作方式
// 1.基础语法
// 2.快捷语法
特殊事件绑定方式:
// 1. 同时绑定多个相同事件
// 单击事件同时触发多个处理函数
// 2. 同时绑定多个不同事件
Vue 中针对事件处理函数中,如果需要获取事件对象进行操作,包含两种操作方式:
...
export default {
methods: {
fn(e) {
// 如果事件函数没有传递参数,第一个形式参数就是事件对象
var e = e ||window.event
},
fn2(e, id, name) {
// e就是事件对象,可以直接使用($event)传递的数据
}
}
}
Vue 语法中提供了一套系统修饰符,用于增强事件函数的功能
事件修饰符 | 使用 | 功能描述 |
---|---|---|
.stop | 目标事件函数执行时,阻止事件冒泡 | |
.prevent | 目标事件函数执行时,阻止标签元素默认行为 | |
.capture | 发送事件操作,事件函数在捕获阶段执行 | |
.self | 只有发生在目标标签节点的事件上才能触发事件函数 事件函数只能由当前标签自身触发;不会捕获/冒泡触发 |
|
.once | 一次性绑定事件,事件函数只会在事件第一次发生时触发 | |
… | … | … |
Vue 语法中为了让事件,可以结合键盘上功能键触发一些特殊操作,如:按下Ctrl 键的同时单击按钮。提供了一些列系统功能修饰符,增强事件操作
功能修饰符 | 使用 | 功能描述 |
---|---|---|
.ctrl | 用户按下 Ctrl(Win)|Control(Mac) 键同时触发事件操作 | |
.alt | 用户按下 Alt(Win)|Options(Mac) 键同时触发事件操作 | |
.shift | 用户按下 Shift(Win)|Shift(Mac) 键同时触发事件操作 | |
.meta | 用户按下 Win 徽标(Win)|command(Mac) 键同时触发事件操作 | |
.left | 事件处理函数响应鼠标左键 | |
.right | 事件处理函数响应鼠标右键 | |
.middle | 事件处理函数响应鼠标滚轮按下 | |
.exact | 精确事件控制修饰符 :按下 Ctrl 单击按钮就会触发事件,即使用户按下 Ctrl 键的同时按下了其它键也会触发事件**** :用户只有按下 Ctrl 功能键的同时点击才会触发事件,同时按下了其它键就不会触发事件****:用户没有按下任何按键的同时点击鼠标发生单击行为才会触发事件函数 |
Vue 除了系统功能键( Ctrl/Alt/Shift/Win )之外,也提供了普通按键配合操作事件的修饰符
按键修饰符 | 功能描述 |
---|---|
.enter | 用户按下回车键触发事件 |
.tab | 用户按下制表键触发事件 |
.delete | 用户按下删除键触发事件 |
.esc | 用户按下取消键 Esc 触发事件 |
.space | 用户按下空格键触发事件 |
.up | 用户按下上方向键触发事件 |
.left | 用户按下左方向键触发事件 |
.down | 用户按下下方向键触发事件 |
.right | 用户按下右方向键触发事件 |
自定义按键 | 用户可以定义键盘上的任意按键 配合触发对应事件 // 事件函数中可以使用 v-on:keyup.f1 Vue.config.keyCodes.f1 = 112 |
Vue 提供了一种特殊的语法,可以通过侦听器函数完成对变量中数据监听,当变量中数据发生更新时自动触发事件处理函数完成一定的 功能
export default {
data() {
return {
msg: "",
title: ""
}
},
watch: { // 当前实例中的侦听器选项,声明各种侦听函数
// 1. 侦听器基础语法
"msg": function(oldVal, newVal) {
// msg中的数据一旦发生变化,这个函数就会立即执行
},
// 2. 侦听器完整语法
"title": {
handler(oldVal, newVal) {
// title变量中的数据一旦发生变化,这个函数就会立即执行
}
}
}
}
export default {
data() {
return {
loginForm: {
username: "",
password: ""
}
}
},
watch: {
// 监听对象中的某个属性
"loginForm.username": function(oldVal, newVal) {
// loginForm中的username数据一旦发生变化,这个函数就会立即执行
}
}
}
export default {
data() {
return {
loginForm: {
username: "",
password: ""
}
}
},
watch: {
// 监听对象
"loginForm": {
handler: function(oldVal, newVal) {
// 对象中的数据一旦发生更新,就会立即执行的函数
},
deep: true, // 开启对象深度监听
immediate: true // 开启首次渲染监视
}
}
}
Vue 中提供了一个选项 computed ,用于完成一些自动运算,提供的好处
export default {
data() {
return {
firstName:"",
lastName: ""
}
},
computed: {
// 计算属性:本质上是一个函数
fullName: function() {
// 当firstName、lastName任意变量发生更新,这个函数内部代码重新执行得到结果
return this.firstName +" " + this.lastName
}
}
}
计算属性的数据(当成属性变量直接访问):{{ fullName }}
计算属性大部分情况下都是读取数据的,特殊情况下也可以通过计算属性进行数据赋值
export default {
data() {
return {
firstName:"",
lastName: ""
}
},
computed: {
// 计算属性:完整语法是一个对象
fullName: {
get() { // 1.读取数据
// 当firstName、lastName任意变量发生更新,这个函数内部代码重新执行得到结果
return this.firstName +" " + this.lastName
},
set(val) { // 2.设置数据/赋值
let [fname, lname] = val.split(" ")
this.firstName = fname
this.lastName = lname
}
}
}
}
计算属性的数据(当成属性变量直接访问):{{ fullName }}
Vue 是一个注重视图的框架,数据可以在页面中进行完善的渲染展示,如果数据展示格式有特殊的要求,甚至提供了一个独立的专门用于数据格式化的选项:过滤器,用于按照要求转换数据
基本语法:
// 基本语法:声明全局过滤器,可以在所有Vue实例中使用
Vue.filter("过滤器名称", value => {
....
return 用于展示的数据
})
{{ message | 过滤器 }}
解释:{{ message | 过滤器 }},message表示需要过滤的数据,过滤器表示一个函数名,这个函数名接受message作为参数进行数据处理,简称过滤
基本语法:
export default {
data() {},
methods: {},
filters: { // 声明局部过滤器
// 整理整理的过滤器,只能用于当前组件(使用较少)
"过滤器名称": value => {
过滤数据处理
return 用于展示的数据
}
}
}
一般中大型项目开发时,需要将过滤器函数部分独立封装起来,方便后期进行功能升级和维护
// src/filters/An.js 单独文件封装过滤器,并导出
export default {
bind (el, binding) {
console.log(el, binding);
}
}
// 使用过滤器
{{ price | formatPrice }}元
备注:高级语法相关的一些面试题
- 什么是侦听器?侦听器和普通函数的区别?
- 什么是计算属性?计算属性和普通函数的区别?
- 什么是过滤器?过滤器和普通函数的区别?
- 侦听器和计算属性的区别?
Vue 为了提高网页视图的复用性,提供了组件的概念:
组件:包含了视图、样式、结构逻辑的完整视图
Vue 上直接声明全局组件,可以让这个组件在所有 Vue 实例中进行使用:
基础语法:
Vue.component("组件名称", {
template: "组件的页面模板",
data() {
return {
// 组件中的数据
}
},
methods: {
// 组件中的函数
}
})
网页中使用
<组件名称>组件名称>
可以在组件内部声明独立的组件,通过 Vue 的 components 选项注册子组件,通过父子组件嵌套方式完成页面中视图的复用,提高开发效率
let 子组件名称 = {
template: "",
data() {
return {}
},
methods: {},
...
}
export default {
components: { // 声明子组件
"子组件名称"
}
}
<子组件名称>子组件名称>
常规模式下声明全局组件、局部组件的方式,不利于小型项目开发!
将常规的组件声明方式,转换成 .vue 单文件组件封装,实现组件化应用开发
主要依托于 webpack 构建的项目:
$ webpack init
通过配置文件,加载项目中的特殊文件: webpack.config.js
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `
父组件 Parent.vue :引入并添加子组件
组件中添加自定义属性,包含多种操作语法,既可以接受数据,还可以对数据进行基础的校验
直接接收数据
export default {
// 自定义属性:通过数组声明需要的属性名称
props: ['title', 'msg'],
...
}
校验数据类型 | 默认值
export default {
// 自定义属性:添加类型校验,如果数据不合法不会报错~控制台提示警告
props: {
title: { // 基础数据
type: string,
default: '默认标题'
},
favs: { // 爱好,数组
type: Array,
default: () =>{
return ["前端开发", "音乐", "游戏"]
}
},
loginForm: { // 表单,对象
type: Object,
default: () => {
return {
username: "",
password: ""
}
}
}
}
}
Vue 中通过自定义属性传递数据,执行的语法规则是:单向数据流
数据需要参与操作:赋值组件数据
export default {
// 自定义属性:商品购买数量,当前页面组件订单页面可以修改购买数量
props: ['buyCnt'],
data() {
return {
// 页面中渲染可以修改的变量:buyCount
buyCount: this.buyCnt
}
}
}
数据需要参与运算:计算属性
export default {
// 自定义属性:商品单价,只需要参与运算
props: ['price'],
computed: {
// 当前页面中渲染展示数据:total进行处理
total: function() {
return this.price * 购买数量
}
}
}
嵌套关系的组件中,子组件通过自定义事件的方式传递数据给父组件使用:
子组件:Child.vue
父组件: Parent.vue
自定义事件:就是开发人员自定义名称的事件,通过 Vue 提供的事件接口函数声明
子组件中触发自定义事件的名称、其他组件中监听自定义事件的名称:尽量保持一致
.sync语法糖
组件通信,子->父
以前技术:
(1):子组件内恰当时机, this.$emit(‘自定义事件名’,值)
(2):父组件内,给子组件标签@自定义事件名="事件处理函数"在事件处理函数中,接到子传出的值,赋予给父变量
.sync语法糖:本质上还是用的上面的技术,但是语法上呢有些简化
(1):子组件内恰当时机, this.$emit( ’ update:props变量名’,值)
(2):父组件内,给子组件对应props变量名.sync=“父范围变量名”
.sync作用:它会在运行时,给当前绑定绑定自定义事件名和事件处理函数
@update:visible=“val => dialogVisible = val”
:visible="dialogVisible”还在
.sync场景:子->父快速传值并赋予给父范围内的变量
如果子->父要执行逻辑代码很多代码,这个.sync不能用了,还用我们自己绑定的事件
// 1. 子组件事件:
this.$emit("child-event")
// 父组件监听
// 2. 子组件事件
this.$emit("childEvent")
// 父组件监听
子父组件传值时,如果子组件中的数据是后续生成的、需要通知父组件进行数据接收
一个网页视图中,可能会包含多个层级关系不同的组件,这样的组件之间实现数据传递就需要借助中间对象完成数据的交互:消息总线(本质上就是一个空的Vue )
编辑 src/main.js
...
// Vue原型上,挂载了一个空的Vue实例对象,作为消息传输对象
Vue.prototype.$bus = new Vue
...
发送数据的组件 Comp1.vue
接收数据的组件: Comp2.vue
接收到其他组件发送的数据:{{ dat }}
Vue 中除了自定义属性/自定义事件完成数据的传递方式,还提供了一种快捷访问数据的方式:
子组件: Child.vue
父组件: Parent.vue
子组件中访问父组件的数据: $parent
父组件中的数据:{{ $parent.parentData }}
父组件可以通过 ref 属性获取到子组件,直接获取和修改子组件中的数据
了解-组件的嵌套关系中,一个上层组件需要将数据注入给下层组件(不一定是直接的嵌套关系),使用代理注入的方式传递组件数据非常方便
提供共享数据的组件
使用数据的后代组件:通过注入的方式,直接访问上层组件提供的数据
网站标题: {{ webTitle }}
开发版本: {{ webVersion }}
项目开发过程中,数据如果只是保存在 .vue 组件中,随着组件的切换或者网页的刷新,很容易造成数据的丢失,可以借助本地缓存或者会话缓存,完成数据的持久化使用!
数据在组件中操作时,可以借助原生 JS 中的缓存语法,完成数据的保存和读取:
// 会话(页面)缓存
sessionStorage.setItem(key, value)
// 本地缓存
localStorage.setItem(key, value)
// 会话缓存
let value = sessionStorage.getItem(key)
// 本地缓存
let value = localStorage.getItem(key)
项目开发中为了优化组件中的代码,可以将一些功能代码拆分到子组件中,可以通过插槽的方式传递数据:
使用情况:
当父组件需要无定向传递数据或者渲染页面时使用
子组件: Msg.vue
消息提醒
父组件: Parent.vue
确定要退出系统吗?
使用情况:
父组件需要向子组件有差别,有固定位置的传递数据或者渲染页面
子组件中通过命名属性的方式,提供了多个数据占位位置:
子组件: Msg.vue
提示:
消息:
父组件: Parent.vue
警告信息
确定要退出系统吗?!
使用情况:
当父组件需要使用子组件传递过来的数据时
使用口诀:
(1):在组件内,用
(2):在插槽内,用template v-slot="变量名”(变量会收集slot身上属性和值形成对象)
子组件可以通过插槽的 作用域变量 ,将数据传递给父组件进行使用
子组件: Goods.vue
商品名称:{{ goods.name }}
商品价格:{{ goods.price }}
父组件:
备注:以后学习了更加高级的语法之后,还有其他的组件数据传值的方式:
- vuex 实现组件数据传值
- router 实现路由组件之间传值
项目开发中,组件是 Vue 项目中 视图的最小单元 ,通过组件的复用可以提高代码/视图的开发效率;
注意:针对组成组件的每个选项,都可以通过特定的语法实现二次复用,进一步规范和提高开发效率
Vue 中视图是以组件为单元,每个组件都包含创建、加载、运行、渲染、销毁的过程, Vue 中提供了特定的语法监控每个执行过程:称为生命周期
生命周期钩子 | 功能描述 |
---|---|
beforeCreate() | 目标组件实例准备创建 真实 DOM:index.html 可以访问 虚拟 DOM :不可以访问 实例数据:不可以访问 掌握程度:了解 |
created() | 目标组件实例创建完成 真实 DOM :可以访问 虚拟DOM :不可以访问 实例数据:可以访问 掌握程度:掌握 |
beforeMount() | 目标组件上 DOM 准备挂载 真实 DOM :可以访问 虚拟 DOM :不可以访问 实例数据:可以访问 掌握程度:了解 |
mounted() | 目标组件上 DOM 结构挂载完 成真实 DOM :可以访问 虚拟 DOM :可以访问 实例数据:可以访问 掌握程度:掌握 |
beforeUpdate() | 目标组件上发生任意数据更新之前执行 真实 DOM :可以访问 虚拟 DOM :可以访问 实例数据:可以访问 掌握程度:了解 |
updated() | 目标组件上发生任意数据更新之后执行 真实 DOM :可以访问 虚拟 DOM :可以访问 实例数据:可以访问 掌握程度:熟悉 |
beforeDestroy() | 目标组件准备销毁时执行 |
destroyed() | 目标组件销毁后执行的钩子函数 |
项目在进行组件化开发时,通用静态视图的组件不需要频繁的创建和销毁,可以将组件进行缓存
被缓存的组件包含两个声明周期:
生命周期钩子 | 功能描述 |
---|---|
activated() | 缓存组件页面上切换显示时执行:主要用于更新数据 掌握程度:掌握 |
deactivated() | 缓存组件页面上切换隐藏时执行 掌握程度:熟悉 |
备注:关于生命周期的面试题
1、请简要描述组件的生命周期钩子都有哪些?有什么特点?**
- 扩展1:简述 created() 和 mounted() 区别
- 扩展2:组件初始化实例数据,可以在 created()/beforeMount() 钩子中执行,你认为应该在那个钩子函数中初始化数据,为什么?
- 扩展3:正常情况下,组件切换过程中是否会发生组件的销毁和重新创建?
- 扩展4:简述 destroyed() 生命周期钩子函数有什么用途?
- …
2、请简要描述在父子组件嵌套的情况下,父子组件生命周期执行顺序?**
- 扩展1:简述父子组件嵌套时,子组件 mounted() 生命周期钩子函数什么时候执行?
- 扩展2:简述父子组件嵌套时,父组件 created() 和子组件created() 分别什么时候执行?
- 扩展3:简述父子组件嵌套时,父组件 mounted() 和子组件mounted() 分别什么时候执行?
- …
组件开发过程中,可能会出现多个组件/全部组件中都出现了重复代码,这些重复代码可能出现在任意的实例选项中,为了提高组件中代码的复用性, Vue 语法中提供了混入 Mixin 模块,可以抽取并复用组件中重复的代码
基本语法:抽取公共混入代码,封装到 mixins/ 文件夹中
export default {
data() {}, // 所有/多个组件共享数据
methods: {}, // 所有/多个组件共享函数
created(){}, // 所有/多个组件共享生命周期钩子
mounted(){}, // 所有/多个组件共享生命周期钩子
....
}
全局混入:所有组件生效,编辑 src/main.js
...
import globalMixin from "../mixins/global.js"
Vue.mixin(globalMixin)
...
局部混入:需要使用混入模块的组件中,通过 mixins 选项添加混入功能
# index.vue
import partMixin from '../mixins/part.js'
export default {
mixins: ['partMixin'], // 组件中添加局部混入模块
data() {...},
methods: {...},
...
}
项目中的应用
项目开发中通常会单独维护一个 mixins/ 文件夹,存放项目中需要的全局混入模块和局部混入模块,提高组件中重复代码的复用性!
项目中可能会出现一些页面上经常用到的视图(包含自己的处理数据和逻辑功能),可以通过 Vue 提供的组件注册语法,专门将特定的视图、数据、业务处理逻辑封装在一起形成一个自定义组件,项目中再次使用自定义组件时就可以直接导入注册,页面中添加使用即可!类似 element-ui 中的各种标签都是自定义组件
基础语法:
// 1、通过Vue.component()声明组件: 常规组件的封装
// 2、通过Vue.extend()声明组件: 通用组件的封装(更加底层)
基于类型的组件封装:
import Vue from 'vue'
import Component from 'vue-class-component'
// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
// 所有的组件选项都可以放在这里
template: ''
})
export default class MyComponent extends Vue {
// 初始数据可以直接声明为实例的 property
message: string = 'Hello!'
// 组件方法也可以直接声明为实例的方法
onClick (): void {
window.alert(this.message)
}
}
插件一般都是用于扩展 Vue 功能的工具模块,如 vue-router 扩展了路由功能!
封装插件:就是一个小型组件
// 封装的.vue视图
import MyPlugin from "./myplugin.vue"
// 暴露install()接口函数
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
使用插件: Vue.use() 接口函数可以注册插件
...
import MyPlugin from '../plugins/myplugin'
Vue.use(MyPlugin)
...
备注:关于自定义插件和自定义组件的封装
- 项目中需要一个复用的功能视图组件,使用频率非常高的情况下,如果业务逻辑并不是十分复杂的情况下可以考虑封装成插件使用
- 项目中需要一个复用的功能视图组件,使用频率非常高的情况下,如果业务逻辑比较复杂,操作过程中会出现各种使用差异,可以考虑封装自定义组件使用
Vue 针对页面组件进行动态切换,出现的页面切换过程,提供了 .css 语法可以添加自定义动画效果,避免了开发人员去添加大量样式代码的实现,提高了动画的复用!
通用动画效果:
<需要动画的组件/>
.v-enter{} /* 组件进入前样式 */
.v-enter-active{} /* 组件进入过程样式:动画 */
.v-enter-to{} /* 组件进入后样式 */
.v-leave{} /* 组件离开前样式 */
.v-leave-active{} /* 组件离开过程样式:动画 */
.v-leave-to{} /* 组件离开后样式 */
自定义动画效果:
<需要动画的组件/>
.mycomp-enter{} /* 组件进入前样式 */
.mycomp-enter-active{} /* 组件进入过程样式:动画 */
.mycomp-enter-to{} /* 组件进入后样式 */
.mycomp-leave{} /* 组件离开前样式 */
.mycomp-leave-active{} /* 组件离开过程样式:动画 */
.mycomp-leave-to{} /* 组件离开后样式 */
需要动画的内容:{{ item }}
.list-enter{} /* 组件进入前样式 */
.list-enter-active{} /* 组件进入过程样式:动画 */
.list-enter-to{} /* 组件进入后样式 */
.list-leave{} /* 组件离开前样式 */
.list-leave-active{} /* 组件离开过程样式:动画 */
.list-leave-to{} /* 组件离开后样式 */
Vue 框架特性之一: 数据的双向绑定 ,添加了框架支持,数据可以在逻辑代码中和视图中实现双向绑定,逻辑中的数据更新同时触发页面视图的更新;页面视图中数据的更新同时触发逻辑代码中的数据更新
面试题解析:
1、简述 Vue2.x 中数据双向绑定原理?
Vue2.x中数据双向绑定底层是通过Object.defineProperty()数据劫持方式实现
2、简述 Vue2.x 中页面数据是如何发生更新?
Vue2.x中将虚拟DOM结构抽取出来形成抽象树,然后通过diff算法完成差异化数据比较,最后对需要更新的视图进行重新渲染
3、简述 Vue2.x 中什么是虚拟 DOM ?
框架中将组件页面结构进行抽取,将抽取的数据形成DOM树结构加载到内存中,加载在内存中的DOM结构就是描述的虚拟DOM结构
4、简述 v-for 循环执行时,如果遍历了10个数据,只有一个数据发生了更新,页面中是否会对10个数据都进行重新渲染?
框架进行虚拟DOM加载时,通过观察者模块收集页面数据 当页面中数据发生了更新,结合虚拟DOM树和diff算法定位需要更新的数据解构进行渲染,属于差异化渲染,可以在数据更新时提高页面加载渲染效率
5、简述 Vue2.x 中你所理解的 diff 算法?
diff算法属于一种差异化比较算法 针对两个需要比较的数据,通过patch进行节点抽取和比较 执行比较先处理入口节点比较,如果相同继续比较下一个节点,如果不同就先标记节点,然后执行尾部节点比较 执行尾部节点比较,如果节点相同继续比较上一个节点,如果不同就先标记节点,然后从上一次开始节点处重新比较前置节点 最终将需要更新的所有节点进行收集,将不需要比较的节点直接过滤,更新需要比较的节点
v-model 是一个作用在表单上的双向绑定指令,完成了表单中的数据和视图中的数据的双向同步,底层执行原理和数据劫持实现的双向绑定原理存在一定的差异!
实现过程中,主要通过表单元素的 @input 事件,事件中收集视图表单的数据将数据同步给逻辑变量;逻辑变量中的数据更新通过表单的 v-bind:value 动态属性的方式完成逻辑变量的数据自动更新到页面视图的过程
Vue 提供了一种语法,不需要观察数据是否发生变化直接强制更新数据/渲染页面!
关于强制更新:官方的描述是这样的
项目开发中需要操作的数据包含普通变量、数组、对象等等各种复杂数据,需要应用到各种复杂场景中,实现数据在页面中的合理的渲染!
- 偶尔遇到的问题:逻辑代码中更新了变量/数组/对象的数据,页面视图中没有发生变化
- 需求紧急的情况下 :没有多余的时间处理遇到的问题;可以在需要更新的组件中,业务函数执行的最后一行代码上添加this.$forceUpdate() 强制页面数据更新,实现需求目标
- 开发人员需要明确 :一旦需要强制更新,肯定是代码中出现了问题导致数据更新没有触发页面渲染/页面数据没有触发自动同步;如果需要强制更新~一定要复盘开发流程找到问题所在解决强制更新问题!
项目中经常遇到这样的情况:需要给一个组件赋值
出现的问题如下:
出现问题的原因:
父子组件生命周期执行过程:
父组件 子组件
-------------------------------------
beforeCreate()
created()
beforeMount()
beforeCreate()
created()
beforeMount()
mounted()
mounted()
------------------------------------- 父组件methods函数中,给子
组件设置数据(失败!)
beforeUpdate()
beforeCreate()
created()
beforeMount()
mounted()
updated()
-------------------------------------
Vue 提供了一个辅助函数,专门用于等待 DOM 更新完成后再去执行相关代码:
Vue 中的组件,为了提高组件的复用性,可以在组件页面中访问调用自身!
组件递归的场景:
使用注意事项:
组件的递归导致组件常驻内存,如果没有设置好结束条件很容易导致无限循环,造成内存消耗完毕出现内存溢出的问题,内存溢出导致程序崩溃!
SPA 应用中,主要使用的是 SFC(Singleton File Component) 单文件组件
一般会在入口模块中设置通用样式
# App.vue
组件中的标签设置样式,不希望影响其他组件,可以给组件的 标签添加 scoped 属性
# Other.vue
底层实际的编辑过程:抽取虚拟 DOM 结构时
# Other.vue
第三方组件(如 element-ui )默认已经自带了样式,我们需要在自己的组件中修改第三方组件样式
# Other.vue
点击
# Other.vue
官方文档:https://v3.router.vuejs.org/zh/installation.html
vue-router 属于 Vue 生态技术中的核心技术之一,主要用于服务项目的页面切换功能!
注意: Vue 和 VueRouter 版本对应关系
- Vue2.x 对应 VueRouter3.x
- Vue3.x 对应 VueRouter4.x
Vue 项目中,添加 VueRouter 支持:安装依赖模块
$ npm install vue-router@3 -S
创建路由规则配置: src/router/index.js
// 导入模块
import Vue from "vue"
import VueRouter from "vue-router"
Vue.use(VueRouter)
// 配置路由规则
import Index from '../views/index.vue'
const routes = [{
path: "/",
component: Index
}]
// 创建路由对象
const router = new VueRouter({
routes, // 路由映射规则
mode: "history", // 路由匹配模式
linkActiveClass: "active", // 导航高亮样式
scrollBehavior: () => { // 页面滚动行为
return {x: 0, y: 0}
}
})
// 导出模块
export default router
实例上添加路由配置:
// src/main.js
...
import router from './router' // 导入路由模块
...
new Vue({
router, // 实例上挂载路由模块
render: h => h(App)
}).$mount("#app")
重定向:将用户的访问路径,自动切换到其他路径访问对应的资源
如:用户访问 http://localhost:8080/ ,访问路径: / ,重定向: /main
浏览器地址: **http://localhost:8080/main**
基本配置:
{
path: "/", // 配置用户访问路径
redirect: "/main" // 配置重定向路径
}
别名:用户访问某个页面时,除了本来的访问路径之外的另一个访问路径
如:用户访问 http://localhost:8080/index ,路径 /index 可以访问系统首页
别名访问 http://localhost:8080/main ,路径 /main 也可以访问系统首页
基本配置
{
path: "/index", // 用户配置 访问路径
alias: "/main", // 配置访问 别名路径
component: Index // 对应的页面组件
}
用户可以通过网页中的导航链接,直接访问到对应的页面组件
基本语法:
首页
登录
注册
开发人员可以将用户在页面中发生的操作,执行对应的事件,在事件函数的代码中完成页面跳转
基本语法:
导航函数 | 功能描述 |
---|---|
$router.push(path) | 保留本次访问记录,跳转显示路径对应的页面组件 |
$router.replace(path) | 丢弃本地访问记录,跳转显示路径对应的页面组件 |
$router.back() | 访问上一次访问记录 |
$router.forward() | 访问下一次访问记录 |
$router.go(idx) | 访问索引对应的访问记录 |
路由路径在页面中的切换方式,称为路由动态匹配模式,主要包含两种匹配方式:
基本配置:
const router = new VueRouter({
routes, // 路由规则
mode: "hash", // 路由匹配模式
})
路由规则进行声明时,可以给规则对象添加 name 属性实现命名路由,方便导航时通过动态属性控制导航效果
基本配置:
{
name: "login", // 命名路由
path: "/login",
Component: Login
}
登录
// 编程式导航
this.$router.push({name: 'login'})
路由组件在页面中主要是通过 路由视图组件 进行显式
一个页面中如果要同时匹配和展示多个路由组件的情况下,就可以启用命名视图:
{
name: "index",
path: "/index",
components: {
header: MyHeader,
aside: Aside,
default: Index
}
}
项目中如果出现多级页面切换,需要嵌套路由的支持
基本配置:主要通过路由规则的 children 选项实现
{
path: "/main",
name: "main",
component: Main,
children: [ // 嵌套路由
{name: "home",
path: "home", // /main/home
// path: "/home", // /home
component: Home
},
....
]
}
除了 Vue 项目本身自带的多个组件之间的数据传递方式之外,路由规则也可以实现不同页面之间的参数传递
基本语法:路由规则定义参数
{
name: "goodsdetail",
path: "/goodsdetail/:id", // 动态路由参数,参数名称id
component: GoodsDetail
}
导航传参:
商品:小米
商品:小米
// 编程式导航传参
this.$router.push('/goodsdetail/12')
this.$router.push({name: 'goodsdetail', params: {id: 12}})
页面组件接收参数:
接收参数
{{ $router.params.id }}
路由规则处理中,可以通过 ?key=value&key2=value2 的查询字符串方式传递参数
基本语法:路由规则不需要多余配置
{
name:"goodsdetail",
path: '/goodsdetail',
component: GoodsDetail
}
导航传参:
商品:华为
商品:华为
// 编程式导航
this.$router.push('/goodsdetail?id=12')
this.$router.push({name: 'goodsdetail', query: {id: 12}})
页面组件接收参数:
接收参数
{{ $router.query.id }}
如果页面声明式导航中出现了多个相同或者带有包含关系的导航链接,很容易出现多个导航同时高亮的情况
VueRouter 中提供了一个关键属性: exact ,可以将路由路径模糊匹配方式转换成精确匹配方式,避免导航同时高亮显示的情况
LOGO
首页
登录
页面配置完成后,如果用户访问了不存在的路径,需要给用户一个友好的页面提示
路由:通配符方式配置404路由实现
const routes = [
{
...
},{
path: "*",
component: NotFount // 配置显示404路由页面,给用户一个自定义提示页面
}
]
路由规则对象配置过程中,可以添加一些基础数据增强路由功能:如路由访问权限、路由页面标题等等
基本语法:
{
name: "ucenter",
path: "ucenter",
component: Ucenter,
meta: { // 路由元数据
title: "用户中心",
roles: ['admin', 'manage', 'member']
}
}
页面组件获取数据
VueRouter 路由对象中,默认可以记录当前页面滚动条位置,增强用户访问体验
基本配置:
const router = new VueRouter({
routes,
mode: 'hash',
scrollBehavior: (to, from, savePosition) => {
// to: 即将跳转到的组件
// from: 跳转之前的组件
// savePosition: 跳转之前组件中滚动条位置
// 需求:需要保留位置,结合vuex/缓存记录某个页面中的位置,当跳转指定页面时回到历史访问位置
return {x: 0; y: 0}
}
})
SPA 单页面应用,常规开发模式中最大的问题:用户访问第一个页面时,会触发项目中所有的路由规则中添加的组件全部加载,导致首页访问缓慢!
基本语法:
{
name: "about",
path: "/about",
component: () => import("../views/About/index.vue") // 懒加载语法 | Vue中的异步组件
}
前端应用中,路由实现了页面和页面之间的切换,如果需要在页面切换时进行条件判断、权限验证等中间功能时,需要拦截用户路由请求, VueRouter 通过路由导航守卫实现:
1. 全局守卫
// 全局前置守卫:通过路由对象添加
router.beforeEach( ( to, from, next ) => {
// 拦截用户页面导航请求,进行权限验证等操作
// 允许访问下一个导航页面
next()
})
// 全局解析守卫
router.beforeResovle(( to, from, next) => {
// 拦截用户页面导航请求(可以访问路由元数据、导航组件基础数据..)
// 允许访问下一个导航页面
next()
})
// 全局后置钩子
router.afterEach( ( to, from ) => {
// 处理导航完成后的资源数据
})
2. 路由独享守卫:配置在单个路由中,拦截指定路由的请求
{
name: 'Ucenter',
path: 'Ucenter',
components: {
header: CommHeader,
default: Ucenter
},
beforeEnter(to, from, next) {
// 路由独享守卫,拦截访问这个路由的页面请求
// 允许访问当前路由
next()
}
}
3. 组件内守卫:配置在单个组件中,拦截组件的访问请求
export default {
data() {
return {}
},
methods: {},
....
beforeRouteEnter(to, from, next) {},
beforeRouteUpdat(to, from, next) {},
beforeRouteLeave(to, from, next) {}
}
导航守卫的执行顺序:
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter 。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter 。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
数据获取方式描述了目标组件中数据的获取行为,通过页面组件切换的中间过度效果提高用户访问体验
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
# Goods.vue
export default {
data() {
return {
goodsList: []
}
},
created() { // 导航已经完成
// 发送请求获取数据
this.getGoodsList()
}
}
# Goods.vue
export default {
data() {
return {
goodsList: []
}
},
beforeRouteEnter (to, from, next) { // 导航没有完成
// 获取数据
this.getGoodsList()
next()
},
}
从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。
路由页面组件切换时添加过渡动画,结合 Vue 过渡动效实现:
.v-enter{} /* 组件进入前样式 */
.v-enter-active{} /* 组件进入过程样式:动画 */
.v-enter-to{} /* 组件进入后样式 */
.v-leave{} /* 组件离开前样式 */
.v-leave-active{} /* 组件离开过程样式:动画 */
.v-leave-to{} /* 组件离开后样式 */
导航故障通常通过 404****路由 就可以实现处理方案:如下三种情况导致页面可能出现空白!
VueRouter 中提供了一中导航异常处理语法,可以给用户提供一个错误信息提示页面/ 500****错误
import VueRouter from 'vue-router'
const { isNavigationFailure, NavigationFailureType } = VueRouter
// 正在尝试访问 admin 页面
router.push('/admin').catch(failure => {
if (isNavigationFailure(failure,NavigationFailureType.redirected)) {
// 向用户显示一个小通知
showToast('Login in order to access the admin panel')
}
})
正常项目开发中,通过 src/router/index.js 就可以实现路由模块的封装但是随着项目规模增加,单个文件中封装路由模块导致代码量过多无法/很难维护出现!
|-- project/
|-- node_modules/
|-- public/
|-- router/
|-- index.js 路由封装
|-- utils/
|-- src/
|-- assets/
|-- ...
|-- App.vue
|-- main.js
|- ...
|-- project/
|-- node_modules/
dsf public/
|-- router/
|-- dynamicRouter.js 动态路由规则封装
|-- permission.js 路由导航守卫封装
|-- routes.js 路由映射规则封装
|-- index.js 路由模块[导入动态路由、导航守卫、映射规则...]
|-- utils/
|-- src/
|-- assets/
|-- ...
|-- App.vue
|-- main.js
|- ...
待补充
映射是将函数配置中的数据和方法,映射到本组件,相当于在本组件中添加这些方法和属性
axios 是一个异步请求框架!
官方网站: https://www.axios-http.cn/
axios 是一个异步请求库/框架,可以用于浏览器和 nodejs 环境
底层是通过 Promise 和 JS ajax 进行封装实现
前端应用中异步请求方式的实现技术有很多
Vue 项目应用中,不推荐浏览器性能消耗巨大 DOM 操作,对于其他的 Ajax 技术的选型依据:
项目中添加 axios 支持,属于一个第三方模块,需要安装和引入才能正常使用
Ⅰ. 安装
$ npm install axios -S
Ⅱ. 引入
需要使用 axios 的组件中,单独引入 axios
如:编辑商品管理组件 Goods.vue
...
Ⅲ. 请求
直接调用 axios 中提供的请求函数,完成数据请求即可!
// 字符串中拼接参数
axios.get("/goods?id=12").then(response => {
console.log("服务器响应数据:", response)
}).catch(error=> {
console.log("请求发生了错误:", error)
})
// 发送参数对象(不需要手工拼接参数:常用)
axios.get('/goods', { params: {id: 12} }).then(response => {
console.log("服务器响应数据:", response)
}).catch(error => {
console.log("请求发生了错误:", error)
})
// async / await配合操作(直接通过同步方式编写异步请求,推荐)
async function getGoods() {
const response = await axios.get("/goods", {params: {id:12}})
console.log("请求到的数据:", response)
}
// 发送不带参数的post请求
axios.post('/login').then(response => {
console.log("服务器返回数据:", response)
})
// 附带参数的post请求
axios.post('/login', {data: {username:"admin", password:"123"}}).then(repsonse => {
console.log("服务器返回了数据:", response)
})
axios({
url: 'http://localhost:3000/goods/list',
method: 'GET', // GET POST PUT DELETE....
params: {page:1, size:10}, // get参数
// data: {id: 12}, // post参数
headers: {'X-Requested-With': 'XMLHttpRequest'}, // 请求头
}).then(response => {
console.log("服务器返回的数据")
})
axios 除了提供的一些快捷函数 axios.get()/post()/put()… 这些操作之外,项目中推荐使用通用性更高的配置方式完成请求的发送
axios({
// 请求配置
}).then(response => {
console.log("获取服务器响应数据", response)
})
axios 中提供了常用的 请求配置
{
// `url` 是用于请求的服务器 URL [掌握]
url: '/user',
// `method` 是创建请求时使用的方法 [掌握]
method: 'get', // 默认值
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
// 你可以修改请求头。
transformRequest: [function (data, headers) {
// 对发送的 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对接收的 data 进行任意转换处理
return data;
}],
// 自定义请求头,可用于解决跨域问题 [掌握]
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是与请求一起发送的 URL 参数;
// GET请求参数 [掌握]
params: {
ID: 12345
},
// `paramsSerializer`是可选方法,主要用于序列化`params`
// (e.g. https://www.npmjs.com/package/qs,http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求体被发送的数据
// 发送POST参数 [掌握]
data: {
firstName: 'Fred'
},
// 发送请求体数据的可选语法
// 请求方式 post
// 只有 value 会被发送,key 则不会
data: 'Country=Brasil&City=Belo Horizonte',
// `timeout` 指定请求超时的毫秒数。
// 如果请求时间超过 `timeout` 的值,则请求会被中断 [掌握]
timeout: 1000, // 默认值是 `0` (永不超时)
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,这使测试更加容易。
// 返回一个 promise 并提供一个有效的响应 (参见lib/adapters/README.md)。
adapter: function (config) {
/* ... */
},
// `auth` HTTP Basic Auth
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示浏览器将要响应的数据类型
// 选项包括: 'arraybuffer', 'document', 'json', 'text','stream'
// 浏览器专属:'blob'
responseType: 'json', // 默认值
// `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
// 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
// Note: Ignored for `responseType` of 'stream' or clientside requests
responseEncoding: 'utf8', // 默认值
// `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
xsrfCookieName: 'XSRF-TOKEN', // 默认值
// `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
// `onUploadProgress` 允许为上传处理进度事件
// 浏览器专属
onUploadProgress: function (progressEvent) {
// 处理原生进度事件
},
// `onDownloadProgress` 允许为下载处理进度事件
// 浏览器专属
onDownloadProgress: function (progressEvent) {
// 处理原生进度事件
},
// `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
maxContentLength: 2000,
// `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
maxBodyLength: 2000,
// `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是reject promise。
// 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或`undefined`),
// 则promise 将会 resolved,否则是 rejected。
validateStatus: function (status) {
return status >= 200 && status < 300; // 默认值
},
// `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
// 如果设置为0,则不会进行重定向
maxRedirects: 5, // 默认值
// `socketPath` 定义了在node.js中使用的UNIX套接字。
// e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
// 只能指定 `socketPath` 或 `proxy` 。
// 若都指定,这使用 `socketPath` 。
socketPath: null, // default
// `httpAgent` and `httpsAgent` define a custom agent to beused when performing http
// and https requests, respectively, in node.js. This allows options to be added like
// `keepAlive` that are not enabled by default.
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// `proxy` 定义了代理服务器的主机名,端口和协议。
// 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
// 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
// `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
// 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers`中已存在的自定义 `Proxy-Authorization` 请求头。
// 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// see https://axios-http.com/zh/docs/cancellation
cancelToken: new CancelToken(function (cancel) {
}),
// `decompress` indicates whether or not the response bodyshould be decompressed
// automatically. If set to `true` will also remove the'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
decompress: true // 默认值
}
axios 针对服务器接口返回的数据进行了 二次包装 ,返回了响应对象,对象内部的数据描述:
参考官方文档: https://www.axios-http.cn/docs/res_schema
data :服务器接口返回的数据,被封装在这个选项中返回了,我们需要的数据可以从 response.data 中获取和使用
{
// `data` 由服务器提供的响应
// 【掌握】:服务器接口返回的真实数据,包含在该选项中
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 是服务器响应头
// 所有的 header 名称都是小写,而且可以使用方括号语法访问
// 例如: `response.headers['content-type']`
headers: {},
// `config` 是 `axios` 请求的配置信息
config: {},
// `request` 是生成此响应的请求
// 在node.js中它是最后一个ClientRequest实例 (in redirects),
// 在浏览器中则是 XMLHttpRequest 实例
request: {}
}
axios 主要完成了前端向后端发送请求的过程,从后端接口中获取数据并使用!
但是常规的请求和响应处理时,会包含一些公共的代码片段,如:
官方文档: https://www.axios-http.cn/docs/interceptors
基本语法:
// 通过axios实例对象,声明拦截器
axios.interceptors.request.use( request => {
// 拦截请求,对请求进行处理
return request
})
axios.interceptors.response.use( response => {
// 拦截响应,对响应数据进行处理
// 如:从响应对象中,拆出服务器返回的真实数据
return response.data
})
封装的 axios ,编辑 src/utils/request.js
/** 封装axios模块 */
import axios from "axios"
// 创建实例
const instance = axios.create({
// 实例中封装的公共代码
baseURL: 'http://localhost:3000'
})
// 实例上添加拦截器
instance.interceptors.request.use(request => {
// 针对请求进行通用设置,如设置请求头
return request
})
instance.interceptors.response.use(response => {
// 针对响应数据进行通用处理,如处理404\500错误等等
// 如;拆分服务器返回的真实数据
return response.data
})
export default instance
思考:前端应用中的 路由导航守卫 、 axios 中的 拦截器 有什么区别?
- 相同点:导航守卫和拦截器都可以拦截用户请求
- 导航守卫:拦截路由请求,从一个页面切换到另一个页面的请求
- 拦截器:拦截 ajax 数据请求,前端页面中发送请求给数据接口
Vue 中一个页面组件 Goods.vue 展示商品数据的时候,需要给后端接口发送请求获取要展示的数据;但是如果数据还没有展示的情况下用户又切换到其他页面About.vue ,这时候前面这个页面组件 Goods.vue 中发送的请求就不需要处理,为了节省系统资源需要取消 ajax 请求
axios 提供了取消请求的实现方式:
官方文档: https://www.axios-http.cn/docs/cancellation
代码操作:
# axios版本有关系:0.22+版本
// 1、创建一个浏览器中的请求控制对象
const reqController = new AbortControll()
// 2、发送请求时需要添加请求信号
axios.get("/goods/list", {
signal: reqController.signal // 添加一个信号控制器
}).then(response => {
console.log("服务器返回的数据:", response)
})
// 3、某些场景中需要取消请求
reqController.abort()
如果工作过程中维护的是一个老版本 axios 的项目:需要使用令牌的方式取消请求
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
Vue 项目中需要对 axios 做一个封装,方便在项目中直接使用 axios 完成请求、请求拦截、响应拦截处理、取消请求等各种操作方式,一般项目中封装方式:
创建 src/utils/request.js
封装基础实现:参考https://blog.csdn.net/DevilAngelia/article/details/125778269
/** 封装axios模块 */
import axios from "axios"
// 创建实例
const instance = axios.create({
// 实例中封装的公共代码
baseURL: 'http://localhost:3000'
})
// 实例上添加拦截器
instance.interceptors.request.use(request => {
// 针对请求进行通用设置,如设置请求头
return request
})
instance.interceptors.response.use(response => {
// 针对响应数据进行通用处理,如处理404\500错误等等
// 如;拆分服务器返回的真实数据
return response.data
})
// 封装一个请求函数
function request({ url, method = 'get', data = {}, params =
{}, reqController }) {
// 获取取消信号对象
const signal = reqController ? reqController.signal :undefined;
// 返回请求
return instance({
url,
method,
data,
params,
signal
})
}
export default request
封装接口业务代码
访问接口的函数,尽量不要直接写在 Vue 组件中,一般开发要求:单独封装获取接口数据的函数
创建: src/api/goods.js (专门操作商品数据的 api接口函数 )
/** 商品接口数据访问模块 */
import axios from '../utils/request'
// 获取接口数据的函数
export const getGoodsPage = (page, size, controller) =>axios({
url: '/goods/page',
method: 'get',
params: { page, size },
controller
})
// 新增商品的接口函数
// export const addGoods => () => {..}
// 编辑商品的接口函数
// export const editGoods = () => {...}
// ...
编辑 src/views/Goods.vue ,使用 api接口函数 获取数据
....
本质上组件中获取数据的流程如图所示:
vue -V
npm i @vue/cli -g
需要vue2版本
需要babel
需要vue-router
需要vuex
需要eslint
需要less
vue create 项目名
选择第3个 自定义预设
3.1 Manually select features
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
(*) Vuex
(*) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
3.2 Choose a version of Vue.js that you want to start the project with (Use arrow keys)
2.x
3.x
3.3 Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)
3.4 Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
Less
Stylus
3.5 Pick a linter / formatter config: (Use arrow keys)
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier
3.6 Pick additional lint features: (Press to select, to toggle all, to invert selection)
(*) Lint on save
( ) Lint and fix on commit
3.7 Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
In dedicated config files
In package.json
3.8 Save this as a preset for future projects? (y/N)
4.梳理项目结构
为项目开发做准备,把不需要的代码、文件删除掉
4.1 重置 src/App.vue 组件中的代码
App 根组件
4.2 重置 src/router/index.js 路由模块中的代码
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = []
const router = new VueRouter({
routes
})
export default router
4.3 清空 src/components 目录和 src/views 目录。
4.4 把 图片素材 目录下的 images 文件夹(项目中需要用到的图片),复制粘贴到 src/assets 目录下。
4.5 并把global.less, 引入到main.js
import '@/assets/global.less' // 全局初始化样式
npm i vue-router
import VueRouter from ‘vue-router’
Vue.use(VueRouter)
添加到Vue.use()上后,会自动注册全局组件RouterLink和RouterView
RouterLink是定义连接的地址
RouterView是使用路由组件
let routesArr = [
{
// 一个对象就是一个规则
// 路径地址
path: '/find',
// 组件的名字
component: Find,
},
{
// 一个对象就是一个规则
path: '/my',
component: My,
},
{
// 一个对象就是一个规则
path: '/part',
component: Part,
},
]
// 5. 用规则数组, 生成路由对象,到处路由表
let routerObj = new VueRouter({
routes: routesArr,
// key名字固定配置项叫routes(等待传入规范数组)
})
new Vue({
// 6. 把路由对象添加到new Vue实例中(让整个项目拥有路由功能)
// 注意: vue内置了配置项(准备) key名固定 router
router: routerObj,
render: (h) => h(App),
}).$mount('#app')
父组件在子组件使用的位置通过属性的方式传递数据,子组件通过prop接收数据
这里是父组件的值{{ aa }} 性别{{ sex }}
this.$parent是用来获取组件的父组件节点,进而获取父组件的数据
这里是父组件的值{{ father }}
解释:
父组件在使用子组件的位置绑定一个自定义事件,传递给子组件,子组件绑定并触发事件,通过$emit方法触发子父组件中的自定义事件,并将数据传递给父组件,父组件的在自定义事件中将数据接受并保存。
流程:
父组件自定义事件,通过props向子组件传递一个方法————>子组件触发事件————>通过$emit方法触发父组件自定义事件,并传递数据————>父组件方法接受并保存数据
这是子组件传递给父组件的数据{{ msg }}
注意:
父子组件钩子函数执行顺序*(父先行动,子先干完)*:
加载渲染过程:
父组件 beforeCreate——>父组件 created——>父组件 beforeMount——>子组件 beforeCreate——>子组件 created——>子组件 beforeMount——>子组件 mounted——>父组件 mounted
更新过程:
父组件 beforeUpdate——>子组件 beforeUpdate——>子组件 updated——>父组件 updated
销毁过程:
父组件 beforeDestroy——>子组件 beforeDestroy——>子组件 destroyed——>父组件 destoryed
r o o t 是获取跟组件,之后配合 root是获取跟组件,之后配合 root是获取跟组件,之后配合on和$emit进行事件的监听和发送
发送组件:
通过this. r o o t . root. root.emit进行数据的发送:this. r o o t . root. root.emit(‘自定义事件名’,所传递的数据)
接受组件:
使用this. r o o t . root. root.on进行自定义事件监听,这里必须要和发送方的自定义事件一致:this. r o o t . root. root.on(‘自定义事件名’,监听变化后的事件回调)
5.{{ brother ? brother : '等数据呢' }}
这个是方法1和方法3的组合方法,方法3先实现子组件向父组件传递数据并保存到父组件,之后再使用方法1将保存的数据传递给另一个子组件;(简单,不在展示)
这个与 c h i l d r e n 实现基本一致,都是获取到子节点,然后取出需要的数据。不同的是 children实现基本一致,都是获取到子节点,然后取出需要的数据。不同的是 children实现基本一致,都是获取到子节点,然后取出需要的数据。不同的是children是获取该组件的所有子节点,而$ref是针对性的获取某个节点;
在父组件中使用子组件的地方,对子组件进行ref打标识,然后在使用的时候通过$refs获取节点进行操作;
7.这里是通过$root获取节点实现子传父的数据:{{ rootS }}
这个方法与方法5$root跟组件实现任意组件之间数据传递一样,只是方法5是利用了该实例自带的根组件实例进行数据传递,而该方法则是在原型上添加一个属性,并将该属性指向该实例方法来实现;全局事件总线的原理是给 Vue 的原型对象上面添加一个属性。这样的话我所有组件的都可以访问到这个属性,然后可以通过这个属性来访问其他组件给这个属性上面绑定的一些方法,从而去传递数据。而且这个属性还可以去访问 Vue 实例对象上的方法。因为 Vue 组件构造函数的原型对象的原型对象是指向 Vue 的原型对象的
main.js安装全局事件总线
安装全局事件总线是在Vue的原型上,绑定一个属性(一般属性名命名为$bus,公交车,都可以进行数据搭载的意思),并将该属性值指向该实例对象;
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate () {
Vue.prototype.$bus = this // 安装全局事件总线
}
}).$mount('#app')
发送方组件:
发送发组件通过$emit方法进行数据发送;
接受方组件:
接收方组件是通过$on进行监听自定义事件,更新渲染页面;
8.这是通过全局事件总线实现的任意组件间数据传递:{{ bus ? bus : '等数据呢' }}
vue和react使用消息订阅与发布进行数据传输是一样的,都是通过安装一些外在的插件库实现数据传递;消息的订阅与发布可以用订阅报纸那样比拟,订阅消息就相当于你订购了哪一家的报纸,等报社发布后,你就会第一时间收到这家的报纸;这种插件多的很,但是我推荐用pubsub.js这个插件,比较好用,用的也比较多;
安装:
npm i pubsub-js
订阅消息:
一般在订阅消息的时候会将该订阅保存到实例对象身上,方便后边取消订阅,减少空间浪费;
9.这是通过消息订阅与发布实现的任意组件间数据传递:{{ pubsub ? pubsub : '等数据呢' }}
发布消息:
取消订阅:
PubSub.unsubscribe(pid)
官网:https://vuex.vuejs.org/zh/
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。是vue官方进行维护的一个用于数据出本地的插件。它采用集中式存储管理应用的所有组件的状态,将所有的数据存放在一个公共的仓库中,并且这个仓库所有的组件都可以访问到,一都可以进行数据的获取和存储,并以相应的规则保证状态以一种可预测的方式发生变化。
状态自管理应用包含三部分,以及对个部分进行的修改的示意图如下:
安装(vue2.0——>vuex3,vue3.0——>vuex4):
yarn add vuex
npm i vuex
state:数据的存储仓库,所有数据只能在这里进行获取,是用来存放数据的;
初始化仓库:
在定义的store仓库中,配置state属性,该属性是一个对象,里边是以键值对的形式存储数据的
const store = new Vuex.Store({
state: {
`变量名: 初始值`
}
})
使用仓库数据:
方法一(直接使用):
this.$store.state.变量名
方法二(使用vuex自带的mapState函数):
在需要使用该数据的组件中分别引入mapState函数,
import { mapState } from 'vuex'
在计算属性computed中使用 … 运算符展开数据
...mapState([变量名])
mutations:是 vuex 同步并且唯一进行数据修改的地方,里边是进行数据修改的一系列方法;
初始化mutations:
在定义的store仓库中,添加mutations配置项,该配置项中存放的是一系列进行数据修改的同步方法:
const store = new Vuex.Store({
state: {
`变量名: 初始值`
},
mutations:{
方法名(state,value){
// 在这里state是仓库,value是进行变化额度的值
state.变量名是需要修改的数据源,这里需要进进行的就是具体的修改方法
}
}
})
使用mutations:
方法一(直接使用):
this.$store.commit("mutations中定义的函数名",修改值)
方法二(函数映射):
从vuex中引入 mapMutations 内置函数,通过该函数映射需要的方法
import { mapState, mapMutations } from'vuex';
映射需要的方法:
...mapMutations(['方法名'])
使用映射出的方法::
this.方法名(修改的数据)
异步:就是下一步的代码不需要等待异步代码完全实现后才实现,可以先执行后边的再执行这里的;常见异步代码:ajax,定时器,计时器…
actions:是进行异步任务修改state中的数据的,但是这里进行修改也需要去调用mutations中的方法进行修改,因为mutations是唯一进行数据修改的地方,里边是进行数据修改的一系列异步方法;
初始化actions:
在定义的store仓库中,添加actions配置项,该配置项中存放的是一系列进行数据修改的异步方法:
const store = new Vuex.Store({
state: {
`变量名: 初始值`
},
mutations:{
方法名(state,value){
// 在这里state是仓库,value是进行变化额度的值
state.变量名是需要修改的数据源,这里需要进进行的就是具体的修改方法
}
},
actions:{
// 这里是异步函数,axios请求,定时器等,异步函数里会包含同步修改方法,所以使用**actions一定需要使用mutations**
}
})
mutations与actions区别:
两个都是进行state中数据修改的地方,mutations进行同步修改,里边的方法接受两个参数,state和修改变量的变化值;actios进行异步修改,里边的方法接受两个参数,store和修改变量的变化值;
使用actions:
方法一(直接使用,一般用于没有第二个参数的情况):
this.$store.dispatch('异步方法名',【修改的值,选填】)
方法二(映射使用,只能用于有第二个参数的情况):
从vuex中引入 mapActions 内置函数,通过该函数映射需要的方法
import { mapState, mapMutations,mapActions } from'vuex';
映射需要的方法:
...mapActions(['异步方法名'])
使用映射出的方法::
this.异步方法名(修改的数据)
// 注意:mutations和actions只能接收一个参数值(如果传递多个,请传递一个完整的对象)
当state中的数据不能够很好的满足页面使用时,getters是进行数据整合和修改的地方,相当于vue中的computed属性,把state中的数据作为参数,将state中的参数进行过滤、整合等操作,得到一个我们能方便利用的数据;
初始化getters:
在定义的store仓库中,添加getters配置项,该配置项中是存放进行数据整合的方法:
const store = new Vuex.Store({
state: {
`变量名: 初始值`
},
mutations:{
方法名(state,value){
// 在这里state是仓库,value是进行变化额度的值
state.变量名是需要修改的数据源,这里需要进进行的就是具体的修改方法
}
},
actions:{
// 这里是异步函数,axios请求,定时器等,异步函数里会包含同步修改方法,所以使用**actions一定需要使用mutations**
},
getters:{
计算属性名(state){
return
}
}
})
使用getter:
方法一(直接使用):
this.$store.getter.计算方法名
方法二(映射):
从vuex中引入 mapGetters 内置函数,通过该函数映射需要的方法
import { mapState, mapMutations,mapActions,mapGetters } from'vuex';
映射需要的方法:
...mapGetters(['计算方法名'])
使用映射出的方法::
this.计算方法名(修改的数据)
注意:
vue2.0中,geeter的返回值会像计算属性一样,根据他的依赖被缓存起来,且只有当它的依赖值被修改之后才被重新计算,但是从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来,这是一个已知的问题,将会在 3.1 版本中修复。
当我们存储的数据较多时,如果将所有的数据都存放在一个仓库中,就会显着臃肿,非常不方便,所以这里引入了模块化开发,即所有的数据存放在不同的仓库中;
使用:
在store中创建不同的仓库,每个仓库都拥有与跟仓库一样的属性,包括state,mutations,actions,getters,modules,唯一不同的是,在子仓库中的state是以函数形式存在的,跟仓库中是以对象形式存在的;
定义模块:
上方使用模块是为了定义子模块,这里的定义,意思是将跟仓库与子仓库建立连接;
modules: {
**模块名: 模块对象**
}
this.$store.state.变量名
分模块后语法:
this.$store.state.变量名
...mapState(['state里变量名'])
...mapState({'变量名': "state里变量名"}) // 给映射过来的state起别的名字
分模块后语法:
...mapState({
'变量名': state => state.模块名.变量名
})
当使用模块化开发时,如果多个模块存在着同一个方法时,在使用时就会引起冲突,所以这里引入了命名空间的概念
开启命名空间
在模块对象内设置namespaced: true
const moduleShopCar = {
namespaced: true,
state () {},
mutations: {},
actions: {},
getters: {},
modules: {}
}
...mapState("模块名", ['state变量名'])
方式1: 组件内 - 直接使用
this.$store.commit("mutations里的函数名", 具体值)
this.$store.commit("模块名/mutations里的函数名", 具体值)
方式2: 组件内 - 映射使用
...mapMutations(['mutations里方法名'])
...mapMutations("模块名", ['mutations里方法名'])
方式1: 组件内 - 直接使用
this.$store.dispatch("actions里的函数名", 具体值)
this.$store.dispatch("模块名/actions里的函数名", 具体值)
方式2: 组件内 - 映射使用
...mapActions(['actions里方法名'])
...mapActions("模块名", ['actions里方法名'])
方式1: 组件内 - 直接使用
this.$store.getters.计算属性名
this.$store.getters['模块名/计算属性名']
方式2: 组件内 - 映射使用
...mapGetters(['getters里计算属性名'])
...mapGetters("模块名", ['getters里计算属性名'])
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4HQTQta-1677134127824)( image\1667378968491-c6ec2920-246e-44f4-ac48-17c91c4a99e2-16687394834183.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LKgcE3ik-1677134127825)(image\image-20230109195833548.png)]