ES6 Webpack Vue
ES6模块化与异步编程高级用法
ES6模块化
node.js 中如何实现模块化
- node.js 遵循了 CommonJS 的模块化规范。其中:
- 导入其它模块使用 require() 方法
- 模块对外共享成员使用 module.exports 对象
- 模块化的好处:
- 大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
- node.js 遵循了 CommonJS 的模块化规范。其中:
前端模块化规范的分类
- 在 ES6 模块化规范诞生之前,JavaScript 社区已经尝试并提出了 AMD、CMD、CommonJS 等模块化规范。
- 但是,这些由社区提出的模块化标准,还是存在一定的差异性与局限性、并不是浏览器与服务器通用的模块化
- 标准,例如:
- AMD 和 CMD 适用于浏览器端的 Javascript 模块化
- CommonJS 适用于服务器端的 Javascript 模块化
- 太多的模块化规范给开发者增加了学习的难度与开发的成本。因此,大一统的 ES6 模块化规范诞生了!
什么是 ES6 模块化规范
- ES6 模块化规范是浏览器端与服务器端通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需再额外学习 AMD、CMD 或 CommonJS 等模块化规范。
- ES6 模块化规范中定义:
- 每个 js 文件都是一个独立的模块
- 导入其它模块成员使用 import 关键字
- 向外共享模块成员使用 export 关键字
在 node.js 中体验 ES6 模块化
- node.js 中默认仅支持 CommonJS 模块化规范,若想基于 node.js 体验与学习 ES6 的模块化语法,可以按照如下两个步骤进行配置:
- 确保安装了 v14.15.1 或更高版本的 node.js
- 在 package.json 的根节点中添加 "type": "module" 节点
- node.js 中默认仅支持 CommonJS 模块化规范,若想基于 node.js 体验与学习 ES6 的模块化语法,可以按照如下两个步骤进行配置:
ES6 模块化的基本语法
ES6 的模块化主要包含如下 3 种用法:
- ① 默认导出与默认导入
- ② 按需导出与按需导入
- ③ 直接导入并执行模块中的代码
默认导出
- 默认导出的语法: export default 默认导出的成员
- 注意事项:
- 每个模块中,只允许使用唯一的一次 export default,否则会报错!
js// 默认导出 let n1 = 10 let n2 = 20 function show(){} export default{ // 使用export default 默认导出语法,向外共享 n1 和 show 两个成员 n1, show }
默认导入
- 默认导入的语法: import 接收名称 from '模块标识符'
- 注意事项:
- 默认导入时的接收名称可以任意名称,只要是合法的成员名称即可
js// 默认导入 import m1 from "./demoSub.js"; console.log(m1)
按需导出
- 按需导出的语法: export 按需导出的成员
js// 按需导出 export let s1 = 50 export let s2 = 'ccc' export function say() { console.log('hi~~') }
按需导入
- 按需导入的语法: import { s1 } from '模块标识符'
js// 按需导入 import { s1, s2 as str2, say } from "./demoSub.js" // import info, { s1, s2 as str2, say } from "./demoSub.js" // 合并写默认导入和按需导入 console.log(s1) console.log(s2) say()
按需导出与按需导入的注意事项
- ① 每个模块中可以使用多次按需导出
- ② 按需导入的成员名称必须和按需导出的名称保持一致
- ③ 按需导入时,可以使用 as 关键字进行重命名
- ④ 按需导入可以和默认导入一起使用
直接导入并执行模块中的代码
- 如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码,示例代码如下:
js// 被导入模块代码 // 直接导入并执行模块中的代码 for (let i = 0; i < 3; i++) { console.log(i) } // ----------------分割线---------------- // 直接导入并执行模块中的代码 import "./demoSub.js"
Promise
- 回调地狱
多层回调函数的相互嵌套,就形成了回调地狱。示例代码如下:
回调地狱的缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,代码的可读性变差
如何解决回调地狱的问题
- 为了解决回调地狱的问题,ES6(ECMAScript 2015 )中新增了 Promise 的概念。
Promise的基本概念
- ① Promise 是一个构造函数
- 我们可以创建 Promise 的实例 const p = new Promise()
- new 出来的 Promise 实例对象, 代表一个异步操作 ② Promise.prototype 上包含一个 .then() 方法
- 每一次 new Promise() 构造函数得到的实例对象,
- 都可以通过原型链的方式访问到 .then() 方法,例如 p.then then() ③ .then() 方法用来预先指定成功和失败的回调函数
- p.then( 成功的回调函数 ,失败的回调函数 )
- p.then( result => { }, error => {
- 调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的
- ① Promise 是一个构造函数
基于回调函数按顺序读取文件内容, 会产生回调地域
基于 then-fs 读取文件内容
由于 node.js 官方提供的 fs 模块仅支持以回调函数的方式读取文件, 不支持 Promise 的调用方式 。因此,需要先运行如下的命令,安装 then-fs 这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容:
调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容, 它的返回值是 Promise 的实例对象 。因此可以调用 .then() 方法为每个 Promise 异步操作指定成功和失败之后的回调函数。示例代码如下:
.then()方法的特性
- 如果上一个 .then() 方法中返回了一个新的 Promise 实例对象 ,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的链式调用 ,就解决了回调地狱的问题。
基于 Promise 按顺序赌气文件的内容
通过 .catch 捕获错误
- 在 Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.catch 方法进行捕获和处理
- 如果不希望前面的错误导致后续的.then 无法正常执行,则可以将 .catch 的调用提前
jsthenFs.readFile('./files/11.txt', 'utf8') .catch(err => { // 捕获错误 console.log(err.message) }) .then((r1) => { console.log(r1) return thenFs.readFile('./files/2.txt', 'utf8') }) .then((r2) => { console.log(r2) return thenFs.readFile('./files/3.txt', 'utf8') }) .then((r3) => { console.log(r3) }) .catch(err => { // 捕获错误 console.log(err.message) })
Promise.all() 方法
- Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then操作(等待机制)。 示例代码如下:
- 注意:数组中 Promise 实例的顺序,就是最终结果的顺序
jsimport thenFs from 'then-fs' const promiseArr = [ thenFs.readFile('./files/1.txt', 'utf8'), thenFs.readFile('./files/2.txt', 'utf8'), thenFs.readFile('./files/3.txt', 'utf8'), ] Promise.all(promiseArr).then(result => { console.log(result) }) .catch(err => { console.log(err.measage) }) ``` - Promise.race() - Promise.race()方法会发起并行的 Promise 异步操作, 只要任何一个异步操作完成,就立即执行下一步的.then 操作 (赛跑机制)。示例代码如下 ```js import thenFs from 'then-fs' const promiseArr = [ thenFs.readFile('./files/1.txt', 'utf8'), thenFs.readFile('./files/1.txt', 'utf8'), thenFs.readFile('./files/3.txt', 'utf8'), ] Promise.race(promiseArr).then(result => { console.log(result) }) .catch(err => { console.log(err.measage) }) ``` - 基于Promise 封装读文件的方法 - 方法的封装要求: - ① 方法的名称要定义为 getFile - ② 方法接收一个形参 fpath ,表示要读取的文件的路径 - ③ 方法的返回值为 Promise 实例对象 ![基于Promise 封装读文件的方法](./assets/%E5%9F%BA%E4%BA%8EPromise%20%E5%B0%81%E8%A3%85%E8%AF%BB%E6%96%87%E4%BB%B6%E7%9A%84%E6%96%B9%E6%B3%95.jpg) ```js // 基于Promise 封装读文件的方法 import fs from 'fs' function getFile(fpath) { // return new Promise() // 只是创建了一个形式上的异步操作 return new Promise((resolve, reject) => { fs.readFile(fpath, 'utf8', (err, dataStr) => { if (err) return reject(err) return resolve(dataStr) }) }) } // 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
- 什么是 async/await
- async/await是 ES8ES8(ECMAScript 2017 )引入的新语法,用来简化 Promise 异步操作 。在 async/await 出现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。示例代码如下:
- .then链式调用的优点 :解决了回调地域的问题
- .then链式调用的缺点 : 代码冗余/阅读性差不易理解
- async/await 的基本使用
- ① 如果在 function 中使用了 await,则 function 必须被 async 修饰
- ② 在 async 方法中, 第一个 await 之前的代码会同步执行 ,await 之后的代码会异步执行
jsimport thenFs from 'then-fs' // 按照顺序读取文件 1/2/3 的内容 // 最终输出顺序 /* A B C 111 222 333 D */ console.log('A') async function getAllFile() { console.log('B') const r1 = await thenFs.readFile('./files/1.txt', 'utf8') console.log(r1) const r2 = await thenFs.readFile('./files/2.txt', 'utf8') console.log(r2) const r3 = await thenFs.readFile('./files/3.txt', 'utf8') console.log(r3) console.log('D') } getAllFile() console.log('C')
EventLoop
JavaScript 是单线程的语言
- JavaScript是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。
- 单线程执行任务队列的问题:
- 如果前一个任务非常耗时 ,则后续的任务就不得不一直等待,从而导致程序假死的问题。
同步任务和异步任务
- 为了防止某个耗时任务导致程序假死的问题, JavaScript 把待执行的任务分为了两类:
- ① 同步任务 (synchronoussynchronous)
- 又叫做非耗时任务,指的是在主线程上排队执行的那些任务
- 只有前一个任务执行完毕,才能执行后一个任务
- ② 异步任务( asynchronousasynchronous)
- 又叫做耗时任务 ,异步任务由 JavaScript 委托给宿主环境进行执行
- 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数
同步任务和异步任务的执行过程
- ① 同步任务由 JavaScript 主线程次序执行
- ② 异步任务委托给宿主环境执行
- ③ 已完成的异步任务对应的回调函数 ,会被加入到任务队列中等待执行
- ④ JavaScript 主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
- ⑤ JavaScript 主线程不断重复上面的第 4 步
EventLoop 的基本概念
- JavaScript主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行 。这个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop (事件循环)。
结合 EventLoop 分析输出的顺序
js// 正确的输出结果: ADCB 。其中: // A 和 D 属于同步任务 。会根据代码的先后顺序依次被执行 // C 和 B 属于异步任务 。它们的回调函数会被加入到任务队列中,等待主线程空闲时再执行 import thenFs from "then-fs"; console.log('A') thenFs.readFile('./files/1.txt', 'utf8').then(dataStr => { console.log('B') }) setTimeout(() => { console.log('C') }, 0) console.log('D')
宏任务和微任务
概念
- JavaScript把异步任务又做了进一步的划分,异步任务又分为两类,分别是:
- 宏任务(macrotask)
- 异步 Ajax 请求
- setTimeout 、setInterval
- 文件操作
- 其他宏任务
- 微任务(microtask)
- Promise. then 、.catch 和 .finally
- process.nextTick
- 其它微任务
宏任务和微任务的执行顺序
- 每一个宏任务执行完之后,都会检查是否存在待执行的微任务,
- 如果有,则执行完所有微任务之后,再继续执行下一个宏任务。
- 执行顺序:同 → 异(宏 → 微) → 异(宏 → 微)
去银行办业务的场景
- ① 小云和小腾去银行办业务。首先,需要取号之后进行排队
- 宏任务队列
- ② 假设当前银行网点只有一个柜员,小云在办理存款业务时, 小腾只能等待
- 单线程 ,宏任务按次序执行
- ③ 小云办完存款业务后,柜员询问他是否还想办理其它业务 ?
- 当前宏任务执行完, 检查是否有微任务
- ④ 小云告诉柜员:想要买理财产品、再办个信用卡、最后再兑换点马年纪念币?
- 执行微任务,后续宏任务被推迟
- ⑤ 小云离开柜台后,柜员开始为小腾办理业务
- 所有微任务执行完毕 ,开始执行下一个宏任务
js/* 正确输出顺序: 2 4 3 1 */ setTimeout(function () { console.log('1') }) new Promise(function (resolve) { console.log('2') resolve() }).then(function () { console.log('3') }) console.log('4')
jsconsole.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(function () { console.log('7') new Promise(function (resolve) { console.log('8') resolve() }).then(function () { console.log('9') }) }) /** * 执行顺序:同 -> 异(宏 -> 微) -> 异(宏 -> 微) * 输出顺序 * 1 * 5 * 6 * 2 * 3 * 4 * 7 * 8 * 9 */
- ① 小云和小腾去银行办业务。首先,需要取号之后进行排队
API 接口案例
webpack
前端工程化
小白眼中的前端开发 vs 实际的前端开发
小白眼中的前端开发:
- 会写 HTML + CSS + JavaScript 就会前端开发
- 需要美化页面样式,就拽一个 bootstrap 过来
- 需要操作 DOM 或发起 Ajax 请求,再拽一个 jQuery 过来
- 需要渲染模板结构,就用 artart-template 等模板引擎
实际的前端开发:
- 模块化 (js 的模块化、 css 的模块化、其它资源的模块化)
- 组件化 (复用现有的 UI 结构、样式行为)
- 规范化 (目录结构的划分、编码规范化、接口规范化、文档规范化、 Git 分支管理)
- 自动化 (自动化构建、自动部署、自动化测试
什么是前端工程化
- 前端工程化指的是:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化、标准化。最终落实到细节上,就是实现前端的“4个现代化”:
- 模块化
- 组件化
- 规范化
- 自动化
- 前端工程化指的是:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化、标准化。最终落实到细节上,就是实现前端的“4个现代化”:
前端工程化的好处
- 前端工程化的好处主要体现在如下两方面:
- ① 前端工程化让前端开发能够“自成体系” ”,覆盖了前端项目从创建到部署的方方面面
- ② 最大程度地提高了前端的开发效率,降低了技术选型、前后端联调等带来的协调沟通成本
- 前端工程化的好处主要体现在如下两方面:
前端工程化的解决方案
- 早期的前端工程化解决方案:
- grunt( https://www.gruntjs.net/ )
- gulp( https://www.gulpjs.com.cn/ )
- 目前主流的前端工程化解决方案:
- webpack( https://www.webpackjs.com/ )
- parcel( https://zh.parceljs.org/ )
- 早期的前端工程化解决方案:
webpack的基本使用
概念
- webpack是前端项目工程化的具体解决方案。
- 主要功能:它提供了友好的前端模块化开发支持,以及代码压缩混淆、处理浏览器端JavaScript的兼容性、性能优化等强大的功能。
- 好处:让程序员把工作的重心放到具体功能的实现上,提高了前端开发效率和项目的可维护性。
- 注意:目前企业级的前端项目开发中,绝大多数的项目都是基于webpack进行打包构件的.
创建列表隔行变色项目
- ① 新建项目空白录,并运行 npm init –y 命令,初始化包管理配置文件 package.json
- ② 新建 src 源代码目录
- ③ 新建 src -> index.html 首页和 src -> index.js 脚本文件
- ④ 初始化首页基本的结构
- ⑤ 运行 npm install jquery –S 命令,安装 jQuery
- ⑥ 通过 ES6 模块化的方式导入 jQuery ,实现列表隔行变色效果
在项目中安装 webpack
npm i webpack webpack-cli -D
在项目中配置 webpack
- ① 在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件,并初始化如下的基本配置:
- mode节点的可选值有两个,分别是:
- ① development
- 开发环境
- 不会对打包生成的文件进行代码压缩和性能优化
- 打包速度快,适合在开发阶段使用
- ② production
- 生产环境
- 会对打包生成的文件进行代码压缩和性能优化
- 打包速度很慢,仅适合在项目发布阶段使用
- ① development
- webpack.config.js文件的作用
- webpack.config.js是 webpack 的配置文件。webpack在真正开始打包构建之前,会先读取这个配置文件,从而基于给定的配置,对项目进行打包。
- 注意:由于 webpack 是基于 node.js 开发出来的打包工具,因此在它的配置文件中,支持使用 node.js 相关的语法和模块进行 webpack 的个性化配置
- webpack 中的默认约定
- 在 webpack 中有如下的默认约定 :
- ① 默认的打包入口文件为 srcsrc-> index.js
- ② 默认的输出文件路径为 dist -> main.js
- 注意:可以在 webpack.config.js 中修改打包的默认约定
- 在 webpack 中有如下的默认约定 :
- 自定义打包的入口与出口
- 在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口 。通过 output 节点指定打包的出口 。 示例代码如下:js
const path = require('path') // 创建 html 插件的实例对象 const HtmlPlugin = require('html-webpack-plugin') const htmlPlugin = new HtmlPlugin({ template: './src/index.html', filename: './index.html' }) // 每次打包前,自动清理dist目录 const { CleanWebpackPlugin } = require('clean-webpack-plugin') const cleanPlugin = new CleanWebpackPlugin() module.exports = { mode: 'development', // mode 用来指定构建模式. 可选值有 development 和 production devtool:'eval-source-map', // 开发环境下,在 webpack.config.js 中添加如下的配置,即可保证 运行时报错的行数与源代码的行数保持一致 // devtool: 'nosources-source-map', // 在生产环境下,如果只想定位报错的具体行数 ,且不想暴露源码 。此时可以将 devtool 的值设置为 nosources-source-map 。实际效果如图所示: // devtool: 'source-map', // 在生产环境下,如果 想在定位报错行数的同时 ,展示具体报错的源码 。此时可以将 devtool 的值设置为 source-map // 指定打包的入口 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: 'file-loader?limit=41895' } { test: /\.jpg|png|gif$/, use: { loader: 'url-loader', options: { limit: 100000, outputPath: 'image' } } } ] } }
- 在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口 。通过 output 节点指定打包的出口 。 示例代码如下:
- mode节点的可选值有两个,分别是:
- ② 在 package.json 的 scripts 节点下,新增 dev 脚本如下:
- script 节点下的加班,可以通过npm run执行. 例如: npm run dev
json"scripts": { "dev": "webpack" }
- ③ 在终端中运行 npm run dev 命令,启动 webpack 进行项目的打包构建js
npm run dev
- ① 在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件,并初始化如下的基本配置:
webpack 中的插件
- webpack 插件的作用
- 通过安装和配置第三方的插件,可以拓展 webpack 的能力 ,从而让 webpack 用起来更方便 。
- 最常用的 webpack 插件有如下两个:
- ① webpackwebpack-devdev-server
- 类似于 node.js 阶段用到的 nodemon 工具
- 每当修改了源代码, webpack 会自动进行项目的打包和构建
- ② htmlhtml-webpackwebpack-plugin
- webpack 中的 HTML 插件(类似于一个模板引擎插件)
- 可以通过此插件自定制 index.html 页
- ① webpackwebpack-devdev-server
- webpack-dev-server
- webpack-devdev-server 可以让 webpack 监听项目源代码的变化 ,从而进行自动打包构建 。js
npm i webpack-dev-server -D
- 配置 webpack-dev-server
- ① 修改 package.json -> scripts 中的 dev 命令如下:
- ② 再次运行 npm run dev 命令,重新进行项目的打包
- ③ 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果
- 打包生成的文件哪去了?
- ① 不配置 webpackwebpack-devdev-server 的情况下, webpack 打包生成的文件,会存放到实际的物理磁盘上
- 严格遵守开发者在 webpack.config.js 中指定配置
- 根据 output 节点指定路径进行存放
- ② 配置了 webpackwebpack-devdev-server 之后,打包生成的文件存放到了内存中
- 不再根据 output 节点指定的路径,存放到实际的物理磁盘上
- 提高了实时打包输出的性能 ,因为内存比物理磁盘速度快很多
- ① 不配置 webpackwebpack-devdev-server 的情况下, webpack 打包生成的文件,会存放到实际的物理磁盘上
- 生成到内存中的文件该如何访问
- webpack-devdev-server 生成到内存中的文件,默认放到了项目的根目录中 ,而且是虚拟的 、不可见的 。
- 可以直接用 / 表示项目根录 ,后面跟上要访问的文件名称 ,即可访问内存中的文件
- 例如 /bundle.js 就表示要访问 webpack-devdev-server 生成到内存中的 bundle.js 文件
- webpack-devdev-server 生成到内存中的文件,默认放到了项目的根目录中 ,而且是虚拟的 、不可见的 。
- webpack-devdev-server 可以让 webpack 监听项目源代码的变化 ,从而进行自动打包构建 。
- html-webpack-plugin
- html-webpack-plugin 是 webpack 中的 HTML 插件 ,可以通过此插件自定制 index.html 页面的内容 。
- 需求 :通过 html-webpack-plugin 插件,将 src 目录下的 index.html 首页, 复制到项目根目录中一份 !
- 解惑 htmlhtml-webpackwebpack-plugin
- ① 通过 HTML 插件复制到项目根目录中的 index.html 页面, 也被放到了内存中
- ② HTML 插件在生成的 index.html 页面的底部 ,自动注入了打包的 bundle.js 文件
- devServer 节点
- 在 webpack.config.js 配置文件中,可以通过 devServer 节点对webpack-dev-server 插件进行更多的配置
- 注意:凡是修改了 webpack.config.js 配置文件,或修改了 package.json 配置文件, 必须重启实时打包的服务器 ,否则最新的配置文件无法生效.
jsdevServer: { open: true, // 初次打包完成后,自动打开浏览器 host: '127.0.0.1', // 实时打包所使用的主机地址 port: 80, // 实时打包所使用的端口号 }
webpack 中的 loader
概述
- 在实际开发过程中, webpack 默认只能打包处理以 .js 后缀名结尾的模块。其他非 .js 后缀名结尾的模块 , webpack 默认处理不了, 需要调用 loader 加载器才可以正常打包 ,否则会报错
- loader加载器的作用:协助 webpack 打包处理特定的文件模块。比如:
- css-loader 可以打包处理 .css 相关的文件
- less-loader 可以打包处理 .less 相关的文件
- babel-loader 可以打包处理 webpack 无法处理的高级 JS 语法
loader 的调用过程
打包处理css文件
- ① 运行 npm i style-loader @2.0.0 css-loader@5.0.1 -D 命令,安装处理 css 文件的 loader
- ② 在 webpack.config.js 的 module-> rules 数组中,添加 loader 规则如下:
- 其中, test 表示匹配的文件类型 , use 表示对应要调用的 loader
- 注意:
- use 数组中指定的 loader 顺序是固定的
- 多个 loader 的调用顺序是: 从后往前调用
打包处理less文件
- ① 运行 npm i less-loader -D 命令
- ② 在 webpack.config.js 的 module-> rules 数组中,添加 loader 规则
打包处理样式表中与 url 路径相关的文件(webpack5不再需要)
- ① 运行 npm i url-loader@4.1.1 file-loader@6.2.0 -D 命令
- ② 在 webpack.config.js 的 module-> rules 数组中,添加 loader 规则如下:
- 其中 ? 之后的是 loader 的参数项 :
- limit 用来指定图片的大小 ,单位是字节 byte
- 只有 ≤ limit 大小的图片,才会被转为 base64 格式的图片
loader的另一种配置方式
- 带参数项的 loader 还可以通过对象的方式进行配置:
- 包的名称及版本号列表如下(红色是包的名称、黑色是包的版本号):
- babel-loader @8.2.1
- @babel/core @7.12.3
- @babel/plugin plugin-proposal-class-properties @7.12.1
- 包的名称及版本号列表如下(红色是包的名称、黑色是包的版本号):
- 带参数项的 loader 还可以通过对象的方式进行配置:
打包处理 js 文件中的高级语法
- webpack只能打包处理一部分高级的 JavaScript 语法。对于那些 webpack 无法处理的高级 js 语法,需要借助于 babel-loader 进行打包处理。例如 webpack 无法处理下面的 JavaScript 代码:
- 安装 babel-loader 相关的包
- 包的名称及版本号列表如下(红色是包的名称、黑色是包的版本号):
- babelbabel-loader @8.2.1
- @babel/core @7.12.3
- @babel/plugin plugin-proposalproposal-classclass-properties @7.12.1
- 包的名称及版本号列表如下(红色是包的名称、黑色是包的版本号):
jsclass Person { // 通过static关键字,为Person类定义了一个静态属性 info // webpack无法打包处理"静态属性"这个高级语法 static info = 'person info' } console.log(Person.info)
打包发布
- 为什么要打包发布
- 项目开发完成之后 ,使用 webpack 对项目进行打包发布的主要原因有以下两点:
- ① 开发环境下,打包生成的文件存放于内存中 ,无法获取到最终打包生成的文件
- ② 开发环境下,打包生成的文件不会进行代码压缩和性能优化为了让项目能够在生产环境中高性能的运行,因此需要对项目进行打包发布。
- 项目开发完成之后 ,使用 webpack 对项目进行打包发布的主要原因有以下两点:
- 配置 webpack 的打包发布
- 在 package.json 文件的 scripts 节点下,新增 build 命令如下:
- --model 是一个参数项,用来指定 webpack 的运行模式 。production 代表生产环境,会对打包生成的文件进行代码压缩和性能优化 。
- 注意:通过 --model 指定的参数项,会覆盖 webpack.config.js 中的 model 选型。
- 把 JavaScript 文件统一生成到 js 目录
- 在 webpack.config.js 配置文件的 output 节点中,进行如下的配置:
- 把图片文件统一生成到 image 目录中(webpack5不起作用)
- 修改 webpack.config.js 中的 urlurl-loader 配置项,新增 outputPath 选项即可指定图片文件的输出路径:
- 自动清理 dist 目录下的旧文件
- 为了在每次打包发布时自动清理掉 dist 目录中的旧文件 ,可以安装并配置clean-webpack-plugin 插件
- 企业级项目的打包发布
- 企业级的项目在进行打包发布时,远比刚才的方式要复杂的多,主要的发布流程如下:
- 生成打包报告,根据报告分析具体的优化方案
- TreeTree-Shaking
- 为第三方库启用 CDN 加载
- 配置组件的按需加载
- 开启路由懒加载
- 自定制首页内容
- 在后面的 vue 项目课程中,会专门讲解如何进行企业级项目的打包发布。
- 企业级的项目在进行打包发布时,远比刚才的方式要复杂的多,主要的发布流程如下:
Source Map
生产环境遇到的问题
- 前端项目在投入生产环境之前,都需要对JavaScript 源代码进行压缩混淆 ,从而减小文件的体积,提高文件的
- 加载效率。此时就不可避免的产生了另一个问题:
- 对压缩混淆之后的代码除错(debugdebug)是一件极其困难的事情
- 变量被替换成没有任何语义的名称
- 空行和注释被剔除
什么是 Source Map
- Source Map就是一个信息文件,里面储存着位置信息 。也就是说, Source Map 文件中存储着代码压缩混淆前后的对应关系 。
- 有了它,出错的时候,除错工具将直接显示原始代码 ,而不是转换后的代码 ,能够极大的方便后期的调试。
webpack开发环境下的 Source Map
- 在开发环境下 ,webpack 默认启用了 Source Map 功能。当程序运行出错时,可以直接在控制台提示错误行的位置 ,并定位到具体的源代码 :
webpack开发环境下的 Source Map
- 在开发环境下 ,webpack 默认启用了 Source Map 功能。当程序运行出错时,可以直接在控制台提示错误行的位置 ,并定位到具体的源代码 :
- 默认 Source Map 的问题
- 开发环境下默认生成的 Source Map Map,记录的是生成后的代码的位置 。会导致运行时报错的行数与源代码的行数不一致的问题。示意图如下:
- 开发环境下,推荐在 webpack.config.js 中添加如下的配置,即可保证运行时报错的行数与源代码的行数保持一致:
webpack生产环境下的 Source Map
- 在生产环境下 ,如果省略了 devtool 选项 ,则最终生成的文件中不包含 Source Map 。这能够防止原始代码通过 Source Map 的形式暴露给别有所图之人。
- 在生产环境下,如果只想定位报错的具体行数 ,且不想暴露源码 。此时可以将 devtool 的值设置为nosources-source-map 。实际效果如图所示:
- 在生产环境下,如果想在定位报错行数的同时 ,展示具体报错的源码 。此时可以将 devtool 的值设置为 sourcesource-map 。实际效果如图所示:
Source Map的最佳实践
- ① 开发环境下:
- 建议把 devtool 的值设置为 eval-source-map
- 好处:可以精准定位到具体的错误行
- ② 生产环境下:
- 建议关闭 Source Map 或将 devtool 的值设置为 nosources-source-map
- 好处:防止源码泄露,提高网站的安全性
- ① 开发环境下:
实际开发中需要自己配置 webpack 吗?
- 答案: 不需要 !
- 实际开发中会使命令行工具(俗称 CLI)一键生成带有 webpack 的项目
- 开箱即用,所有 webpack 配置项都是现成的!
- 我们只需要知道 webpack 中的基本概念即可
Vue 3
Vue 基础入门
Vue 简介
- 什么是 vue:官方给出的概念:Vue (读音/vjuː/,类似于 view) 是一套用于构建用户界面的前端框架。
- 构建用户界面
- 前端开发者最主要的工作,就是为网站的使用者(又称为:网站的用户)构建出美观、舒适、好用的网页。
- 构建用户界面的传统方式:在传统的 Web 前端开发中,是基于 jQuery + 模板引擎的方式来构建用户界面的。
- 使用 vue 构建用户界面:使用vue 构建用户界面,解决了 jQuery + 模板引擎的诸多痛点,极大的提高了前端开发的效率和体验。
- 框架
- 官方给vue 的定位是前端框架,因为它提供了构建用户界面的一整套解决方案(俗称vue 全家桶):
- vue(核心库)
- vue-router(路由方案)
- vuex(状态管理方案)
- vue 组件库(快速搭建页面 UI 效果的方案)
- 以及辅助vue 项目开发的一系列工具:
- vue-cli(npm 全局包:一键生成工程化的vue 项目 - 基于webpack、大而全)
- vite(npm 全局包:一键生成工程化的 vue 项目 - 小而巧)
- vue-devtools(浏览器插件:辅助调试的工具)
- vetur(vscode 插件:提供语法高亮和智能提示)
- 官方给vue 的定位是前端框架,因为它提供了构建用户界面的一整套解决方案(俗称vue 全家桶):
- 构建用户界面
- vue 的特性
vue 框架的特性,主要体现在如下两方面:
- ① 数据驱动视图
- 在使用了vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。
- 好处:当页面数据发生变化时,页面会自动重新渲染!
- 注意:数据驱动视图是单向的数据绑定。
- ① 数据驱动视图
② 双向数据绑定
- 在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。
- 好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值!
MVVM
- MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。它把每个HTML 页面都拆分成了如下三个部分:
- View 表示当前页面所渲染的 DOM 结构。
- Model 表示当前页面渲染时所依赖的数据源。
- ViewModel 表示 vue 的实例,它是MVVM 的核心。
- MVVM 的工作原理
- ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起。
- 当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构
- 当表单元素的值发生变化时,也会被VM 监听到,VM 会把变化过后最新的值自动同步到Model 数据源中
- MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。它把每个HTML 页面都拆分成了如下三个部分:
- Vue的版本
- 当前,vue 共有 3 个大版本,其中:
- 2.x 版本的vue 是目前企业级项目开发中的主流版本
- 3.x 版本的vue 于 2020-09-19 发布,生态还不完善,尚未在企业级项目开发中普及和推广
- 1.x 版本的 vue 几乎被淘汰,不再建议学习与使用
- 总结:
- 3.x 版本的vue 是未来企业级项目开发的趋势;
- 2.x 版本的vue 在未来(1 ~ 2年内)会被逐渐淘汰;
- vue3.x 和 vue2.x 版本的对比
- vue2.x 中绝大多数的API 与特性,在vue3.x 中同样支持。同时,vue3.x 中还新增了 3.x 所特有的功能、并废弃了某些 2.x 中的旧功能:
- 新增的功能例如:
- 组合式API、多根节点组件、更好的 TypeScript 支持等
- 废弃的旧功能如下:
- 过滤器、不再支持$on,$off 和 $once 实例方法等
- 详细的变更信息,请参考官方文档给出的迁移指南:
- 当前,vue 共有 3 个大版本,其中:
Vue 的基本使用
- 基本使用步骤
- ① 导入 vue.js 的 script 脚本文件
- ② 在页面中声明一个将要被vue 所控制的 DOM 区域
- ③ 创建 vm 实例对象(vue 实例对象)
- 基本代码与 MVVM 的对应关系
Vue 的调试工具
- 安装 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 的浏览器调试工具不能交叉使用!
Vue 的指令与过滤器
指令的概念
- 指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。
- vue 中的指令按照不同的用途可以分为如下 6 大类:
- ① 内容渲染指令
- ② 属性绑定指令
- ③ 事件绑定指令
- ④ 双向绑定指令
- ⑤ 条件渲染指令
- ⑥ 列表渲染指令
内容渲染指令
- 用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:
- v-text
- 注意:v-text 指令会覆盖元素内默认的值。
- vue 提供的
语法,专门用来解决v-text 会覆盖默认文本内容的问题。这种
语法的专业名称是插值表达式(英文名为:Mustache)。
- 注意:相对于v-text 指令来说,插值表达式在开发中更常用一些!因为它不会覆盖元素中默认的文本内容。
- 使用 Javascript 表达式
- 在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算html
<p>{{number + 1}}</p> <p>{{ok ? 'True' : 'False'}}</p> <p>{{message.split('').reverse().join('')}}</p> <p :id="'list-' + id">xxx</p> <p>{{user.name}}</p>
- 在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算
- vue 提供的
- v-html
- v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的HTML 元素, 则需要用到v-html 这个指令:
属性绑定指令
- 如果需要为元素的属性动态绑定属性值,则需要用到v-bind 属性绑定指令。用法示例如下:
- 由于v-bind 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 : )
事件绑定指令
- vue 提供了v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听。语法格式如下:
- 注意:原生 DOM 对象有onclick、oninput、onkeyup 等原生事件,替换为vue 的事件绑定形式后, 分别为:v-on:click、v-on:input、v-on:keyup
- 通过v-on 绑定的事件处理函数,需要在methods 节点中进行声明:
- 由于v-on 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 @ )。
- 在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件对象event。同理,在v-on 指令(简写为@ )所绑定的事件处理函数中,同样可以接收到事件对象event,示例代码如下:
- 在使用v-on 指令绑定事件时,可以使用 ( ) 进行传参,示例代码如下:
- $event 是 vue 提供的特殊变量,用来表示原生的事件参数对象event。$event 可以解决事件参数对象event被覆盖的问题。示例用法如下:
- 事件修饰符 在事件处理函数中调用preventDefault() 或 stopPropagation() 是非常常见的需求。因此,vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的 5 个事件修饰符如下:
- .prevent 阻止默认行为(例如:阻止 a 连接的跳转、阻止表单的提交等)
- .stop 阻止事件冒泡
- .capture 以捕获模式触发当前的事件处理函数
- .once 绑定的事件只触发1次
- .self 只有在 event.target 是当前元素自身时触发事件处理函数
按键修饰符
- 在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符.
双向绑定指令
- vue 提供了v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据。
- 注意:v-model 指令只能配合表单元素一起使用!
- 了方便对用户输入的内容进行处理,vue 为 v-model 指令提供了 3 个修饰符,分别是:
- .number 自动将用户的输入值转为数值类型
<input v-model.number="age" />
- .trim 自动过滤用户输入的首尾空白字符
<input v-model.trim="msg" />
- .lazy 在“change”时而非“input”时更新
<input v-model.lazy="msg" />
- .number 自动将用户的输入值转为数值类型
条件渲染指令
- 条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:
- v-if
- v-show
- 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 指令一起使用:
- v-else-if
- v-else-if 指令,顾名思义,充当v-if 的“else-if 块”,可以连续使用:
- 条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:
列表渲染指令
- vue 提供了v-for 指令,用来辅助开发者基于一个数组来循环渲染相似的 UI 结构。
- v-for 指令需要使用 item in items 的特殊语法,其中:
- items 是待循环的数组
- item 是当前的循环项
- v-for 中的索引
- v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为(item, index) in items,示例代码如下:
- 注意:v-for 指令中的 item 项和 index 索引都是形参,可以根据需要进行重命名。例如(user, i) in userlist
- 使用 key 维护列表的状态
- 当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。
- 为了给vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能。此时,需要为每项提供一个唯一的key 属性:
- key 的注意事项
- ① key 的值只能是字符串或数字类型
- ② key 的值必须具有唯一性(即:key 的值不能重复)
- ③ 建议把数据项 id 属性的值作为key 的值(因为 id 属性的值具有唯一性)
- ④ 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)
- ⑤ 建议使用v-for 指令时一定要指定key 的值(既提升性能、又防止列表状态紊乱)
过滤器
- 概况
- 过滤器(Filters)常用于文本的格式化。例如:
- hello -> Hello
- 过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用,示例代码如下:
- 过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定。
- 定义过滤器
- 在创建vue 实例期间,可以在filters 节点中定义过滤器,示例代码如下:
- 过滤器
- 过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定。
- 过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用,示例代码如下:
- 私有过滤器和全局过滤器
- 在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前vm 实例所控制的el 区域内使用。
- 如果希望在多个vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:
- 连续调用多个过滤器
- 过滤器可以串联地进行调用,例如:
- 过滤器传参
- 过滤器的本质是 JavaScript 函数,因此可以接收参数,格式如下:
- 过滤器的兼容性
- 过滤器仅在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
品牌列表案例
组件基础
单页面应用程序
- 什么是单页面应用程序
- 单页面应用程序(英文名:Single Page Application)简称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的一个HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。
- 单页面应用程序的特点
- 单页面应用程序将所有的功能局限于一个web 页面中,仅在该web 页面初始化时加载相应的资源( HTML、JavaScript 和 CSS)。
- 一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。而是利用 JavaScript 动态地变换HTML 的内容,从而实现页面与用户的交互。
- 单页面应用程序的优点
- SPA 单页面应用程序最显著的 3 个优点如下:
- ① 良好的交互体验
- 单页应用的内容的改变不需要重新加载整个页面
- 获取数据也是通过Ajax 异步获取
- 没有页面之间的跳转,不会出现“白屏现象”
- ② 良好的前后端工作分离模式
- 后端专注于提供API 接口,更易实现API 接口的复用
- 前端专注于页面的渲染,更利于前端工程化的发展
- ③ 减轻服务器的压力
- 服务器只提供数据,不负责页面的合成与逻辑的处理,吞吐能力会提高几倍
- 单页面应用程序的缺点
- 任何一种技术都有自己的局限性,对于 SPA 单页面应用程序来说,主要的缺点有如下两个:
- 首屏加载慢
- 路由懒加载
- 代码压缩
- CDN 加速
- 网络传输压缩
- 不利于 SEO
- SSR 服务器端渲染
- 如何快速创建 vue 的 SPA 项目
- vue 官方提供了两种快速创建工程化的 SPA 项目的方式:
- ① 基于 vite 创建 SPA 项目
- ② 基于 vue-cli 创建 SPA 项目
- vue 官方提供了两种快速创建工程化的 SPA 项目的方式:
vite 的基本使用
创建 vite 的项目
- 按照顺序执行如下的命令,即可基于vite 创建vue 3.x 的工程化项目:
jsnpm init vite-app 项目名称 cd 项目名称 npm install npm run dev
梳理项目的结构
- node_modules 目录用来存放第三方依赖包
- public 是公共的静态资源目录
- src 是项目的源代码目录(程序员写的所有代码都要放在此目录下)
- .gitignore 是 Git 的忽略文件
- index.html 是 SPA 单页面应用程序中唯一的HTML 页面
- package.json 是项目的包管理配置文件
- src/assets 目录用来存放项目中所有的静态资源文件(css、fonts等)
- src/components 目录用来存放项目中所有的自定义组件
- src/components/App.vue 是项目的根组件
- src/index.css 是项目的全局样式表文件
- src/main.js 是整个项目的打包入口文件
vite 项目的运行流程
- 概述
- 在工程化的项目中,vue 要做的事情很单纯:通过main.js 把 App.vue 渲染到 index.html 的指定区域中。
- 其中:
- ① App.vue 用来编写待渲染的模板结构
- ② index.html 中需要预留一个el 区域
- ③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中
- 在 App.vue 中编写模板结构
- 清空App.vue 的默认内容,并书写如下的模板结构:
- 在 index.html 中预留 el 区域
- 打开 index.html 页面,确认预留了el 区域:
- 在 main.js 中进行渲染
- 按照vue 3.x 的标准用法,把App.vue 中的模板内容渲染到 index.html 页面的el 区域中:
- 概述
组件化开发思想
- 什么是组件化开发
- 组件化开发指的是:根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。例如:http://www.ibootstrap.cn/ 所展示的效果,就契合了组件化开发的思想。用户可以通过拖拽组件的方式,快速生成一个页面的布局结构。
- 组件化开发的好处
- 前端组件化开发的好处主要体现在以下两方面:
- 提高了前端代码的复用性和灵活性
- 提升了开发效率和后期的可维护性
- 前端组件化开发的好处主要体现在以下两方面:
- vue 中的组件化开发
- vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是.vue。之前接触到的 App.vue 文件本质上就是一个vue 的组件。
vue 组件的构成
vue 组件组成结构
- 每个.vue 组件都由 3 部分构成,分别是:
- template -> 组件的模板结构
- script -> 组件的 JavaScript 行为
- style -> 组件的样式
- 其中,每个组件中必须包含template 模板结构,而 script 行为和 style 样式是可选的组成部分。
- 每个.vue 组件都由 3 部分构成,分别是:
组件的 template 节点
- vue 规定:每个组件对应的模板结构,需要定义到
<template>
节点中。 - 注意:
<template>
是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素。 - 在 template 中使用指令
- 在组件的
<template>
节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前组件的 DOM 结构。代码示例如下: - 在 template 中定义根节点
- 在 vue 2.x 的版本中,
<template>
节点内的 DOM 结构仅支持单个根节点: - 但是,在vue 3.x 的版本中,
<template>
中支持定义多个根节点:
- 在 vue 2.x 的版本中,
- 在组件的
- vue 规定:每个组件对应的模板结构,需要定义到
组件的 script 节点
- vue 规定:组件内的
<script>
节点是可选的,开发者可以在<script>
节点中封装组件的 JavaScript 业务逻辑。<script>
节点的基本结构如下: - script 中的 name 节点
- 可以通过name 节点为当前组件定义一个名称:
- 在使用vue-devtools 进行项目调试的时候,自定义的组件名称可以清晰的区分每个组件:
- script 中的 data 节点
- vue 组件渲染期间需要用到的数据,可以定义在data 节点中:
- 组件中的 data 必须是函数
- vue 规定:组件中的data 必须是一个函数,不能直接指向一个数据对象。因此在组件中定义 data 数据节点时,下面的方式是错误的:
- script 中的 methods 节点
- 组件中的事件处理函数,必须定义到methods 节点中,示例代码如下:
- vue 规定:组件内的
组件的 style 节点
- vue 规定:组件内的
<style>
节点是可选的,开发者可以在<style>
节点中编写样式美化当前组件的 UI 结构。 <script>
节点的基本结构如下:- 其中
<style>
标签上的 lang="css" 属性是可选的,它表示所使用的样式语言。默认只支持普通的 css 语法,可选值还有 less、scss 等 - 让 style 中支持 less 语法
- 如果希望使用 less 语法编写组件的 style 样式,可以按照如下两个步骤进行配置:
- ① 运行npm install less -D 命令安装依赖包,从而提供 less 语法的编译支持
- ② 在
<style>
标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式
- vue 规定:组件内的
组件的基本使用
组件的注册
- 组件之间可以进行相互的引用,例如:
- vue 中组件的引用原则:先注册后使用。
- 注册组件的两种方式:
- vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:
- 被全局注册的组件,可以在全局任何一个组件内使用
- 被局部注册的组件,只能在当前注册的范围内使用
- vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:
- 全局注册组件
- 使用全局注册组件
- 使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用即可,例如:
- 局部注册组件
- 全局注册和局部注册的区别
- 被全局注册的组件,可以在全局任何一个组件内使用
- 被局部注册的组件,只能在当前注册的范围内使用
- 应用场景:
- 如果某些组件在开发期间的使用频率很高,推荐进行全局注册;
- 如果某些组件只在特定的情况下会被用到,推荐进行局部注册。
- 组件注册时名称的大小写
- 在进行组件的注册时,定义组件注册名称的方式有两种:
- ① 使用kebab-case 命名法(俗称短横线命名法,例如my-swiper 和 my-search)
- ② 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如MySwiper 和 MySearch)
- 短横线命名法的特点:
- 必须严格按照短横线名称进行使用
- 帕斯卡命名法的特点:
- 既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用
- 注意:在实际开发中,推荐使用帕斯卡命名法为组件注册名称,因为它的适用性更强。
- 在进行组件的注册时,定义组件注册名称的方式有两种:
- 通过 name 属性注册组件
- 在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称, 示例代码如下:
组件之间的样式冲突问题
- 默认情况下,写在.vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件之间样式冲突的根本原因是:
- ① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
- ② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
- 如何解决组件样式冲突的问题
- 为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:
- style 节点的 scoped 属性
- 为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:
- /deep/ 样式穿透
- 如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用/deep/ 深度选择器。
- 注意:/deep/ 是 vue2.x 中实现样式穿透的方案。在vue3.x 中推荐使用:deep() 替代/deep/。
- 默认情况下,写在.vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件之间样式冲突的根本原因是:
组件的 props
- 概述
- 为了提高组件的复用性,在封装vue 组件时需要遵守如下的原则:
- 组件的 DOM 结构、Style 样式要尽量复用
- 组件中要展示的数据,尽量由组件的使用者提供
- 为了方便使用者为组件提供要展示的数据,vue 组件提供了props 的概念。
- 为了提高组件的复用性,在封装vue 组件时需要遵守如下的原则:
- 什么是组件的 props
- props 是组件的自定义属性,组件的使用者可以通过props 把数据传递到子组件内部,供子组件内部进行使用。代码示例如下:
- props 的作用:父组件通过props 向子组件传递要展示的数据。
- props 的好处:提高了组件的复用性。
- 在组件中声明 props
- 在封装vue 组件时,可以把动态的数据项声明为props 自定义属性。自定义属性可以在当前组件的模板结构中被直接使用。示例代码如下:
- 无法使用未声明的 props
- 如果父组件给子组件传递了未声明的 props 属性,则这些属性会被忽略,无法被子组件使用,示例代码如下:
- 动态绑定 props 的值
- 可以使用v-bind 属性绑定的形式,为组件动态绑定props 的值,示例代码如下:
- props 的大小写命名
- 组件中如果使用“camelCase (驼峰命名法)”声明了props 属性的名称,则有两种方式为其绑定属性的值:
- 概述
Class 与 Style 绑定
- 在实际开发中经常会遇到动态操作元素样式的需求。因此,vue 允许开发者通过v-bind 属性绑定指令,为元素动态绑定class 属性的值和行内的 style 样式。
- 动态绑定 HTML 的 class
- 可以通过三元表达式,动态的为元素绑定class 的类名。示例代码如下:
- 以数组语法绑定 HTML 的 class
- 如果元素需要动态绑定多个class 的类名,此时可以使用数组的语法格式:
- 以对象语法绑定 HTML 的 class
- 使用数组语法动态绑定class 会导致模板结构臃肿的问题。此时可以使用对象语法进行简化:
- 以对象语法绑定内联的 style
:style
的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔(kebab-case,记得用引号括起来) 来命名:
封装组件的案例
案例效果
- 封装要求:
- ① 允许用户自定义 title 标题
- ② 允许用户自定义bgcolor 背景色
- ③ 允许用户自定义color 文本颜色
- ④ MyHeader 组件需要在页面顶部进行fixed 固定定位,且z-index 等于 999
- 使用示例如下:
- 封装要求:
用到的知识点
- 组件的封装与注册
- props
- 样式绑定
整体实现步骤
- 创建MyHeader 组件
- 渲染MyHeader 组件的基本结构
- 在 App 组件中注册并使用MyHeader 组件
- 通过 props 为组件传递数据
props 验证
- 什么是 props 验证
- 指的是:在封装组件时对外界传递过来的 props 数据进行合法性的校验,从而防止数据不合法的问题。
- 使用数组类型的props 节点的缺点:无法为每个prop 指定具体的数据类型。
- 对象类型的 props 节点
- 使用对象类型的props 节点,可以对每个prop 进行数据类型的校验,示意图如下:
- props 验证
- 对象类型的props 节点提供了多种数据验证方案,例如:
- ① 基础的类型检查
- ② 多个可能的类型
- ③ 必填项校验
- ④ 属性默认值
- ⑤ 自定义验证函数
- 基础的类型检查
- 可以直接为组件的 prop 属性指定基础的校验类型,从而防止组件的使用者为其绑定错误类型的数据:
- 多个可能的类型
- 如果某个prop 属性值的类型不唯一,此时可以通过数组的形式,为其指定多个可能的类型,示例代码如下:
- 必填项校验
- 如果组件的某个prop 属性是必填项,必须让组件的使用者为其传递属性的值。此时,可以通过如下的方式将其设置为必填项:
- 属性默认值
- 在封装组件时,可以为某个prop 属性指定默认值。示例代码如下:
- 自定义验证函数
- 在封装组件时,可以为prop 属性指定自定义的验证函数,从而对prop 属性的值进行更加精确的控制:
- 对象类型的props 节点提供了多种数据验证方案,例如:
计算属性
- 什么是计算属性
- 计算属性本质上就是一个function 函数,它可以实时监听data 中数据的变化,并return 一个计算后的新值, 供组件渲染 DOM 时使用。
- 如何声明计算属性
- 计算属性需要以function 函数的形式声明到组件的computed 选项中,示例代码如下:
- 注意:计算属性侧重于得到一个计算的结果,因此计算属性中必须有return 返回值!
- 计算属性的使用注意点
- ① 计算属性必须定义在computed 节点中
- ② 计算属性必须是一个function 函数
- ③ 计算属性必须有返回值
- ④ 计算属性必须当做普通属性使用
- 计算属性 vs 方法
- 相对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算。因此 计算属性的性能更好:
自定义事件
- 什么是自定义事件
- 在封装组件时,为了让组件的使用者可以监听到组件内状态的变化,此时需要用到组件的自定义事件。
- 自定义事件的 3 个使用步骤
- 在封装组件时:
- ① 声明自定义事件
- ② 触发自定义事件
- 在使用组件时:
- ③ 监听自定义事件
- 在封装组件时:
- 声明自定义事件
- 开发者为自定义组件封装的自定义事件,必须事先在emits 节点中声明,示例代码如下:
- 触发自定义事件
- 在 emits 节点下声明的自定义事件,可以通过this.$emit('自定义事件的名称') 方法进行触发,示例代码如下:
- 监听自定义事件
- 在使用自定义的组件时,可以通过v-on 的形式监听自定义事件。示例代码如下:
- 自定义事件传参
- 在调用this.$emit() 方法触发自定义事件时,可以通过第 2 个参数为自定义事件传参,示例代码如下:
组件上的 v-model
- 为什么需要在组件上使用 v-model
- model 是双向数据绑定指令,当需要维护组件内外数据的同步时,可以在组件上使用v-model 指令。示意图如下:
- 外界数据的变化会自动同步到counter 组件中
- counter 组件中数据的变化,也会自动同步到外界
- model 是双向数据绑定指令,当需要维护组件内外数据的同步时,可以在组件上使用v-model 指令。示意图如下:
- 在组件上使用 v-model 的步骤
- 父传子
- ① 父组件通过 v-bind: 属性绑定的形式,把数据传递给子组件
- ② 子组件中,通过 props 接收父组件传递过来的数据
- 子传父
- ① 父组件在 v-bind: 指令之前添加 v-model 指令
- ② 在子组件中声明 emits 自定义事件,格式为 update:xxx
- ③ 调用 $emit() 触发自定义事件,更新父组件中的数据
- 父传子
任务列表案例
- ① 使用vite 初始化项目
- ② 梳理项目结构
- ③ 封装todo-list 组件
- ④ 封装todo-input 组件
- ⑤ 封装todo-button 组件
组件高级
watch 侦听器
- 什么是 watch 侦听器
- watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。例如,监视用户名的变化并发起请求,判断用户名是否可用。
- watch 侦听器的基本语法
- 开发者需要在watch 节点下,定义自己的侦听器。实例代码如下:
- 使用 watch 检测用户名是否可用
- 监听 username 值的变化,并使用 axios 发起Ajax 请求,检测当前输入的用户名是否可用:
- immediate 选项
- 默认情况下,组件在初次加载完毕后不会调用watch 侦听器。如果想让watch 侦听器立即被调用,则需要使用 immediate 选项。实例代码如下:
- deep 选项
- 当 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用deep 选项, 代码示例如下:
- 监听对象单个属性的变化
- 如果只想监听对象中单个属性的变化,则可以按照如下的方式定义watch 侦听器:
- 计算属性 vs 侦听器
- 计算属性和侦听器侧重的应用场景不同:
- 计算属性侧重于监听多个值的变化,最终计算并返回一个新值
- 侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值
组件的生命周期
- 组件运行的过程
- 组件的生命周期指的是:组件从创建 -> 运行(渲染) -> 销毁的整个过程,强调的是一个时间段。
- 如何监听组件的不同时刻
- vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。例如:
- ① 当组件在内存中被创建完毕之后,会自动调用 created 函数
- ② 当组件被成功的渲染到页面上之后,会自动调用mounted 函数
- ③ 当组件被销毁完毕之后,会自动调用 unmounted 函数
- vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。例如:
- 如何监听组件的更新
- 当组件的data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证View 视图展示的数据和
- Model 数据源保持一致。
- 当组件被重新渲染完毕之后,会自动调用 updated 生命周期函数。
- 组件中主要的生命周期函数
- 注意:在实际开发中,created 是最常用的生命周期函数!
- 组件中全部的生命周期函数
- 疑问:为什么不在beforeCreate 中发 ajax 请求初始数据?
- 答:data数据未被创建,无法存储 ajax 数据
- 完整的生命周期图示
组件之间的数据共享
- 组件之间的关系
- 在项目开发中,组件之间的关系分为如下 3 种:
- ① 父子关系
- ② 兄弟关系
- ③ 后代关系
- 父子组件之间的数据共享
- 概况
- 父子组件之间的数据共享又分为:
- ① 父 -> 子共享数据
- ② 子 -> 父共享数据
- ③ 父 <-> 子双向数据同步
- 父组件向子组件共享数据
- 父组件通过v-bind 属性绑定向子组件共享数据。同时,子组件需要使用props 接收数据。示例代码如下:
- 子组件向父组件共享数据
- 子组件通过自定义事件的方式向父组件共享数据。示例代码如下:
- 父子组件之间数据的双向同步
- 父组件在使用子组件期间,可以使用v-model 指令维护组件内外数据的双向同步:
- 概况
- 兄弟组件之间的数据共享
- 兄弟组件之间实现数据共享的方案是EventBus。可以借助于第三方的包 mitt 来创建eventBus 对象,从而实现兄弟组件之间的数据共享。示意图如下:
- 安装 mitt 依赖包
- 在项目中运行如下的命令,安装mitt 依赖包:
jsnpm i mitt
- 创建公共的 EventBus 模块
- 在项目中创建公共的eventBus 模块如下:
- 在数据接收方自定义事件
- 在数据接收方,调用bus.on('事件名称', 事件处理函数) 方法注册一个自定义事件。示例代码如下:
- 在数据接发送方触发事件
- 在数据发送方,调用bus.emit('事件名称', 要发送的数据) 方法触发自定义事件。示例代码如下:
- 后代关系组件之间的数据共享
- 后代关系组件之间共享数据,指的是父节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂, 可以使用provide 和 inject 实现后代关系组件之间的数据共享。
- 父节点通过 provide 共享数据
- 父节点的组件可以通过provide 方法,对其子孙组件共享数据:
- 子孙节点通过 inject 接收数据
- 子孙节点可以使用 inject 数组,接收父级节点向下共享的数据。示例代码如下:
- 父节点对外共享响应式的数据
- 父节点使用provide 向下共享数据时,可以结合 computed 函数向下共享响应式的数据。示例代码如下:
- 子孙节点使用响应式的数据
- 如果父级节点共享的是响应式的数据,则子孙节点必须以.value 的形式进行使用。示例代码如下:
- vuex
- 概述
- 组件之间的共享数据方式
- 父向子传值: v-bind 属性绑定
- 子向父传值: v-on 事件绑定
- 兄弟组件之间共享数据: EventBus
- $on 接收数据的那个组件
- $emit 发送数据的那个组件
- 后代关系组件之间传值: provide/inject
- vuex的好处
- 能够在vuex中集中管理共享的数据,易于开发和后期维护
- 能够高效地实现组件之间的数据共享,提高开发效率
- 存储在vuex中的数据,都是响应式的,能够实时保持数据与页面的同步
- 什么样的数据适合存储在唉vuex中
- 一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;对于组件中的私有数据,依旧存储在组件自身的data中即可.
- 组件之间的共享数据方式
- vuex的基本使用
- 安装vuex依赖包
- npm i vuex -D
- 导入vuex包
jsimport Vuex from 'vuex' Vue.use(Vuex)
- 创建store对象
jsconst store = new Vuex.Store({ // state 中存放的就是全局共享的数据 state:{ count:0 } })
- 将store 对象挂载到vue实例中
jsnew Vue({ el:'#app', render:h=>h(app), router, // 将创建的共享数据对象,挂载到Vue实例中 // 所有的组件,就可以直接从 store 中获取全局的数据了 })
- 概述
vue 3.x 中全局配置 axios
- 为什么要全局配置 axios
- 在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:
- ① 每个组件中都需要导入 axios(代码臃肿)
- ② 每次发请求都需要填写完整的请求路径(不利于后期的维护)
- 如何全局配置 axios
- 在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios,示例代码如下:
购物车案例
ref 引用
- 什么是 ref 引用
- ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
- 每个vue 的组件实例上,都包含一个$refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下, 组件的$refs 指向一个空对象。
- 使用 ref 引用 DOM 元素
- 如果想要使用ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
- 使用 ref 引用组件实例
- 如果想要使用ref 引用页面上的组件实例,则可以按照如下的方式进行操作:
- 控制文本框和按钮的按需切换
- 通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。示例代码如下:
- 让文本框自动获得焦点
- 当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加ref 引用,并调用原生 DOM 对象的.focus() 方法即可。示例代码如下:
- this.$nextTick(cb) 方法
- 组件的$nextTick(cb) 方法,会把cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的DOM 异步地重新渲染完成后,再执行cb 回调函数。从而能保证cb 回调函数可以操作到最新的 DOM 元素。
动态组件
- 什么是动态组件
- 动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的
<component>
组件,专门用来实现组件的动态渲染。- ①
<component>
是组件的占位符 - ② 通过is 属性动态指定要渲染的组件名称
- ③
<component is="要渲染的组件的名称"></component>
- ①
- 动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的
- 如何实现动态组件渲染
- 使用 keep-alive 保持状态
- 默认情况下,切换动态组件时无法保持组件的状态。此时可以使用vue 内置的
<keep-alive>
组件保持动态组件的状态。示例代码如下:
- 默认情况下,切换动态组件时无法保持组件的状态。此时可以使用vue 内置的
插槽
什么是插槽
- 插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。
- 可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
体验插槽的基础用法
- 在封装组件时,可以通过
<slot>
元素定义插槽,从而为用户预留内容占位符。示例代码如下: - 没有预留插槽的内容会被丢弃
- 后备内容
- 封装组件时,可以为预留的
<slot>
插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。示例代码如下:
- 封装组件时,可以为预留的
- 具名插槽
- 如果在封装组件时需要预留多个插槽节点,则需要为每个
<slot>
插槽指定具体的name 名称。这种带有具体名称的插槽叫做“具名插槽”。示例代码如下: - 注意:没有指定 name 名称的插槽,会有隐含的名称叫做 “default”。
- 为具名插槽提供内容
- 在向具名插槽提供内容的时候,我们可以在一个
<template>
元素上使用v-slot 指令,并以v-slot 的参数的形式提供其名称。示例代码如下:
- 在向具名插槽提供内容的时候,我们可以在一个
- 具名插槽的简写形式
- 跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容(v-slot:) 替换为字符#。例如v-slot:header可以被重写为#header
- 如果在封装组件时需要预留多个插槽节点,则需要为每个
- 作用域插槽
- 在封装组件的过程中,可以为预留的
<slot>
插槽绑定props 数据,这种带有props 数据的<slot>
叫做“作用域插槽”。示例代码如下: - 解构作用域插槽的 Prop
- 作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。示例代码如下:
- 声明作用域插槽
- 在封装MyTable 组件的过程中,可以通过作用域插槽把表格每一行的数据传递给组件的使用者。示例代码如下:
- 使用作用域插槽
- 在使用MyTable 组件时,自定义单元格的渲染方式,并接收作用域插槽对外提供的数据。示例代码如下:
- 在封装组件的过程中,可以为预留的
- 在封装组件时,可以通过
自定义指令
- 什么是自定义指令
- vue 官方提供了v-for、v-model、v-if 等常用的内置指令。除此之外vue 还允许开发者自定义指令。
- vue 中的自定义指令分为两类,分别是:
- 私有自定义指令
- 全局自定义指令
- 声明私有自定义指令的语法
- 在每个vue 组件中,可以在directives 节点下声明私有自定义指令。示例代码如下:
- 使用自定义指令
- 在使用自定义指令时,需要加上v- 前缀。示例代码如下:
- 声明全局自定义指令的语法
- 全局共享的自定义指令需要通过“单页面应用程序的实例对象”进行声明,示例代码如下:
- updated 函数
- mounted 函数只在元素第一次插入 DOM 时被调用,当 DOM 更新时mounted 函数不会被触发。 updated函数会在每次 DOM 更新完成后被调用。示例代码如下:
- 注意:在vue2 的项目中使用自定义指令时,【 mounted -> bind 】【 updated -> update 】
- 函数简写
- 如果mounted 和updated 函数中的逻辑完全相同,则可以简写成如下格式:
- 指令的参数值
- 在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值,示例代码如下:
Table 案例
组件高级总结
① 能够说出什么是单页面应用程序及组件化开发
- SPA、只有一个页面、组件是对 UI 结构的复用
② 能够说出 .vue 单文件组件的组成部分
- template、script、style(scoped、lang)
能够知道如何注册 vue 的组件
- 全局注册(app.component)、局部注册(components)
④ 能够知道如何声明组件的props 属性
- props 数组
④ 能够知道如何在组件中进行样式绑定
- 动态绑定class、动态绑定 style
① 能够知道如何对 props 进行验证
- 数组格式、对象格式
- type、default、required、validator
- computed 节点、必须 return 一个结果、缓存计算结果
③ 能够知道如何为组件绑定自定义事件
- v-on 绑定自定义事件、emits、$emit()
④ 能够知道如何在组件上使用 v-model
- 应用场景:实现组件内外的数据同步
- v-model:props名称、emits、$emit('update:props名称')
① 能够知道如何使用 ref 引用 DOM 和组件实例
- 通过ref 属性指定引用的名称、使用this.$refs 访问引用实例
② 能够知道 $nextTick 的调用时机
- 组件的 DOM 更新之后,才执行$nextTick 中的回调
③ 能够说出 keep-alive 元素的作用
- 保持动态组件的状态
④ 能够掌握插槽的基本用法
<slot>
标签、具名插槽、作用域插槽、v-slot: 简写为#
⑤ 能够知道如何自定义指令
- 私有自定义指令、全局自定义指令
路由
前端路由的概念与原理
- 什么是路由
- 路由(英文:router)就是对应关系。路由分为两大类:
- ① 后端路由
- ② 前端路由
- 回顾:后端路由
- 后端路由指的是:请求方式、请求地址与function 处理函数之间的对应关系。在node.js 课程中,express路由的基本用法如下:
- SPA 与前端路由
- SPA 指的是一个web 网站只有唯一的一个HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现。
- 结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!
- 什么是前端路由
- 通俗易懂的概念:Hash 地址与组件之间的对应关系。
- 前端路由的工作方式
- ① 用户点击了页面上的路由链接
- ② 导致了 URL 地址栏中的Hash 值发生了变化
- ③ 前端路由监听了到Hash 地址的变化
- ④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
- 结论:前端路由,指的是Hash 地址与组件之间的对应关系!
- 实现简易的前端路由
- 步骤1:导入并注册MyHome、MyMovie、MyAbout 三个组件。示例代码如下:
- 步骤2:通过
<component>
标签的 is 属性,动态切换要显示的组件。示例代码如下: - 步骤3:在组件的结构中声明如下 3 个
<a>
链接,通过点击不同的<a>
链接,切换浏览器地址栏中的Hash 值: - 步骤4:在created 生命周期函数中监听浏览器地址栏中Hash 地址的变化,动态切换要展示的组件的名称:
vue-router 的基本用法
什么是 vue-router
- vue-router 是 vue.js 官方给出的路由解决方案。它只能结合vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
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/
- vue-router 目前有 3.x 的版本和 4.x 的版本。其中:
vue-router 4.x 的基本使用步骤
- ① 在项目中安装vue-router
- 在 vue3 的项目中,只能安装并使用vue-router 4.x。安装的命令如下:
jsnpm i vue-router
- ② 定义路由组件
- 在项目中定义MyHome.vue、MyMovie.vue、MyAbout.vue 三个组件,将来要使用vue-router 来控制它们的展示与切换:
- ③ 声明路由链接和占位符
- 可以使用
<router-link>
标签来声明路由链接,并使用<router-view>
标签来声明路由占位符。示例代码如下:
- 可以使用
- ④ 创建路由模块
- 在项目中创建router.js 路由模块,在其中按照如下 4 个步骤创建并得到路由的实例对象:
- ① 从 vue-router 中按需导入两个方法
- ② 导入需要使用路由控制的组件
- ③ 创建路由实例对象
- ④ 向外共享路由实例对象
- 在项目中创建router.js 路由模块,在其中按照如下 4 个步骤创建并得到路由的实例对象:
- ⑤ 在 main.js 中导入并挂载路由模块
- ① 在项目中安装vue-router
vue-router 的高级用法
- 路由重定向
- 路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址C ,从而展示特定的组件页面。通过路由规则的redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
- 路由高亮
- ① 使用默认的高亮class 类
- 被激活的路由链接,默认会应用一个叫做router-link-active 的类名。开发者可以使用此类名选择器,为激活的路由链接设置高亮的样式:
- ② 自定义路由高亮的class 类
- 在创建路由的实例对象时,开发者可以基于 linkActiveClass 属性,自定义路由链接被激活时所应用的类名:1
- ① 使用默认的高亮class 类
- 嵌套路由
- 通过路由实现组件的嵌套展示,叫做嵌套路由。
- ① 声明子路由链接和子路由占位符
- 在 About.vue 组件中,声明tab1 和 tab2 的子路由链接以及子路由占位符。示例代码如下:
- ② 在父路由规则中,通过children 属性嵌套声明子路由规则
- 在 router.js 路由模块中,导入需要的组件,并使用children 属性声明子路由规则。示例代码如下:
- 注意:子路由规则的path 不要以/ 开头!
- ① 声明子路由链接和子路由占位符
- 通过路由实现组件的嵌套展示,叫做嵌套路由。
- 动态路由匹配
- 思考:有如下 3 个路由链接:
- 定义如下 3 个路由规则,是否可行???
- 缺点:路由规则的复用性差 。
- 动态路由的概念
- 动态路由指的是:把Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下:
- $route.params 参数对象
- 通过动态路由匹配的方式渲染出来的组件中,可以使用$route.params 对象访问到动态匹配的参数值。
- 使用 props 接收路由参数
- 为了简化路由参数的获取形式,vue-router 允许在路由规则中开启props 传参。示例代码如下:
- 思考:有如下 3 个路由链接:
- 编程式导航
- 通过调用API 实现导航的方式,叫做编程式导航。与之对应的,通过点击链接实现导航的方式,叫做声明式导航。例如:
- 普通网页中点击
<a>
链接、vue 项目中点击<router-link>
都属于声明式导航 - 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
- 普通网页中点击
- vue-router 中的编程式导航 API
- vue-router 提供了许多编程式导航的API,其中最常用的两个 API 分别是:
- ① this.$router.push('hash 地址')
- 跳转到指定Hash 地址,从而展示对应的组件
- 调用this.$router.push() 方法,可以跳转到指定的hash 地址,从而展示对应的组件页面。示例代码如下:
- ② this.$router.go(数值 n)
- 实现导航历史的前进、后退
- 调用this.$router.go() 方法,可以在浏览历史中进行前进和后退。示例代码如下:
- ① this.$router.push('hash 地址')
- vue-router 提供了许多编程式导航的API,其中最常用的两个 API 分别是:
- 通过调用API 实现导航的方式,叫做编程式导航。与之对应的,通过点击链接实现导航的方式,叫做声明式导航。例如:
- 命名路由
- 概念
- 通过name 属性为路由规则定义名称的方式,叫做命名路由。示例代码如下:
- 注意:命名路由的name 值不能重复,必须保证唯一性!
- 使用命名路由实现声明式导航
- 为
<router-link>
标签动态绑定to 属性的值,并通过name 属性指定要跳转到的路由规则。期间还可以用 - params 属性指定跳转期间要携带的路由参数。示例代码如下:
- 为
- 使用命名路由实现编程式导航
- 调用push 函数期间指定一个配置对象,name 是要跳转到的路由规则、params 是携带的路由参数:
- 概念
- 导航守卫
- 导航守卫可以控制路由的访问权限。示意图如下:
- 如何声明全局导航守卫
- 全局导航守卫会拦截每个路由规则,从而对每个路由进行访问权限的控制。可以按照如下的方式定义全局导航守卫
- 守卫方法的 3 个形参
- 全局导航守卫的守卫方法中接收 3 个形参,格式为:
- 注意:
- ① 在守卫方法中如果不声明next 形参,则默认允许用户访问每一个路由!
- ② 在守卫方法中如果声明了 next 形参,则必须调用 next() 函数,否则不允许用户访问任何一个路由!
- next 函数的 3 种调用方式
- 参考示意图,分析next 函数的 3 种调用方式最终导致的结果:
- 直接放行:next()
- 强制其停留在当前页面:next(false)
- 强制其跳转到登录页面:next('/login')
- 结合 token 控制后台主页的访问权限
后台管理案例
![pdf](./08_%E8%B7%AF%E7%94%B1%E6%A1%88%E4%BE%8B.pdf)
Vue综合案例
vue-cli
什么是 vue-cli
- vue-cli(俗称:vue 脚手架)是vue 官方提供的、快速生成vue 工程化项目的工具。
- 特点:
- ① 开箱即用
- ② 基于webpack
- ③ 功能丰富且易于扩展
- ④ 支持创建vue2 和 vue3 的项目
- vue-cli 的中文官网首页:https://cli.vuejs.org/zh/
安装 vue-cli
- vue-cli 是基于Node.js 开发出来的工具,因此需要使用npm 将它安装为全局可用的工具:
解决 Windows PowerShell 不识别 vue 命令的问题
- 默认情况下,在PowerShell 中执行vue --version 命令会提示如下的错误消息:
- 解决方案如下:
- ① 以管理员身份运行 PowerShell
- ② 执行 set-ExecutionPolicy RemoteSigned 命令
- ③ 输入字符Y ,回车即可
创建项目
- vue-cli 提供了创建项目的两种方式:
基于 vue ui 创建 vue 项目
- 步骤1:在终端下运行 vue ui 命令,自动在浏览器中打开创建项目的可视化面板:
- 步骤2:在详情页面填写项目名称:
- 步骤3:在预设页面选择手动配置项目:
- 步骤4:在功能页面勾选需要安装的功能(Choose Vue Version、Babel、CSS 预处理器、使用配置文件):
- 步骤5:在配置页面勾选vue 的版本和需要的预处理器:
- 步骤6:将刚才所有的配置保存为预设(模板),方便下一次创建项目时直接复用之前的配置:
- 步骤7:创建项目并自动安装依赖包:
- vue ui 的本质:通过可视化的面板采集到用户的配置信息后,在后台基于命令行的方式自动初始化项目:
- 项目创建完成后,自动进入项目仪表盘:
基于命令行创建 vue 项目
- 步骤1:在终端下运行 vue create demo2 命令,基于交互式的命令行创建vue 的项目:
- 步骤2:选择要安装的功能:
- 步骤3:使用上下箭头选择vue 的版本,并使用回车键确认选择:
- 步骤4:使用上下箭头选择要使用的 css 预处理器,并使用回车键确认选择:
- 步骤5:使用上下箭头选择如何存储插件的配置信息,并使用回车键确认选择:
- 步骤6:是否将刚才的配置保存为预设:
- 步骤7:选择如何安装项目中的依赖包:
- 步骤8:开始创建项目并自动安装依赖包:
- 步骤9:项目创建完成:
梳理 vue2 项目的基本结构
- 主要的文件:
- src -> App.vue
- src -> main.js
分析 vue2 main.js 中的主要代码
在 vue2 的项目中使用路由
- 在 vue2 的项目中,只能安装并使用 3.x 版本的vue-router。
- 版本 3 和版本 4 的路由最主要的区别:创建路由模块的方式不同!
- 回顾:4.x 版本的路由如何创建路由模块
- 学习:3.x 版本的路由如何创建路由模块
- 步骤1:在vue2 的项目中安装 3.x 版本的路由:
- 步骤2:在 src -> components 目录下,创建需要使用路由切换的组件:
- 步骤3:在 src 目录下创建router -> index.js 路由模块:
- 步骤4:在main.js 中导入路由模块,并通过router 属性进行挂载:
- 步骤5:在App.vue 根组件中,使用
<router-view>
声明路由的占位符:
组件库
什么是 vue 组件库
- 在实际开发中,前端开发者可以把自己封装的.vue 组件整理、打包、并发布为npm 的包,从而供其他人下载和使用。这种可以直接下载并在项目中使用的现成组件,就叫做vue 组件库。
vue 组件库和 bootstrap 的区别
- 二者之间存在本质的区别:
- bootstrap 只提供了纯粹的原材料( css 样式、HTML 结构以及 JS 特效),需要由开发者做进一步的组装和改造
- vue 组件库是遵循 vue 语法、高度定制的现成组件,开箱即用
- 二者之间存在本质的区别:
最常用的 vue 组件库
- ① PC 端
- Element UI(https://element.eleme.cn/#/zh-CN)
- View UI(http://v1.iviewui.com/)
- ② 移动端
- ① PC 端
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)
- 在 vue2 的项目中安装 element-ui
jsnpm i element-ui -S
- 引入 element-ui
- 开发者可以一次性完整引入所有的element-ui 组件,或是根据需求,只按需引入用到的element-ui 组件:
- 完整引入:操作简单,但是会额外引入一些用不到的组件,导致项目体积过大
- 按需引入:操作相对复杂一些,但是只会引入用到的组件,能起到优化项目体积的目的
- 完整引入
- 在 main.js 中写入以下内容:
- 按需引入
- 借助babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
- 步骤1,安装babel-plugin-component:js
npm i babel-plugin-component -D
- 步骤2,修改根目录下的babel.config.js 配置文件,新增plugins 节点如下:
- 步骤3,如果你只希望引入部分组件,比如Button,那么需要在 main.js 中写入以下内容:
- 把组件的导入和注册封装为独立的模块
- 在 src 目录下新建element-ui/index.js 模块,并声明如下的代码:
axios 拦截器
回顾:在 vue3 的项目中全局配置axios
在 vue2 的项目中全局配置 axios
- 需要在main.js 入口文件中,通过Vue 构造函数的prototype 原型对象全局配置 axios:
什么是拦截器
- 拦截器(英文:Interceptors)会在每次发起 ajax 请求和得到响应的时候自动被触发。
- 应用场景:
- ① Token 身份认证
- ② Loading 效果
- ③ etc…
配置请求拦截器
- 通过 axios.interceptors.request.use(成功的回调, 失败的回调) 可以配置请求拦截器。示例代码如下:
- 注意:失败的回调函数可以被省略!
请求拦截器 – Token 认证
请求拦截器 – 展示 Loading 效果
- 借助于element ui 提供的 Loading 效果组件(https://element.eleme.cn/#/zh-CN/component/loading)可以方便的实现 Loading 效果的展示:
配置响应拦截器
- 通过 axios.interceptors.response.use(成功的回调, 失败的回调) 可以配置响应拦截器。示例代码如下:
- 注意:失败的回调函数可以被省略!
响应拦截器 – 关闭 Loading 效果
- 调用 Loading 实例提供的close() 方法即可关闭 Loading 效果,示例代码如下:
proxy 跨域代理
- 回顾:接口的跨域问题
- vue 项目运行的地址:http://localhost:8080/
- API 接口运行的地址:https://www.escook.cn/api/users
- 由于当前的 API 接口没有开启 CORS 跨域资源共享,因此默认情况下,上面的接口无法请求成功!
- 通过代理解决接口的跨域问题
- 通过vue-cli 创建的项目在遇到接口跨域问题时,可以通过代理的方式来解决:
- ① 把 axios 的请求根路径设置为vue 项目的运行地址(接口请求不再跨域)
- ② vue 项目发现请求的接口不存在,把请求转交给proxy 代理
- ③ 代理把请求根路径替换为devServer.proxy 属性的值,发起真正的数据请求
- ④ 代理把请求到的数据,转发给 axios
- 在项目中配置 proxy 代理
- 步骤1,在main.js 入口文件中,把 axios 的请求根路径改造为当前web 项目的根路径:
- 步骤2,在项目根目录下创建vue.config.js 的配置文件,并声明如下的配置:
- 注意:
- ① devServer.proxy 提供的代理功能,仅在开发调试阶段生效
- ② 项目上线发布时,依旧需要API 接口服务器开启 CORS 跨域资源共享