Vue3.js【未完成】

2023-05-16

Vue3.js

如何关闭烦人的vscode的提示框

https://blog.csdn.net/liuyuemozhu/article/details/101056556

ES6模块化与异步编程高级用法

ES6模块化

1. 回顾:node.js 中如何实现模块化

node.js 遵循了 CommonJS 的模块化规范。其中:

  • 导入其它模块使用 require() 方法
  • 模块对外共享成员使用 module.exports 对象

模块化的好处: 大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己

2. 前端模块化规范的分类

在 ES6 模块化规范诞生之前,JavaScript 社区已经尝试并提出了 AMD、CMD、CommonJS 等模块化规范。 但是,这些由社区提出的模块化标准,还是存在一定的差异性与局限性、并不是浏览器与服务器通用的模块化 标准,例如:

AMD 和 CMD 适用于浏览器端的 Javascript 模块化

CommonJS 适用于服务器端的 Javascript 模块化

太多的模块化规范给开发者增加了学习的难度与开发的成本。因此,大一统的 ES6 模块化规范诞生了

3.什么是 ES6 模块化规范

ES6 模块化规范是浏览器端与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学 习成本,开发者不需再额外学习 AMD、CMD 或 CommonJS 等模块化规范。

ES6 模块化规范中定义:

  • 每个 js 文件都是一个独立的模块
  • 导入其它模块成员使用 import 关键字
  • 向外共享模块成员使用 export 关键字

4. 在 node.js 中体验 ES6 模块化

node.js 中默认仅支持 CommonJS 模块化规范,若想基于 node.js 体验与学习 ES6 的模块化语法,可以按照 如下两个步骤进行配置:

① 确保安装了 v14.15.1 或更高版本的 node.js

② 在 package.json 的根节点中添加 “type”: “module” 节点

5. ES6 模块化的基本语法

ES6 的模块化主要包含如下 3 种用法:

① 默认导出与默认导入

② 按需导出与按需导入

③ 直接导入并执行模块中的代码

5.1 默认导出

默认导出的语法: export default 默认导出的成员

//定义模块私有成员n1
let n1 = 10
    //定义模块私有成员n2(外界访问不到n2,因为他没有被共享出去)
let n2 = 20

function show() {}

export default {
    //使用export default 默认导出语法,向外共享n1 和show 两个创建
    n1,
    show
}

默认导入的语法: import 接收名称 from ‘模块标识符’

import m1 from './01-默认导出'

console.log(m1)

每个模块中,只允许使用唯一的一次 export default,否则会报错!

5.2 按需导出

按需导出的语法: export 按需导出的成员

export let s1 = 'aaa'
export let s2 = 'ccc'
export function say() {}

按需导入的语法: import { s1 } from ‘模块标识符’

//导入模块成员
import { s1, s2, say } from './03- 按需导出 .js'

console.log(s1)
console.log(s2)
console.log(say)

这里插播一个:真的就是无语他妈给无语开门,无语到家了,代码是这个代码,然后在导出时,一直报错,错误提示是:code: ‘ERR_MODULE_NOT_FOUND’,然后我一直到网上找这个那个解决方法,然后没有用。然后,我没有理他,准备跳过的时候,他就好了。如果有大佬看见,希望可以解答一下我的疑惑。

按需导出与按需导入的注意事项

① 每个模块中可以使用多次按需导出

② 按需导入的成员名称必须和按需导出的名称保持一致

③ 按需导入时,可以使用 as 关键字进行重命名

④ 按需导入可以和默认导入一起使用

5.3 直接导入并执行模块中的代码

如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模 块代码,示例代码如下:

//在当前模块中执行一个for循环操作
for (let i = 0; i < 3 ; i++) {
	console.log(i)
}

----------分割线-------------
//直接导入并执行模块代码,不需要得到模块向外共享的成员
//注意要加后缀名
import './05-直接导入并执行模块中的代码.js'

Promise

1. 回调地狱

多层回调函数的相互嵌套,就形成了回调地狱。示例代码如下:

setTimeout(() => {
    console.log('延时 1 秒后输出')

    setTimeout(() => {
        console.log('延时 2 秒后输出')

        setTimeout(() => {
            console.log('延时 3 秒后输出')
        }, 3000)

    }, 2000)

}, 1000)

回调地狱的缺点:

  • 代码耦合性太强,牵一发而动全身,难以维护
  • 大量冗余的代码相互嵌套,代码的可读性变差
1.1 如何解决回调地狱的问题

为了解决回调地狱的问题,ES6(ECMAScript 2015)中新增了 Promise 的概念。

1.2 Promise 的基本概念

① Promise 是一个构造函数

  • 我们可以创建 Promise 的实例 const p = new Promise()
  • new 出来的 Promise 实例对象,代表一个异步操作

② Promise.prototype 上包含一个 .then() 方法

  • 每一次 new Promise() 构造函数得到的实例对象,
  • 都可以通过原型链的方式访问到 .then() 方法,例如 p.then()

③ .then() 方法用来预先指定成功和失败的回调函数

  • p.then(成功的回调函数,失败的回调函数)
  • p.then(result => { }, error => { })
  • 调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的

2. 基于回调函数按顺序读取文件内容

//读取文件1.txt
fs.readFiles('./files/1.txt', 'utf-8', (err1, r1) => {
    if (err1) return console.log(err1.message) //读取文件失败
    console.log(r1)
        //读取文件1.txt
    fs.readFiles('./files/2.txt', 'utf-8', (err2, r2) => {
        if (err2) return console.log(err2.message) //读取文件失败
        console.log(r2)
        fs.readFiles('./files/3.txt', 'utf-8', (err3, r3) => {
            if (err3) return console.log(err3.message) //读取文件失败
            console.log(r3)
        })
    })
})

3. 基于 then-fs 读取文件内容

由于 node.js 官方提供的 fs 模块仅支持以回调函数的方式读取文件,不支持 Promise 的调用方式。因此,需 要先运行如下的命令,安装 then-fs 这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容:

npm install then-fs
3.1 then-fs 的基本使用

调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象。因 此可以调用 .then() 方法为每个 Promise 异步操作指定成功和失败之后的回调函数。示例代码如下:

import thenFs from 'then-fs'

thenFs.readFile('./files/1.txt', 'utf-8').then((r1) => { console.log(r1) })
thenFs.readFile('./files/2.txt', 'utf-8').then((r2) => { console.log(r2) })
thenFs.readFile('./files/3.txt', 'utf-8').then((r3) => { console.log(r3) })

注意:上述的代码无法保证文件的读取顺序,需要做进一步的改进!

3.2 .then() 方法的特性

如果上一个 .then() 方法中返回了一个新的 Promise 实例对象,则可以通过下一个 .then() 继续进行处理。通 过 .then() 方法的链式调用,就解决了回调地狱的问题。

3.3 基于 Promise 按顺序读取文件的内容

Promise 支持链式调用,从而来解决回调地狱的问题。示例代码如下:

import thenFs from 'then-fs'

// 返回值是Promise的实例对象
thenFs.readFile('./files/1.txt', 'utf-8')
    .then((r1) => { //2.通过.then为第一个Promise实例指定成功之后的回调函数
        console.log(r1)
        return thenFs.readFile('./files/2.txt', 'utf-8') //3.在第一个.then中返回一个新的Promise实例对象
    })
    .then((r2) => { //4.继续调用.then,为上一个.then 的返回值(新的 Promise实例)指定成功之后的回调函数

        console.log(r2)
        return thenFs.readFile('./files/3.txt', 'utf-8') //5.在第二个 .then中再返回一个新的 Promise实例对象

    })
    .then((r3) => { //6.继续调用.then,为上一个.then的返回值(新的Promise实例)指定成功之后的回调函数
        console.log(r3)
    })
3.4 通过 .catch 捕获错误

在 Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.catch 方法进行捕获和处理:

.catch((err) => {
    console.log(err.message)
})

如果不希望前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前,示例代码如下:

import thenFs from 'then-fs'

// 返回值是Promise的实例对象
thenFs.readFile('./files/11.txt', 'utf-8')
    .catch((err) => {
        console.log(err.message)
    })
    .then((r1) => { //2.通过.then为第一个Promise实例指定成功之后的回调函数
        console.log(r1)
        return thenFs.readFile('./files/2.txt', 'utf-8') //3.在第一个.then中返回一个新的Promise实例对象
    })
    .then((r2) => { //4.继续调用.then,为上一个.then 的返回值(新的 Promise实例)指定成功之后的回调函数

        console.log(r2)
        return thenFs.readFile('./files/3.txt', 'utf-8') //5.在第二个 .then中再返回一个新的 Promise实例对象

    })
    .then((r3) => { //6.继续调用.then,为上一个.then的返回值(新的Promise实例)指定成功之后的回调函数
        console.log(r3)
    })

.catch((err) => {
    console.log(err.message)
})
3.5 Promise.all() 方法

Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)。示例代码如下:

import thenFs from 'then-fs'

const promiseArr = [
    thenFs.readFile('./files/1.txt', 'utf-8'),
    thenFs.readFile('./files/2.txt', 'utf-8'),
    thenFs.readFile('./files/3.txt', 'utf-8')
]

Promise.all(promiseArr).then(result => {
    console.log(result)
})
3.6 Promise.race() 方法

Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制)。示例代码如下:

import thenFs from 'then-fs'

const promiseArr = [
    thenFs.readFile('./files/1.txt', 'utf-8'),
    thenFs.readFile('./files/2.txt', 'utf-8'),
    thenFs.readFile('./files/3.txt', 'utf-8')
]

Promise.race(promiseArr)
    .then(result => {
        console.log(result)  //111
    })

.catch(err => {
    console.log(err.message)
})

4. 基于 Promise 封装读文件的方法

方法的封装要求:

① 方法的名称要定义为 getFile

② 方法接收一个形参 fpath,表示要读取的文件的路径

③ 方法的返回值为 Promise 实例对象

4.1 getFile 方法的基本定义
// 1.方法的名称为getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath){
    //3.方法的返回值为Promise的实例对象
    return new Promise()
}

注意:第 5 行代码中的 new Promise() 只是创建了一个形式上的异步操作。

4.2 创建具体的异步操作

如果想要创建具体的异步操作,则需要在 new Promise() 构造函数期间,传递一个 function 函数,将具体的 异步操作定义到 function 函数内部。示例代码如下:

import fs from 'fs'

// 1.方法的名称为getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath){
    //3.方法的返回值为Promise的实例对象
    return new Promise(function () {
        fs.readFile(fpath,'utf-8',(err,dataStr) => {})
    })
}
4.3 获取 .then 的两个实参

通过 .then() 指定的成功和失败的回调函数,可以在 function 的形参中进行接收,示例代码如下:

import fs from 'fs'

// 1.方法的名称为getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
    //3.方法的返回值为Promise的实例对象
    // resolve形参是:调用getFiles()方法时,通过 .then指定的成功的"回调函数
    // reject形参是:调用 getFiles()方法时,通过.then指定的"失败的""回调函数

    return new Promise(function(resolve,reject) {
        fs.readFile(fpath, 'utf-8', (err, dataStr) => {})
    })
}

//getFile方法的调用过程
getFile('./files/1.txt').then(成功的回调函数,失败的回调函数)
4.4 调用 resolve 和 reject 回调函数

Promise 异步操作的结果,可以调用 resolve 或 reject 回调函数进行处理。示例代码如下:

import fs from 'fs'

// 1.方法的名称为getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
    //3.方法的返回值为Promise的实例对象
    // resolve形参是:调用getFiles()方法时,通过 .then指定的成功的"回调函数
    // reject形参是:调用 getFiles()方法时,通过.then指定的"失败的""回调函数

    return new Promise(function(resolve, reject) {
        fs.readFile(fpath, 'utf-8', (err, dataStr) => {
            if (err) return reject(err) //如果读取失败,则调用"失败的回调函数"
            resolve(dataStr) //如果读取成功,则调用"成功的回调函数"
        })
    })
}



//getFile方法的调用过程
getFile('./files/1.txt').then((r1) => { console.log(r1) }, (err) => { console.log(err.message) })

getFile('./files/11.txt').then((r1) => { console.log(r1) }).catch(err => console.log(err.message))

async/await

1. 什么是 async/await

async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出 现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。示例代码如下:

import thenFs from 'then-fs'

// 返回值是Promise的实例对象
thenFs.readFile('./files/11.txt', 'utf-8')
    .then((r1) => { //2.通过.then为第一个Promise实例指定成功之后的回调函数
        console.log(r1)
        return thenFs.readFile('./files/2.txt', 'utf-8') //3.在第一个.then中返回一个新的Promise实例对象
    })
    .then((r2) => { //4.继续调用.then,为上一个.then 的返回值(新的 Promise实例)指定成功之后的回调函数

        console.log(r2)
        return thenFs.readFile('./files/3.txt', 'utf-8') //5.在第二个 .then中再返回一个新的 Promise实例对象

    })
    .then((r3) => { //6.继续调用.then,为上一个.then的返回值(新的Promise实例)指定成功之后的回调函数
        console.log(r3)
    })

.then 链式调用的优点:

解决了回调地狱的问题

.then 链式调用的缺点:

代码冗余、阅读性差、 不易理解

2. async/await 的基本使用

使用 async/await 简化 Promise 异步操作的示例代码如下:

import thenFs from 'then-fs'

async function getAllFile() {
    const r1 = await thenFs.readFile('./files/1.txt', 'utf-8')
    console.log(r1)
    const r2 = await thenFs.readFile('./files/2.txt', 'utf-8')
    console.log(r2)
    const r3 = await thenFs.readFile('./files/3.txt', 'utf-8')
    console.log(r3)

}

getAllFile()

3. async/await 的使用注意事项

① 如果在 function 中使用了 await,则 function 必须被 async 修饰

② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行

console.log('A')

async function getAllFile(){
    console.log('B')
    const r1 = await thenFs.readFile('./files/1.txt', 'utf-8')
    const r2 = await thenFs.readFile('./files/2.txt', 'utf-8')
    const r3 = await thenFs.readFile('./files/3.txt', 'utf-8')
    console.log(r1,r2,r3)
    console.log('D')
}

getAllFile()
console.log('c')

//执行顺序为
//a 
//b 
//c 
//111 222 333 
//d

EventLoop

1.JavaScript 是单线程的语言

JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。请添加图片描述

单线程执行任务队列的问题:

如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。

2. 同步任务和异步任务

请添加图片描述

为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:

① 同步任务(synchronous)

  • 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
  • 只有前一个任务执行完毕,才能执行后一个任务

② 异步任务(asynchronous)

  • 又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行
  • 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数 EventLoop

3. 同步任务和异步任务的执行过程

① 同步任务由 JavaScript 主线程次序执行

② 异步任务委托给宿主环境执行

③ 已完成的异步任务对应的回调函数,会被 加入到任务队列中等待执行

④ JavaScript 主线程的执行栈被清空后,会 读取任务队列中的回调函数,次序执行

⑤ JavaScript 主线程不断重复上面的第 4 步 EventLoop

4. EventLoop 的基本概念

JavaScript 主线程从“任务队列”中读取异步 任务的回调函数,放到执行栈中依次执行。这 个过程是循环不断的,所以整个的这种运行机 制又称为 EventLoop(事件循环)。

5.结合 EventLoop 分析输出的顺序

import thenFs from "then-fs"

console.log('a')

thenFs.readFile('./files/1.txt', 'utf-8').then(dataStr => {
    console.log('b')
})

setTimeout(() => {
    console.log('c')
}, 0)

console.log('d')

// a
// d
// c  因为延时为0,所以比b读取文件更快,所以先打印c后打印b
// b

正确的输出结果:ADCB。其中: A 和 D 属于同步任务。会根据代码的先后顺序依次被执行 C 和 B 属于异步任务。它们的回调函数会被加入到任务队列中,等待主线程空闲时再执行

宏任务和微任务

1. 什么是宏任务和微任务

JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:

① 宏任务(macrotask)

异步 Ajax 请求、

setTimeout、setInterval、

文件操作

其它宏任务

② 微任务(microtask)

Promise.then、.catch 和 .finally

process.nextTick

其它微任务 宏任务和微任务

请添加图片描述

2. 宏任务和微任务的执行顺序

每一个宏任务执行完之后,都会检查是否存在待执行的微任务, 如果有,则执行完所有微任务之后,再继续执行下一个宏任务。 宏任务和微任务

请添加图片描述

3. 去银行办业务的场景

① 小云和小腾去银行办业务。首先,需要取号之后进行排队

宏任务队列

② 假设当前银行网点只有一个柜员,小云在办理存款业务时,小腾只能等待

单线程,宏任务按次序执行

③ 小云办完存款业务后,柜员询问他是否还想办理其它业务?

当前宏任务执行完,检查是否有微任务

④ 小云告诉柜员:想要买理财产品、再办个信用卡、最后再兑换点马年纪念币?

执行微任务,后续宏任务被推迟

⑤ 小云离开柜台后,柜员开始为小腾办理业务

所有微任务执行完毕,开始执行下一个宏任务 宏任务和微任务

4. 分析以下代码输出的顺序

setTimeout(function() {
    console.log('1')
})

new Promise(function(resolve) {
    console.log('2')
    resolve()
}).then(function() {
    console.log('3')
})

console.log('4')

正确的输出顺序是:2431

分析:

① 先执行所有的同步任务

执行第 6 行、第 12 行代码

② 再执行微任务

执行第 9 行代码

③ 再执行下一个宏任务

执行第 2 行代码 宏任务和微任务

5.经典面试题

请分析以下代码输出的顺序 :

console.log('1')
setTimeout(function() {
    console.log('2')
    new Promise(function(resolve) {
        console.log('3')
        resolve()
    }).then(function() {
        console.log('4')
    })
})

new Promise(function(resolve) {
    console.log('5')
    resolve()
}).then(function() {
    console.log('6')
})


setTimeout(() => {
    console.log('7')
    new Promise(function(resolve) {
        console.log('8')
    }).then(function() {
        console.log('9')
    })
})

正确的输出顺序是:156234789

API接口案例

前端工程化与 webpack

前端工程化

1. 小白眼中的前端开发 vs 实际的前端开发

小白眼中的前端开发:

  • 会写 HTML + CSS + JavaScript 就会前端开发
  • 需要美化页面样式,就拽一个 bootstrap 过来
  • 需要操作 DOM 或发起 Ajax 请求,再拽一个 jQuery 过来
  • 需要渲染模板结构,就用 art-template 等模板引擎

实际的前端开发:

  • 模块化(js 的模块化、css 的模块化、其它资源的模块化)
  • 组件化(复用现有的 UI 结构、样式、行为)
  • 规范化(目录结构的划分、编码规范化、接口规范化、文档规范化、 Git 分支管理)
  • 自动化(自动化构建、自动部署、自动化测试)

2. 什么是前端工程化

前端工程化指的是:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化、 标准化。最终落实到细节上,就是实现前端的“4 个现代化”: 模块化、组件化、规范化、自动化

3. 前端工程化的好处

前端工程化的好处主要体现在如下两方面:

① 前端工程化让前端开发能够“自成体系”,覆盖了前端项目从创建到部署的方方面面

② 最大程度地提高了前端的开发效率,降低了技术选型、前后端联调等带来的协调沟通成本 前端工程化

4.前端工程化的解决方案

早期的前端工程化解决方案:

  • grunt( https://www.gruntjs.net/ )
  • gulp( https://www.gulpjs.com.cn/ )

目前主流的前端工程化解决方案:

  • webpack( https://www.webpackjs.com/ )
  • parcel( https://zh.parceljs.org/ )

webpack 的基本使用

1. 什么是 webpack

概念:webpack 是前端项目工程化的具体解决方案。

主要功能:它提供了友好的前端模块化开发支持,以及代码压缩混淆、处理浏览器端 JavaScript 的兼容性、性 能优化等强大的功能。

好处:让程序员把工作的重心放到具体功能的实现上,提高了前端开发效率和项目的可维护性。

注意:目前企业级的前端项目开发中,绝大多数的项目都是基于 webpack 进行打包构建的。

2. 创建列表隔行变色项目

① 新建项目空白目录,并运行 npm init –y 命令,初始化包管理配置文件 package.json

② 新建 src 源代码目录

③ 新建 src -> index.html 首页和 src -> index.js 脚本文件

④ 初始化首页基本的结构

⑤ 运行 npm install jquery –S 命令,安装 jQuery

⑥ 通过 ES6 模块化的方式导入 jQuery,实现列表隔行变色效果 webpack 的基本使用

3. 在项目中安装 webpack

在终端运行如下的命令,安装 webpack 相关的两个包:

npm install webpack@5.5.1 webpack-cli@4.2.0 -D

4. 在项目中配置

webpack ① 在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件,并初始化如下的基本配置:

module.exports = {
    mode: 'development' 
    //mode 用来指定构建模式,可选值有development和production
}

② 在 package.json 的 scripts 节点下,新增 dev 脚本如下:

"scripts": {
        "dev": "webpack" //script 节点下的脚本,可以通过npm run 执行,例如npm run dev
    }

③ 在终端中运行 npm run dev 命令,启动 webpack 进行项目的打包构建 webpack 的基本使用

4.1 mode 的可选值

mode 节点的可选值有两个,分别是:

① development

  • 开发环境
  • 不会对打包生成的文件进行代码压缩和性能优化
  • 打包速度快,适合在开发阶段使用

② production

  • 生产环境
  • 会对打包生成的文件进行代码压缩和性能优化
  • 打包速度很慢,仅适合在项目发布阶段使用
4.2 webpack.config.js 文件的作用

webpack.config.js 是 webpack 的配置文件。webpack 在真正开始打包构建之前,会先读取这个配置文件, 从而基于给定的配置,对项目进行打包。

注意:由于 webpack 是基于 node.js 开发出来的打包工具,因此在它的配置文件中,支持使用 node.js 相关 的语法和模块进行 webpack 的个性化配置。

4.3 webpack 中的默认约定

在 webpack 中有如下的默认约定:

① 默认的打包入口文件为 src -> index.js

② 默认的输出文件路径为 dist -> main.js

注意:可以在 webpack.config.js 中修改打包的默认约定

4.4 自定义打包的入口与出口

在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口。通过 output 节点指定打包的出口。 示例代码如下:

const path = require('path')

module.exports = {
    mode: 'development',
    //打包入口文件的路径
    entry: path.join(__dirname, './src/index.js'),
    output: {
        //输出文件的存放路径
        path: path.join(__dirname, './dist'),
        // 输出文件的名称
        filename: 'bundle.js'
    }
}

webpack 中的插件

1. webpack 插件的作用

通过安装和配置第三方的插件,可以拓展 webpack 的能力,从而让 webpack 用起来更方便。最常用的 webpack 插件有如下两个:

① webpack-dev-server

  • 类似于 node.js 阶段用到的 nodemon 工具
  • 每当修改了源代码,webpack 会自动进行项目的打包和构建

② html-webpack-plugin

  • webpack 中的 HTML 插件(类似于一个模板引擎插件)
  • 可以通过此插件自定制 index.html 页面的内容

2. webpack-dev-server

webpack-dev-server 可以让 webpack 监听项目源代码的变化,从而进行自动打包构建。

2.1 安装 webpack-dev-server

运行如下的命令,即可在项目中安装此插件:

npm install webpack-dev-server@3.11.0 -D
2.2 配置 webpack-dev-server

① 修改 package.json -> scripts 中的 dev 命令如下:

  "scripts": {
        "dev": "webpack serve"
    }

② 再次运行 npm run dev 命令,重新进行项目的打包

③ 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果 注意:webpack-dev-server 会启动一个实时打包的 http 服务器 webpack 中的插件

2.3 打包生成的文件哪儿去了?

① 不配置 webpack-dev-server 的情况下,webpack 打包生成的文件,会存放到实际的物理磁盘上

  • 严格遵守开发者在 webpack.config.js 中指定配置
  • 根据 output 节点指定路径进行存放

② 配置了 webpack-dev-server 之后,打包生成的文件存放到了内存中

  • 不再根据 output 节点指定的路径,存放到实际的物理磁盘上
  • 提高了实时打包输出的性能,因为内存比物理磁盘速度快很多
2.4 生成到内存中的文件该如何访问?

webpack-dev-server 生成到内存中的文件,默认放到了项目的根目录中,而且是虚拟的、不可见的。

  • 可以直接用 / 表示项目根目录,后面跟上要访问的文件名称,即可访问内存中的文件

  • 例如 /bundle.js 就表示要访问 webpack-dev-server 生成到内存中的 bundle.js 文件

  • <script src="/bundle.js"></script>
    

3. html-webpack-plugin

是 webpack 中的 HTML 插件,可以通过此插件自定制 index.html 页面的内容。 需求:通过 html-webpack-plugin 插件,将 src 目录下的 index.html 首页,复制到项目根目录中一份!

3.1 安装 html-webpack-plugin

运行如下的命令,即可在项目中安装此插件:

npm install html-webpack-plugin@4.5.0 -D
3.2 配置 html-webpack-plugin

在webpack.config.js里面添加

// 1.导入插件,得到构造函数
const HtmlPlugin = require('html-webpack-plugin')

// 2. 创建插件的实例对象
const HtmlPlugin = new HtmlPlugin({
    template: './src/index.html',
    filename: './index.html'
})


module.exports = {
    mode: 'development',
    // 3.挂载插件的实例对象
    Plugin: [HtmlPlugin]
}
const path = require('path')
    // 1.导入插件,得到构造函数
const HtmlPlugin = require('html-webpack-plugin')
    // 2. 创建插件的实例对象
const HtmlPlugin = new HtmlPlugin({
    template: './src/index.html',
    filename: './index.html',
})


module.exports = {
    mode: 'development',
    //打包入口文件的路径
    entry: path.join(__dirname, './src/index.js'),
    output: {
        //输出文件的存放路径
        path: path.join(__dirname, './dist'),
        // 输出文件的名称
        filename: 'bundle.js',
    },
    // 挂载插件的实例对象
    plugins: [htmlPlugin]
}




3.3 解惑 html-webpack-plugin

① 通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中

② HTML 插件在生成的 index.html 页面的底部,自动注入了打包的 bundle.js 文件

4. devServer 节点

在 webpack.config.js 配置文件中,可以通过 devServer 节点对 webpack-dev-server 插件进行更多的配置, 示例代码如下:

devServer: {
        open: true,//初次打包完成后,自动打开浏览器
        host: '12.0.0.1',//实时打包所使用的主机地址
        port: 80 //实时打包所使用的端口号
    }

注意:凡是修改了 webpack.config.js 配置文件,或修改了 package.json 配置文件,必须重启实时打包的服 务器,否则最新的配置文件无法生效!

webpack.config.js

const path = require('path')
    // 1.导入插件,得到构造函数
const HtmlPlugin = require('html-webpack-plugin')
    // 2. 创建插件的实例对象
const htmlPlugin = new HtmlPlugin({
    template: './src/index.html',
    filename: './index.html',
})


module.exports = {
    mode: 'development',
    //打包入口文件的路径
    entry: path.join(__dirname, './src/index.js'),
    output: {
        //输出文件的存放路径
        path: path.join(__dirname, './dist'),
        // 输出文件的名称
        filename: 'bundle.js',
    },
    // 挂载插件的实例对象
    plugins: [htmlPlugin],
    devServer: {
        open: true,
        host: '127.0.0.1',
        port: 80
    }
}

webpack 中的 loader

1.loader 概述

在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块。其他非 .js 后缀名结尾的模块, webpack 默认处理不了,需要调用 loader 加载器才可以正常打包,否则会报错!

loader 加载器的作用:协助 webpack 打包处理特定的文件模块。比如:

  • css-loader 可以打包处理 .css 相关的文件

  • less-loader 可以打包处理 .less 相关的文件

  • babel-loader 可以打包处理 webpack 无法处理的高级 JS 语法

2.loader 的调用过程

请添加图片描述

3. 打包处理 css 文件

① 运行 npm i style-loader@2.0.0 css-loader@5.0.1 -D 命令,安装处理 css 文件的 loader

② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

module: {//所有第三方文件配置模块的匹配规则
        rules: [//文件后缀的匹配规则
            { test: /\.css/, use: ['style-loager', 'css-loader'] }//\代表转译的意思,把这个点转译成一个真正的引号,以css结尾的文件,都用ues数组调用的loader
        ]
    }

其中,test 表示匹配的文件类型, use 表示对应要调用的 loader

注意:

  • use 数组中指定的 loader 顺序是固定的

  • 多个 loader 的调用顺序是:从后往前调用 webpack 中的 loader

4. 打包处理 less 文件

① 运行 npm i less-loader@7.1.0 less@3.12.2 -D 命令

② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

module: {//所有第三方文件配置模块的匹配规则
        rules: [//文件后缀的匹配规则
            { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
        ]
    }

5. 打包处理样式表中与 url 路径相关的文件

① 运行 npm i url-loader@4.1.1 file-loader@6.2.0 -D 命令

② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

module: {//所有第三方文件配置模块的匹配规则
        rules: [//文件后缀的匹配规则
            {test:/\/.jpg|png|gif$/,ues:'url-loader?limit=22229'},
        ]
    }

其中 ? 之后的是 loader 的参数项:

  • limit 用来指定图片的大小,单位是字节(byte)

  • 只有 ≤ limit 大小的图片,才会被转为 base64 格式的图片

相关文件

index.less

html,body,ul{
    margin: 0;
    padding: 0;
    li{
        line-height: 35px;
        padding: 10px;
        font-size: 12px;
    }
}

#box {
    width: 380px;
    height: 114px;
    background-color: red;
    background: url('../image/1BC1D6FF35C9D9FE28A5C4FEB17F55A2.png');
}

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- index.js存在兼容性问题 -->
    <!-- <script src="./index.js"></script> -->
    <!-- <script src="../dist/bundle.js"></script> -->
    <!-- <script src="/bundle.js"></script> -->
</head>

<body>
    <ul>
        <li>这是第1个li</li>
        <li>这是第2个li</li>
        <li>这是第3个li</li>
        <li>这是第4个li</li>
        <li>这是第5个li</li>
        <li>这是第6个li</li>
        <li>这是第7个li</li>
        <li>这是第8个li</li>
        <li>这是第9个li</li>
    </ul>

    <div id='box'></div>
</body>

</html>
5.1 loader 的另一种配置方式

带参数项的 loader 还可以通过对象的方式进行配置:

module: {//所有第三方文件配置模块的匹配规则
        rules: [//文件后缀的匹配规则
            {test:/\/.jpg|png|gif$/,ues:
            loader:'url-loader',//通过loader属性指定调用的loader
            options:{//通过options属性指定参数项
            	limit:66300
            	}
            },
        ]
    }
const path = require('path')
    // 1.导入插件,得到构造函数
const HtmlPlugin = require('html-webpack-plugin')
    // 2. 创建插件的实例对象
const htmlPlugin = new HtmlPlugin({
    template: './src/index.html',
    filename: './index.html',
})


module.exports = {
    mode: 'development',
    //打包入口文件的路径
    entry: path.join(__dirname, './src/index.js'),
    output: {
        //输出文件的存放路径
        path: path.join(__dirname, './dist'),
        // 输出文件的名称
        filename: 'bundle.js',
    },
    // 挂载插件的实例对象
    plugins: [htmlPlugin],
    devServer: {
        open: true,
        host: '127.0.0.1',
        port: 80
    },
    module: {
        rules: [
            { test: /\.css$/, use: ['style-loader', 'css-loader'] },
            { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
            {
                test: /\/.jpg|png|gif$/,
                use: {
                    loader: 'url-loader', //通过loader属性指定调用的loader
                    options: { //通过options属性指定参数项
                        limit: 66300
                    }
                }
            },

        ],
    },
}

6. 打包处理 js 文件中的高级语法

webpack 只能打包处理一部分高级的 JavaScript 语法。对于那些 webpack 无法处理的高级 js 语法,需要借 助于 babel-loader 进行打包处理。例如 webpack 无法处理下面的 JavaScript 代码:

class Person{
	//通过start关键字,为Person类定义了一个静态属性info
	//webpack无法打包处理"静态属性"这个高级语法
	static info = 'person info'
}

console.log(Person.info)
6.1 安装 babel-loader 相关的包

运行如下的命令安装对应的依赖包:

npm install babel-loader@8.2.1 @babel/core@7.12.3 @babel/plugin-proposal-class-properties@7.12.1 -D

包的名称及版本号列表如下(红色是包的名称、黑色是包的版本号):

  • babel-loader@8.2.1

  • @babel/core@7.12.3

  • @babel/plugin-proposal-class-properties@7.12.1

6.2 配置 babel-loader

在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

{
                test: /\.js$/,
                // exclude为排除项
                // 表示badel-loader 只需要处理开发编写的js文件,不需要处理node-modules下的js文件
                exclude: /node-modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        //参数项
                        // 声明一个badel插件,此插件用来转化class中的高级语法
                        plugins: ['@babel/plugin-proposal-class-properties'],
                    }
                }
            },

打包发布

1. 为什么要打包发布

项目开发完成之后,使用 webpack 对项目进行打包发布的主要原因有以下两点:

① 开发环境下,打包生成的文件存放于内存中,无法获取到最终打包生成的文件

② 开发环境下,打包生成的文件不会进行代码压缩和性能优化

为了让项目能够在生产环境中高性能的运行,因此需要对项目进行打包发布。

2. 配置 webpack 的打包发布

在 package.json 文件的 scripts 节点下,新增 build 命令如下:

"scripts": {
        "dev": "webpack serve",
        //项目发布时,运行build命令
        "build":"webpack --mode production"
    },

–model 是一个参数项,用来指定 webpack 的运行模式。production 代表生产环境,会对打包生成的文件 进行代码压缩和性能优化。

注意:通过 --model 指定的参数项,会覆盖 webpack.config.js 中的 model 选项。

3. 把 JavaScript 文件统一生成到 js 目录中

在 webpack.config.js 配置文件的 output 节点中,进行如下的配置:

output: {
        //输出文件的存放路径
        path: path.join(__dirname, './dist'),
        // 明确告诉webpack把生成的bundle.js文件存放到dist目录下的js子目录中
        filename: 'js/bundle.js',
    },

4. 把图片文件统一生成到 image 目录中

修改 webpack.config.js 中的 url-loader 配置项,新增 outputPath 选项即可指定图片文件的输出路径:

 {
                test: /\/.jpg|png|gif$/,
                use: {
                    loader: 'url-loader', //通过loader属性指定调用的loader
                    options: { //通过options属性指定参数项
                        limit: 66300,
                        outputPath: 'image'
                    },
                }
            },

5. 自动清理 dist 目录下的旧文件

为了在每次打包发布时自动清理掉 dist 目录中的旧文件,可以安装并配置 clean-webpack-plugin 插件:

//1.安装清理dist目录的webpack插件
npm i clean-webpack-plugin@3.0.0 -D

//2.按需导入插件,得到插件的构造函数之后,创建插件的实例对象
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const cleanPlugin = new CleanWebpackPlugin()

//3.把创建的cleanPlugin 插件实例对象,挂载到plugins节点中
plugins: [htmlPlugin, cleanPlugin],
const path = require('path')
    // 1.导入插件,得到构造函数
const HtmlPlugin = require('html-webpack-plugin')
    // 2. 创建插件的实例对象
const htmlPlugin = new HtmlPlugin({
    template: './src/index.html',
    filename: './index.html',
})


const { CleanWebpackPlugin } = require('clean-webpack-plugin')
new cleanPlugin = CleanWebpackPlugin()



module.exports = {
    mode: 'development',
    //打包入口文件的路径
    entry: path.join(__dirname, './src/index.js'),
    output: {
        //输出文件的存放路径
        path: path.join(__dirname, './dist'),
        // 输出文件的名称
        filename: 'js/bundle.js',
    },
    // 挂载插件的实例对象
    plugins: [htmlPlugin, cleanPlugin],
    devServer: {
        open: true,
        host: '127.0.0.1',
        port: 80
    },
    module: {
        rules: [
            { test: /\.css$/, use: ['style-loader', 'css-loader'] },
            { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
            {
                test: /\/.jpg|png|gif$/,
                use: {
                    loader: 'url-loader', //通过loader属性指定调用的loader
                    options: { //通过options属性指定参数项
                        limit: 66300,
                        outputPath: 'image'
                    },
                }
            },
            {
                test: /\.js$/,
                // exclude为排除项
                // 表示badel-loader 只需要处理开发编写的js文件,不需要处理node-modules下的js文件
                exclude: /node-modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        //参数项
                        // 声明一个badel插件,此插件用来转化class中的高级语法
                        plugins: ['@babel/plugin-proposal-class-properties'],
                    }
                }
            },

        ],
    },
}

6. 企业级项目的打包发布

企业级的项目在进行打包发布时,远比刚才的方式要复杂的多,主要的发布流程如下:

  • 生成打包报告,根据报告分析具体的优化方案

  • Tree-Shaking

  • 为第三方库启用 CDN 加载

  • 配置组件的按需加载

  • 开启路由懒加载

  • 自定制首页内容

Source Map

1. 生产环境遇到的问题

前端项目在投入生产环境之前,都需要对 JavaScript 源代码进行压缩混淆,从而减小文件的体积,提高文件的 加载效率。此时就不可避免的产生了另一个问题:

对压缩混淆之后的代码除错(debug)是一件极其困难的事情

  • 变量被替换成没有任何语义的名称

  • 空行和注释被剔除

2. 什么是 Source Map

Source Map 就是一个信息文件,里面储存着位置信息。也就是说,Source Map 文件中存储着代码压缩混淆 前后的对应关系。

有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,能够极大的方便后期的调试。

3. webpack 开发环境下的 Source Map

在开发环境下,webpack 默认启用了 Source Map 功能。当程序运行出错时,可以直接在控制台提示错误行 的位置,并定位到具体的源代码:
请添加图片描述

请添加图片描述

3.1 默认 Source Map 的问题

开发环境下默认生成的 Source Map,记录的是生成后的代码的位置。会导致运行时报错的行数与源代码的行 数不一致的问题。示意图如下:
请添加图片描述

请添加图片描述

3.2 解决默认 Source Map 的问题

开发环境下,推荐在 webpack.config.js 中添加如下的配置,即可保证运行时报错的行数与源代码的行数 保持一致:

module.exports = {
	mode: 'development',
	//此选项生成的Source Map 能够保证"运行时报错的行数"与""源代码的行数"保持一致
	devtool: 'eval-source-map',
	//省略其他配置
}

4. webpack 生产环境下的 Source Map

在生产环境下,如果省略了 devtool 选项,则最终生成的文件中不包含 Source Map。这能够防止原始代码通 过 Source Map 的形式暴露给别有所图之人。

4.1 只定位行数不暴露源码

在生产环境下,如果只想定位报错的具体行数,且不想暴露源码。此时可以将 devtool 的值设置为 nosources-source-map。实际效果如图所示:

请添加图片描述

请添加图片描述

4.2 定位行数且暴露源码

在生产环境下,如果想在定位报错行数的同时,展示具体报错的源码。此时可以将 devtool 的值设置为 source-map。实际效果如图所示:
请添加图片描述

采用此选项后:你应该将你的服务器配置为,不允许普通用户访问 source map 文件!

5. Source Map 的最佳实践

① 开发环境下:

  • 建议把 devtool 的值设置为 eval-source-map

  • 好处:可以精准定位到具体的错误行

② 生产环境下:

  • 建议关闭 Source Map 或将 devtool 的值设置为 nosources-source-map

  • 好处:防止源码泄露,提高网站的安全性

vue 基础入门

vue 简介

1. 什么是 vue

官方给出的概念:Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的前端框架。

核心关键词:构建用户 界面、框架

1.1 解读核心关键词:构建用户界面

前端开发者最主要的工作,就是为网站的使用者(又称为:网站的用户)构建出美观、舒适、好用的网页。

编写结构:基于 HTML 超文本标记语言,搭建出网页的内容结构。

美化样式:基础 CSS 样式,美化网页的可视化效果。

处理交互:基于 Javascript 来操作网页中的 DOM 对象,处理用户和网页之间的交互行为。

1.2 构建用户界面的传统方式

在传统的 Web 前端开发中,是基于 jQuery + 模板引擎 的方式来构建用户界面的。

编写结构:基于模板引擎技术,把数据渲染到页面上。

优点:初步解放了前端开发者,从此不用手动拼接字符串来渲染网页结构了。

缺点:1.需要定义大量的模板结构;2.缺少语法高亮和智能提示;3.数据变化时需要重新调 用模板编译的函数,否则页面结构不会更新;

美化样式:基础 CSS 样式,美化网页的可视化效果。

处理交互:基于 jQuery 技术,处理用户和网页之间的交互行为。

优点:屏蔽了 DOM API 之间的兼容性,提高了 DOM 操作的效率和体验。 缺点:当业务复杂时、数据变化频繁时,前端程开发者需要把大量的时间和精力浪费在 DOM 的操作上,而不是核心业务的处理上。

1.3 使用 vue 构建用户界面

使用 vue 构建用户界面,解决了 jQuery + 模板引擎 的诸多痛点,极大的提高了前端开发的效率和体验。

编写结构:基于 vue 中提供的指令,可以方便快捷的渲染页面的结构(乐不思蜀)。

数据驱动视图(只要页面依赖的数据源变化,则页面自动重新渲染)

Ps:指令是 vue 为开发者提供的模板语法,用来辅助开发者渲染页面的结构。

美化样式:基础 CSS 样式,美化网页的可视化效果。

处理交互:基于 vue 中提供的事件绑定,可以轻松处理用户和页面之间的交互行为。

Ps:开发者把工作的重心放在核心业务的实现上

1.4 解读核心关键词:框架

官方给 vue 的定位是前端框架,因为它提供了构建用户界面的一整套解决方案(俗称 vue 全家桶):

  • vue(核心库)
  • vue-router(路由方案)
  • vuex(状态管理方案)
  • vue 组件库(快速搭建页面 UI 效果的方案)

以及辅助 vue 项目开发的一系列工具:

  • vue-cli(npm 全局包:一键生成工程化的 vue 项目 - 基于 webpack、大而全)
  • vite(npm 全局包:一键生成工程化的 vue 项目 - 小而巧)
  • vue-devtools(浏览器插件:辅助调试的工具)
  • vetur(vscode 插件:提供语法高亮和智能提示)
1.5 总结:什么是 vue

vue 是一套用于构建用户界面的前端框架。

前端框架:

  • 构建用户界面的一整套解决方案

  • vue 全家桶:vue + vue-router + vuex + 组件库

  • 提供了辅助项目开发的配套工具 vue-cli + vue-devtools

构建用户界面:

  • 指令: 用于辅助开发者渲染页面的模板语法
  • 事件绑定: 用于处理用户和网页之间的交互行为
  • 数据驱动视图: 数据源变化,页面结构自动重新渲染

2. vue 的特性

vue 框架的特性,主要体现在如下两方面:

① 数据驱动视图

② 双向数据绑定

2.1 数据驱动视图

在使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。示意图如下:

请添加图片描述

好处:当页面数据发生变化时,页面会自动重新渲染!

注意:数据驱动视图是单向的数据绑定。

2.2 双向数据绑定

在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源 中。示意图如下:
在这里插入图片描述
在这里插入图片描述

好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值!

2.3 MVVM

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。它把每个 HTML 页面都拆分成了如下三个部分:

在 MVVM 概念中:

  • View 表示当前页面所渲染的 DOM 结构。
  • Model 表示当前页面渲染时所依赖的数据源。
  • ViewModel 表示 vue 的实例,它是 MVVM 的核心。
2.4 MVVM 的工作原理

ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起。

在这里插入图片描述

当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构

当表单元素的值发生变化时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中

3.vue 的版本

当前,vue 共有 3 个大版本,其中:

2.x 版本的 vue 是目前企业级项目开发中的主流版本

3.x 版本的 vue 于 2020-09-19 发布,生态还不完善,尚未在企业级项目开发中普及和推广

1.x 版本的 vue 几乎被淘汰,不再建议学习与使用

总结:

3.x 版本的 vue 是未来企业级项目开发的趋势;

2.x 版本的 vue 在未来(1 ~ 2年内)会被逐渐淘汰;

3.1 vue3.x 和 vue2.x 版本的对比

vue2.x 中绝大多数的 API 与特性,在 vue3.x 中同样支持。同时,vue3.x 中还新增了 3.x 所特有的功能、并 废弃了某些 2.x 中的旧功能:

新增的功能例如:

组合式 API、多根节点组件、更好的 TypeScript 支持等

废弃的旧功能如下:

过滤器、不再支持 o n , on, onoff 和 $once 实例方法等

详细的变更信息,请参考官方文档给出的迁移指南: https://v3.vuejs.org/guide/migration/introduction.html

vue 的基本使用

1. 基本使用步骤

① 导入 vue.js 的 script 脚本文件

② 在页面中声明一个将要被 vue 所控制的 DOM 区域

③ 创建 vm 实例对象(vue 实例对象)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- 2.声明要被vue所控制的dom区域 -->
    <div id='app'>{{username}}</div>

    <!-- 1.导入vue的脚本文件 -->
    <script src="./lib/vue-2.6.12.js"></script>


    <!-- 3.创建vue实例对象 -->
    <script>
        const vm = new Vue({
            // 3.1使用el属性指定vue要控制的区域
            el: '#app',
            // 3.2数据源
            data: {
                username: 'zs',
            },
        })
    </script>
</body>

</html>

2. 基本代码与 MVVM 的对应关系

vue 的调试工具

1. 安装 vue-devtools 调试工具

vue 官方提供的 vue-devtools 调试工具,能够方便开发者对 vue 项目进行调试与开发。

Chrome 浏览器在线安装 vue-devtools

vue 2.x 调试工具:

https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

vue 3.x 调试工具:

https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg

注意:vue2 和 vue3 的浏览器调试工具不能交叉使用!

2. 配置 Chrome 浏览器中的 vue-devtools

点击 Chrome 浏览器右上角的在这里插入图片描述
按钮,选择更多工具 -> 扩展程序 -> Vue.js devtools 详细信息,并勾选如下 的两个选项:

注意:修改完配置项,须重启浏览器才能生效!

3. 使用 vue-devtools 调试 vue 页面

在浏览器中访问一个使用了 vue 的页面,打开浏览器的开发者工具,切换到 Vue 面板,即可使用 vue-devtools 调试当前的页面。

vue 的指令与过滤器

1. 指令的概念

指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。

vue 中的指令按照不同的用途可以分为如下 6 大类:

① 内容渲染指令

② 属性绑定指令

③ 事件绑定指令

④ 双向绑定指令

⑤ 条件渲染指令

⑥ 列表渲染指令

注意:指令是 vue 开发中最基础、最常用、最简单的知识点。

1.1 内容渲染指令

内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:

  • v-text
  • {{ }}
  • v-html
v-text

用法示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id='app'>
        <!-- 把username对应的值,渲染到第一个p标签中 -->
        <p v-text="username"></p>

        <!-- 把gender对应的值,渲染到第二个p标签中 -->
        <!-- 注意:第二个p标签中,默认的文本‘性别’会被值覆盖掉 -->
        <p v-tetx="gender">性别</p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: 'app',
            data: {
                username: 'zs',
                gender: '男'
            }
        })
    </script>
</body>

</html>

注意:v-text 指令会覆盖元素内默认的值。

{{ }} 语法

vue 提供的 {{ }} 语法,专门用来解决 v-text 会覆盖默认文本内容的问题。这种 {{ }} 语法的专业名称是插值表达式(英文名为:Mustache)。

<!-- 使用{{}} 插值表达式,将对应的值渲染到元素的内容节点中 -->
<!-- 同时保留元素自身的默认值 -->
<p>姓名:{{username}}</p>
<p>性别:{{gender}}</p>

注意:相对于 v-text 指令来说,插值表达式在开发中更常用一些!因为它不会覆盖元素中默认的文本内容。

v-html

v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素, 则需要用到 v-html 这个指令:

<--desc: '<i>abc<i>'-->
<P v-html="desc"></P>
1.2 属性绑定指令

如果需要为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令。用法示例如下:

<!--假设有如下的data数据:
data:{
	inputValue:'请输入内容',
	imgSrc:'https://cn.vuejs.org/images/logo.png'
	}
-->
<!--使用v-bind指令,为input的placeholder动态绑定属性值-->
<input type="text" v-bind:placcholder="inputvalue"/>
<br/>
<!--使用v-bind指令,为img的src动态绑定属性值-->
<img v-bind:src="imgSrc" alt=""/>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id='app'>
        <input type="text" v-bind:placeholder="inputValue">
        <hr>
        <img v-bind:src="imgSrc">
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                inputValue: '请输入内容',
                //图片的src地址
                imgSrc: './image/1.png',
            },
        })
    </script>
</body>

</html>
属性绑定指令的简写形式

由于 v-bind 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 : )。

<!--假设有如下的data数据:
data:{
	inputValue:'请输入内容',
	imgSrc:'https://cn.vuejs.org/images/logo.png'
	}
-->
<!--使用v-bind指令,为input的placeholder动态绑定属性值-->
<input type="text" :placcholder="inputvalue"/>
<br/>
<!--使用v-bind指令,为img的src动态绑定属性值-->
<img :src="imgSrc" alt=""/>
使用 Javascript 表达式

在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算,例如:

{{number+1}}

{{ok? 'yes':'no'}}

{{message.split('').reverse().join('')}}

<div v-bind:id="list-"+id></div>
1.3 事件绑定指令

vue 提供了 v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听。语法格式如下:

<h3>count 的值为:{{count}}</h3>
<!--语法格式为v-on:事件名称="事件处理函数的名称"-->
<button v-on: click='addCount'>+1</button>

注意:原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件,替换为 vue 的事件绑定形式后, 分别为:v-on:click、v-on:input、v-on:keyup

通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明:

const vm = new Vue({
            el: '#app',
            data: {
                count: 0,
            },
            methods: {
                //v-on 绑定的事件处理函数,需要声明在methods节点中
                addCount() {
                    //事件处理函数的名字
                    //this表示当前new出来的vm实例对象
                    //通过this可以访问到data中的数据
                    this.count += 1
                }
            },
        })
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h3>count 的值为:{{count}}</h3>
        <!--语法格式为v-on:事件名称="事件处理函数的名称"-->
        <button v-on:click="addCount">+1</button>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                count: 0,
            },
            methods: {
                //v-on 绑定的事件处理函数,需要声明在methods节点中
                addCount() {
                    //事件处理函数的名字
                    //this表示当前new出来的vm实例对象
                    //通过this可以访问到data中的数据
                    this.count += 1
                }
            },
        })
    </script>
</body>

</html>
事件绑定的简写形式

由于 v-on 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 @ )。

<div id="app">
	<h3>count 的值为:{{count}}</h3>
	<!--语法格式为v-on:事件名称="事件处理函数的名称"-->
	<button @click="count+=1">+1</button>
</div>
事件对象 event

在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件对象 event。同理,在 v-on 指令(简 写为 @ )所绑定的事件处理函数中,同样可以接收到事件对象 event,示例代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h3>count 的值为:{{count}}</h3>
        <!--语法格式为v-on:事件名称="事件处理函数的名称"-->
        <button @click="addCount">+1</button>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                count: 0,
            },
            methods: {
                //v-on 绑定的事件处理函数,需要声明在methods节点中
                addCount(e) {
                    const nowBgColor = e.target.style.backgroundColor
                    e.target.style.backgroundColor = nowBgColor === 'red' ? '' : 'red'
                    this.count += 1
                }
            },
        })
    </script>
</body>

</html>
绑定事件并传参

在使用 v-on 指令绑定事件时,可以使用 ( ) 进行传参,示例代码如下:

<div id="app">
	<h3>count 的值为:{{count}}</h3>
	<!--语法格式为v-on:事件名称="事件处理函数的名称"-->
	<button @click="addCount(2)">+2</button>
</div>


methods: {
                //在形参处用step接收传递过来的参数值
                addCount(step) {
                    this.count += step
                }
            },
$event

e v e n t 是 v u e 提供的特殊变量,用来表示原生的事件参数对象 e v e n t 。 event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event。 eventvue提供的特殊变量,用来表示原生的事件参数对象eventevent 可以解决事件参数对象 event 被覆盖的问题。示例用法如下:

<h3>count 的值为:{{count}}</h3>
<!--语法格式为v-on:事件名称="事件处理函数的名称"-->
<button @click="addCount(2,$event)">+2</button>

methods: {
                //v-on 绑定的事件处理函数,需要声明在methods节点中
                addCount(step, e) {
                    const nowBgColor = e.target.style.backgroundColor
                    e.target.style.backgroundColor = nowBgColor === 'red' ? '' : 'red'
                    this.count += step
                }
            },
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h3>count 的值为:{{count}}</h3>
        <!--语法格式为v-on:事件名称="事件处理函数的名称"-->
        <button @click="addCount(2,$event)">+2</button>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                count: 0,
            },
            methods: {
                //v-on 绑定的事件处理函数,需要声明在methods节点中
                addCount(step, e) {
                    const nowBgColor = e.target.style.backgroundColor
                    e.target.style.backgroundColor = nowBgColor === 'red' ? '' : 'red'
                    this.count += step
                }
            },
        })
    </script>
</body>

</html>
事件修饰符

在事件处理函数中调用 preventDefault() 或 stopPropagation() 是非常常见的需求。因此,vue 提供了事件 修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的 5 个事件修饰符如下:

事件修饰符说明
.prevent阻止默认行为(例如:阻止 a 连接的跳转、阻止表单的提交等)
.stop阻止事件冒泡
.capture以捕获模式触发当前的事件处理函数
.once绑定的事件只触发1次
.self只有在 event.target 是当前元素自身时触发事件处理函数

语法格式如下:

<!--触发click点击事件,阻止a链接的默认行为-->
<a href="https://www.baidu.com" @click.prevent="onLinkClick">百度首页</a>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .inner {
            line-height: 100px;
            background-color: aliceblue;
            font-size: 13px;
            text-align: center;
        }
        
        .outer {
            background-color: bisque;
            padding: 50px;
            font-size: 13px;
        }
        
        .box {
            background-color: coral;
            padding: 50px;
        }
    </style>
</head>

<body>
    <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
    <div id="app">
        <h4>① .prevent 事件修饰符的应用场景</h4>
        <a href="https://www.baidu.com" @click.prevent="onLinkClick">百度首页</a>

        <hr />

        <h4>② .stop 事件修饰符的应用场景</h4>
        <div class="outer" @click="onOuterClick">
            外层的 div
            <div class="inner" @click.stop="onInnerClick">内部的 div</div>
        </div>

        <hr />

        <h4>③ .capture 事件修饰符的应用场景</h4>
        <div class="outer" @click.capture="onOuterClick">
            外层的 div
            <div class="inner" @click="onInnerClick">内部的 div</div>
        </div>

        <hr />

        <h4>④ .once 事件修饰符的应用场景</h4>
        <div class="inner" @click.once="onInnerClick">内部的 div</div>

        <hr />

        <h4>⑤ .self 事件修饰符的应用场景</h4>
        <div class="box" @click="onBoxClick">
            最外层的 box
            <div class="outer" @click.self="onOuterClick">
                中间的 div
                <div class="inner" @click="onInnerClick">内部的 div</div>
            </div>
        </div>

        <hr />
    </div>

    <script src="./lib/vue-2.6.12.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            // 声明处理函数的节点
            methods: {
                // 超链接的点击事件处理函数
                onLinkClick() {
                    alert('ok')
                },
                // 点击了外层的 div
                onOuterClick() {
                    console.log('触发了 outer 的 click 事件处理函数')
                },
                // 点击了内部的 div
                onInnerClick() {
                    console.log('触发了 inner 的 click 事件处理函数')
                },
                onBoxClick() {
                    console.log('触发了 box 的 click 事件处理函数')
                }
            },
        })
    </script>
</body>

</html>
按键修饰符

在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符,例如:

<!--只有在‘key”是“Enter’时调用“vm. submit()'' -->
<input @keyup.enter="submit">
<!--只有在'key'是'ESc'时调用'vm.clearInput()' -->
<input @keyup.esc="clearInput">
1.4 双向绑定指令

vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据。

<!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">
        <p>用户名是:{{username}}</p>
        <input type="text" v-model="username" />

        <hr />

        <p>选中的省份是:{{province}}</p>
        <select v-model="province">
        <option value="">请选择</option>
        <option value="1">北京</option>
        <option value="2">河北</option>
        <option value="3">黑龙江</option>
      </select>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                // 姓名
                username: 'zs',
                // 省份
                province: '1',
            },
        })
    </script>
</body>

</html>

注意:v-model 指令只能配合表单元素一起使用!

v-model 指令的修饰符

为了方便对用户输入的内容进行处理,vue 为 v-model 指令提供了 3 个修饰符,分别是:

修饰符作用示例
.number自动将用户的输入值转为数值类型
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id='app'>
        姓名:<input type="text" v-model.trim="username">

        </hr>

        年龄:<input type="text" v-model.number="age">

        </hr>

        地址:<input type="text" v-model.lazt="address">
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                address: '北京市',
                age: '12',
                username: "zs"
            },

        })
    </script>
</body>

</html>
1.5 条件渲染指令

条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:

  • v-if
  • v-show
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <button @click="flag =!flag">Toggle Flag</button>

        <p v-if="flag">请求成功---被v-if控制</p>
        <p v-show="flag">请求成功---被v-show控制</p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                // flag用来控制元素的显示与隐藏
                // 值为 true 时显示元素
                // 值为false时隐藏元素
                flag: true,
            }
        })
    </script>
</body>

</html>
v-if 和 v-show 的区别

实现原理不同:

  • v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
  • v-show 指令会动态为元素添加或移除 style=“display: none;” 样式,从而控制元素的显示与隐藏;

性能消耗不同:

v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。

  • 如果需要非常频繁地切换,则使用 v-show 较好
  • 如果在运行时条件很少改变,则使用 v-if 较好
v-else

v-if 可以单独使用,或配合 v-else 指令一起使用:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p v-if="num>0.5">随机数 > 0.5</p>
        <p v-else>随机数 ≤ 0 .5</p>

        <hr>

        <p>优秀</p>
        <p>良好</p>
        <p>一般</p>
        <p></p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                // 生成1以内的随机数
                num: Math.random(),
                // 类型
                type: 'A'
            },
        })
    </script>
</body>

</html>
v-else-if

v-else-if 指令,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:

<p v-if="type === 'A'">优秀</p>
<p v-else-if="type === 'B'">良好</p>
<p v-else-if="type === 'C'">一般</p>
<p v-else>差</p>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p v-if="num>0.5">随机数 > 0.5</p>
        <p v-else>随机数 ≤ 0 .5</p>

        <hr>

        <p v-if="type === 'A'">优秀</p>
        <p v-else-if="type === 'B'">良好</p>
        <p v-else-if="type === 'C'">一般</p>
        <p v-else></p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                // 生成1以内的随机数
                num: Math.random(),
                // 类型
                type: 'A'
            },
        })
    </script>
</body>

</html>
1.6 列表渲染指令

vue 提供了 v-for 指令,用来辅助开发者基于一个数组来循环渲染相似的 UI 结构。

v-for 指令需要使用 item in items 的特殊语法,其中:

items 是待循环的数组

item 是当前的循环项

data{
	list:[
		{id:1,name:'zs'},
		{id:2,name:'ls'}
	]
}

//------------------------

<ul>
	<li v-for='item in list'>姓名是:{{item.name}}</li>
</ul>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <ul>
            <li v-for="user in list">姓名是:{{user.name}}</li>
        </ul>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                list: [{
                    id: 1,
                    name: 'zs'
                }, {
                    id: 2,
                    name: 'ls'
                }]
            }
        })
    </script>
</body>

</html>
v-for 中的索引

v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items,示例代码如下:

data: {
    list: [{
        id: 1,
        name: 'zs'
    }, {
        id: 2,
        name: 'ls'
    }]
}

//------------------------

<ul>
	<li v-for='(item , index)in list'>索引是:{{index}},姓名是:{{item.name}}</li>
</ul>

注意:v-for 指令中的 item 项和 index 索引都是形参,可以根据需要进行重命名。例如 (user, i) in userlist

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <ul>
            <li v-for="(user,i) in list">索引是:{{i+1}},姓名是:{{user.name}}</li>
        </ul>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                list: [{
                    id: 1,
                    name: 'zs'
                }, {
                    id: 2,
                    name: 'ls'
                }]
            }
        })
    </script>
</body>

</html>
使用 key 维护列表的状态

当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种 默认的性能优化策略,会导致有状态的列表无法被正确更新。

为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲 染的性能。此时,需要为每项提供一个唯一的 key 属性:

<!-- 用户列表区域 -->
<ul>
	<!-- 加key属性的好吃: -->
	<!-- 1.正确维护列表的状去-->
	<!-- 2.复用现有的DOM元素,提升渲染的性能 -->
    <li v-for="(user, index) in userlist" :key="user id">
    <input type="checkbox" /> 
    姓名:{{user.name}}
    </li>
</ul>
key 的注意事项

① key 的值只能是字符串或数字类型

② key 的值必须具有唯一性(即:key 的值不能重复)

③ 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性) ④ 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)

⑤ 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)

2. 过滤器

过滤器(Filters)常用于文本的格式化。例如:

hello -> Hello

过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用,示例代码如下:

<!--在双花括号中通过""管道符""调用capitalize过滤器,对message 的值进行格式化-->
<P>{{ message | capitalize }}</P>
<!--在v-bind中通过"管道符"调用formatId 过滤器,对rawId 的值进行格式化-->
<div v-bind:id="rawId | formatId"></div>

过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定。

2.1 定义过滤器

在创建 vue 实例期间,可以在 filters 节点中定义过滤器,示例代码如下:

const vm = new Vue({
	el: "#app",
	data: {
		message: 'hello vuc.js ',
		info: 'title info'
},
	filters: {
		//在 filters节点下定义"过滤器"
		capitalize(str) {
			//把首字母转为大写的过滤器
		return str.charAt(0).toUpperCase() + str.slice(1)
		}
	}
})
过滤器

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式 和 v-bind 属性绑定。

过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用,示例代码如下:

2.2 私有过滤器和全局过滤器

在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p :title="info">{{message | capitalize}}</p>
    </div>

    <div id="app2">
        <p>{{abc|capitalize}}</p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                message: 'hello',
                info: 'title info',
            },
            filters: {
                capitalize(str) {
                    return str.charAt(0).toUpperCase() + str.slice(1)
                }
            }
        })
    </script>
    <script>
        const vm2 = new Vue({
            el: '#app2',
            data: {
                abc: 'abc',

            },

        })
    </script>
</body>

</html>

如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:

//全局过滤器–独立于每个vm实例之外
// Vue.filter()方法接收两个参数
//第1个参数,是全局过滤器的""名字”
//第2个参数,是全局过滤器的""处理函数”
Vue.filter( ' capitalize", (str)=> {
	return str.charAt(0).toUpperCase() + str.slice(1) + '~~'
})
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p :title="info">{{message | capitalize}}</p>
    </div>

    <div id="app2">
        <p>{{abc|capitalize}}</p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        Vue.filter('capitalize', (str) => {
            return str.charAt(0).toUpperCase() + str.slice(1) + '~~'
        })
    </script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                message: 'hello',
                info: 'title info',
            },
            filters: {
                capitalize(str) {
                    return str.charAt(0).toUpperCase() + str.slice(1)
                }
            }
        })
    </script>
    <script>
        const vm2 = new Vue({
            el: '#app2',
            data: {
                abc: 'abc',
            },

        })
    </script>
</body>

</html>
2.3 连续调用多个过滤器

过滤器可以串联地进行调用,例如:

<!--把message 的值。交给 filterA进行处理-->
<!--把 filtcrA 处理的结果,再交给filterB进行处理-->
<!--最终把 filterB处理的结果,作为最终的值渲染到页面上 --> 
{{message | filterA | filterB }}

2.3 连续调用多个过滤器

示例代码如下:

<!--串联调用多个过滤器-->
<p>{{text | capitalize | maxLength}}</p>
// 全局过滤器–首字母大写
Vue.filter( ' capitalize", (str) => {
	return str.charAt(O).touppercase() + str.slice(1) + '~-')	
	
//全局过滤器–控制文本的最大长度
Vue.filter( 'maxLength". (str)=>{
	if (str.length <= 10) return str
	return str.slice(o,10)+ '...'
})

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p :title="info | capitalize">{{message | capitalize | maxLength}}</p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        Vue.filter('capitalize', (str) => {
            return str.charAt(0).toUpperCase() + str.slice(1)
        })


        Vue.filter('maxLength', (str) => {
            if (str.length <= 10) return str
            return str.slice(0, 10) + '...'
        })
    </script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                message: 'hello  vue javascript',
                info: 'title info',
            },
        })
    </script>
</body>

</html>
2.4 过滤器传参

过滤器的本质是 JavaScript 函数,因此可以接收参数,格式如下:

<!-- arg1和arg2是传递给filterA 的参数-->
<p>{{message | filterAarg1, arg2)}}</p>

//过滤器处理函数的形参列表中:
//第一个参数。永远都是管道符前面待处理的值
//从第二个参数开始,才是调用过滤器时传递过来的arg1 和arg2参数Vue.filter( "filterA',(msg.arg1,arg2)=> {
	//过滤器的代码逻辑...
})

2.4 过滤器传参

示例代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p :title="info | capitalize">{{message | capitalize | maxLength(5)}}</p>
    </div>

    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        Vue.filter('capitalize', (str) => {
            return str.charAt(0).toUpperCase() + str.slice(1)
        })


        Vue.filter('maxLength', (str, len = 10) => {
            if (str.length <= len) return str
            return str.slice(0, len) + '...'
        })
    </script>

    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                message: 'hello  vue javascript',
                info: 'title info',
            },
        })
    </script>
</body>

</html>
2.5 过滤器的兼容性

过滤器仅在 vue 2.x 和 1.x 中受支持,在 vue 3.x 的版本中剔除了过滤器相关的功能。

在企业级项目开发中:

如果使用的是 2.x 版本的 vue,则依然可以使用过滤器相关的功能

如果项目已经升级到了 3.x 版本的 vue,官方建议使用计算属性或方法代替被剔除的过滤器功能

具体的迁移指南,请参考 vue 3.x 的官方文档给出的说明: https://v3.vuejs.org/guide/migration/filters.html#migration-strategy

品牌列表案例

1. 案例效果

2. 用到的知识点

bootstrap 4.x 相关的知识点:

卡片(Card)、表单相关(Forms)、按钮(Buttons)、表格(Tables)

vue 指令与过滤器相关的知识点: 插值表达式、属性绑定、事件绑定、双向数据绑定、修饰符、条件渲染、列表渲染、全局过滤器

3.整体实现步骤

① 创建基本的 vue 实例

② 基于 vue 渲染表格数据

③ 实现添加品牌的功能

④ 实现删除品牌的功能

⑤ 实现修改品牌状态的功能

3.1 创建基本的 vue 实例

步骤1:导入 vue 的 js 文件:

步骤2:在 标签中声明 el 区域:

步骤3:创建 vue 实例对象:

3.2 基于 vue 渲染表格数据

步骤1:使用 v-for 指令循环渲染表格的数据:

步骤2:将品牌的状态渲染为 Switch 开关效果:

Switch 开关效果的官方文档地址: https://v4.bootcss.com/docs/components/forms/#switches

步骤3:使用全局过滤器对时间进行格式化:

使用全局过滤器对时间进行格式化:

使用全局过滤器对时间进行格式化:

3.3 添加品牌

步骤1:阻止表单的默认提交行为:

步骤2:为 input 输入框进行 v-model 双向数据绑定: 注意:需要在 data 数据中声明 brandname 属性字段。

步骤3:为“添加品牌”的 button 按钮绑定 click 事件处理函数:

步骤4:在 data 中声明 nextId 属性(用来记录下一个可用的 id 值),并在 methods 中声明 addNewBrand 事件处理函数:

步骤5:监听 input 输入框的 keyup 事件,通过 .esc 按键修饰符快速清空文本框中的内容:

3.4 删除品牌

步骤1:为删除的 a 链接绑定 click 点击事件处理函数,并阻止其默认行为:

步骤2:在 methods 节点中声明 removeBrand 事件处理函数如下:

组件基础

单页面应用程序

1. 什么是单页面应用程序

单页面应用程序(英文名:Single Page Application)简称 SPA,顾 名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的 功能与交互都在这唯一的一个页面内完成。

2. 单页面应用程序的特点

单页面应用程序将所有的功能局限于一个 web 页面中,仅在该 web 页面初始化时加载相应的资源( HTML、 JavaScript 和 CSS)。

一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。而是利用 JavaScript 动态地变换 HTML 的内容,从而实现页面与用户的交互。

3. 单页面应用程序的优点

SPA 单页面应用程序最显著的 3 个优点如下:

① 良好的交互体验

  • 单页应用的内容的改变不需要重新加载整个页面
  • 获取数据也是通过 Ajax 异步获取
  • 没有页面之间的跳转,不会出现“白屏现象”

② 良好的前后端工作分离模式

  • 后端专注于提供 API 接口,更易实现 API 接口的复用
  • 前端专注于页面的渲染,更利于前端工程化的发展

③ 减轻服务器的压力

  • 服务器只提供数据,不负责页面的合成与逻辑的处理,吞吐能力会提高几倍

4. 单页面应用程序的缺点

任何一种技术都有自己的局限性,对于 SPA 单页面应用程序来说,主要的缺点有如下两个:

① 首屏加载慢

  • 路由懒加载

  • 代码压缩

  • CDN 加速

  • 网络传输压缩

② 不利于 SEO

  • SSR 服务器端渲染

5. 如何快速创建 vue 的 SPA 项目

vue 官方提供了两种快速创建工程化的 SPA 项目的方式:

① 基于 vite 创建 SPA 项目

② 基于 vue-cli 创建 SPA 项目

vitevue-cli
支持的 vue 版本仅支持 vue3.x支持 3.x 和 2.x
是否基于 webpack
运行速度较慢
功能完整度小而巧(逐渐完善)大而全
是否建议在企业级开发中使用目前不建议建议在企业级开发中使用

vite 的基本使用

1. 创建 vite 的项目

按照顺序执行如下的命令,即可基于 vite 创建 vue 3.x 的工程化项目:

npm install vite-app 项目名称

cd 项目名称
npm install
npm run dev

2. 梳理项目的结构

使用 vite 创建的项目结构如下: 其中:

在这里插入图片描述

  • node_modules 目录用来存放第三方依赖包

  • public 是公共的静态资源目录

  • src 是项目的源代码目录(程序员写的所有代码都要放在此目录下)

  • .gitignore 是 Git 的忽略文件

  • index.html 是 SPA 单页面应用程序中唯一的 HTML 页面

  • package.json 是项目的包管理配置文件

在 src 这个项目源代码目录之下,包含了如下的文件和文件夹:

在这里插入图片描述

其中:

  • assets 目录用来存放项目中所有的静态资源文件(css、fonts等)

  • components 目录用来存放项目中所有的自定义组件

  • App.vue 是项目的根组件

  • index.css 是项目的全局样式表文件

  • main.js 是整个项目的打包入口文件

3. vite 项目的运行流程

在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。

其中:

① App.vue 用来编写待渲染的模板结构

② index.html 中需要预留一个 el 区域

③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中

3.1 在 App.vue 中编写模板结构

清空 App.vue 的默认内容,并书写如下的模板结构:

<template>
  <h1>这是app.vue根组件</h1>
  <h3>abc</h3>
</template>
3.2 在 index.html 中预留 el 区域

打开 index.html 页面,确认预留了 el 区域:

<body>
    <!-- id 为app的div元素,就是将来vue要控制的区域 -->
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
</body>

3.3 在 main.js 中进行渲染

按照 vue 3.x 的标准用法,把 App.vue 中的模板内容渲染到 index.html 页面的 el 区域中:

// 1.从 vue中按需导入 createApp函数
import { createApp } from 'vue'
// 2.导入待渲染的App 组件
import App from './App.vue'

//3.调用createApp()函数,返回值是"单页面应用程序的实例",
// 用常量 spa_app进行接收,
const app = createApp(App)

//4.调用 mount方法,把App 组件的模板结构,渲染到指定的el区域中
app.mount('#app')

组件化开发思想

1. 什么是组件化开发

组件化开发指的是:根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。 例如:http://www.ibootstrap.cn/ 所展示的效果,就契合了组件化开发的思想。用户可以通过拖拽组件的 方式,快速生成一个页面的布局结构。

2. 组件化开发的好处

前端组件化开发的好处主要体现在以下两方面:

  • 提高了前端代码的复用性和灵活性

  • 提升了开发效率和后期的可维护性

3. vue 中的组件化开发

vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。之前接触到的 App.vue 文件本质 上就是一个 vue 的组件。

vue 组件的构成

1. vue 组件组成结构

每个 .vue 组件都由 3 部分构成,分别是:

  • template -> 组件的模板结构

  • script -> 组件的 JavaScript 行为

  • style -> 组件的样式

其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。

2. 组件的 template 节点

vue 规定:每个组件对应的模板结构,需要定义到 < template> 节点中。

<template>
  <!-- 当前组件的DOM结构,需要定义到template标签的内部-->
</template>

注意:< template> 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素。

2.1 在 template 中使用指令

在组件的 < template> 节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前组件的 DOM 结构。代码示例如下:

<template>
  <h1>这是 App 根组件</h1>
  <!--使用i该插值表达式-->
  <p>生成一个随机数字: {{(Math.random()*10).toFixed(2)}}</p>
  <!--使用v-bind 属性绑定-->
  <p :title="new Date().toLocaleTimeString()">vue.js</p>
  <!--属性v-on事件绑定-->
  <button eclick="showInfo">按钮</button>
  </template>

2.2 在 template 中定义根节点

在 vue 2.x 的版本中,< template> 节点内的 DOM 结构仅支持单个根节点:

<template>
  <div>
    <h1>这是app.vue根组件</h1>
    <h3>abc</h3>
  </div>
</template>

但是,在 vue 3.x 的版本中,< template> 中支持定义多个根节点:

<template>
  <h1>这是app.vue根组件</h1>
  <h3>abc</h3>
</template>


3. 组件的 script 节点

vue 规定:组件内的 < script> 节点是可选的,开发者可以在 < script> 节点中封装组件的 JavaScript 业务逻辑。< script > 节点的基本结构如下:

<script>
  // 今后,组件相关的data数据、methods方法等,
  // 都需要定义到export default所导出的对象中。
  export default {}
</script>
3.1 script 中的 name 节点

可以通过 name 节点为当前组件定义一个名称:

<script>
  // 今后,组件相关的data数据、methods方法等,
  // 都需要定义到export default所导出的对象中。
  export default {
    // name属性指向的是当前组件的名称《建议:每个单词的首字母大写>
    name:'MyApp'
  }
</script>

在使用 vue-devtools 进行项目调试的时候,自定义的组件名称可以清晰的区分每个组件

3.2 script 中的 data 节点

vue 组件渲染期间需要用到的数据,可以定义在 data 节点中:

<script>
  // 今后,组件相关的data数据、methods方法等,
  // 都需要定义到export default所导出的对象中。
  export default {
    // name属性指向的是当前组件的名称《建议:每个单词的首字母大写>
    name:'MyApp',
    data() {
      return {
        username:'zs',
      }
    }
  }
</script>
组件中的 data 必须是函数

vue 规定:组件中的 data 必须是一个函数,不能直接指向一个数据对象。因此在组件中定义 data 数据节点 时,下面的方式是错误的:

data:{
	//组件中,不能直接让data 指向一个数据对象(会报错)
	count:0
}
3.3 script 中的 methods 节点

组件中的事件处理函数,必须定义到 methods 节点中,示例代码如下

<template>
  <h1>这是app.vue根组件</h1>
  <h3>abc ---{{username}}</h3>
  <p>count值是--{{count}}</p>
  <button @click="addCount">+1</button>
</template>

<script>
  // 今后,组件相关的data数据、methods方法等,
  // 都需要定义到export default所导出的对象中。
  export default {
    // name属性指向的是当前组件的名称《建议:每个单词的首字母大写>
    name:'MyApp',
    data() {
      return {
        count:0,
      }
    },
    methods: {
        addCount() {
          this.count++
        },
    },
  }
</script>

4. 组件的 style 节点

vue 规定:组件内的 < style> 节点是可选的,开发者可以在 < style> 节点中编写样式美化当前组件的 UI 结构。< script > 节点的基本结构如下:

<style lang="css">
  h1{
    color:red
  }
</style>

其中 < style> 标签上的 lang=“css” 属性是可选的,它表示所使用的样式语言。默认只支持普通的 css 语法,可选值还有 less、scss 等。

4.1 让 style 中支持 less 语法

如果希望使用 less 语法编写组件的 style 样式,可以按照如下两个步骤进行配置:

① 运行 npm install less -D 命令安装依赖包,从而提供 less 语法的编译支持 ② 在 < style> 标签上添加 lang=“less” 属性,即可使用 less 语法编写组件的样式

<template>
  <h1>这是<i>App.vue</i>根组件</h1>
  <h3>abc ---{{username}}</h3>
  <p>count值是--{{count}}</p>
  <button @click="addCount">+1</button>
</template>

<style lang="less">
  h1{
    color:red;
    i {
      color: blue;
    }
  }
</style>

<script>
  // 今后,组件相关的data数据、methods方法等,
  // 都需要定义到export default所导出的对象中。
  export default {
    // name属性指向的是当前组件的名称《建议:每个单词的首字母大写>
    name:'MyApp',
    data() {
      return {
        count:0,
      }
    },
    methods: {
        addCount() {
          this.count++
        },
    },
  }
</script>

组件的基本使用

1. 组件的注册

组件之间可以进行相互的引用,例如:

在这里插入图片描述

vue 中组件的引用原则:先注册后使用。

1.1 注册组件的两种方式

vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:

  • 被全局注册的组件,可以在全局任何一个组件内使用
  • 被局部注册的组件,只能在当前注册的范围内使用
  • 在这里插入图片描述

在这里插入图片描述

1.2 全局注册组件
// 1.从 vue中按需导入 createApp函数
import { createApp } from 'vue'
// 2.导入待渲染的App 组件
import App from './App.vue'
//1.导入需要被全局注册的组件
import Swiper from './components/01-globalReg/Swiper.vue'
import Test from './components/01-globalReg/Test.vue'

//3.调用createApp()函数,返回值是"单页面应用程序的实例"
const app = createApp(App)

//2.调用app . component()方法全局注册组件
app.component('my-swiper', Swiper)
app.component('my-test', Test)

//4.调用 mount方法,把App 组件的模板结构,渲染到指定的el区域中
app.mount('#app')

1.3 使用全局注册组件

使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用即可,例如:

<template>
  <h1>这是<i>App.vue</i>根组件</h1>
  <h3>abc ---{{username}}</h3>
  <p>count值是--{{count}}</p>
  <button @click="addCount">+1</button>

  <my-swiper></my-swiper>
  <my-test></my-test>
</template>
1.4 局部注册组件
<template>
  <h1>这是<i>App.vue</i>根组件</h1>
  <h3>abc ---{{username}}</h3>
  <p>count值是--{{count}}</p>
  <button @click="addCount">+1</button>

  <my-swiper></my-swiper>
  <my-search></my-search>
</template>

<script>
  import Search from './components/02-privateReg/Search.vue'
  export default{
  components : {
    'my-search': Search
  }
}
</script>

1.5 全局注册和局部注册的区别

被全局注册的组件,可以在全局任何一个组件内使用

被局部注册的组件,只能在当前注册的范围内使用

应用场景:

如果某些组件在开发期间的使用频率很高,推荐进行全局注册;

如果某些组件只在特定的情况下会被用到,推荐进行局部注册。

1.6 组件注册时名称的大小写

在进行组件的注册时,定义组件注册名称的方式有两种:

① 使用 kebab-case 命名法(俗称短横线命名法,例如 my-swiper 和 my-search)

② 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper 和 MySearch)

短横线命名法的特点:

  • 必须严格按照短横线名称进行使用

帕斯卡命名法的特点:

  • 既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用

注意:在实际开发中,推荐使用帕斯卡命名法为组件注册名称,因为它的适用性更强。

1.7 通过 name 属性注册组件

在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称, 示例代码如下:

<template>
    <h3>Swiper轮播图</h3>
</template>

<script>
    export default {
        name:'MySwiper'
    }
</script>


import Swiper from './components/01-globalReg/Swiper.vue'
app.component(Swiper,name,Swiper)
//等同于app.component('my-swiper', Swiper)

2. 组件之间的样式冲突问题

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件 之间样式冲突的根本原因是:

① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的

② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

2.1 思考:如何解决组件样式冲突的问题

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:

<template>
    <div class="container" data-v-001>
        <h3 data-v-001>Swiper轮播图</h3>
    </div>
</template>

<script>
    export default {
        name:'MySwiper'
    }
</script>


<style>
    .container[data-v-001] {
        border:1px solid red;
    }
</style>
<template>
    <div data-v-002>
        <h1 data-v-002>这是App.vue根组件</h1>
        <p data-v-002> app中的p标签</p>
        <p data-v-002>app中的p标签</p>


        <hr data-v-002>

        <my-list data-v-002></my-list>
    </div>
</template>


<script>
    import MyList from './list.vue'

    export default {
        name:'MyApp',
        components: {
            MyList
        }
    }
</script>

<style lang="less">
    p[data-v-002] {
        color: aqua;
    }
</style>
<template>
    <div data-v-003>
        <h3 data-v-003>这是list.vue组件</h3>
        <p data-v-003>这是list.vue中的p标签</p>
        <p data-v-003>这是list.vue中的p标签</p>
    </div>
</template>

<script>
    
    export default {
        name:'MyList',
    }
</script>
2.2 style 节点的 scoped 属性

为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:

<template>
    <div >
        <h3>这是list.vue组件</h3>
        <p>这是list.vue中的p标签</p>
        <p>这是list.vue中的p标签</p>
    </div>
</template>

<script>
    
    export default {
        name:'MyList',
    }
</script>

<style lang="less" scoped></style>>
2.3 /deep/ 样式穿透

如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样 式对子组件生效,可以使用 /deep/ 深度选择器。

<style lang="less" scoped>
    p {
        color: aqua;
    }

    /deep/.title {
        color:aquamarine;
        // 加上 /deep/时,生成的选择器格式为[data-v-052242de] .title

    }

注意:/deep/是 vue2.x中实现样式穿透的方案。在vue3.x中推荐使用:deep()替代/deep/。

3. 组件的 props

为了提高组件的复用性,在封装 vue 组件时需要遵守如下的原则:

组件的 DOM 结构、Style 样式要尽量复用

组件中要展示的数据,尽量由组件的使用者提供

为了方便使用者为组件提供要展示的数据,vue 组件提供了 props 的概念。

3.1 什么是组件的 props

props 是组件的自定义属性,组件的使用者可以通过 props 把数据传递到子组件内部,供子组件内部进行使 用。代码示例如下:

<!--通过自定义props,把文章的标题和作者,传递到 my-article 组件中 -->
<my-article title="面朝大海,春暖花开 author="海子"></my-article>

props 的作用:父组件通过 props 向子组件传递要展示的数据。

props 的好处:提高了组件的复用性。

//App.vue
<template>
    <div>
        <h1>这是App.vue根组件</h1>
        <hr>
        <my-article title="面朝大海,春暖花开" author="海子"></my-article>
        
    </div>
</template>


<script>
    import MyArticle from './Article.vue'

    export default {
        name:'MyApp',
        components: {
            MyArticle
        }
    }
</script>
//----------Article.vue
<template>
    <div>
        <h1>标题:{{title}}</h1>
        <h3>作者:{{author}}</h3>
    </div>
</template>


<script>
    export default {
        name:'MyArticle',
        // 外界可以传递指定的数据到当前的组件中
        props:['title','author']
    }
</script>
3.2 在组件中声明 props

在封装 vue 组件时,可以把动态的数据项声明为 props 自定义属性。自定义属性可以在当前组件的模板结构 中被直接使用。示例代码如下:

<!-- my-article 组件的定义如下:-->
<tcmplate>
	<h3>标题:{{title}}</h3><h5>作者: {{author}}</h5>
</template>

<script>
	export default {
		props: ['title' ,"author"],
		//父组件传递给my-article组件的数据,必须在props节点中声明
		}
</script>

3.3 无法使用未声明的 props

如果父组件给子组件传递了未声明的 props 属性,则这些属性会被忽略,无法被子组件使用,示例代码如下:

<my-article title="致橡树”author="舒婷"></my-article>

<template>
	<h3>标题: {{title}}</h3>
	<h5>作者:{{author}}</h5>
</template>

export default {
	props: ['title'],// author属性没有声明,因此子组件中无法访问到 author 的值
}
</script>

3.4 动态绑定 props 的值

可以使用 v-bind 属性绑定的形式,为组件动态绑定 props 的值,示例代码如下:

<!--通过v-bind属性绑定,为title 动态赋予一个变量的值-->
<!--通过v-bind属性绑定,为author 动态赋予一个表达式的值-->
<my-article :title="info.title" :author='post by' + info. author"></my-article>

<template>
    <div>
        <h1>这是App.vue根组件</h1>
        <hr>
        <my-article :title="info.title" :author="'post by' +info.author"></my-article>
        
    </div>
</template>


<script>
    import MyArticle from './Article.vue'

    export default {
        name:'MyApp',
        data() {
            return {
                info:{
                    title:'abc',
                    author:'123',
                }
            }
        },
        components: {
            MyArticle
        }
    }
</script>
3.5 props 的大小写命名

组件中如果使用“camelCase (驼峰命名法)”声明了 props 属性的名称,则有两种方式为其绑定属性的值:

<template>
	<p>发布时问:iipubTime}}</p>
</template>
<script>
	export default {
		props: [ "pubTime '],
		//采用“驼峰命名法"为当前的组件声明了 pubTime属性
</ script>
<!--既可以直接使用“驼峰命名”的形式为组件绑定属性的值-->
<my-article pubTime="1989"></my-article>
<!--也可以使用其等价的“短横线分隔命名”的形式为组件绑定属性的值-->
<my-article pub-time=1989"></my-article>

4. Class 与 Style 绑定

在实际开发中经常会遇到动态操作元素样式的需求。因此,vue 允许开发者通过 v-bind 属性绑定指令,为元 素动态绑定 class 属性的值和行内的 style 样式。

4.1 动态绑定 HTML 的 class

可以通过三元表达式,动态的为元素绑定 class 的类名。示例代码如下:

<h3 class=""thin" :class="isItalic ? 'italic' : """">MyDeep组件</h3>
<button @click="isItalic=!isItalic">Toggle Italic</button>
data() {
	rcturn { isItalic: true }
.thin { //字体变细
	font-weight: 200;
}
.italic { //倾斜字体
	font-style: italic;
}

<template>
    <div>
        <h3 class="thin" :class="isItalic ? 'italic' : '' "> MyStyle组件</h3>
        <button @click="isItalic = !isItalic">Toggle Itali</button>
    </div>
</template>

<script>
    export default {
        name:'MyStyle',
        data() {
            return {
                isItalic:true,
            }
        }
    }
</script>

<style lang="less">
    // 字体粗细
    .thin {
        font-weight:200 ;
    }

    .italic {
        // 倾斜字体
        font-style: italic;
    }
</style>
4.2 以数组语法绑定 HTML 的 class

如果元素需要动态绑定多个 class 的类名,此时可以使用数组的语法格式:

<h3 class="thin" :class="[isItalic ? 'italic' : "",isDelete ? 'delete' : ""]">
	MyDcep 组件
</h3>

<button eclick="isItalic-!isItalic">Toggle Italic</button><button eclickm"isDelete-!isDelete">Toggle Delete</button>
data(){
	return{
		isItalic:true,
		isDelete:false,
	}
}

<template>
    <div>
        <!-- <h3 class="thin" :class="isItalic ? 'italic' : '' "> MyStyle组件</h3> -->
        <h3 class="thin" :class="[isItalic ? 'italic' : '',isDelete ? 'delete' : '']"> MyStyle组件</h3>
        <button @click="isItalic = !isItalic">Toggle Itali</button>
        <br>
        <button @click="isDelete = !isDelete">Toggle delete</button>
    </div>
</template>

<script>
    export default {
        name:'MyStyle',
        data() {
            return {
                isItalic:false,
                isDelete:false
            }
        }
    }
</script>

<style lang="less">
    // 字体粗细
    .thin {
        font-weight:200 ;
    }

    .italic {
        // 倾斜字体
        font-style: italic;
    }

    .delete {
        text-decoration: line-through;
    }
</style>
4.3 以对象语法绑定 HTML 的 class

使用数组语法动态绑定 class 会导致模板结构臃肿的问题。此时可以使用对象语法进行简化:

<h3 class="thin" :class="[isItalic ? 'italic' : '',isDelete ? 'delete' : '']"> MyStyle组件</h3>
        <button @click="isItalic = !isItalic">Toggle Itali</button>
        <br>
        <button @click="isDelete = !isDelete">Toggle delete</button>
        
data() {
    return {
        classObj: {
        	//对象中,属性名是 class类名,值是布尔值
        	italic:false,
            delete:false,
		}
    }
}
<template>
    <div>
        <!-- <h3 class="thin" :class="isItalic ? 'italic' : '' "> MyStyle组件</h3> -->
        <!-- <h3 class="thin" :class="[isItalic ? 'italic' : '',isDelete ? 'delete' : '']"> MyStyle组件</h3>
        <button @click="isItalic = !isItalic">Toggle Itali</button>
        <br>
        <button @click="isDelete = !isDelete">Toggle delete</button> -->

        <h3 class="thin" :class="classObj"> MyStyle组件</h3>
        <button @click="classObj.italic = !classObj.italic">Toggle Itali</button>
        <br>
        <button @click="classObj.delete = !classObj.delete">Toggle delete</button>
    </div>
</template>

<script>
    export default {
        name:'MyStyle',
        data() {
            return {
                isItalic:false,
                isDelete:false,
                classObj:{
        	        //对象中,属性名是 class类名,值是布尔值
        	        italic:false,
                    delete:false,
		        },
            }
        }
    }
</script>

<style lang="less">
    // 字体粗细
    .thin {
        font-weight:200 ;
    }

    .italic {
        // 倾斜字体
        font-style: italic;
    }

    .delete {
        text-decoration: line-through;
    }
</style>
4.4 以对象语法绑定内联的 style

:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼 峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

<div :style="{color: active,fontSize: fsize + 'px",'background-color ': bgcolor)">
vue组件
</div>
<button @click="fsizc += 1">字号 + 1</button>
<button @click="fsize -= 1">字号 - 1</button>



data(){
	return {
		active: 'red',
		fsize: 30,
		bgcolor: "pink" ,
		}
}

<template>
    <div>
        <!-- <h3 class="thin" :class="isItalic ? 'italic' : '' "> MyStyle组件</h3> -->
        <!-- <h3 class="thin" :class="[isItalic ? 'italic' : '',isDelete ? 'delete' : '']"> MyStyle组件</h3>
        <button @click="isItalic = !isItalic">Toggle Itali</button>
        <br>
        <button @click="isDelete = !isDelete">Toggle delete</button> -->

        <h3 class="thin" :class="classObj"> MyStyle组件</h3>
        <button @click="classObj.italic = !classObj.italic">Toggle Itali</button>
        <br>
        <button @click="classObj.delete = !classObj.delete">Toggle delete</button>

        <hr>
        <!-- font-szie要写成fontSzie -->
        <div :style="{color:active, fontSize:fsize+'px','background-color':bgcolor}">vue根组件</div>
        <button @click="fsize+=1">字号 + 1</button>
        <button @click="fsize-=1">字号 - 1</button>
    </div>
</template>

<script>
    export default {
        name:'MyStyle',
        data() {
            return {
                isItalic:false,
                isDelete:false,
                classObj:{
        	        //对象中,属性名是 class类名,值是布尔值
        	        italic:false,
                    delete:false,
		        },
                //高亮时的文本颜色
                active: 'red',
		        fsize: 30,
		        bgcolor: "pink" ,
            }
        }
    }
</script>

<style lang="less">
    // 字体粗细
    .thin {
        font-weight:200 ;
    }

    .italic {
        // 倾斜字体
        font-style: italic;
    }

    .delete {
        text-decoration: line-through;
    }
</style>

封装组件的案例

1. 案例效果 封装要求:

① 允许用户自定义 title 标题

② 允许用户自定义 bgcolor 背景色

③ 允许用户自定义 color 文本颜色

④ MyHeader 组件需要在页面顶部进行 fixed 固定定位,且 z-index 等于 999

使用示例如下:

2. 用到的知识点

组件的封装与注册

props

样式绑定

3. 整体实现步骤

创建 MyHeader 组件

渲染 MyHeader 组件的基本结构

在 App 组件中注册并使用 MyHeader 组件

通过 props 为组件传递数据

props 验证

1. 什么是 props 验证

指的是:在封装组件时对外界传递过来的 props 数据进行合法性的校验,从而防止数据不合法的问题。

使用数组类型的 props 节点的缺点:无法为每个 prop 指定具体的数据类型。

2. 对象类型的 props 节点

使用对象类型的 props 节点,可以对每个 prop 进行数据类型的校验,示意图如下:

//app.vue
<template>
    <div>
        <h1>App组件</h1>
    </div>
    <hr>

    <!-- <my-count count="abc" :state="3"></my-count> -->
    <my-count count=12 :state="true"></my-count>
</template>

<script>
    import MyCount from './count.vue'

    export default {
        name: 'MyApp',
        components: {
            MyCount
        }
    }
</script>
//count.vue
<template>
    <div>
        <p>数量:{{count}}</p>
        <p>状态:{{state}}</p>
    </div>
</template>

<script>
    export default {
        name:'MyCount',
        // props:['count','state']
        props: {
            count:Number,
            state:Boolean

        }
    }
</script>

<style lang="less" scoped></style>

3. props 验证

对象类型的 props 节点提供了多种数据验证方案,例如:

① 基础的类型检查

② 多个可能的类型

③ 必填项校验

④ 属性默认值

⑤ 自定义验证函数

3.1 基础的类型检查

可以直接为组件的 prop 属性指定基础的校验类型,从而防止组件的使用者为其绑定错误类型的数据:

export default {
	props: { //支持的8种基础关型
		propA: string,   //字符串类型
		propB: Number,   //数字类型
		propC: Boolean, //布尔值类型
		propD: Array,    //数组类型
		propE: Object,   //对象类型 
		propF: Date,     //日期类型
		propG: Function,//函数类型
		propH: Symbol	 //符号类型
	}
}
3.2 多个可能的类型

如果某个 prop 属性值的类型不唯一,此时可以通过数组的形式,为其指定多个可能的类型,示例代码如下:

export default {
	props: {
		/// propA属性的值可以是"字符串"或"数字"
		propA:[String,Number],
	},
}
3.3 必填项校验

如果组件的某个 prop 属性是必填项,必须让组件的使用者为其传递属性的值。此时,可以通过如下的方式将 其设置为必填项:

export default {
	props: {
		// 通过“配置对象"的形式,来定义propB属性的“验证规则”
		propB:{
			type:String,//当前属性的值必须是String字符串类型
			erquired:true //当前属性的值是必填项,如果使用者没指定 propB属性的值,则在终端进行警告提示
		},
	},
}
<template>
    <div>
        <p>数量:{{count}}</p>
        <p>状态:{{state}}</p>
    </div>
</template>

<script>
    export default {
        name:'MyCount',
        // props:['count','state']
        props: {
            count:{
                type:[String,Number],
                required:true
            },
            state:Boolean,
            info:[String,Number]

        }
    }
</script>

<style lang="less" scoped></style>
3.4 属性默认值

在封装组件时,可以为某个 prop 属性指定默认值。示例代码如下:

export default {
	props: {
		// 通过“配置对象"的形式,来定义propB属性的“验证规则”
		propC:{
			type:Number,
			default:100  //如果使用者没有指定propC的值,则propC属性的默认值为100
		},
	},
}
3.5 自定义验证函数

在封装组件时,可以为 prop 属性指定自定义的验证函数,从而对 prop 属性的值进行更加精确的控制:

export default {
	props: {
		// 通过“配置对象"的形式,来定义propB属性的“验证规则”
		propD:{
			//通过 validator函数,对propD属性的值进行校验,“属性的值"可以通过形参value进行接收
			validator(value){
				//propD属性的值,必须匹配下列字符串中的一个
				// validator函数的返回值为true表示验证通过,false表示验证失败
				return [ "success", "warning". "danger " ].indexOf(value) !== -1
			},
		},
	},
}

计算属性

1. 什么是计算属性

计算属性本质上就是一个 function 函数,它可以实时监听 data 中数据的变化,并 return 一个计算后的新值, 供组件渲染 DOM 时使用。

2. 如何声明计算属性

计算属性需要以 function 函数的形式声明到组件的 computed 选项中,示例代码如下:

<input type='text' v-model.nmuber='count'/>
<p>{{count}} 乘以2的值为:{{plus}}</p>

data() {
	return {count:1}	
},

computed: {
	plus() {
		//计算属性。监听 data中 count值的变化,自动计算出count * 2 之后的新值
		return this.count * 2
	},
}

注意:计算属性侧重于得到一个计算的结果,因此计算属性中必须有 return 返回值!

<template>
    <div>
        <input type="text" v-model.number="count">
        <p>{{count}} 乘以2的值为:{{plus}}</p>
    </div>
</template>

<script>
    export default {
        name: 'MyCount',
        data() {
            return {
                count:1,
            }
        },
        computed:{
            plus() {
                return this.count * 2
            }
        }
    }
</script>

3. 计算属性的使用注意点

① 计算属性必须定义在 computed 节点中

② 计算属性必须是一个 function 函数

③ 计算属性必须有返回值

④ 计算属性必须当做普通属性使用

4. 计算属性 vs 方法

相对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算。因此 计算属性的性能更好:

5. 计算属性案例

案例需求,使用计算属性动态计算:

① 已勾选的商品总个数

② 已勾选的商品总价

③ 结算按钮的禁用状态

自定义事件

1. 什么是自定义事件

在封装组件时,为了让组件的使用者可以监听到组件内状态的变化,此时需要用到组件的自定义事件。

2. 自定义事件的 3 个使用步骤

在封装组件时:

① 声明自定义事件

② 触发自定义事件

在使用组件时:

③ 监听自定义事件

2.1 声明自定义事件

开发者为自定义组件封装的自定义事件,必须事先在 emits 节点中声明,示例代码如下:

<template>
    <h3>Counter组件</h3>
    <button> +1 </button>
</template>

<script>
export default {
	//my-counter组件的自定义事件,必须事先声明到 emits节点中
	emits:['change']
}
</script>
2.2 触发自定义事件

在 emits 节点下声明的自定义事件,可以通过 this.$emit(‘自定义事件的名称’) 方法进行触发,示例代码如下:

<template>
    <h3>Counter组件</h3>
    <button @click="onBtnClick"> +1 </button>
</template>

<script>
export default {
	//my-counter组件的自定义事件,必须事先声明到 emits节点中
	emits:['change'],
	methods: {
		onBtnClick() {
			this.$emit('change')
			//当点击+1按钮时,调用this.$cmitO)方法,触发自定义的changc事件
		}
	}
}
</script>
2.3 监听自定义事件

在使用自定义的组件时,可以通过 v-on 的形式监听自定义事件。示例代码如下:

<my-counter @change='getCount'>

methods: {
	getCount() {
		console.log('监听到了count值的变化')
	}
}
//App.vue
<template>
  <div>
    <h1>app根组件</h1>
  </div>
  <hr>
  <my-counter @count-change="getCount"></my-counter>
</template>

<script>
import MyCounter from './Counter.vue'


export default {
    name:'MyApp',
    methods:{
        getCount() {
            console.log('触发了countChange自定义事件');
        }
    },
    components:{
        MyCounter,
    }
}
</script>

<style>

</style>
//Counter.vue
<template>
  <div>Count的值是:{{count}}</div>
  <button @click="add">  +1</button>
</template>

<script>
export default {
    name:'MyCounter',
    //声明自定义事件
    emits:['countChange'],
    data() {
        return {
            count:0,
        }
    },
    methods:{
        add() {
            this.count++
            // this .$emito触发自定义事件
            this.$emit('countChange')
        }
    }
}
</script>

<style>

</style>

3. 自定义事件传参

在调用 this.$emit() 方法触发自定义事件时,可以通过第 2 个参数为自定义事件传参,示例代码如下:

<template>
    <h3>Counter组件</h3>
    <button @click="onBtnClick"> +1 </button>
</template>

<script>
export default {
	//my-counter组件的自定义事件,必须事先声明到 emits节点中
	emits:['change'],
	methods: {
		onBtnClick() {
			this.$emit('change',this.count)
			//触发白定义事件时,通过第二个参数传参
		},
	},
}
</script>
<template>
  <div>
    <h1>app根组件</h1>
  </div>
  <hr>
  <my-counter @count-change="getCount"></my-counter>
</template>

<script>
import MyCounter from './Counter.vue'


export default {
    name:'MyApp',
    methods:{
        getCount(val) {
            console.log('触发了countChange自定义事件',val);
        }
    },
    components:{
        MyCounter,
    }
}
</script>

<style>

</style>
<template>
  <div>Count的值是:{{count}}</div>
  <button @click="add">  +1</button>
</template>

<script>
export default {
    name:'MyCounter',
    //声明自定义事件
    emits:['countChange'],
    data() {
        return {
            count:0,
        }
    },
    methods:{
        add() {
            this.count++
            // this .$emito触发自定义事件
            this.$emit('countChange',this.count)
        }
    }
}
</script>

<style>

</style>

组件上的 v-model

1. 为什么需要在组件上使用 v-model

v-model 是双向数据绑定指令,当需要维护组件内外数据的同步时,可以在组件上使用 v-model 指令。示意 图如下:
在这里插入图片描述

  • 外界数据的变化会自动同步到 counter 组件中
  • counter 组件中数据的变化,也会自动同步到外界

2. 在组件上使用 v-model 的步骤

① 父组件通过 v-bind: 属性绑定的形式,把数据传递给子组件

② 子组件中,通过 props 接收父组件传递过来的数据

<template>
  <div>App根组件------{{count}}</div>
  <button @click="count += 1">+1</button>
  <hr>
  <my-counter :number="count"></my-counter>
</template>

<script>
import MyCounter from './Counter.vue'

export default {
    name:"MyApp",
    data() {
        return {
            count:0,
        }
    },
    components: {
        MyCounter
    }
}
</script>

<style>

</style>
<template>
  <div>
    <p>count值是:{{number}}</p>
  </div>
</template>

<script>
export default {
    name:'MyCounter',
    props:['number']
}
</script>

<style>

</style>

① 在 v-bind: 指令之前添加 v-model 指令

② 在子组件中声明 emits 自定义事件,格式为 update:xxx

③ 调用 $emit() 触发自定义事件,更新父组件中的数据

<template>
  <div>App根组件------{{count}}</div>
  <button @click="count += 1">+1</button>
  <hr>
  <my-counter v-model:number="count"></my-counter>
</template>

<script>
import MyCounter from './Counter.vue'

export default {
    name:"MyApp",
    data() {
        return {
            count:0,
        }
    },
    components: {
        MyCounter
    }
}
</script>

<style>

</style>
<template>
  <div>
    <p>count值是:{{number}}</p>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
    name:'MyCounter',
    props:['number'],
    emits:['update:number'],
    methods: {
        add() {
            this.$emit('update:number',this.number + 1)
        }
    }
}
</script>

<style>

</style>

任务列表案例

1. 案例效果

2. 用到的知识点

① vite 创建项目

② 组件的封装与注册

③ props

④ 样式绑定

⑤ 计算属性

⑥ 自定义事件

⑦ 组件上的 v-model

3. 整体实现步骤

① 使用 vite 初始化项目

② 梳理项目结构

③ 封装 todo-list 组件

④ 封装 todo-input 组件

⑤ 封装 todo-button 组件

watch 侦听器

1. 什么是 watch

侦听器 watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。例如,监视用户名的变化并发 起请求,判断用户名是否可用。

2. watch 侦听器的基本语法

开发者需要在 watch 节点下,定义自己的侦听器。实例代码如下:

export default {
	data() {
		return {username:''}
	},
    watch: {
    	//监听username的值的变化,
    	//形参列表中,第一个值是"变化后的新值",第二个值是"变化之前的旧值”
    	username(newVal,oldVal) {
    		console.log(newVal,oldVal)
    	},
	},
}
<template>
  <div>
    <h3>watch侦听器的用法</h3>
    <input type="text" class="form-control" v-model.trim="username">
  </div>
</template>

<script>
export default {
    name:'MyWatch',
    data() {
        return {
            username: ''
        }
    },
    watch: {
        username(newVal,oldVal) {
            console.log(newVal,oldVal)
        }
    }
}
</script>

<style>

</style>

3. 使用 watch 检测用户名是否可用

监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:

import axios from axios
export default {
    name:'MyWatch',
    data() {
        return {
            username: ''
        }
    },
    watch: {
        async username(newVal,oldVal) {
            console.log(newVal,oldVal)
            const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal)
            console.log(res)
        }
    }
}

4. immediate 选项

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使 用 immediate 选项。实例代码如下:

watch: {
	//1.监听username值的变化
	username:{
		//2.handle属性是固定写法,但username变化时,调用handle
        async handler(newVal,oldVal) {
            console.log(newVal,oldVal)
            const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal)
            console.log(res)
        },
        //3.表示组件加载完毕后立即调用一次当前的watch侦听器
        immediate:true
      },
    }
<template>
  <div>
    <h3>watch侦听器的用法</h3>
    <input type="text" class="form-control" v-model.trim="username">
  </div>
</template>

<script>
import axios from 'axios'

export default {
    name:'MyWatch',
    data() {
        return {
            username: 'admin'
        }
    },
    watch: {
        
        // async username(newVal,oldVal) {
        //     console.log(newVal,oldVal)
        //     const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal)
        //     console.log(res)
        // }
        username: {
            async handler(newVal,oldVal) {
                console.log(newVal,oldVal)
                const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal)
                console.log(res)
            },
            immediate:true
        }
    }
}
</script>

<style>

</style>

5. deep 选项

当 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项, 代码示例如下:

data() {
        return {
            username: 'admin',
            info:{
                username:'zs' 
                //info中包含username属性
            }
        }
    },
    
watch: {
        info: {
            async handler(newVal) {
                const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal.username)
                console.log(res)
            },
            deep:true  //霄要使用deep选项,否则username值的变化无法被监听到
        },
    }
<template>
  <div>
    <h3>watch侦听器的用法</h3>
    <input type="text" class="form-control" v-model.trim="info.username">
  </div>
</template>

<script>
import axios from 'axios'

export default {
    name:'MyWatch',
    data() {
        return {
            username: 'admin',
            info:{
                username:'zs'
            }
        }
    },
    watch: {
        
        // async username(newVal,oldVal) {
        //     console.log(newVal,oldVal)
        //     const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal)
        //     console.log(res)
        // }
        // username: {
        //     async handler(newVal,oldVal) {
        //         console.log(newVal,oldVal)
        //         const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal)
        //         console.log(res)
        //     },
        //     immediate:true
        // }
        info: {
            async handler(newVal) {
                const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal.username)
                console.log(res)
            },
            deep:true
        },
    }
}
</script>

<style>

</style>

6. 监听对象单个属性的变化

如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:

data() {
        return {
            username: 'admin',
            info:{
                username:'zs' 
                //info中包含username属性
            }
        }
    },
    
watch: {
        'info.username': {
            async handler(newVal) {
                const {data:res} = await axios('https://www.escook.cn/api/finduser/'+newVal.username)
                console.log(res)
            },
            deep:true  //霄要使用deep选项,否则username值的变化无法被监听到
        },
    }

7. 计算属性 vs 侦听器

计算属性和侦听器侧重的应用场景不同:

计算属性侧重于监听多个值的变化,最终计算并返回一个新值

侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值

组件的生命周期

1. 组件运行的过程

组件的生命周期指的是:组件从创建 -> 运行(渲染) -> 销毁的整个过程,强调的是一个时间段。

2. 如何监听组件的不同时刻

vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。例如:

① 当组件在内存中被创建完毕之后,会自动调用 created 函数

② 当组件被成功的渲染到页面上之后,会自动调用 mounted 函数

③ 当组件被销毁完毕之后,会自动调用 unmounted 函数

<template>
  <div>
    <h1>App根组件</h1>
    <hr>
    <button @click="flag = !flag">Toggle</button>
    <!-- 3.以标签形式使用组件 -->
    <life-cycle v-if="flag"></life-cycle>
  </div>
</template>

<script>
import LifeCycle from './life-cycle.vue'

export default {
    name:'MyApp',
    data() {
        return{
            flag:true
        }
    },
    // 2.components注册组件
    components:{
        LifeCycle,
    },
}
</script>

<style>

</style>
<template>
    <div>
        <h3>LifeCycle组件</h3>
    </div>
</template>

<script>
export default {
    name:'LifeCycle',
    // 组件在内存中创建完毕
    created() {
        console.log('created:组件在内存中创建完毕');
    },
    // 组件第一次被渲染到页面
    mounted() {
        console.log('mounted:组件第一次被渲染到页面');
    },
    // 组件被销毁完毕
    unmounted() {
        console.log('unmounted:组件被销毁完毕');
    }
}
</script>

<style lang="less" scoped>

</style>

3. 如何监听组件的更新

当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和 Model 数据源保持一致。

当组件被重新渲染完毕之后,会自动调用 updated 生命周期函数。

<template>
    <div>
        <h3>LifeCycle组件-------{{count}}</h3>

        <button @click="count += 1">+1</button>
    </div>
</template>

<script>
export default {
    name:'LifeCycle',
    data() {
        return {
            count:0,
        }
    },
    // 组件在内存中创建完毕
    created() {
        console.log('created:组件在内存中创建完毕');
    },
    // 组件第一次被渲染到页面
    mounted() {
        console.log('mounted:组件第一次被渲染到页面');
    },
    // 组件被重新渲染完毕了
    updated() {
        console.log('updated:组件被重新渲染完毕了');
    },
    // 组件被销毁完毕
    unmounted() {
        console.log('unmounted:组件被销毁完毕');
    }
}
</script>

<style lang="less" scoped>

</style>

4. 组件中主要的生命周期函数

注意:在实际开发中,

生命周期函数执行时机所属阶段执行次数应用场景
created组件在内存中创建完毕后创建阶段唯一一次发Ajax请求初始数据
mounted组件初次在页面渲染完毕后创建阶段唯一一次操作DOM元素
updated组件在页面中被重新渲染完毕后运行阶段0次或多次-
unmounted组件被销毁后(页面和内存)销毁阶段唯一一次-

created 是最常用的生命周期函数!

5. 组件中全部的生命周期函数

疑问:为什么不在 beforeCreate 中发 ajax 请求初始数据?

6. 完整的生命周期图示

可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程: https://www.vue3js.cn/docs/zh/guide/instance.html#生命周期图示

组件之间的数据共享

1. 组件之间的关系

在项目开发中,组件之间的关系分为如下 3 种:

① 父子关系

② 兄弟关系

③ 后代关系

2. 父子组件之间的数据共享

父子组件之间的数据共享又分为:

① 父 -> 子共享数据

② 子 -> 父共享数据

③ 父 <-> 子双向数据同步

2.1 父组件向子组件共享数据

父组件通过 v-bind 属性绑定向子组件共享数据。同时,子组件需要使用 props 接收数据。示例代码如下:

//父组件
<template>
  <div>
    <h1>App根组件---{{count}}</h1>
    <button @click="count += 1">+1</button>
    <hr>

    <my-son :num="count"></my-son>
  </div>
</template>

<script>
    import MySon from './son.vue'

export default {
    name:"MyApp",
    data() { 
        return {
            count: 0
        }
    },
    components: {
        MySon,
    },       
}
</script>

<style>

</style>
//子组件
<template>
  <div>
    <h3>Son子组件----{{num}}</h3>
  </div>
</template>

<script>
export default {
    name:'MySon',
    props:['num']
}
</script>

<style lang="less" scoped>

</style>
2.2 子组件向父组件共享数据

子组件通过自定义事件的方式向父组件共享数据。示例代码如下:

<template>
  <div>
    <h1>App根组件---{{count}}</h1>
    <button @click="count += 1">+1</button>
    <hr>

    <my-son :num="count" @numchange="getNum"></my-son>
  </div>
</template>

<script>
import MySon from './son.vue'

export default {
    name:"MyApp",
    data() { 
        return {
            count: 0
        }
    },
    methods:{
        getNum (num) {
            this.count = num
        }
    },
    components: {
        MySon,
    },       
}
</script>

<style>

</style>
<template>
  <div>
    <h3>Son子组件----{{num}}</h3>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
    name:'MySon',
    props:['num'],
    emits:['numchange'],
    methods: {
        add() {
            this.$emit('numchange',this.num+1)
        }
    }
 }
</script>

<style lang="less" scoped>

</style>
2.3 父子组件之间数据的双向同步

父组件在使用子组件期间,可以使用 v-model 指令维护组件内外数据的双向同步:

<template>
  <div>
    <h1>App根组件---{{count}}</h1>
    <button @click="count += 1">+1</button>
    <hr>

    <!-- <my-son :num="count" @numchange="getNum"></my-son> -->
    <my-son v-model:num="count"></my-son>
  </div>
</template>

<script>
import MySon from './son.vue'

export default {
    name:"MyApp",
    data() { 
        return {
            count: 0
        }
    },
    // methods:{
    //     getNum (num) {
    //         this.count = num
    //     }
    // },
    components: {
        MySon,
    },       
}
</script>

<style>

</style>
<template>
  <div>
    <h3>Son子组件----{{num}}</h3>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
    name:'MySon',
    props:['num'],
    // emits:['numchange'],
    emits:['update:num'],
    methods: {
        add() {
            // this.$emit('numchange',this.num+1)
            this.$emit('update:num',this.num+1)
        }
    }
 }
</script>

<style lang="less" scoped>

</style>

3. 兄弟组件之间的数据共享

兄弟组件之间实现数据共享的方案是 EventBus。可以借助于第三方的包 mitt 来创建 eventBus 对象,从而实 现兄弟组件之间的数据共享。示意图如下:

在这里插入图片描述

3.1 安装 mitt 依赖包

在项目中运行如下的命令,安装 mitt 依赖包:

npm install mitt@2.1.0 -S
3.2 创建公共的 EventBus 模块

在项目中创建公共的 eventBus 模块如下:

//eventBus
//导入mitt包
import mitt from 'mitt'
//创建EventBus的实例对象
const bus = mitt()
//将EventBus的实例对象共享出去
export default bus
3.3 在数据接收方自定义事件

在数据接收方,调用 bus.on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件。示例代码如下:

export default {
    nname:'MyRight',
    data() {
        return {
            num:0,
        }
    },
    created() {
    // 调用bus.on()方法注册一个自定义事件,通过事件处理函数的形参接收数据
        bus.on('countChange',count =>{
            this.num = count
        })
    }
}
3.4 在数据接发送方触发事件

在数据发送方,调用 bus.emit(‘事件名称’, 要发送的数据) 方法触发自定义事件。示例代码如下:

import bus from './eventBus.js'

export default {
    name:'MyLeft',
    data() {
        return {
            count:0,
        }
    },
    methods: {
        add() {
            this.count++
            bus.emit('countChange',this.count)
        }
        
    }

}
<template>
  <div>
    <h1>App根组件----{{count}}</h1>
    <!-- <button @click="count += 1">+1</button> -->


    <hr>

    <level-two></level-two>
  </div>
</template>

<script>
import LevelTwo from './level-two.vue'

export default {
    name:'MyApp',
    data() {
        return {
            color:'red',
            count: 1
        }
    },
    provide() {
        return {
            // 返回要共享的数据对象
            color:this.color,
            count:this.count
        }
    },
    components: {
        LevelTwo,
    }

}
</script>

<style lang="less" scoped>

</style>
<template>
    <div>
      <h3>Lever Two 二级组件</h3>
  
      <hr>
  
      <level-three></level-three>
    </div>
  </template>
  
  <script>
import LevelThree from './level-three.vue'

  export default {
      name:'LevelTwo',
      components: {
          LevelThree,
      }
  }
  </script>
  
  <style lang="less" scoped>
  
  </style>
<template>
  <div>
    <h5>Level Three 三级组件---{{color}}---{{count}}</h5>
  </div>
</template>

<script>
export default {
    name:'LevelThree',
    inject:['color','count']
}
</script>

<style lang="less" scoped>

</style>

4. 后代关系组件之间的数据共享

后代关系组件之间共享数据,指的是父节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂, 可以使用 provide(发送数据用) 和 inject (谁接收谁用)实现后代关系组件之间的数据共享。

4.1 父节点通过 provide 共享数据

父节点的组件可以通过 provide 方法,对其子孙组件共享数据:

export default {
	data() {
		return {
			color:'red'//1.定义"父组件"要向"子孙组件共享的数招
		}
	},
	provide() {
		//2.provide函数return的对象中,包含了"要向子孙组件共享的数据
		return {
			color: this.color,
		}
	}
}
4.2 子孙节点通过 inject 接收数据

子孙节点可以使用 inject 数组,接收父级节点向下共享的数据。示例代码如下:

<template>
  <div>
    <h3>子孙组件-----{{color}}</h3>
  </div>
</template>

<script>
	export default {
		//子孙组件,使用inject接收父节点向下共享的color数据,并在页面上使用
		inject:['color']
	}
</script>
4.3 父节点对外共享响应式的数据

父节点使用 provide 向下共享数据时,可以结合 computed 函数向下共享响应式的数据。示例代码如下:

//1.从 vue中按需导入computed函数
import {computed} from 'vue'

export default {
	data() {
        return {
            color:'red',
            count: 1
        }
    },
     provide() {
     	return {
     		color: computed(() => this.color),
     	}
     },
}
4.4 子孙节点使用响应式的数据

如果父级节点共享的是响应式的数据,则子孙节点必须以 .value 的形式进行使用。示例代码如下:

<template>
  <div>
    <h5>Level Three 三级组件---{{color}}---{{count.value}}</h5>
  </div>
</template>

<script>
export default {
    name:'LevelThree',
    inject:['color','count']
}
</script>

5. vuex

vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中,vuex 可以让组件之间的数据共享变得高 效、清晰、且易于维护。

在这里插入图片描述

5.1 vuex基本使用

安装vuex依赖包

npm install vuex --save

导入vuex包

import Vuex from 'vuex'
Vue.use(Vuex)

创建store对象

const store = new Vuex.Store({
	//state 中存放的就是全局共享的数据
	state:{count:0}
})

将store对象挂载到vue 实例中

new Vue ({
	el:'#app',
	render:h => h(app),
	router,
	//将创建的共享数据对象,挂载到vue实例中
	//所有的组件,就可以直接从store中获取全局的数据了
	store
})

6. 总结

父子关系

① 父 -> 子 属性绑定

② 子 -> 父 事件绑定

③ 父 <-> 子 组件上的 v-model

兄弟关系

④ EventBus

后代关系

⑤ provide & inject

全局数据共享

⑥ vuex

vue 3.x 中全局配置 axios

1. 为什么要全局配置 axios

在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:

① 每个组件中都需要导入 axios(代码臃肿)

② 每次发请求都需要填写完整的请求路径(不利于后期的维护)

在这里插入图片描述

2. 如何全局配置 axios

在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios,示例代码如下:

在这里插入图片描述

//main.js
// 1.从 vue中按需导入 createApp函数
import { createApp } from 'vue'
// 2.导入待渲染的App 组件
// import App from './App.vue'
// import App from './components/03-style/App.vue'
// import App from './components/04-props/App.vue'
// import App from './components/05-class&style/App.vue'
// import App from './components/10-watch/App.vue'
import App from './components/15-network/App.vue'
import axios from 'axios'

//1.导入需要被全局注册的组件
import Swiper from './components/01-globalReg/Swiper.vue'
import Test from './components/01-globalReg/Test.vue'

//3.调用createApp()函数,返回值是"单页面应用程序的实例"
const app = createApp(App)

//2.调用app . component()方法全局注册组件
app.component('my-swiper', Swiper)
    // app.component(Swiper,name,Swiper)
app.component('my-test', Test)

axios.defaults.baseURL = 'https://www.escook.cn'
app.config.globalProperties.$http = axios

//4.调用 mount方法,把App 组件的模板结构,渲染到指定的el区域中
app.mount('#app')
//App.vue
<template>
    <div>
      <h1>App根组件</h1>

      <hr>

      <div class="box">
        <get-info></get-info>
        <post-info></post-info>
      </div>
    </div>
  </template>


<script>
import GetInfo from './GetInfo.vue'
import PostInfo from './PostInfo.vue'

export default {
    name:"MyApp",
    // 注册组件
    components:{
        GetInfo,
        PostInfo
    }
}
</script>

<style lang="less" scoped></style>
//GetInfo.vue
<template>
  <div>
    <h3>Get Info 组件</h3>

    <hr>

    <button @click="getinfo">发起GET请求</button>
  </div>
</template>

<script>
export default {
    name:'GetInfo',
    methods: {
        async getinfo() {
            const {data:res} = await this.$http.get('/api/get',{
                params: {
                    name:'ls',
                    age:33
                }
            })
            console.log(res)
        }
    }
}
</script>

<style lang="less" scoped>

</style>
//PostInfo.vue
<template>
    <div>
      <h3>Post Info 组件</h3>
  
      <hr>
  
      <button @click="postinfo">发起POST请求</button>
    </div>
  </template>
  
  <script>
  export default {
      name:'PostInfo',
      methods: {
        async postinfo() {
            const {data:res} = await this.$http.post('/api/post',{name:'zs',age:20})
            console.log(res)
        }
      }
  }
  </script>
  
  <style lang="less" scoped>
  
  </style>

购物车案例

  1. 案例效果

  2. 实现步骤

① 初始化项目基本结构

② 封装 EsHeader 组件

③ 基于 axios 请求商品列表数据( GET 请求,地址为 https://www.escook.cn/api/cart )

④ 封装 EsFooter 组件

⑤ 封装 EsGoods 组件

⑥ 封装 EsCounter 组件

ref 引用

1. 什么是 ref 引用

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。

每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下, 组件的 $refs 指向一个空对象。

<template>
    <div>
      <h3>MyRef 组件</h3>
      <button @click="getRef">获取$refs引用</button>
    </div>
  </template>
 
<script>
  export default {
  	methods:{
  		getRef() {
  		//this代表当前组件的实例对象,this.$refs默认指向空对象
  			console.log(this)
  		}
  	}
  }
</script>

2. 使用 ref 引用 DOM 元素

如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:

<template>
    <div>
      <h3 ref='myh3'>MyRef 组件</h3>
      <button @click="getRef">获取$refs引用</button>
    </div>
  </template>
 
<script>
  export default {
  	methods:{
  		getRef() {
  		//this代表当前组件的实例对象,this.$refs默认指向空对象
  			console.log(this.$refs.myh3)
  		//操作DOM元素,把文本颜色改为红色
        this.$refs.myh3.style.color = 'red'
  		}
  	}
  }
</script>

3. 使用 ref 引用组件实例

如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:

<!--使用ref属性,为对应的"组件"添加引用名称-->
<my-counter ref="counterRef"></my-counter>
<button @click="getRef">获取$refs 引用</button>

methods: {
	getRef() {
		//通过this.$refs.引用的名称可以引用组件的实例			 		 console.log(this.$refs.counterRef)
		//引用到组件的实例之后,就可以调用组件上的 methods方法			this.$refs.counterRef.add()
	},
}

4. 控制文本框和按钮的按需切换

通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。示例代码如下:

<template>
   <input type='text' v-if='inputVisible'>
        <button v-eles @click="showinput">展示input输入框</button>
</template>
export default {
    data() {
        return {
        	//控制文本框和按钮的按需切换
            inputVisible:false,
        }
    },
    methods: {
        showinput() {
            thisinputVisible = true
        }
    }

}
</script>

5. 让文本框自动获得焦点

当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可。示例代码如下:

<input type='text' class="form-control" v-if='inputVisible' ref="ipt" >
      <!-- v-eles @click="showinput" -->
<button v-else @click="showinput">展示input输入框</button>


methods: {
  showinput() {
    this.inputVisible = true
    //获取文本框的DOM引用。并调用.focus(使其自动获得焦点
    this.$refs.ipt.focus()
  }
}

6. this.$nextTick(cb) 方法

组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的 DOM 异步地重新渲染完成后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。

<input type='text' class="form-control" v-if='inputVisible' ref="ipt" >
      <!-- v-eles @click="showinput" -->
<button v-else @click="showinput">展示input输入框</button>


methods: {
  showinput() {
    this.inputVisible = true
    //获取文本框的DOM引用。并调用.focus()使其自动获得焦点
    this.$nextTick(() => {
        this.$refs.ipt.focus()
    })
  }
}

动态组件

1. 什么是动态组件

动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的

< component> 组件,专门用来实现组件 的动态渲染。

① 是组件的占位符

② 通过 is 属性动态指定要渲染的组件名称

③ < component is=“要渲染的组件的名称”>< /component>

2. 如何实现动态组件渲染

示例代码如下:

data() {
	return {
		comName: "my-dynamic-1'
		// 1.当前要渲染的组件的名称)
	}
}


<template>
	<-- 3,点击按钮,动态切换组件的名称-->
	<button @click="comName='my-dynamic-1"">组件1</button>	<button @click="comName= 'my-dynamic-2"">组件2</button>	 <!--2。通过is属性,动态指定要渲染的组件的名称-->
	<component :is="comName ></component>
</template>

3. 使用 keep-alive 保持状态

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 < keep-alive> 组件保持动态组 件的状态。示例代码如下:

<keep-alive>
  <component :is = 'comName'></component>
</keep-alive>

插槽

1. 什么是插槽

插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的 部分定义为插槽。

在这里插入图片描述

可以把插槽认为是组件封装期间,为用户预留的内容的占位符。

2. 体验插槽的基础用法

在封装组件时,可以通过 slot元素定义插槽,从而为用户预留内容占位符。示例代码如下:

<template>
    <p>这是MyCom1组件的第1个p标签</p>
    <!--通过slot标签,为用户预留内容占位符(插槽) -->
    <slot></slot>
    <p>这是MyCom1组件的最后一个p标签</p>
  </template>
<my-com-1>
	<!--在使用MyCom1组件时,为插槽指定具体的内容-->
	<P>---用户自定义的内容---</p>
</my-com-1>
2.1 没有预留插槽的内容会被丢弃

如果在封装组件时没有预留任何 插槽,则用户提供的任何自定义内容都会被丢弃。示例代码如下:

<template>
    <p>这是MyCom1组件的第1个p标签</p>
    <!--封装组件时吗,没有预留任何插槽 -->
    <p>这是MyCom1组件的最后一个p标签</p>
  </template>
<my-com-1>
	<!--在使用MyCom1组件时,为插槽指定具体的内容-->
	<P>---用户自定义的内容---</p>
</my-com-1>
2.2 后备内容

封装组件时,可以为预留的 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何 内容,则后备内容会生效。示例代码如下:

<template>
    <p>这是MyCom1组件的第1个p标签</p>
    <!--通过slot标签,为用户预留内容占位符(插槽) -->
    <slot>这是后备内容</slot>
    <p>这是MyCom1组件的最后一个p标签</p>
  </template>

3. 具名插槽

如果在封装组件时需要预留多个插槽节点,则需要为每个 插槽指定具体的 name 名称。这种带有具体 名称的插槽叫做“具名插槽”。示例代码如下:

<div class="container">
	<header>
		<!--我们希望把页头放这里-->
		<slot name="header"></slot>
	</header>
	<main>
		<!--我们希望把主要内容放这里-->
		<slot></slot>
	</main>
	<footer>
		<!--我们希望把页脚放这里-->
		<slot name="footer"></slot>	
	</footer>
</div>

注意:没有指定 name 名称的插槽, 会有隐含的名称叫做 “default”。

3.1 为具名插槽提供内容

在向具名插槽提供内容的时候,我们可以在一个< template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。示例代码如下:

<my-com-2>
	<template v-slot:header>
		<h1>滕王阁序</h1>
	</template>
	
	<template v-slot : default>
		<p>豫章故郡,洪都新府。</p>
		<p>星分翼轸,地接衡庐。</p>
		<p>襟三江而带五湖,控蛮荆而引瓯越。</p>
	</template>
	
	<template v-slot:footer>
		<p>落款:王勃</p>
	</template>
</my-com-2>

3.2 具名插槽的简写形式

跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

4. 作用域插槽

在封装组件的过程中,可以为预留的 插槽绑定 props 数据,这种带有 props 数据的 叫做“作用 域插槽”。示例代码如下:

<div>
	<h3>这是TEST组件</h3>
	<slot :info="infomation"></slot>
</div>

<!--使用自定义组件-->
<my-test>
	<template v-slot:default="scope">
		{{ scope }}
	</template>
</my-test>

4.1 解构作用域插槽的 Prop

作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。示例代码如下:

<my-table>
	<!-- v-slot:可以简写成#-->
	<!-- 作用域插槽对外提供的数据对象,可以通过“解构赋值"简化接收的过程->	
	<template #default="{ user , info }">
		<!--【使用】作用域插槽的数据-->
		<td>i{ user.id }}</td>
		<td>{{ user.name }}</td>
		<td>{{ user.state }</td>
	</template>
</my-table>

4.2 声明作用域插槽

在封装 MyTable 组件的过程中,可以通过作用域插槽把表格每一行的数据传递给组件的使用者。示例代码 如下:

<!--表格主体区域-->
	<tbody>
	<!--循环渲染表格数据-->
	<tr v-for="item in list" :key="item.id">
		<!-- 下面的 slot是一个【作用域插槽】-->
		<slot :user="item"></slot>
	</tr>
</tbody>

4.3 使用作用域插槽

在使用 MyTable 组件时,自定义单元格的渲染方式,并接收作用域插槽对外提供的数据。示例代码如下:

自定义指令

1. 什么是自定义指令

vue 官方提供了 v-for、v-model、v-if 等常用的内置指令。除此之外 vue 还允许开发者自定义指令。

vue 中的自定义指令分为两类,分别是:

  • 私有自定义指令
  • 全局自定义指令

2. 声明私有自定义指令的语法

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。示例代码如下:

directives: {
	//自定义一个私有指令
	focus: {
		//当被绑定的元素插入到DOM中时,自动触发mounted函数		   			mounted(el) {
			el.focus() //让被绑定的元素自动获得焦点
		}
	}
}

3. 使用自定义指令

在使用自定义指令时,需要加上 v- 前缀。示例代码如下:

<!--声明自定义指令时,指令的名字是focus -->
<!--使用自定义指令是,需要加上v-指令前缀-->
<input v-focus />

4. 声明全局自定义指令的语法

全局共享的自定义指令需要通过“单页面应用程序的实例对象”进行声明,示例代码如下:

const app = vue.createApp({})

//注册一个全局自定义指令`v-focus '
app.directive( 'focus ', {
	//当被绑定的元素插入到DOM中时,自动触发 mounted函数
	mounted(el) {
		// Focus the element
		el.focus()
	}
})

5. updated 函数

mounted 函数只在元素第一次插入 DOM 时被调用,当 DOM 更新时 mounted 函数不会被触发。 updated 函数会在每次 DOM 更新完成后被调用。示例代码如下:

app.directive( 'focus ', {
	mounted(el) { 
	//第一次插入DOM时触发这个函数
	el.focus()
	},
	updated(el) {//每次DOM更新时都会触发updated函数
	el.focus(
	}
})

注意:在 vue2 的项目中使用自定义指令时,【 mounted -> bind 】【 updated -> update 】

6. 函数简写

如果 mounted 和updated 函数中的逻辑完全相同,则可以简写成如下格式:

app.directive( 'focus', (el) =>{
	//在mounted 和 updated时都会触发相同的业务处理
	el.focus()
})

7. 指令的参数值

在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值,示例代码如下:

<!--在使用v-color 指令时,可以通过“等号"绑定指令的值-->
<input type="text" v-model.number="count" v-focus v-color=" ' red'">
<p v-color=" ' cyan' ">{{count}}</p>

<button @click=" count++">+1</button>

//自定义v-color 指令

app.directive( 'color ' , (el, binding) =>{
	// binding.value就是通过"等号"为指令绑定的值
	el.style.color = binding.value
})

Table 案例

1. 案例效果

2. 用到的知识点

组件封装

具名插槽

作用域插槽

自定义指令

3. 实现步骤

① 搭建项目的基本结构

② 请求商品列表的数据

③ 封装 MyTable 组件

④实现删除功能

⑤ 实现添加标签的功能

路由

前端路由的概念与原理

1. 什么是路由

路由(英文:router)就是对应关系。路由分为两大类:

① 后端路由

② 前端路由

2. 回顾:后端路由

后端路由指的是:请求方式、请求地址与 function 处理函数之间的对应关系。在 node.js 课程中,express 路由的基本用法如下:

const express = require( 'express ")
const router = express.Router()

router.get( " /userlist ', function(req,res) {产路由的处理函数*/ })
router.post( " ladduser " , function(req,res) { /*路由的处理函数*/ })
module.exports = router

3. SPA 与前端路由

SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。 此时,不同组件之间的切换需要通过前端路由来实现。

结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!

4. 什么是前端路由

通俗易懂的概念:Hash 地址与组件之间的对应关系。

5. 前端路由的工作方式

① 用户点击了页面上的路由链接

② 导致了 URL 地址栏中的 Hash 值发生了变化

③ 前端路由监听了到 Hash 地址的变化

④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

在这里插入图片描述

6. 实现简易的前端路由

步骤1:导入并注册 MyHome、MyMovie、MyAbout 三个组件。示例代码如下:

import MyHome from './MyHome.vue'
import MyMovie from './MyMovie.vue'
import MyAbout from './MyAbout.vue'
export default {
	components: {
		MyHome,
		MyMovie,
		MyAbout,
	},
}

步骤2:通过 标签的 is 属性,动态切换要显示的组件。示例代码如下:

<template>
	<h1>App组件</h1>
	<component :is="comName"></component>
</template>

export default {
	data() {
		return {
		comName: 'my-home ', 
		//要展示的组件的名称}
		},
	}
}

步骤3:在组件的结构中声明如下 3 个 链接,通过点击不同的 链接,切换浏览器地址栏中的 Hash 值:

<a href="#/home">Home</a>&nbsp;
<a href="# /movie">Moviec</a>&nbsp;
<a href="#/about">About</a>

步骤4:在 created 生命周期函数中监听浏览器地址栏中 Hash 地址的变化,动态切换要展示的组件的名称:

created(){
	windaw.onhashchange = ()> {		
		switch (location.hash){
			case '#/home' : 
			//点击了“首页"的链接
				this.comName = 'my-home"
				break
			case '#/movie': 
			//点击了“电影""的链接
				this.comName = 'my-movie‘
				break
			case '#/about' : 
			//点击了“关于"的链接
				this.comName = 'my-about"
				break
		}
	}
}

vue-router 的基本使用

1. 什么是 vue-router

vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目 中组件的切换。

2. vue-router 的版本

vue-router 目前有 3.x 的版本和 4.x 的版本。其中:

vue-router 3.x 只能结合 vue2 进行使用

vue-router 4.x 只能结合 vue3 进行使用

vue-router 3.x 的官方文档地址:https://router.vuejs.org/zh/

vue-router 4.x 的官方文档地址:https://next.router.vuejs.org/

3. vue-router

4.x 的基本使用步骤

① 在项目中安装 vue-router

② 定义路由组件

③ 声明路由链接和占位符

④ 创建路由模块

⑤ 导入并挂载路由模块

3.1 在项目中安装 vue-router

在 vue3 的项目中,只能安装并使用 vue-router 4.x。安装的命令如下

npm install vue-router@next -s
3.2 定义路由组件

在项目中定义 MyHome.vue、MyMovie.vue、MyAbout.vue 三个组件,将来要使用 vue-router 来控制它们 的展示与切换

3.3 声明路由链接和占位符

可以使用 < router-link> 标签来声明路由链接,并使用< router-view> 标签来声明路由占位符。示例代码如下:

<template>
	<h1>App 组件</h1>
	<!--声明路由链接-->
	<router-link to="/home">首页</router-link>&nbsp:
	<router-link to="/movie">电影</router-link>&nbsp;
	<router-link to="/about">关于</router-link>
	<!--声明路由占位符-->
	<router-view></router-view>
</template>

3.4 创建路由模块

在项目中创建 router.js 路由模块,在其中按照如下 4 个步骤创建并得到路由的实例对象:

① 从 vue-router 中按需导入两个方法

② 导入需要使用路由控制的组件

③ 创建路由实例对象

④ 向外共享路由实例对象

⑤ 在 main.js 中导入并挂载路由模块

从 vue-router 中按需导入两个方法
//创建router.js
//1.从 vue-router中按需导入两个方法
//createRouter方法用于创建路由的实例对象
//createwebHashHistory用于指定路由的工作模式(hash模式)
import { createRouter, createWebHashHistory } from 'vue-router'
导入需要使用路由控制的组件
// 2.导入组件,这些组件将要以路由的方式,来控制它们的切换
import Home from './MyHome.vue'
import Movie from './MyMovie.vue'
import About from './MyAbout.vue'

创建路由实例对象
//3.创建路由实例对象
const router = createRouter({
    // 3.1通过history属性指定路由的工作模式
    history: createwebHashHistory(),
    // 3.2 通过routes数组,指定路由规则
    routes: [
        // path 是 hash 地址,component是要展示的组件
        { path: '/home', component: Home },
        { path: '/movie', component: Movie },
        { path: '/about', component: About },
    ],
})

向外共享路由实例对象
// 4.向外共享路由实例对象,
//供其它模块导入并使用
export default router

在 main.js 中导入并挂载路由模块
import { createApp } from "vue '
import App from './App. vue"import './index.css'
// 1.导入路由模块
import router from './router"

const app = creatcApp(App)

// 2.挂载路由模块
//app.use()方法用来挂载"第三方的插件模块”
app.use(router)
app.mount( ' #app ')

vue-router 的高级用法

1. 路由重定向

路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。 通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

const router = createRouter({
    // 3.1通过history属性指定路由的工作模式
    history: createWebHashHistory(),
    // 3.2 通过routes数组,指定路由规则
    routes: [
        // path 是 hash 地址,component是要展示的组件
        { path: '/', redirect: "/home" },
        { path: '/home', component: Home },
        { path: '/movie', component: Movie },
        { path: '/about', component: About },
    ],
})

2. 路由高亮

可以通过如下的两种方式,将激活的路由链接进行高亮显示:

① 使用默认的高亮 class 类

② 自定义路由高亮的 class 类

2.1 默认的高亮 class 类

被激活的路由链接,默认会应用一个叫做 router-link-active 的类名。开发者可以使用此类名选择器,为激活 的路由链接设置高亮的样式:

/*在index.css全局样式表中,重新router-link-active 的样*/
.routcr-link-active {
	background-color: red;
	color : white;
	font-weight: bold;
}

2.2 自定义路由高亮的 class 类

在创建路由的实例对象时,开发者可以基于 linkActiveClass 属性,自定义路由链接被激活时所应用的类名:

const router = createRouter({
	history: createwebHashHistory(),
	//指定被激活的路由链接,会应用router-active 这个类名,
	//默认的 router-link-active类名会被覆盖掉
	linkActiveClass : 'router-active',
	routes: [
		{ path: ' / ' , redirect: "/home’ }.
		{ path: '/home ',component: Home },
		{path: ' /movie ', component: Movie },
		{ path: ' labout ', component: About },
	],
})

3. 嵌套路由

通过路由实现组件的嵌套展示,叫做嵌套路由。

① 声明子路由链接和子路由占位符

② 在父路由规则中,通过 children 属性嵌套声明子路由规则

3.1 声明子路由链接和子路由占位符

在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。示例代码如下:

<template>
    <div>
        <h3>MyAbout组件</h3>
        <hr>
        <!-- 说明子路由链接 -->
        <router-link to="'/about/tab1"></router-link>
        <router-link to="'/about/tab2"></router-link>

        <!-- 在关于页面中,声明两个子路由链接 -->
        <router-view></router-view>
    </div>
</template>
3.2 通过 children 属性声明子路由规则

在 router.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则。示例代码如下:

router:{
        path:'/about',
        component:about,
        children:[
            {
                path:'tab1',component:Tab1
            },
            {
                path:'tab2',component:Tab2
            }
        ]
    }

4. 动态路由匹配

思考:有如下 3 个路由链接:

<router-link to="/movie/1">电影1</router-link>
<router-link to="/movie/2">电影2</router-link>
<router-link to="/movie/3">电影3</router-link>

定义如下 3 个路由规则,是否可行???可行,但复用性差

{path:'/movie/1", component: Movie }
{path:'/movie/2", component: Movie }
{path:"/movie/3", component: Movie }

4.1 动态路由的概念

动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。在 vue-router 中 使用英文的冒号(:)来定义路由的参数项。示例代码如下:

//路由中的动态参数以︰进行声明,冒号后面的是动态参数的名称
{path:'/movie/:id',component:Movie}

//字以下3个路由规则,合并成了一个,提高了路由规则的复用性
{path:'/movie/1',component:Movie}
{path:'/movie/2',component:Movie}
{path:'/movie/3',component:Movie}
4.2 $route.params 参数对象

通过动态路由匹配的方式渲染出来的组件中,可以使用 $route.params 对象访问到动态匹配的参数值。

<template>
    <h3>MyMovie组件 --- {{$route.params.id}}</h3>
</template>

<script>
export default {
    name:'MyMovie',
}
</script>
4.3 使用 props 接收路由参数

为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参。示例代码如下:

//1.在定义路由规则中,声明props:ture选项
// 即可在Movic组件中,以props 的形式接收到路由规则匹配到的参数项
{path:'/momvie/:id',component:Movie,props:ture}

<template>
<div>
    <h3>MyMovie组件 --- {{id}}</h3>
</div>
</template>

<script>
export default {
    props:['id'],//使用props接收路由规则中匹配到的参数项
}
</script>

5. 编程式导航

通过调用 API 实现导航的方式,叫做编程式导航。与之对应的,通过点击链接实现导航的方式,叫做声明式导 航。例如:

  • 普通网页中点击 链接、vue 项目中点击 都属于声明式导航
  • 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
5.1 vue-router 中的编程式导航 API

vue-router 提供了许多编程式导航的 API,其中最常用的两个 API 分别是: ① this.$router.push(‘hash 地址’)

  • 跳转到指定 Hash 地址,从而展示对应的组件

② this.$router.go(数值 n)

  • 实现导航历史的前进、后退
5.2 $router.push

调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。示例代码如下:

<template>
  <div>
    <h3>MyHome 组件</h3>
    <button type="button" class="btn btn-primary" @click="goToMovie(3)">导航到Movie页面</button>
  </div>
</template>

<script>
export default {
  name: 'MyHome',
  methods: {
    goToMovie(id) {
      this.$router.push('/movie/${id}')
    },
  },
}
</script>

<style lang="less" scoped></style>
5.2 $router.go

调用 this.$router.go() 方法,可以在浏览历史中进行前进和后退。示例代码如下:

<template>
  <div>
    <h3>MyHome 组件</h3>
    <button type="button" class="btn btn-primary" @click="goBack)">后退</button>
  </div>
</template>

<script>
export default {
  name: 'MyHome',
  props:["id"],
  methods: {
    goBack() {
      this.$router.go(-1)
    },
  },
}
</script>

<style lang="less" scoped></style>

6. 命名路由

通过 name 属性为路由规则定义名称的方式,叫做命名路由。示例代码如下:


注意:命名路由的 name 值不能重复,必须保证唯一性!

6.1 使用命名路由实现声明式导航

为 < router-link> 标签动态绑定 to 属性的值,并通过 name 属性指定要跳转到的路由规则。期间还可以用 params 属性指定跳转期间要携带的路由参数。示例代码 如下:


6.2 使用命名路由实现编程式导航

调用 push 函数期间指定一个配置对象,name 是要跳转到的路由规则、params 是携带的路由参数


7. 导航守卫

导航守卫可以控制路由的访问权限。示意图如下:

7.1 如何声明全局导航守卫

全局导航守卫会拦截每个路由规则,从而对每个路由进行访问权限的控制。可以按照如下的方式定义全局导航 守卫:


7.2 守卫方法的 3 个形参

全局导航守卫的守卫方法中接收 3 个形参,格式为:

const router = createRouter({...})

//全局前置守卫
router.beforeEach((to,from,next)=>{
	//to目标路由对象
	//from当前导航正要离开的路由对象
	//next上一个函数。表示放行
})

注意:

① 在守卫方法中如果不声明 next 形参,则默认允许用户访问每一个路由! ② 在守卫方法中如果声明了 next 形参,则必须调用 next() 函数,否则不允许用户访问任何一个路由!

7.3 next 函数的 3 种调用方式

参考示意图,分析 next 函数的 3 种调用方式最终导致的结果:

直接放行:next()

强制其停留在当前页面:next(false)

强制其跳转到登录页面:next(‘/login’)

7.4 结合 token 控制后台主页的访问权限


后台管理案例

1. 案例效果

2. 案例用到的知识点

  • 命名路由
  • 路由重定向
  • 导航守卫
  • 嵌套路由
  • 动态路由匹配
  • 编程式导航

vue-cli、组件库、axios、proxy

vue-cli

1. 什么是 vue-cli

vue-cli(俗称:vue 脚手架)是 vue 官方提供的、快速生成 vue 工程化项目的工具。

特点:

① 开箱即用

② 基于 webpack

③ 功能丰富且易于扩展

④ 支持创建 vue2 和 vue3 的项目

vue-cli 的中文官网首页:https://cli.vuejs.org/zh/

2. 安装 vue-cli

vue-cli 是基于 Node.js 开发出来的工具,因此需要使用 npm 将它安装为全局可用的工具:

#全局安装vue-cli
npm install -g @vue/cli

#查看vue-cli的版本,检验vue-cli是否安装成功
vue --version
2.1 解决 Windows PowerShell 不识别 vue 命令的问题

默认情况下,在PowerShell 中执行 vue --version 命令会提示如下的错误消息:

在这里插入图片描述

解决方案如下:

① 以管理员身份运行 PowerShell

② 执行 set-ExecutionPolicy RemoteSigned 命令

③ 输入字符 Y ,回车即可

3. 创建项目

vue-cli 提供了创建项目的两种方式:

#教育【命令行】的方式创建vue项目
vue create 项目名称

#OR

#基于【可视化模板】创建vue项目
vue ui

4. 基于 vue ui 创建 vue 项目

步骤1:在终端下运行 vue ui 命令,自动在浏览器中打开创建项目的可视化面板:

在这里插入图片描述

步骤2:在详情页面填写项目名称:

在这里插入图片描述

步骤3:在预设页面选择手动配置项目
在这里插入图片描述

步骤4:在功能页面勾选需要安装的功能(Choose Vue Version(没有了)、Babel、CSS 预处理器、使用配置文件)

在这里插入图片描述

步骤5:在配置页面勾选 vue 的版本和需要的预处理器

步骤6:将刚才所有的配置保存为预设(模板),方便下一次创建项目时直接复用之前的配置:

在这里插入图片描述

步骤7:创建项目并自动安装依赖包:

在这里插入图片描述

4. 基于 vue ui 创建 vue 项目

vue ui 的本质:通过可视化的面板采集到用户的配置信息后,在后台基于命令行的方式自动初始化项目:

在这里插入图片描述

在这里插入图片描述

项目创建完成后,自动进入项目仪表盘:

在这里插入图片描述

5 . 基于命令行创建 vue 项目

步骤1:在终端下运行 vue create demo2 命令,基于交互式的命令行创建 vue 的项目:

步骤2:选择要安装的功能

步骤3:使用上下箭头选择 vue 的版本,并使用回车键确认选择

步骤4:使用上下箭头选择要使用的 css 预处理器,并使用回车键确认选择:

步骤5:使用上下箭头选择如何存储插件的配置信息,并使用回车键确认选择:

步骤6:是否将刚才的配置保存为预设:

步骤7:选择如何安装项目中的依赖包:

步骤8:开始创建项目并自动安装依赖包

步骤9:项目创建完成:

6. 梳理 vue2 项目的基本结构

7. 分析 main.js 中的主要代码

8. 在 vue2 的项目中使用路由

在 vue2 的项目中,只能安装并使用 3.x 版本的 vue-router。

版本 3 和版本 4 的路由最主要的区别:创建路由模块的方式不同!

8.1 回顾:4.x 版本的路由如何创建路由模块

8.2 学习:3.x 版本的路由如何创建路由模块

步骤1:在 vue2 的项目中安装 3.x 版本的路由:


步骤2:在 src -> components 目录下,创建需要使用路由切换的组件:

步骤3:在 src 目录下创建 router -> index.js 路由模块: vue-cli

步骤4:在 main.js 中导入路由模块,并通过 router 属性进行挂载: vue-cli

步骤5:在 App.vue 根组件中,使用 声明路由的占位符

组件库

1. 什么是 vue 组件库

在实际开发中,前端开发者可以把自己封装的 .vue 组件整理、打包、并发布为 npm 的包,从而供其他人下载 和使用。这种可以直接下载并在项目中使用的现成组件,就叫做 vue 组件库。

2. vue 组件库和 bootstrap 的区别

二者之间存在本质的区别:

bootstrap 只提供了纯粹的原材料( css 样式、HTML 结构以及 JS 特效),需要由开发者做进一步的组装和改造

vue 组件库是遵循 vue 语法、高度定制的现成组件,开箱即用

3. 最常用的 vue 组件库

① PC 端

  • Element UI(https://element.eleme.cn/#/zh-CN)
  • View UI(http://v1.iviewui.com/)

② 移动端

Mint UI(http://mint-ui.github.io/#!/zh-cn)

Vant(https://vant-contrib.gitee.io/vant/#/zh-CN/)

4. Element UI

Element UI 是饿了么前端团队开源的一套 PC 端 vue 组件库。支持在 vue2 和 vue3 的项目中使用:

vue2 的项目使用旧版的 Element UI(https://element.eleme.cn/#/zh-CN)

vue3 的项目使用新版的 Element Plus(https://element-plus.gitee.io/#/zh-CN)

4.2 在 vue2 的项目中安装 element-ui

运行如下的终端命令:


引入 element-ui

开发者可以一次性完整引入所有的 element-ui 组件,或是根据需求,只按需引入用到的 element-ui 组件:

完整引入:操作简单,但是会额外引入一些用不到的组件,导致项目体积过大

按需引入:操作相对复杂一些,但是只会引入用到的组件,能起到优化项目体积的目的

4.3 完整引入

在 main.js 中写入以下内容:

4.4 按需引入

借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。 步骤1,安装 babel-plugin-component:

步骤2,修改根目录下的 babel.config.js 配置文件,新增 plugins 节点如下:

步骤3,如果你只希望引入部分组件,比如 Button,那么需要在 main.js 中写入以下内容:

4.5 把组件的导入和注册封装为独立的模块

在 src 目录下新建 element-ui/index.js 模块,并声明如下的代码:

axios 拦截器

1. 回顾:在 vue3 的项目中全局配置 axios


2. 在 vue2 的项目中全局配置 axios

需要在 main.js 入口文件中,通过 Vue 构造函数的 prototype 原型对象全局配置 axios:

3. 什么是拦截器

拦截器(英文:Interceptors)会在每次发起 ajax 请求和得到响应的时候自动被触发。

应用场景: ① Token 身份认证 ② Loading 效果 ③ etc…

4. 配置请求拦截器

通过 axios.interceptors.request.use(成功的回调, 失败的回调) 可以配置请求拦截器。示例代码如下:


注意:失败的回调函数可以被省略!

4.1 请求拦截器 – Token 认证
4.2 请求拦截器 – 展示 Loading 效果

借助于 element ui 提供的 Loading 效果组件(https://element.eleme.cn/#/zh-CN/component/loading) 可以方便的实现 Loading 效果的展示

5. 配置响应拦截器

通过 axios.interceptors.response.use(成功的回调, 失败的回调) 可以配置响应拦截器。示例代码如下:

5.1 响应拦截器 – 关闭 Loading 效果

调用 Loading 实例提供的 close() 方法即可关闭 Loading 效果,示例代码如下

proxy 跨域代理

1. 回顾:接口的跨域问题

vue 项目运行的地址:http://localhost:8080/ API 接口运行的地址:https://www.escook.cn/api/users 由于当前的 API 接口没有开启 CORS 跨域资源共享,因此默认情况下,上面的接口无法请求成功!

2. 通过代理解决接口的跨域问题

通过 vue-cli 创建的项目在遇到接口跨域问题时,可以通过代理的方式来解决:

① 把 axios 的请求根路径设置为 vue 项目的运行地址(接口请求不再跨域)

② vue 项目发现请求的接口不存在,把请求转交给 proxy 代理

③ 代理把请求根路径替换为 devServer.proxy 属性的值,发起真正的数据请求

④ 代理把请求到的数据,转发给 axios

3. 在项目中配置 proxy 代理

步骤1,在 main.js 入口文件中,把 axios 的请求根路径改造为当前 web 项目的根路径:

步骤2,在项目根目录下创建 vue.config.js 的配置文件,并声明如下的配置: 注意:

① devServer.proxy 提供的代理功能,仅在开发调试阶段生效

② 项目上线发布时,依旧需要 API 接口服务器开启 CORS 跨域资源共享

用户列表案例

1. 案例效果

2. 用到的知识点

vue-cli 创建 vue2 项目

element ui 组件库

axios 拦截器

proxy 跨域接口代理

vuer-router 路由

3. 整体实现步骤

① 初始化项目

② 渲染用户表格的数据

③ 基于全局过滤器处理时间格式

④ 实现添加用户的操作

⑤ 实现删除用户的操作

⑥ 通过路由跳转到详情页

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue3.js【未完成】 的相关文章

随机推荐

  • Ubuntu20.04——一篇文章让你从零配置VINS_Mono环境以及运行(2023年最新)

    注 xff1a 文末包含该文章涉及的所有安装包的网盘链接 零 换源 xff08 也可以先不换 xff0c 后面觉得下载慢再换也行 xff09 1 备份原来的源 sudo cp etc apt sources list etc apt sou
  • 学C语言推荐的书和软件—C Primer Plus和Dev C++

    写这个的目的是复习巩固C Primer Plus的知识 xff0c 我会一直更新这个系列 对于这本书 xff0c 入门C语言是完全够了 xff0c 后面的链表 队列和二叉树比较综合 xff0c 难度大一些 用这本书学C语言非常好 xff0c
  • mavlink python

    from pymavlink import mavutil Create the connection m 61 mavutil mavlink connection 39 udpin 0 0 0 0 14550 39 dir m mav
  • C++Vector浅析,Vector用法大全

    vector基本概念 功能 xff1a vector数据结构和数组非常相似 xff0c 也成为单端数组 vector与普通数组的区别 xff1a 不同之处在与数组是静态空间 xff0c 而vector可以动态扩展 动态扩展 xff1a 并不
  • 网络通信--Linux

    文章目录 网络通信的基础通信模型IP地址和端口port 网络套接字网络字节序初识UDP与TCP两种协议sockaddr结构体家族认识一些网络常用基础函数 UDP实现简单通信TCP实现简单通信总结 网络通信的基础 网络通信是建立在多层协议之下
  • Git分支和版本(标签)

    目录 一 Git分支 1 1 四大环境 xff08 分支 xff09 1 1 1 分支和标签的关系 1 2 分支的作用 1 3 演示分支 1 3 1 在Gitee中创建项目 1 3 2 克隆到本地 1 3 3 建立分支 1 3 4 切换分支
  • 前端必会算法——栈和队列

    上一篇 前端必会算法 标准快速排序 栈和队列 栈 xff08 Stack xff09 可以理解为是一个箱子 xff0c 存放东西的容器 栈结构的特点 xff1a 先入后出 xff0c 栈相当于一个箱子 xff0c 先放进去的东西被压在了下面
  • OpenMV的单颜色识别讲解

    OpenMV的官方教程 xff1a 寻找色块 xff1b single color rgb565 blob tracking示例讲解 xff1b 视频讲解 需要提前看的文章 xff1a 程序烧录 xff1b 颜色阈值设置 目录 thresh
  • STM32CubeMX串口通讯

    串口的简单介绍 RS 232与TTL 根据通讯使用的电平标准不同 xff0c 串口通讯可分为 TTL 标准及 RS 232 标准 而STM32的串口是TTL电平标准的 如果需要使用到RS 232则需要一个电平转换芯片 单工通信 半双工通信和
  • 自制超简易通讯协议(中断接收)

    前言 在做蓝牙语音小车时 xff0c 总遇到各式各样的问题 本文主题是笔者在解决串口不够并且数据量小的问题时突发奇想自定义一个简易的通讯协议 由于是用89c51做的主控 xff0c 外设的资源比较紧张 串口只有一个 xff0c 但两个系统需
  • 【Java杂谈】Iterator(迭代器)的使用

    x1f3b8 Iterator是什么 xff1f 迭代器是Java提供的一种访问集合的方法 xff0c Iterator 是 Java 迭代器最简单的实现 xff0c 常用来访问ArrayList HashMap等类的对象 Iterator
  • orb 纯背景物体识别

    include lt chrono gt include lt iostream gt include lt opencv2 core core hpp gt include lt opencv2 features2d features2d
  • Windows逆向安全(一)之基础知识(九)

    汇编比较三种循环 众所周知 xff0c 在C语言可以使用可以使用三种循环 xff0c 分别是 xff1a while do while和for 本文从汇编的角度出发 xff0c 观察这三种循环的差异 范例代码 先贴出三种循环的代码 xff0
  • c++读取yolov5模型进行目标检测(读取摄像头实时监测)

    文章介绍 本文是篇基于yolov5模型的一个工程 xff0c 主要是利用c 43 43 将yolov5模型进行调用并测试 xff0c 从而实现目标检测任务 任务过程中主要重点有两个 xff0c 第一 版本问题 xff0c 第二配置问题 一
  • ROS初学(二):ROS的三种基本通信机制(来自赵虚左老师)

    一 综述 ROS引入通信机制 xff0c 是为了实现ROS节点 xff08 进程 xff09 之间的通信 利用ROS进程的分布式框架 xff0c 可以使得每个进程独立的工作 xff0c 甚至分布于不同的主机工作 机器人上的各种传感器 xff
  • webpack 5.5.1 compiled with 1 error in 63 ms

    新建项目空白目录 xff0c 并运行 npm init y 命令 xff0c 初始化包管理配置文件 package json 新建 src 源代码目录 新建 src gt index html 首页和 src gt index js 脚本文
  • TypeError: Class constructor ServeCommand cannot be invoked without ‘new‘

    安装webpack插件 webpack dev server配置时将package json里面webpack改为webpack serve后 xff0c 运行npm run 脚本名 时出现 TypeError Class construc
  • Field ‘browser‘ doesn‘t contain a valid alias configuration

    意思是字段 39 browser 39 不包含有效的别名配置 意思就是说你打包的css路径不对 这个是我的代码 这里js里面导入的css路径不对 xff0c css文件夹不是和index js平级 xff0c 应该是上级所以正确的代码应该是
  • Failed to load resource: the server responded with a status of 404 (Not Found)

    问题场景 xff1a 编写路由模块 解决方法 xff1a 1 先检测路径是否有写错 2 将vscode中开的插件vetur关闭 3 上面方法不管用时 xff0c 将浏览器插件关闭 xff0c 例如油猴 有什么错误望大佬指出 xff01 xf
  • Vue3.js【未完成】

    Vue3 js 如何关闭烦人的vscode的提示框 https blog csdn net liuyuemozhu article details 101056556 ES6模块化与异步编程高级用法 ES6模块化 1 回顾 xff1a no