一、前端工程化与webpack

1.前端工程化

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

2. 什么是前端工程化

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

2.webpack的基本使用

1. 什么是 webpack:是前端项目工程化的具体解决方案

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

3. 在全局项目中安装 webpack

【这里注意:后面使用到webpack-dev-server这个插件的时候,会报错,需要将webpack-cli进行修改,将重新安装为最新版本】:npm i webpack-cli 

4. 创建名为 webpack.config.js 的 webpack 配置文件:在项目中配置 webpack

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

② 在 package.json 的 scripts 节点下,新增 dev 脚本如下:("dev”是脚本的名字,是可以修改的,“webpack”是固定写法)

③ 在终端中运行 npm run dev 命令,启动 webpack 进行项目的打包构建(创建了一个dist的文件夹,里面有一个main.js)

4.1 mode 的可选值mode:development、production(对代码进行压缩)

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

① development(项目开发过程中使用)

⚫ 开发环境

⚫ 不会对打包生成的文件进行代码压缩和性能优化

⚫ 打包速度快,适合在开发阶段使用

② production(项目上线的时候使用)

⚫ 生产环境

⚫ 会对打包生成的文件进行代码压缩和性能优化

⚫ 打包速度很慢,仅适合在项目发布阶段使用

mode 用来指定构建模式 可以选中有development 和 production

结论:开发的时候一定使用development ,因为追求的是打包的速度,而不是体积

反过来,发布上线的时候一定要用production ,因为上线追求的是体积小,而不是打包速度。

4.2 webpack.config.js 文件的作用

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

【在npm run dev(这个dev是在package.json文件中的script脚本中的,它会先去读取webpack.config.js这个文件,然后再进行配置】

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

4.3 webpack 中的默认约定:默认打包入口文件为src->index.js,打包输出文件为dist->main.js

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

在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口。通过 output 节点指定打包的出口。 示例代码如下:(记得先将前面生成的dist文件夹删除)


const path=require('path')//使用Node.js 中的导出语法,向外导出一个webpack的配置对象
module.exports={// mode 用来指定构建模式 可以选中有development 和 production// 结论:开发的时候一定使用development ,因为追求的是打包的速度,而不是体积// 反过来,发布上线的时候一定要用production ,因为上线追求的是体积小,而不是打包速度。mode:'development',entry:path.join(__dirname,'./src/index.js'),//输出文件的名称//指定生成的文件要存放的位置output:{// 存放的目录path: path.join(__dirname,'dist'),// 生成的文件名filename:'bundle.js'}}

    <!-- 加载和引用内存中的main.js 因为每一次一修改代码,run一下后,实际上dist中的main.js文件是存放再内存中 --><!-- 并且是存放再根目录下的内存中 --><script src="../dist/bundle.js"></script>

3.webpack中的插件【都是将产生的文件存放在内存中】

1. webpack 插件的作用

2. webpack-dev-server:自动更新dist文件夹中的bundle.js

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

2.1 安装 webpack-dev-server

2.2 配置 webpack-dev-server(3.11.2版本的)

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

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

③ 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果(记得再这个网址才可以看到)

 实际上,代码修改后,执行run,dist中的bundle.js并没有被修改,而是再根目录下的bundle.js被修改,所以我们要使用的是根目录下的bundle.js而且不是dist中的bundle.js。

    <!-- 加载和引用内存中的bundle.js 因为每一次一修改代码,run一下后,实际上dist中的bundle.js文件是存放再内存中 --><!-- 并且是存放再根目录下的内存中 --><!-- 一定要加上“/” 如果不加就错误 --><script src="/bundle.js"></script>//表示根目录下的bundle.js

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 文件

3. html-webpack-plugin:将页面文件放在根目录中,才不用每次进入src中才可以查看页面

3.1 安装 html-webpack-plugin(版本为5.3.2)

3.2 配置 html-webpack-plugin

// 1. 导入 html-webpack-plugin 这个插件,得到插件的构造函数
const HtmlPlugin = require('html-webpack-plugin')
// 2. new 构造函数,创建插件的实例对象
const htmlPlugin = new HtmlPlugin({// 指定要复制哪个页面template: './src/index.html',// 指定复制出来的文件名和存放路径filename: './index.html'
})module.exports = {plugins:[htmlPlugin],
}

3.3 解惑 html-webpack-plugin

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

HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件【所以可以不用在index.html文件中引入script文件路径】

4. devServer 节点 【自动打开浏览器】

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

module.exports = {devServer:{// 首次打包成功后,自动打开浏览器open:true,//原来打开浏览器的时候,地址是:--http://localhost:8080/// 在 http 协议中,如果端口号是 80,则可以被省略port:80,// 指定运行的主机地址//地址栏出现为:http://127.0.0.1/host: '127.0.0.1'}
}

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

4.webpack中的loader(加载器)

1. loader 概述

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

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

比如:

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

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

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

2. loader 的调用过程

在导入css文件并且没有导包前:

修改后面 run 一下:报错

3. 打包处理 css 文件

① 运行 npm i style-loader@3.0.0 css-loader@5.2.6 -D 命令,安装处理 css 文件的 loader ② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

module.exports = {module:{rules:[// 定义了不同模块对应的 loader//use指定的顺序不能改变,要按顺序(从后往前调用){test:/\.less$/,use:['style-loader','css-loader']}]}
}

4. 打包处理 less 文件

① 运行 npm i less-loader@10.0.1 less@4.1.1 -D 命令

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

 


// 导入样式(在 webpack 中,一切皆模块,都可以通过 ES6 导入语法进行导入和使用)
// 如果某个模块中,使用 from 接收到的成员为 undefined,则没必要进行接收
//因为index1.js文件是模块导入的入口文件,并且bundle.js是由本文件(index1.js)产生的
//所以在这个文件导入样式
import'./css/index.css';module.exports = {module:{rules:[// 处理 .less 文件的 loader{test:/\.less$/,use:['style-loader','css-loader','less-loader']}]}
}

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

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

⚫ 多个 loader 的调用顺序是:从后往前调用

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

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

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

如果需要调用的 loader 只有一个,则只传递一个字符串也行,如果有多个loader,则必须指定数组

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

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

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

图片base64:将图片转换为base64的形式:console.log(logo)


// 导入样式(在 webpack 中,一切皆模块,都可以通过 ES6 导入语法进行导入和使用)
// 如果某个模块中,使用 from 接收到的成员为 undefined,则没必要进行接收
//因为index1.js文件是模块导入的入口文件,并且bundle.js是由本文件(index1.js)产生的
//所以在这个文件导入样式
import'./css/index.less';module.exports = {module:{rules:[// 处理图片文件的 loader// 如果需要调用的 loader 只有一个,则只传递一个字符串也行,如果有多个loader,则必须指定数组// 在配置 url-loader 的时候,多个参数之间,使用 & 符号进行分隔{ test: /\.jpg|png|gif$/, use: 'url-loader' },]}
}

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

6.1 安装 babel-loader 相关的包

运行如下的命令安装对应的依赖包: npm i babel-loader@8.2.2 @babel/core@7.14.6 @babel/plugin-proposal-decorators@7.14.5 -D

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

6.2 配置 babel-loade【创建名为bable.config.js跟webpack.config.js同级】

在项目根目录下,创建名为 babel.config.js 的配置文件,定义 Babel 的配置项如下:

module.exports = {// 声明 babel 可用的插件// 将来,webpack 在调用 babel-loader 的时候,会先加载 plugins 插件来使用plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]]}

5.打包发布

1. 为什么要打包发布

2. 配置 webpack 的打包发布

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

--model 是一个参数项,用来指定 webpack 的运行模式。

production 代表生产环境,会对打包生成的文件 进行代码压缩和性能优化。

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

  "scripts": {"dev": "webpack serve","bulid":"webpack --mode production"},

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

//   __dirname--当前文件所处的根目录// entry: '指定要处理哪个文件'entry:path.join(__dirname,'./src/index1.js'),// 指定生成的文件要存放到哪里output:{// 存放的目录path:path.join(__dirname,'./dist'),// 生成的文件名//表示新产生的文件存放在dist文件夹下的js文件夹目录下filename:'js/bundle.js'},

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

   module:{rules:[// 处理图片文件的 loader// 如果需要调用的 loader 只有一个,则只传递一个字符串也行,如果有多个loader,则必须指定数组// 在配置 url-loader 的时候,多个参数之间,使用 & 符号进行分隔// &outputPath=images--表示生成文件的存放路径{ test: /\.jpg|png|gif$/, use: 'url-loader?limit=470&outputPath=images' },]}

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

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


// 注意:左侧的 { } 是解构赋值
const { CleanWebpackPlugin } = require('clean-webpack-plugin')// 3. 插件的数组,将来 webpack 在运行时,会加载并调用这些插件plugins:[htmlPlugin, new CleanWebpackPlugin()],

6.Source Map

1. 生产环境遇到的问题

2. 什么是 Source Map:一个信息文件,里面储存着位置信息

3. webpack 开发环境下的 Source Map

3.1 默认 Source Map 的问题:记录是生成后的代码位置

              3.2 解决默认 Source Map 的问题:在webpack.config.js文件中进行配置

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

module.exports = {// 在开发调试阶段,建议大家都把 devtool 的值设置为 eval-source-map// devtool: 'eval-source-map',// 在实际发布的时候,建议大家把 devtool 的值设置为 nosources-source-map 或直接关闭 SourceMapdevtool:'nosources-source-map',// mode 代表 webpack 运行的模式,可选值有两个 development 和 production// 结论:开发时候一定要用 development,因为追求的是打包的速度,而不是体积;// 反过来,发布上线的时候一定能要用 production,因为上线追求的是体积小,而不是打包速度快!mode: 'development',
}

4. webpack 生产环境下的 Source Map【要关闭Source Map】

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

4.1 只定位行数不暴露源码: devtool:'nosources-source-map'

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

4.2 定位行数且暴露源码:devtool:'source-map'

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

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

5. Source Map 的最佳实践

① 开发环境下:

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

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

② 生产环境下:

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

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

实际开发中需要自己配置 webpack 吗?

@的使用:代表src这个根目录【使用前要先进行配置】

配置代码:在webpack.config.js这个文件中进行配置的

 module.exports = {resolve:{alias:{// 告诉 webpack,程序员写的代码中,@ 符号表示 src 这一层目录'@':path.join(__dirname,'./src/')}}
}

演示代码:

// import'./css/index.css';
//跟下面等价
import'@/css/index.css';// import'./css/index.less';
//跟下面等价
import'@/css/index.less';// 导入 src/js/test/info.js
import '@/js/test/info.js'

总结

二、vue基础入门

1.vue简介

1. 什么是 vue

1. 构建用户界面
    用 vue 往 html 页面中填充数据,非常的方便
2. 框架
     框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
     要学习 vue,就是在学习 vue 框架中规定的用法!
      vue 的指令、组件(是对 UI 结构的复用)、路由、Vuex、vue 组件库
     只有把上面老师罗列的内容掌握以后,才有开发 vue 项目的能力!

2. vue 的特性

2.1 数据驱动视图(单向数据绑定-从服务器到客户端)

数据驱动视图:

+ 数据的变化会驱动视图自动更新
   + 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!

2.2 双向数据绑定

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

双向数据绑定:

> 在网页中,form 表单负责**采集数据**,Ajax 负责**提交数据**。

+ js 数据的变化,会被自动渲染到页面上
   + 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到 js 数据中开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值)

> 注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)

2.3 MVVM:是 vue 实现数据驱动视图和双向数据绑定的核心原理

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel, 它把每个 HTML 页面都拆分成了这三个部分,如图所示:

2.4 MVVM 的工作原理:把当前页面的数据源(Model)和页面的结构(View)连接在了一起

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

注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源【data】、View 视图【el】、ViewModel 【vm】就是 vue 的实例) 

3. vue 的版本

2.vue的基本使用

1. 基本使用步骤

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

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

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

        “el”---是指定当前要控制页面的哪一块(最好先用div将整个括起来,然后给div加上一个id,然后将el赋值给这个id)

<!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>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app">{{ username }}</div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><script>//创建Vue实例对象const vm=new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el:'#app',// data 对象就是要渲染到页面上的数据data:{username:'zhangsan'}})</script>
</body>
</html>

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

3.vue的调试工具

1. 安装 vue-devtools 调试工具

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

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

4.vue的指令与过滤器

1. 指令的概念

1.1 内容渲染指令

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>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app"><p v-text="username"></p><!-- “女”将“性别”覆盖住了 --><p v-text="gender">性别</p>
</div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><script>//创建Vue实例对象const vm=new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el:'#app',// data 对象就是要渲染到页面上的数据data:{username:'zhangsan',gender:'女'}})</script>
</body>
</html>

 {{ }} 语法插值表达式】:不会将默认值覆盖【开发中常用】

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

<!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>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app"><hr><p>姓名:{{ username }}</p><p>性别:{{ gender }}</p><hr></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><script>//创建Vue实例对象const vm=new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el:'#app',// data 对象就是要渲染到页面上的数据data:{username:'zhangsan',gender:'女'}})</script>
</body>
</html>

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

v-html

v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素, 则需要用到 v-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>
</head>
<body>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app"><div v-text="info"></div><div>{{ info }}</div><div v-html="info"></div></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><script>//创建Vue实例对象const vm=new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el:'#app',// data 对象就是要渲染到页面上的数据data:{username:'zhangsan',gender:'女',info: '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>'}})</script>
</body>
</html>

总结

1. `v-text` 指令的缺点:会覆盖元素内部原有的内容!
2. `{{ }}` 插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!
3. `v-html` 指令的作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!

1.2 属性绑定指令:v-bind:【记得加上冒号】

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

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><input type="text" v-bind:placeholder="tips"><hr><!-- vue 规定 v-bind: 指令可以简写为 : --><img v-bind:src="photo" alt=""></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {tips: '请输入用户名',photo:'https://www.h5w3.com/wp-content/uploads/2020/05/1460000022734939.png'}})</script>
</body></html>

属性绑定指令的简写形式

  vue 规定 v-bind: 指令可以简写为 ":"

 <img v-bind:src="photo" alt="">   等价于    <img :src="photo" alt="">

使用 Javascript 表达式

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

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><input type="text" v-bind:placeholder="tips"><hr><!-- vue 规定 v-bind: 指令可以简写为 : --><img v-bind:src="photo" alt=""><hr><div>1+2 的结果是:{{ 1+2 }}</div><div>{{ tips }}反转的结果是:{{ tips.split(' ').reverse().join() }}</div><!-- 记得给box加上单引号 --><!-- box相当于一个字符串 --><div :title="'box' + index">这是一个div</div></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {tips: '请输入用户名',photo:'https://www.h5w3.com/wp-content/uploads/2020/05/1460000022734939.png',index:3}})</script>
</body></html>

总结:

>  注意:插值表达式只能用在元素的**内容节点**中,不能用在元素的**属性节点**中

+ 在 vue 中,可以使用 `v-bind:` 指令,为元素的属性动态绑定值;

+ 简写是英文的 `:`

+ 在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:

<div :title=" 'box' + index">这是一个 div</div>

1.3 事件绑定指令:v-on【methods节点中进行声明】

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

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

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

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><p>count 的值是 {{ count }}</p><!-- <button v-on:click="add">+1</button> --><button @click="add">+1</button><!-- <button v-on:cilck="sub">-1</button> --><button @cilck="sub">-1</button></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {count: 0},// methods 的作用,就是给v-on定义事件的处理函数methods:{add(){//add:function(){console.log('ok');},sub(){//sub:function()console.log('触发了sub处理函数');}}})</script>
</body></html>

事件绑定的简写形式:@

由于 v-on 指令在开发中使用频率非常高,因此,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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><p>count 的值是 {{ count }}</p><!-- <button v-on:click="add">+1</button> --><button @click="add">+1</button><!-- <button v-on:cilck="sub">-1</button> --><button @cilck="sub">-1</button></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {count: 0},// methods 的作用,就是给v-on定义事件的处理函数methods:{add(){//add:function(){console.log('ok');},sub(){//sub:function()console.log('触发了sub处理函数');}}})</script>
</body></html>

事件参数对象:实例对象===this【使用this来调用】

在原生的 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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><p>count 的值是 {{ count }}</p><!-- <button v-on:click="add">+1</button> --><button @click="add">+1</button></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {count: 0},// methods 的作用,就是给v-on定义事件的处理函数methods:{add(){//add:function(){console.log(vm);// console.log(vm===this);//true// vm.count+=1;//当点击add的时候,count+1this.count+=1;}}}})</script>
</body></html>

绑定事件并传参:可以使用(参数)进行传参

    <!-- 当点击add的时候,一起将参数进行传入 --><button @click="add(4)">+4</button><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {count: 0},// methods 的作用,就是给v-on定义事件的处理函数methods:{add(n){//add:function(){// 在 methods 处理函数中,this 就是 new 出来的 vm 实例对象console.log(vm);// console.log(vm===this);//true// vm.count+=1;//当点击add的时候,count+1this.count+=1;//当点击add的时候,一起将参数进行传入this.count+=n;}}})</script>

      $event:表示原生的事件参数对象event

$event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event。$event 可以解决事件参数对象 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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><p>count 的值是:{{ count }}</p><!-- 如果 count 是偶数,则 按钮背景变成红色,否则,取消背景颜色 --><!-- <button @click="add(1)">+N</button> --><!-- vue 提供了内置变量,名字叫做 $event,它就是原生 DOM 的事件对象 e --><button @click="add($event, 1)">+N</button></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {count: 0},methods: {add(e,n){this.count+=n;console.log(e);// 判断 this.count 的值是否为偶数if(this.count%2==0){//偶数// e.target---鼠标点击事件e.target.style.backgroundColor = 'red'}else{//奇数e.target.style.backgroundColor = ''}}},})</script>
</body></html>

事件修饰符:【prevent,stop】@click.stop="divHandler"

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

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><a href="http://www.baidu.com" @click.prevent="show">跳转到百度页面</a><hr><div style="height: 150px;background-color: orange;padding-left: 100px;line-height: 150px;" @click.stop="divHandler"><button @click.stop="btnHandler">按钮</button></div></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {},methods:{show(e){// 阻止事件e的默认行为// e.preventDefault()console.log('点击了a');},btnHandler(){//btnHandler(e){e.stop();//阻止冒泡}console.log('btnHandler');},divHandler(){console.log('divHandler');}}})</script>
</body></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>
</head><body><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><!-- @keyup.esc="clearInput"---表示当我们按下键盘中的“esc”的时候,就触发clearInput事件 --><input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax" ></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {},methods:{clearInput(e){//e--接收事件console.log('触发了clearInput事件');e.target.value='';},commitAjax(){console.log('触发了commitAjax请求');}}})</script>
</body></html>

1.4 双向绑定指令:v-model

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

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --><div id="app"><p>用户的名字是:{{ username }}</p><!-- 当在页面对v-model中的文字进行修改,vm中的data也会被修改【双向数据修改】 --><input type="text" v-model="username"><hr><!-- value--是文本框中的默认值,如果这个value中的值被修改,数据源并不会改变【单向数据修改】 --><input type="text" :value="username"><hr><!-- v-model只能跟表单元素进行交互:input,textarea,select --><select v-model="city"><option value="">请选择城市</option><option value="1">北京</option><option value="2">上海</option><option value="3">广州</option></select></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {username: 'zhangsan',// 默认选中value为2的city: '2'}})</script>
</body></html>

v-model 指令的修饰符:v-model.number="n1"

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

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 --><div id="app"><!-- v-model.number=""  以防在进行数值计算的时候输入的是字符串 --><input type="text" v-model.number="n1">+<input type="text" v-model.number="n2">=<span>{{n1+n2 }}</span><hr><!-- v-model.trim=""   去除username中前后的空格  中间的空格不去除 --><input type="text" v-model.trim="username"><button @click="showName">获取用户名</button><hr><!-- v-model.lazy=""   防抖,不会每一次删除文本框中的文字的时候就进行更新,而是在删除结束后(就是失去焦点的时候)才更新 --><input type="text" v-model.lazy="username"></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {username: 'zhangsan',n1: 1,n2: 2},methods:{showName(){// 里面用的是模板字符串console.log(`用户名是"${this.username}"`)}}})</script>
</body></html>

1.5 条件渲染指令:v-if/v-show

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

⚫ v-show

v-if 和 v-show 的区别

1. `v-show` 的原理是:动态为元素添加或移除 `display: none` 样式,来实现元素的显示和隐藏
     如果要频繁的切换元素的显示状态,用 v-show 性能会更好
2. `v-if` 的原理是:每次动态创建或移除元素,实现元素的显示和隐藏
     如果刚进入页面的时候,某些元素默认不需要被展示,而且后期这个元素很可能也不需要被展示出来,此时 v-if 性能更好

>  在实际开发中,绝大多数情况,不用考虑性能问题,直接使用 v-if 就好了!!!

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 --><div id="app"><!-- 当flag=false的时候  v-if  会被动态删除,v-show 会添加上style="none" --><p v-if="flag">这是被 v-if 控制的元素</p><p v-show="flag">这是被 v-show 控制的元素</p></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {// 如果 flag 为 true,则显示被控制的元素;如果为 false 则隐藏被控制的元素flag: false,}})</script>
</body></html>

v-else:后面不用加条件

v-else-if:一定要跟v-if一起使用

<!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><!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 --><hr><div v-if="type === 'A'">优秀</div><div v-else-if="type === 'B'">良好</div><div v-else-if="type === 'C'">一般</div><div v-else>差</div></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {type: 'A'}})</script>
</body></html>

1.6 列表渲染指令:v-for【data:{list:[]}】

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

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

⚫ items 是待循环的数组

⚫ item 是被循环的每一项

v-for 中的索引:(item, index) in items

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

注意: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><link rel="stylesheet" href="./lib/bootstrap.css">
</head><body><!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 --><div id="app"><table class="table table-bordered table-hover table-striped"><thead><th>索引</th><th>Id</th><th>姓名</th></thead><tbody><!-- item in list---item可以自己定 --><tr v-for="(item, index) in list" ><!-- index--索引号 --><td>{{ index }}</td><td>{{ item.id }}</td><td>{{ item.name }}</td></tr></tbody></table></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {list: [{ id: 1, name: '张三' },{ id: 2, name: '李四' },{ id: 3, name: '王五' },{ id: 4, name: '张三' },]}})</script>
</body></html>

使用 key 维护列表的状态:要用到了 v-for 指令,那么一定要绑定一个 :key 属性(尽量使用id)

当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种 默认的性能优化策略,会导致有状态的列表无法被正确更新。 为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲 染的性能。此时,需要为每项提供一个唯一的 key 属性:

<!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><link rel="stylesheet" href="./lib/bootstrap.css">
</head><body><!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 --><div id="app"><table class="table table-bordered table-hover table-striped"><thead><th>索引</th><th>Id</th><th>姓名</th></thead><tbody><!-- 官方建议:只要用到了 v-for 指令,那么一定要绑定一个 :key 属性 --><!-- 而且,尽量把 id 作为 key 的值 --><!-- 官方对 key 的值类型,是有要求的:字符串或数字类型 --><!-- key 的值是千万不能重复的,否则会终端报错:Duplicate keys detected --><!-- item in list---item可以自己定 --><tr v-for="(item, index) in list" :key="item.id"><!-- index--索引号 --><td>{{ index }}</td><td>{{ item.id }}</td><td>{{ item.name }}</td></tr></tbody></table></div><!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --><script src="./lib/vue-2.6.12.js"></script><!-- 2. 创建 Vue 的实例对象 --><script>// 创建 Vue 的实例对象const vm = new Vue({// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器el: '#app',// data 对象就是要渲染到页面上的数据data: {list: [{ id: 1, name: '张三' },{ id: 2, name: '李四' },{ id: 3, name: '王五' },{ id: 4, name: '张三' },]}})</script>
</body></html>

key 的注意事项

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

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

③ 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)

使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)--因为index跟数据没有强制的绑定关系

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

key不能选索引值为key值

<!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><!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 --><div id="app"><!-- 添加用户的区域 --><div><input type="text" v-model="name"><button @click="addNewUser">添加</button></div><!-- 用户列表区域 --><ul><li v-for="(user, index) in userlist" :key="user.index"><input type="checkbox" />姓名:{{user.name}}</li></ul></div><script src="./lib/vue-2.6.12.js"></script><script>const vm = new Vue({el: '#app',data: {// 用户列表userlist: [{ id: 1, name: 'zs' },{ id: 2, name: 'ls' }],// 输入的用户名name: '',// 下一个可用的 id 值nextId: 3},methods: {// 点击了添加按钮addNewUser() {this.userlist.unshift({ id: this.nextId, name: this.name })this.name = ''this.nextId++}},})</script>
</body></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>品牌列表案例</title><link rel="stylesheet" href="./lib/bootstrap.css"><link rel="stylesheet" href="./css/brandlist.css">
</head><body><div id="app"><!-- 卡片区域 --><div class="card"><div class="card-header">添加品牌</div><div class="card-body"><!-- 添加品牌的表单区域 --><!-- form 表单元素有 submit 事件 --><!-- 阻止按钮的默认提交行为 并触发add这个行为 --><form @submit.prevent="add"><div class="form-row align-items-center"><div class="col-auto"><div class="input-group mb-2"><div class="input-group-prepend"><div class="input-group-text">品牌名称</div></div><!-- 给添加按钮添加点击事件 --><!-- 因为我们点击按钮要获取文本框中用户输入的内容,最快的方法就是直接使用v-model双向获取数据 --><input type="text" class="form-control" placeholder="请输入品牌名称" v-model.trim="brand"></div></div><div class="col-auto"><button type="submit" class="btn btn-primary mb-2">添加</button></div></div></form></div></div><!-- 表格区域 --><table class="table table-bordered table-hover table-striped"><thead><tr><th scope="col">#</th><th scope="col">品牌名称</th><th scope="col">状态</th><th scope="col">创建时间</th><th scope="col">操作</th></tr></thead><tbody><tr v-for="item in list" :key="item.id"><td>{{  item.id }}</td><td>{{item.name}}</td><td><div class="custom-control custom-switch"><!-- 因为表单中的禁用和启动是双向的数据交互,所以使用v-model --><!-- 使用 v-model 实现双向数据绑定 --><!-- 给id添加动态绑定,让id后面跟着item.id --><input type="checkbox" class="custom-control-input" :id="'cb' + item.id" v-model="item.status"><!-- 当statue为true的时候,就为启动,为false的时候为禁用,所以要使用v-if和v-else --><!-- 使用 v-if 结合 v-else 实现按需渲染 --><label class="custom-control-label" :for="'cb' + item.id" v-if="item.status">已启用</label><label class="custom-control-label" :for="'cb' + item.id" v-else>已禁用</label></div></td><td>{{item.time}}</td><td><!-- 给删除按钮绑定一个点击事件 并且传入要进行删除的id --><a href="javascript:;" @click="remove(item.id)">删除</a></td></tr></tbody></table></div><script src="./lib/vue-2.6.12.js"></script><script>//1.创建实例对象const vm=new Vue({el:'#app',data:{//用户输入的匹配名称brand:'',// nextId 是下一个,可用的 idnextId: 4,//品牌的列表数据list:[{id:1, name:'宝马', status:true, time:new Date()},{id:2, name:'奔驰', status:true, time:new Date()},{id:3, name:'奥迪', status:true, time:new Date()},]},methods:{//点击链接,删除对应的数据remove(id){// console.log(id);// filter--过滤,返回一个数组// filter--返回不满足条件的数据this.list=this.list.filter(item=>item.id!==id)},//阻止表单的默认提交行为之后,触发add行为add(){// console.log(this.brand);// 如果判断brand的值为空字符串,则return出去if(this.brand===''){alert('必须填写汽车名称')return}// 如果没有被 return 出去,应该执行添加的逻辑// 1. 先把要添加的品牌对象,整理出来const obj={id:this.nextId,name:this.brand,status:true,time:new Date()}//2.把this.list数组中push步骤1中得到的对象this.list.push(obj)// 3.清空this.brand,让this.nextId自增1this.brand=''this.nextId++;}}})</script></body></html>

2. 过滤器:过滤器本质上是函数

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

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

2.1 定义过滤器:在filters节点中定义【过滤器中,一定要有一个返回值】

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

<!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"><!-- “| ”:管道符 --><!-- 将message的值传递给capi,然后capi将新得到的值return出来 --><p>message 的值是:{{ message | capi }}</p></div><script src="./lib/vue-2.6.12.js"></script><script>const vm = new Vue({el: '#app',data: {message: 'hello vue.js'},// 过滤器函数,必须被定义到 filters 节点之下// 过滤器本质上是函数filters: {// 注意:过滤器函数形参中的 val,永远都是“管道符”前面的那个值capi(val) {// 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来// val.charAt(0)--获取第一个字母const first = val.charAt(0).toUpperCase()// 字符串的 slice 方法,可以截取字符串,从指定索引往后截取const other = val.slice(1)// 强调:过滤器中,一定要有一个返回值return first + other}}})</script>
</body></html>

1. 要定义到 filters 节点下,本质是一个函数
2. 在过滤器函数中,一定要有 return 值
3. 在过滤器的形参中,可以获取到“管道符”前面待处理的那个值

2.2 私有过滤器和全局过滤器(Vue.filter('函数名',构造函数))【注意:是filter不是filters】

在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。 如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器2.3 连续调用多个过滤器

<!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>message 的值是:{{ message | capi }}</p></div><div id="app2"><p>message 的值是:{{ message | capi }}</p></div><script src="./lib/vue-2.6.12.js"></script><script>// 使用 Vue.filter() 定义全局过滤器Vue.filter('capi', function (str) {const first = str.charAt(0).toUpperCase()const other = str.slice(1)return first + other + '~~~'})const vm = new Vue({el: '#app',data: {message: 'hello vue.js'},// 过滤器函数,必须被定义到 filters 节点之下// 过滤器本质上是函数// 这个filters是私有过滤器,外部不能使用filters: {// 注意:过滤器函数形参中的 val,永远都是“管道符”前面的那个值capi(val) {// 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来// val.charAt(0)const first = val.charAt(0).toUpperCase()// 字符串的 slice 方法,可以截取字符串,从指定索引往后截取const other = val.slice(1)// 强调:过滤器中,一定要有一个返回值return first + other}}})// ----------------------------------const vm2 = new Vue({el: '#app2',data: {message: 'heima'}})</script>
</body></html>

 如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“

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

<!-- 使用名为dateFormat这个过滤器,使得时间进行格式化 --><td>{{ item.time | dateFormat }}</td><!-- 只要导入了 dayjs 的库文件,在 window 全局,就可以使用 dayjs() 方法了 --><script src="./lib/dayjs.min.js"></script><script>// 声明格式化时间的全局过滤器Vue.filter('dateFormat', function (time) {// 1. 对 time 进行格式化处理,得到 YYYY-MM-DD HH:mm:ss// 2. 把 格式化的结果,return 出去// 直接调用 dayjs() 得到的是当前时间// dayjs(给定的日期时间) 得到指定的日期const dtStr = dayjs(time).format('YYYY-MM-DD HH:mm:ss')return dtStr})</script>

2.3 连续调用多个过滤器 :过滤器可以串联地进行调用

2.4 过滤器传参:本质是 JavaScript 函数,因此可以接收参数

2.5 过滤器的兼容性:vue3.x中没有过滤器

5.品牌列表案例

1. 案例效果

3. 整体实现步骤

总结

6.侦听器

1. 什么是 watch 侦听器:允许开发者监视数据的变化,从而针对数据的变化做特定的操作

2.监听器定义:应该被定义到 watch 节点下

本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可

新值在前,旧值在后 username(newVal,oldVal)

<!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="username"></div><script src="./lib/vue-2.6.12.js"></script><script src="./lib/jquery-v3.6.0.js"></script><script>const vm = new Vue({el: '#app',data: {username: 'admin'},// 所有的侦听器,都应该被定义到 watch 节点下watch: {// 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可// 新值在前,旧值在后 username(newVal,oldVal)username(newVal,oldVal) {console.log('username进行监听',newVal,oldVal);}}})</script>
</body></html>

3. 使用 watch 检测用户名是否可用

<!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="username"></div><script src="./lib/vue-2.6.12.js"></script><script src="./lib/jquery-v3.6.0.js"></script><script>const vm = new Vue({el: '#app',data: {username: 'admin'},// 所有的侦听器,都应该被定义到 watch 节点下watch: {// 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可// 新值在前,旧值在后username(newVal) {if (newVal === '') return// 1. 调用 jQuery 中的 Ajax 发起请求,判断 newVal 是否被占用!!!$.get('https://www.escook.cn/api/finduser/' + newVal, function (result) {console.log(result)})}}})</script>
</body></html>

4. 对象格式监听器:immediate 选项(immediate 选项的默认值是 false)

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使 用 immediate 选项。示例代码如下:

<!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="username"></div><script src="./lib/vue-2.6.12.js"></script><script src="./lib/jquery-v3.6.0.js"></script><script>const vm = new Vue({el: '#app',data: {username: 'admin'},// 所有的侦听器,都应该被定义到 watch 节点下watch: {// 定义对象格式的侦听器username: {// 侦听器的处理函数// 当监听到username的时候,会自动触发handler这个函数handler(newVal, oldVal) {console.log(newVal, oldVal)},// immediate 选项的默认值是 false// immediate为true表示进入的时候自动触发监听// immediate 的作用是:控制侦听器是否自动触发一次!immediate: true}}})</script>
</body></html>

5. 对象格式监听器:deep 选项(监听对象中的属性值)

如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选 项,代码示例如下:

<!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"><!-- 因为data数据中的info是一个对象,要进行深度拷贝才可以访问到 --><input type="text" v-model="info.username"><input type="text" v-model="info.address.city"></div><script src="./lib/vue-2.6.12.js"></script><script src="./lib/jquery-v3.6.0.js"></script><script>const vm = new Vue({el: '#app',data: {// 用户的信息对象info: {username: 'admin',address: {city: '北京'}}},// 所有的侦听器,都应该被定义到 watch 节点下watch: {/* 并不会触发侦听器,因为属性是一个对象info(newVal){console.log(newVal);}*///这个是侦听整个对象info: {handler(newVal) {console.log(newVal)},// 开启深度监听,只要对象中任何一个属性变化了,都会触发“对象的侦听器”deep: true} }})</script>
</body></html>

6.对象监听器和方法监听器的优缺点

1. 方法格式的侦听器
   + 缺点1:无法在刚进入页面的时候,自动触发!!!
   + 缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器!!!
2. 对象格式的侦听器
   + 好处1:可以通过 **immediate** 选项,让侦听器自动触发!!!
   + 好处2:可以通过 **deep** 选项,让侦听器深度监听对象中每个属性的变化!!!

7. 监听对象单个属性的变化

如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:

<!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"><!-- 因为data数据中的info是一个对象,要进行深度拷贝才可以访问到 --><input type="text" v-model="info.username"><input type="text" v-model="info.address.city"></div><script src="./lib/vue-2.6.12.js"></script><script src="./lib/jquery-v3.6.0.js"></script><script>const vm = new Vue({el: '#app',data: {// 用户的信息对象info: {username: 'admin',address: {city: '北京'}}},// 所有的侦听器,都应该被定义到 watch 节点下watch: {/* 并不会触发侦听器,因为属性是一个对象info(newVal){console.log(newVal);}*/// 如果要侦听的是对象的子属性的变化,则必须包裹一层单引号'info.username'(newVal) {console.log(newVal)}}})</script>
</body></html>

7.计算属性

1. 什么是计算属性:computed:{}

计算属性指的是通过一系列运算之后,最终得到一个属性值。

这个动态计算出来的属性值可以被模板结构或 methods 方法使用。示例代码如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><script src="./lib/vue-2.6.12.js"></script><style>.box {width: 200px;height: 200px;border: 1px solid #ccc;}</style>
</head><body><div id="app"><div><span>R:</span><input type="text" v-model.number="r"></div><div><span>G:</span><input type="text" v-model.number="g"></div><div><span>B:</span><input type="text" v-model.number="b"></div><hr><!-- 专门用户呈现颜色的 div 盒子 --><!-- 在属性身上,: 代表  v-bind: 属性绑定 --><!-- :style 代表动态绑定一个样式对象,它的值是一个 {  } 样式对象 --><!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 --><div class="box" :style="{ backgroundColor: `rgb(${r}, ${g}, ${b})` }">{{ `rgb(${r}, ${g}, ${b})` }}</div><button @click="show">按钮</button></div><script>// 创建 Vue 实例,得到 ViewModelvar vm = new Vue({el: '#app',data: {// 红色r: 0,// 绿色g: 0,// 蓝色b: 0},methods: {// 点击按钮,在终端显示最新的颜色show() {console.log(`rgb(${this.r}, ${this.g}, ${this.b})`)}},});</script>
</body></html>

2. 计算属性的特点

① 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性

② 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算

特点:

1. 定义的时候,要被定义为“方法”
2. 在使用计算属性的时候,当普通的属性使用即可

好处:

1. 实现了代码的复用
2. 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值!

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><script src="./lib/vue-2.6.12.js"></script><style>.box {width: 200px;height: 200px;border: 1px solid #ccc;}</style>
</head><body><div id="app"><div><span>R:</span><input type="text" v-model.number="r"></div><div><span>G:</span><input type="text" v-model.number="g"></div><div><span>B:</span><input type="text" v-model.number="b"></div><hr><!-- 专门用户呈现颜色的 div 盒子 --><!-- 在属性身上,: 代表  v-bind: 属性绑定 --><!-- :style 代表动态绑定一个样式对象,它的值是一个 {  } 样式对象 --><!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 --><div class="box" :style="{ backgroundColor: rgb }">{{ rgb }}</div><button @click="show">按钮</button></div><script>// 创建 Vue 实例,得到 ViewModelvar vm = new Vue({el: '#app',data: {// 红色r: 0,// 绿色g: 0,// 蓝色b: 0},methods: {// 点击按钮,在终端显示最新的颜色show() {console.log(this.rgb)}},// 所有的计算属性,都要定义到 computed 节点之下// 计算属性在定义的时候,要定义成“方法格式”computed: {// rgb 作为一个计算属性,被定义成了方法格式,// 最终,在这个方法中,要返回一个生成好的 rgb(x,x,x) 的字符串// rgb在声明的时候是一个函数,但是实际在vm对象中是一个属性,所以可以直接vm.rgb来调用rgb() {return `rgb(${this.r}, ${this.g}, ${this.b})`}}});console.log(vm)</script>
</body></html>

8.axios数据请求

1. 什么是axios:是专注于网络数据请求的库

2. axios发起GET请求

<!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><!-- 导入axios --><script src="./lib/axios.js"></script><script>// http://www.liulongbin.top:3006/api/getbooks// 1. 调用 axios 方法得到的返回值是 Promise 对象axios({// 请求方式method: 'GET',// 请求的地址url: 'http://www.liulongbin.top:3006/api/getbooks',// URL 中的查询参数}).then(function (result) {// result是套完数据之后的console.log(result)})</script>
</body></html>

3. axios传参

1. 直接使用axios发起GET请求:使用params属性

    // 1. 调用 axios 方法得到的返回值是 Promise 对象axios({// 请求方式method: 'GET',// 请求的地址url: 'http://www.liulongbin.top:3006/api/getbooks',// URL 中的查询参数params: {id: 1},}).then(function (result) {console.log(result)})

2. 直接使用axios发起POST请求:使用data属性

    // 1. 调用 axios 方法得到的返回值是 Promise 对象axios({// 请求方式method: 'GET',// 请求的地址url: 'http://www.liulongbin.top:3006/api/getbooks',// 请求体参数data: {}}).then(function (result) {console.log(result)})
<!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><button id="btnPost">发起post请求</button>
<script src="./lib/axios.js"></script>
<script>document.querySelector('#btnPost').addEventListener('click',function(){axios:({method:'POST',url:'http://www.liulongin.top:3006/api/post',data:{name:'zs',age:20}}).then(function(result){console.log(result);})})
</script></body>
</html>

4. 结合async和await调用axios

<!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><button id="btnPost">发起POST请求</button><button id="btnGet">发起GET请求</button><script src="./lib/axios.js"></script><script>document.querySelector('#btnPost').addEventListener('click', async function () {// 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!// await 只能用在被 async “修饰”的方法中//const result=await axios---其中result不是真正的数据 真正的数据是result.data()const { data } = await axios({method: 'POST',url: 'http://www.liulongbin.top:3006/api/post',data: {name: 'zs',age: 20}})console.log(data)})document.querySelector('#btnGet').addEventListener('click', async function () {// 解构赋值的时候,使用 : 进行重命名// 1. 调用 axios 之后,使用 async/await 进行简化// 2. 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来// 3. 把解构出来的 data 属性,使用 冒号 进行重命名,一般都重命名为 { data: res }const { data: res } = await axios({method: 'GET',url: 'http://www.liulongbin.top:3006/api/getbooks'})console.log(res.data)})// $.ajax()   $.get()  $.post()// axios()    axios.get()    axios.post()    axios.delete()   axios.put()</script>
</body></html>

5.axios.get

<!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><button id="btnGET">GET</button><script src="./lib/axios.js"></script><script>document.querySelector('#btnGET').addEventListener('click', async function () {/* axios.get('url地址', {// GET 参数params: {}}) */// 将data重命名为resconst { data: res } = await axios.get('http://www.liulongbin.top:3006/api/getbooks', {params: { id: 1 }})console.log(res)})</script>
</body></html>

6.axios.post

<!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><button id="btnPOST">POST</button><script src="./lib/axios.js"></script><script>document.querySelector('#btnPOST').addEventListener('click', async function () {// axios.post('url', { /* POST 请求体数据 */ })const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', gender: '女' })console.log(res)})</script>
</body></html>

9.vue-cli

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

2. 什么是 vue-cli:自动搭建Webpack

3. 安装和使用:全局变量

4. vue 项目的运行流程

1.快速生成vue项目:vue create 项目名称(记得再要创建的地址(再当前地址上cmd)上进行执行,并且项目的名字不要有中文和空格)

4.1 了解src目录的构建

assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源
   components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下
   main.js 是项目的入口文件。整个项目的运行,要先执行 main.js
   App.vue 是项目的根组件。

4.2 vue项目运行的过程

在工程化的项目中,vue 要做的事情很单纯:通过 main.js(渲染的方式记录) 把 App.vue (要渲染的UI结构)渲染到 index.html (将app.vue渲染到index.html)的指定区域中。

4.3 vue-cli组件的基本使用

4.4 .$mount():作用和el属性完全一样

//创建Vue实例对象
new Vue({//将写的这个实例挂载到app上// el:'#app',// 把render函数指定的组件,渲染到HTML页面中render: h => h(Test),
}).$mount('#app')//跟上面的el:'#app'   效果一样// Vue实例的$mount()方法,作用和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">{{username}}</div><script src="./lib/vue-2.6.12.js"></script><script>const vm = new Vue({data: {username: 'admin'}})// 指定调用app这个位置的元素vm.$mount('#app')</script>
</body></html>

10.vue组件

1. 什么是组件化开发

组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护.

2. vue 中的组件化开发

3. vue 组件(对UI结构的复用)的三个组成部分

3.1 template:声明组件的UI结构

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

注意:

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

template 中只能包含唯一的根节点 【一个template只能包含唯一一个div】

<!-- 声明组件的UI结构 -->
<template><div class="test-box"><h3>这是用户自定义的组件  -- {{ username }}</h3></div></template>

3.2 script:定义组件的行为(数据,调用方法)

vue 规定:开发者可以在<script>节点中封装组件的JavaScript业务逻辑

1. 必须写::默认导出。固定写法--exprot default{}

2.vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象

.vue 组件中的 data 必须是函数

<!-- 定义组件的行为(数据,调用方法) -->
<script>// 默认导出。固定写法!!!!!!!!export default{// datat数据源// 注意:.vue组件中的data不能像之前一样,不能指向对象// 注意:组件中的data必须是一个函数// data:{//     username:'zs'// }// data:function(){}data(){// 这个return出去的{}中 可以定义数据return {username:'admin'}}}
</script>

3.3 style :定义样式结构

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

<!-- 定义样式结构 -->
<style>.test-box{background-color: pink;}
</style>

让 style 中支持 less 语法 :在style属性上加上[lang="less"]

<!-- 定义样式结构 -->
<style lang="less">.test-box{background-color: pink;}h3 {color: red;}
</style>

4.组件中定义methods方法

5.组件之间的父子关系

 4.1 使用组件的三个步骤

4.2 通过 components 注册的是私有子组件

4.3 注册全局组件:在main.js文件中进行注册【自己不能调用自己】

在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。

// 导入需要被全局注册的那个组件
import Count from '@/components/Count.vue'
// 注册
// 参数1:字符串格式,表示组件的“注册名称”,可以自己取
// 参数2:需要被全局注册的那个组件
Vue.component('MyCount',Count)
<!-- 因为在main.js文件中自己定义的名字就是MyCount所以标签名就为MyCount --><MyCount></MyCount>

5. 组件的 props:自定义属性

props 是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!

<script>export default {//props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值// 这个"init"可以自己取props:['init'],data(){return {count:0}},methods:{add(){this.count+=1}}}
</script>

5.1 props 是只读的:不能修改【可以将值转存到data中】

vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错:

要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!

<template><div><h5>Count 组件</h5><p>count的值是:{{ count }}</p><button @click="count+=1">+1</button></div>
</template><script>export default {//props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值// 这个"init"可以自己取// props中的数据,可以直接在模板结构中被使用props:['init'],data(){return {// 将props中的属性转存到count上count:this.init}},methods:{show(){console.log(this);}}}
</script>

5.2 props 的 default 默认值:default:0

<template><div class="right-container"><!-- 传入数字9 --><!-- <MyCount :init="9"></MyCount> --><!-- 用户没有输入数值,则使用props的默认值 --><MyCount ></MyCount></div>
</template><script>export default {//props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值// 这个"init"可以自己取// props中的数据,可以直接在模板结构中被使用// 注意:props是只读,必要直接修改props的值,否则终端报错// props:['init'],//数组格式的props不能指定默认值// props:{//     自定义属性A:{/*配置选项*/},//     自定义属性A:{/*配置选项*/},//     自定义属性A:{/*配置选项*/},// }props:{init:{// 如果外界使用Count组件的时候,没有传递init属性,则默认值生效default:0}}}
</script>

5.3 props 的 type 值类型:记得在标签中的init前加上冒号

    <!-- 因为在main.js文件中自己定义的名字就是MyCount所以标签名就为MyCount --><MyCount :init="9"></MyCount><script>export default {//props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值// 这个"init"可以自己取// props中的数据,可以直接在模板结构中被使用// 注意:props是只读,必要直接修改props的值,否则终端报错// props:['init'],//数组格式的props不能指定默认值// props:{//     自定义属性A:{/*配置选项*/},//     自定义属性A:{/*配置选项*/},//     自定义属性A:{/*配置选项*/},// }props:{init:{// 如果外界使用Count组件的时候,没有传递init属性,则默认值生效default:0,// init的值类型必须是Number数字// type:Number}}}
</script>

5.4 props 的 required 必填项:如果用户没有输入属性值,则控制台报错

<!-- 因为在main.js文件中自己定义的名字就是MyCount所以标签名就为MyCount --><MyCount :init="9"></MyCount><script>export default {//props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值// 这个"init"可以自己取// props中的数据,可以直接在模板结构中被使用// 注意:props是只读,必要直接修改props的值,否则终端报错// props:['init'],//数组格式的props不能指定默认值// props:{//     自定义属性A:{/*配置选项*/},//     自定义属性A:{/*配置选项*/},//     自定义属性A:{/*配置选项*/},// }props:{init:{// 如果外界使用Count组件的时候,没有传递init属性,则默认值生效default:0,// init的值类型必须是Number数字type:Number,// 必填项校验,如果用户不传入init属性值,则强制报错required:true}}
}
</script>

5.5 props和v-bind(“:”)使用自定义属性

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

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

导致组件之间样式冲突的根本原因是:

① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的 ② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

6.1 思考:如何解决组件样式冲突的问题:【分配唯一的自定义属性】

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

6.2 style 节点的 scoped 属性:会自动添加上类选择器【用于解决组件之间的样式冲突】

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

<style lang="less" scoped>
.left-container {padding: 0 20px 20px;background-color: orange;min-height: 250px;flex: 1;
}
h3 {color: red;
}
</style>

6.3 /deep/ 样式穿透

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

当使用第三方组件的时候,如果有修改组件默认样式的需求,需要用到/deep/

<style lang="less" scoped>//没有加/deep/前:h5[data-v-3c83f0b7]
// 加上/deep/后:[data-v-3c83f0b7] h5 --->给父亲加上data-v-3c83f0b7这个类
// 当使用第三方组件的时候,如果有修改组件默认样式的需求,需要用到/deep/
// 这个/deep/就是通过父组件修改子组件
/deep/ h5 {color: pink;
}
</style>

7.vue组件的实例对象

 生成的.vue代码并不是直接到浏览器中进行实现,而且是通过package.json文件中的插件进行转换为js代码

在.vue文件中使用标签相当于new一个实例对象 

<MyCount></MyCount>-->相当于创建了MyCounr这个实例

三、生命周期 & 数据共享

1.组件的生命周期

1. 生命周期 & 生命周期函数

 注意:生命周期强调的是时间段,生命周期函数强调的是时间点

3. 组件生命周期函数的分类

4. 生命周期图示

Vue 实例 — Vue.js (vuejs.org)

5.生命周期的实现过程

5.1 初步了解组件创建的过程

5.2 【组件的创建阶段】了解beforeCreate生命周期函数:组件中的props/data/methods不可用

<script>
export default {props:['info'],data(){return {message:'hello vue.js',// 定义books数组,存储的是所有图书的列表数据,默认为空数组books:[]}},methods:{show(){console.log('调用了Test组件的show方法');},// 使用Ajax请求图书列表的数据initBookList() {const xhr = new XMLHttpRequest()xhr.addEventListener('load', () => {const result = JSON.parse(xhr.responseText)// 获取对象console.log(result)this.books = result.data})xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')xhr.send()}},// 创建阶段的第一个生命周期---props,data,methods都处于不可用状态beforeCreate(){// console.log(this.info);//拿不到// console.log(this.message);//拿不到// this.show()//拿不到}}
</script>

5.3 【组件的创建阶段】了解created生命周期函数:组件中的props/data/methods可用


<template><div class="test-container"><h3>Test.vue 组件 一共有{{ books.length }}本书</h3></div>
</template><script>
export default {props:['info'],data(){return {message:'hello vue.js',// 定义books数组,存储的是所有图书的列表数据,默认为空数组books:[]}},methods:{show(){console.log('调用了Test组件的show方法');},// 使用Ajax请求图书列表的数据initBookList() {const xhr = new XMLHttpRequest()xhr.addEventListener('load', () => {const result = JSON.parse(xhr.responseText)// 获取对象console.log(result)this.books = result.data})xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')xhr.send()}},//创建阶段的第二个生命周期---props,data,methods都处于可用状态// 在这个状态下可以调用Ajax请求拿数据// created生命周期函数,非常常用// 经常在它里面,调用methods中的方法,请求服务器的数据// 并且,把请求到的数据,转存到data中,供template模板渲染的时候使用created(){console.log(this.info);console.log(this.message);this.show(),//可以访问到data数据this.initBookList()}}
</script>

5.4【组件的创建阶段】 了解beforeMount生命周期函数: 未拿到DOM结构

<script>
export default {props:['info'],data(){return {message:'hello vue.js',// 定义books数组,存储的是所有图书的列表数据,默认为空数组books:[]}},methods:{show(){console.log('调用了Test组件的show方法');},// 使用Ajax请求图书列表的数据initBookList() {const xhr = new XMLHttpRequest()xhr.addEventListener('load', () => {const result = JSON.parse(xhr.responseText)// 获取对象console.log(result)this.books = result.data})xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')xhr.send()}},// 将要渲染DOM结构,但是没有拿到DOM元素beforeMount(){console.log('beforeMount');const dom =document.querySelector('#myh3')console.log(dom);//拿不到dom元素}}
</script>

5.5【组件的创建阶段】 了解mounted生命周期函数: 将DOM结构渲染到页面上了

<script>
export default {props:['info'],data(){return {message:'hello vue.js',// 定义books数组,存储的是所有图书的列表数据,默认为空数组books:[]}},methods:{show(){console.log('调用了Test组件的show方法');},// 使用Ajax请求图书列表的数据initBookList() {const xhr = new XMLHttpRequest()xhr.addEventListener('load', () => {const result = JSON.parse(xhr.responseText)// 获取对象console.log(result)this.books = result.data})xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')xhr.send()}},mounted(){console.log(this.$el);}}
</script>

5.6 【组件的创建阶段】生命周期的示意图

5.7 【组件的运行阶段】生命周期函数

beforeupdate

<script>
export default {props:['info'],data(){return {message:'hello vue.js',// 定义books数组,存储的是所有图书的列表数据,默认为空数组books:[]}},methods:{show(){console.log('调用了Test组件的show方法');},// 使用Ajax请求图书列表的数据initBookList() {const xhr = new XMLHttpRequest()xhr.addEventListener('load', () => {const result = JSON.parse(xhr.responseText)// 获取对象console.log(result)this.books = result.data})xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')xhr.send()}},// 要触发beforeUpdata就要数据发生改变的时候// 这个时候数据是新的,但是UI结构是旧的beforeUpdate(){console.log('beforeUpdate');console.log(this.message);const dom=document.querySelector('#pppp')console.log(dom.innerHTML);//跟上面获取的message数据不一样}}
</script>

updated

<script>
export default {props:['info'],data(){return {message:'hello vue.js',// 定义books数组,存储的是所有图书的列表数据,默认为空数组books:[]}},methods:{show(){console.log('调用了Test组件的show方法');},// 使用Ajax请求图书列表的数据initBookList() {const xhr = new XMLHttpRequest()xhr.addEventListener('load', () => {const result = JSON.parse(xhr.responseText)// 获取对象console.log(result)this.books = result.data})xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')xhr.send()}},// 创建阶段的第一个生命周期---props,data,methods都处于不可用状态beforeCreate(){// console.log(this.info);// console.log(this.message);// this.show()},//创建阶段的第二个生命周期---props,data,methods都处于可用状态// 在这个状态下可以调用Ajax请求拿数据// created生命周期函数,非常常用// 经常在它里面,调用methods中的方法,请求服务器的数据// 并且,把请求到的数据,转存到data中,供template模板渲染的时候使用created(){// console.log(this.info);// console.log(this.message);// this.show(),//可以访问到data数据this.initBookList()},// 将要渲染DOM结构,但是没有拿到DOM元素beforeMount(){// console.log('beforeMount');// const dom =document.querySelector('#myh3')// console.log(dom);//拿不到dom元素},mounted(){// console.log(this.$el);},// 当数据变化之后,为了能够操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中updated(){console.log('beforeUpdate');console.log(this.message);const dom=document.querySelector('#dddd')console.log(dom.innerHTML);//跟上面获取的message数据一样}}
</script>

5.8 【组件的销毁阶段】生命周期函数

<script>
export default {props:['info'],data(){return {message:'hello vue.js',// 定义books数组,存储的是所有图书的列表数据,默认为空数组books:[]}},methods:{show(){console.log('调用了Test组件的show方法');},// 使用Ajax请求图书列表的数据initBookList() {const xhr = new XMLHttpRequest()xhr.addEventListener('load', () => {const result = JSON.parse(xhr.responseText)// 获取对象console.log(result)this.books = result.data})xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')xhr.send()}},beforeDestroy() {console.log('beforeDestroy')this.message = 'aaa'console.log(this.message)},destroyed() {console.log('destroyed')// this.message = 'aaa'}}
</script>

2.组件之间的数据共享

1. 组件之间的关系

2. 父子组件之间的数据共享

2.1 父组件向子组件共享数据:自定义属性  (:msg="messsage")

父组件向子组件共享数据需要使用自定义属性。示例代码如下:

 父组件

<template><div class="app-container"><h1>App 根组件</h1><hr /><div class="box"><!-- 渲染 Left 组件和 Right 组件 --><!-- 3. 以标签形式,使用注册好的组件 --><!-- 注意:如果没有使用v-bind绑定属性的话,则输出的是“msg 的值为message” --><!-- 表示将message这个字符串传入,而我们要接收的是message这个对应数据 --><Left :msg="message" :user="userinfo"></Left></div></div>
</template><script>
// 1.将其他组件导入
import Left from '@/components/Left.vue'
import { userInfo } from 'os';
// import Right from '@/components/Right.vue'export default {data(){return {message:'hello',userinfo:{ name: 'wsc',age:'18'}}},// 2. 注册组件components:{Left,}
}
</script><style lang="less">
.app-container {padding: 1px 20px 20px;background-color: #efefef;
}
.box {display: flex;
}
</style>

子组件

<template><div class="left-container"><h3>Left 组件</h3><p>msg 的值为:{{ msg }}</p><p>user的值为:{{ user }}</p></div>
</template><script>
export default {props:['msg','user']
}
</script><style lang="less">
.left-container {padding: 0 20px 20px;background-color: orange;min-height: 250px;flex: 1;
}
</style>

注意:不要修改props属性里面的值

对于简单数据类型来说:子组件是将父组件的传入的内容复制了一份,所以如果修改子组件中的内容,则父组件不会变

对于复杂数据类型来说:子组件指向父组件,当子组件发生改变的时候,父组件中的值也发生改变

2.2 子组件向父组件共享数据 :使用自定义事件

子组件向父组件共享数据使用自定义事件

父组件

<template><div class="app-container"><h1>App 根组件 --- {{ countFromSon }}</h1><hr /><div class="box"><!-- 渲染 Left 组件和 Right 组件 --><!-- 3. 以标签形式,使用注册好的组件 --><!-- 注意:如果没有使用v-bind绑定属性的话,则输出的是“msg 的值为message” --><!-- 表示将message这个字符串传入,而我们要接收的是message这个对应数据 --><Left :msg="message" :user="userinfo"></Left><Right @numchange="getNewCount"></Right></div></div>
</template><script>
// 1.将其他组件导入
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'export default {data(){return {message:'hello',userinfo:{ name: 'wsc',age:'18'},// 接收子组件传递过来的countcountFromSon:0}},methods:{// 获取子组件传递过来的数据// val--是子组件传递过来的值getNewCount(val){console.log('numchange事件被触发了');// 将子组件传递过来的数据重新赋值给父组件this.countFromSon=val}},// 2. 注册组件components:{Left,Right}
}
</script>

子组件

<template><div class="right-container"><h3>Right 组件----{{ count }}</h3><button @click="add">+1</button></div>
</template><script>
export default {data(){return {// 子组件自己的数据 将来希望把count的值传递给父组件count:0}},methods:{add(){// 让子组件的count值自增1this.count+=1// 把自增的结果,传给父组件// 这个numchange是自己取的// 自定义事件this.$emit('numchange',this.count)}}
}
</script>

3. 兄弟组件之间/组件之间相离较远  的数据共享:EventBus

在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus。

EventBus 的使用步骤

1.在数据发送方data写入数据,然后再接收方data定义一个变量接收来自发送方发来的数据

2.再components文件中创建eventBus.js文件

import Vue from 'vue'// 将新创建的Vue对象向外暴露
export default new Vue()

3.再发送方创建一个DOM实例

<template><div class="left-container"><button @click="send">把诗歌发送出去</button></div>
</template>

4.将eventBus模块导入发送方和接收方

//1.导入eventBus.js模块
import bus from '@/components/eventBus.js'import bus from '@/components/eventBus.js'

5.在发送方中声明触发事件

<script>
//1.导入eventBus.js模块
import bus from '@/components/eventBus.js'export default {props:['msg','user'],data(){return {str:'鹅鹅鹅'}},methods:{send(){//2.通过eventBus发送数据bus.$emit('share',this.str)}}
}
</script>

6.在接收方中绑定自定义事件(跟data同级的)

<script>export default {data(){return {// 子组件自己的数据 将来希望把count的值传递给父组件count:0,//创建一个对象接收从Left组件接收过来的数据msgfFromLeft:''}},created(val) {//2.为bus绑定自定义事件bus.$on('share',(val)=>{console.log('在Right组件中定义的share被触发了',val);// 接收从发送方发过来的数据this.msgfFromLeft=val})},methods:{add(){// 让子组件的count值自增1this.count+=1// 把自增的结果,传给父组件// 这个numchange是自己取的// 自定义事件this.$emit('numchange',this.count)},}
}
</script>

3.ref引用

1. 什么是 ref 引用:在不依赖jQuery的作用下,使用DOM

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。

每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下, 组件的 $refs 指向一个空对象。

<script>
export default {methods:{showThis(){//指向空对象console.log(this);}}
}
</script>

2. 使用 $ref 引用 DOM 元素

如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:

<template><div class="app-container"><h1 ref="myh12">App 根组件</h1><button @click="showThis">打印this</button><hr /><div class="box"><!-- 渲染 Left 组件和 Right 组件 --></div></div>
</template><script>
export default {methods:{showThis(){//指向空对象console.log(this);// 拿到DOM元素console.log(this.$refs.myh12);// 给DOM添加样式this.$refs.myh12.style.color='red'}}
}
</script><style lang="less">
.app-container {padding: 1px 20px 20px;background-color: #efefef;
}
.box {display: flex;
}
</style>

3. 使用 ref 引用组件实例

如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:

使用组件

<template><div class="app-container"><h1 ref="myh12">App 根组件</h1><button @click="showThis">打印this</button><!-- 这里调用到Left组件中的this --><button @click="onReset" >重置Left组件的count值为0</button><hr /><!-- 3.将组件渲染出来 --><div class="box"><!-- 渲染 Left 组件和 Right 组件 --><!-- 给Left组件添加ref可以拿到Left组件中的引用 --><!-- 因为ref是拿DOM的方法,所以通过这个方法就可以拿到Left元素(这里将Left看作是DOM元素) --><Left ref="comLeft"></Left></div></div>
</template><script>
//1.导入组件
import Left from '@/components/Left.vue'
export default {methods:{showThis(){//指向空对象console.log(this);// 拿到DOM元素console.log(this.$refs.myh12);// 给DOM添加样式this.$refs.myh12.style.color='red'},onReset(){// this.$refs.comLeft.resetCount()// 等价于this.$refs.comLeft.count=0}},//2.注册组件components:{Left}
}
</script><style lang="less">
.app-container {padding: 1px 20px 20px;background-color: #efefef;
}
.box {display: flex;
}
</style>

被使用的组件

<template><div class="left-container"><h3>Left 组件----{{ count }}</h3><button @click="count+=1">+1</button><button @click="resetCount">重置</button></div>
</template><script>
export default {data(){return {count:0}},methods:{resetCount(){this.count=0}}
}
</script><style lang="less">
.left-container {padding: 0 20px 20px;background-color: orange;min-height: 250px;flex: 1;
}
</style>

4. 控制文本框和按钮的按需切换

通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。示例代码如下:

<template><div class="app-container"><h1 ref="myh12">App 根组件</h1><button @click="showThis">打印this</button><!-- 这里调用到Left组件中的this --><button @click="onReset" >重置Left组件的count值为0</button><hr /><!-- 当v-if为true的时候,展示输入框 --><!-- 当文本框失去焦点的时候,展示按钮  使用了@blur --><input type="text" v-if="inputVisible" @blur="showButton"><!-- 当点击按钮后,按钮消失,出现文本框 --><button v-else @click="showInput">展示输入框</button><hr><!-- 3.将组件渲染出来 --><div class="box"><!-- 渲染 Left 组件和 Right 组件 --><!-- 给Left组件添加ref可以拿到Left组件中的引用 --><!-- 因为ref是拿DOM的方法,所以通过这个方法就可以拿到Left元素(这里将Left看作是DOM元素) --><Left ref="comLeft"></Left></div></div>
</template><script>
//1.导入组件
import Left from '@/components/Left.vue'
export default {data(){return {// 控制输入框和按钮的按需切换// 默认值为false,表示默认展示按钮,隐藏输入框inputVisible:false}},methods:{showInput(){this.inputVisible=true},showButton(){this.inputVisible=false},showThis(){//指向空对象console.log(this);// 拿到DOM元素console.log(this.$refs.myh12);// 给DOM添加样式this.$refs.myh12.style.color='red'},onReset(){// this.$refs.comLeft.resetCount()// 等价于this.$refs.comLeft.count=0}},//2.注册组件components:{Left}
}
</script><style lang="less">
.app-container {padding: 1px 20px 20px;background-color: #efefef;
}
.box {display: flex;
}
</style>

5. 让文本框自动获得焦点

当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可

6. this.$nextTick(cb) 方法

组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素

4.购物车案例

1. 案例效果

2. 实现步骤

1.导入Header组件

<template><div class="app-container"><!-- 3.使用组件 --><!-- Header头部区域 --><Header></Header></div>
</template><script>
// 1.导入Header组件
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
export default {// 2.注册组件components:{Header}
}
</script>

2.基于axios请求列表数据

1.安装axios,并在App.vue中导入axios

2.在methods方法中,定义initCartList函数请求列表数据

3.在created生命周期函数中,调用步骤2封装的initCartList函数

<template><div class="app-container"><!-- 3.使用组件 --><!-- Header头部区域 --><Header></Header><h1>App 根组件</h1></div>
</template><script>
// 导入axios请求库
import axios from 'axios'// 1.导入Header组件
// 导入需要的组件
import Header from '@/components/Header/Header.vue'export default {created(){//调用请求数据的方法// 当数据一旦创建好就调用下面的这个方法this.initCartList();},methods:{// 封装请求列表数据的方法--发起数据请求都是在生命周期created中进行请求的async initCartList(){// 调用axios的get方法,请求列表数据// axios.get--得到的是一个对象// 如果使用了await,则记得在函数名前面加上async// data:res--是将获得的对象中,实际获得的数据是:对象.data// data:res--表示对data这个属性进行重命名const {data:res}=await axios.get('https://www.escook.cn/api/cart')console.log(res);}},// 2.注册组件components:{Header}
}
</script><style lang="less" scoped>
.app-container {padding-top: 45px;padding-bottom: 50px;
}
</style>

3.请求回来的数据存放在data中

1.先在data中声明一个空的数组,将获取得到的数据存入

2.在initCartList函数中判断是否获取数据成功

3.获取成功后,将真正的数据存入

<script>
// 导入axios请求库
import axios from 'axios'// 1.导入Header组件
// 导入需要的组件
import Header from '@/components/Header/Header.vue'export default {data(){return {list:[]}},created(){//调用请求数据的方法// 当数据一旦创建好就调用下面的这个方法this.initCartList();},methods:{// 封装请求列表数据的方法--发起数据请求都是在生命周期created中进行请求的async initCartList(){// 调用axios的get方法,请求列表数据// axios.get--得到的是一个对象// 如果使用了await,则记得在函数名前面加上async// data:res--是将获得的对象中,实际获得的数据是:对象.data// data:res--表示对data这个属性进行重命名const {data:res}=await axios.get('https://www.escook.cn/api/cart')// 只要请求回来的数据,在页面中渲染期间需要用到,必须转存到data中console.log(res);if(res.status === 200){// 如果数据请求成功,则将res中的list(存放数据的)赋值给data中list数组this.list=res.list}}},// 2.注册组件components:{Header}
}
</script>

4.循环渲染Goods组件

1.先将Goods组件引入App.vue文件中

2.组件的循环使用v-for,记得后面加上:key

    <!-- 循环渲染每一个商品的信息 --><!-- 组件的循环使用v-for --><!-- 使用v-for记得加上key,key指的是id(唯一性) --><Goods v-for="item in list" :key="item.id"></Goods>//2.导入Goods组件
import Goods from '@/components/Goods/Goods.vue'// 2.注册组件components:{Header,Goods,}

5.为Goods组件封装title、pic属性、prices属性和state属性:【父组件向子组件传值】

主要思路:父向子传值(就是在App.vue文件中通过axios获得的数据传递给Goods组件进行渲染)

1.在Goods组件中声明props属性

2.在标签中(App.vue和Goods.vue中)进行动态绑定

注意点:

插值表达式"{{}}"不能用在属性中 只能使用动态绑定来实现 ":"

      <h6 class="goods-title">{{title}}</h6><!-- 插值表达式"{{}}"不能用在属性中 只能使用动态绑定来实现 ":" --><!-- <img src="../../assets/logo.png" alt="" /> --><img :src="pic" alt="" />

App.vue

<template><div class="app-container"><!-- 3.使用组件 --><!-- Header头部区域 --><Header></Header><!-- 循环渲染每一个商品的信息 --><!-- 组件的循环使用v-for --><!-- 使用v-for记得加上key,key指的是id(唯一性) --><!-- 记得在title前面加上 ":"  要不然传入的是字符串 --><Goods v-for="item in list" :key="item.id" :title="item.goods_name"  :pic="item.goods_img" :price="item.goods_price" :state="item.goods_state"></Goods></div>
</template>

Goods.vue

<template><div class="goods-container"><!-- 左侧图片 --><div class="thumb"><div class="custom-control custom-checkbox"><!-- 复选框 --><input type="checkbox" class="custom-control-input" id="cb1" :checked="state" /><label class="custom-control-label" for="cb1"><!-- 商品的缩略图 --><!-- 插值表达式"{{}}"不能用在属性中 只能使用动态绑定来实现 ":" --><!-- <img src="../../assets/logo.png" alt="" /> --><img :src="pic" alt="" /></label></div></div><!-- 右侧信息区域 --><div class="goods-info"><!-- 商品标题 --><!-- 这里需要用到动态绑定 --><!-- <h6 class="goods-title">图片图片</h6> --><h6 class="goods-title">{{title}}</h6><div class="goods-info-bottom"><!-- 商品价格 --><span class="goods-price">{{price}}</span><!-- 商品的数量 --></div></div></div>
</template><script>
export default {props:{// 要渲染的商品的标题title:{default:'',type:String},pic:{default:'',// 图片是字符串类型type:String},price:{// 商品的单价default:0,type:Number},state:{// 商品的勾选状态// 默认为选中状态default:true,type:Boolean}}
}
</script>

6.关于自定义组件的属性传递问题【分析封装props两种方案的优缺点】

7.如何修改商品勾选状态【子组件向父组件传值】

8.自定义state-change事件

父组件:App.vue

<template><div class="app-container"><!-- 3.使用组件 --><!-- Header头部区域 --><Header></Header><!-- 循环渲染每一个商品的信息 --><!-- 组件的循环使用v-for --><!-- 使用v-for记得加上key,key指的是id(唯一性) --><!-- 记得在title前面加上 ":"  要不然传入的是字符串 --><Goods v-for="item in list" :key="item.id" :title="item.goods_name"  :pic="item.goods_img" :price="item.goods_price" :state="item.goods_state" :id="item.id" @state-change="getNewState"></Goods></div>
</template>methods:{},
// 接收子组件
getNewState(val){console.log('父组件接收到数据了');console.log(val);
}

子组件:Count.vue

        <!-- 复选框 --><!-- 这里的checked不能使用v-model,因为props是只读属性,而v-model是双向数据绑定的 --><input type="checkbox" class="custom-control-input" id="cb1" :checked="state" @change="stateChange"/>methods:{// 只要复选框的选中状态发生了改变,就会调用这个出来函数stateChange(e){// console.log('ok');// console.log(e);// 获得最新的状态const newState=e.target.checked// console.log(newState);// 触发自定义事件this.$emit('state-change',{id:this.id,value:newState})}}

9.修改对应商品的勾选状态

父组件

// 接收子组件传递过来的数据
// e的格式是{id,value}
getNewState(e){console.log('父组件接收到数据了');// console.log(e);// 遍历传入的数组,判断是否有符合条件的this.list.some(item=>{if(item.id===e.id){//将商品的状态进行修改item.goods_state=e.value// 终止后续的状态return true}})
}

10.底部的“全选”按钮是否选中:【计算属性:computed/父组件向子组件传递】

因为全选按钮要被其他按钮影响到,所以要使用计算属性

1.先判断是否将复选框勾选上---->计算属性:computed

2.【当小按钮全部选中后,大按钮也被选中】将在App.vue文件中获得步骤1的结果渲染到Footer组件中---->父组件向子组件传递(自定义事件)

3.【当大按钮被选中后,小按钮全部被选中】将在Footer组件中触发到的事件渲染到App.vue组件中-->子组件向父组件传递(自定义属性)

  // 计算属性,用于底部"总计"复选框是否选中computed:{// 动态计算出全选的状态是true还是falsefullState(){// 数组.every返回的是布尔值return this.list.every(item=>item.goods_state===true)}},

    <!-- Footer区域 --><Footer :isFull="fullState" ></Footer>// 计算属性,用于底部"总计"复选框是否选中computed:{// 动态计算出全选的状态是true还是falsefullState(){// 数组.every返回的是布尔值return this.list.every(item=>item.goods_state===true)}},<input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull"  />

<!-- Footer区域 --><Footer :isFull="fullState" @full-change="getFullState"></Footer>// 接收Footer子组件传递过来的全选按钮的状态getFullState(e){console.log('在App中拿到了全选的按钮');console.log(e);// 因为是要遍历小复选框中的每一项所以使用forEachthis.list.forEach(item=>item.goods_state=e)},      <input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull" @change="fullChange" />methods:{// 监听到了全选状态的变化fullChange(e){// console.log(e.target.checked);this.$emit('full-change',e.target.checked)},}

11.计算商品的总价格

1.先在父组件App.vue组件中先写出计算属性

2.然后将计算出来的结果渲染到Footer组件(子组件)

父组件

<template><div class="app-container"><!-- 3.使用组件 --><!-- Header头部区域 --><Header></Header><p>{{ amt }}</p><!-- 循环渲染每一个商品的信息 --><!-- 组件的循环使用v-for --><!-- 使用v-for记得加上key,key指的是id(唯一性) --><!-- 记得在title前面加上 ":"  要不然传入的是字符串 --><Goods v-for="item in list" :key="item.id" :title="item.goods_name"  :pic="item.goods_img" :price="item.goods_price" :state="item.goods_state" :id="item.id" @state-change="getNewState"></Goods><!-- Footer区域 --><Footer :isFull="fullState" :amount="amt" @full-change="getFullState"></Footer></div>
</template><script>
// 导入axios请求库
import axios from 'axios'// 导入需要的组件
// 1.导入Header组件
import Header from '@/components/Header/Header.vue'
//2.导入Goods组件
import Goods from '@/components/Goods/Goods.vue'
//3.导入Footer组件
import Footer from '@/components/Footer/Footer.vue'export default {data(){return {list:[]}},// 计算属性,用于底部"总计"复选框是否选中computed:{// 动态计算出全选的状态是true还是falsefullState(){// 数组.every返回的是布尔值return this.list.every(item=>item.goods_state===true)},// 已勾选商品的总价格amt(){// 1.先filter过滤// 2.在reduce累加return this.list.filter(item=>item.goods_state).reduce((total,item)=>{return total+=item.goods_price*item.goods_count},0)}},created(){//调用请求数据的方法// 当数据一旦创建好就调用下面的这个方法this.initCartList();},methods:{// 封装请求列表数据的方法--发起数据请求都是在生命周期created中进行请求的async initCartList(){// 调用axios的get方法,请求列表数据// axios.get--得到的是一个对象// 如果使用了await,则记得在函数名前面加上async// data:res--是将获得的对象中,实际获得的数据是:对象.data// data:res--表示对data这个属性进行重命名const {data:res}=await axios.get('https://www.escook.cn/api/cart')// 只要请求回来的数据,在页面中渲染期间需要用到,必须转存到data中console.log(res);if(res.status === 200){// 如果数据请求成功,则将res中的list(存放数据的)赋值给data中list数组this.list=res.list}},
// 接收子组件传递过来的数据
// e的格式是{id,value}
getNewState(e){console.log('父组件接收到数据了');// console.log(e);// 遍历传入的数组,判断是否有符合条件的this.list.some(item=>{if(item.id===e.id){//将商品的状态进行修改item.goods_state=e.value// 终止后续的状态return true}})
},// 接收Footer子组件传递过来的全选按钮的状态getFullState(e){console.log('在App中拿到了全选的按钮');console.log(e);// 因为是要遍历小复选框中的每一项所以使用forEachthis.list.forEach(item=>item.goods_state=e)},},// 2.注册组件components:{Header,Goods,Footer,}
}
</script><style lang="less" scoped>
.app-container {padding-top: 45px;padding-bottom: 50px;
}
</style>

子组件

<template><div class="footer-container"><!-- 左侧的全选 --><div class="custom-control custom-checkbox"><input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull" @change="fullChange" /><label class="custom-control-label" for="cbFull">全选</label></div><!-- 中间的合计 --><div><span>合计:</span><!-- toFixed---表示显示2位小数 --><span class="total-price">¥{{ amount.toFixed(2) }}</span></div><!-- 结算按钮 --><button type="button" class="btn btn-primary btn-settle">结算({{ 0 }})</button></div>
</template><script>
export default {// 定义一个属性,用于接收父组件传递过来的复选框的勾选状态props:{// 全选状态isFull:{type:Boolean,default:true},// 总价格amount:{type:Number,default:0},},methods:{// 监听到了全选状态的变化fullChange(e){// console.log(e.target.checked);this.$emit('full-change',e.target.checked)},}
}
</script><style lang="less" scoped>
.footer-container {font-size: 12px;height: 50px;width: 100%;border-top: 1px solid #efefef;position: fixed;bottom: 0;background-color: #fff;display: flex;justify-content: space-between;align-items: center;padding: 0 10px;
}.custom-checkbox {display: flex;align-items: center;
}#cbFull {margin-right: 5px;
}.btn-settle {height: 80%;min-width: 110px;border-radius: 25px;font-size: 12px;
}.total-price {font-weight: bold;font-size: 14px;color: red;
}
</style>

12.把购买数量传给counter组件

1.Counter组件是Goods的子组件

2.要从Goods组件中获得的数量传递给Counter组件,但是在Goods组件中得不到(因为没有数量属性),所以要先从App组件中获得,才能给Counter组件

2.1 将App组件中的数据传递给Goods组件

2.2 将Goods组件中的数据传递给Counter组件中

13.将在增删商品数量的结果传递给App组件

1.向App组件传递数据记得要传入id,则先在Counter中获得id属性[记得在Goods中将属性传递给Counter]

2.给Counter组件添加点击事件

3.创建EventBus.js文件,然后再发送方和接收方上加上EventBus

13.动态计算已勾选上商品数量

总结

 四、动态组件 & 插槽 & 自定义指令

1.动态组件

1. 什么是动态组件:动态切换组件的显示与隐藏

2. 如何实现动态组件渲染:<components :is="要渲染的组件名">组件

vue 提供了一个内置的<components >组件专门用来实现动态组件的渲染。【相当于一个占位符】

1.components标签是vue内置的,作用:组件的占位符

2.is属性的值,表示要渲染的组件的名字

3.is属性的,应该是组件再components节点下注册的名称

3.动态切换组件的展示与隐藏

<template><div class="app-container"><h1>App 根组件</h1><hr /><button @click="comName='Left'">展示Left</button><button @click="comName='Right'">展示Right</button><div class="box"><!-- 渲染 Left 组件和 Right 组件 --><!-- 1.components标签是vue内置的,作用:组件的占位符 --><!-- 2.is属性的值,表示要渲染的组件的名字 --><!-- 3.is属性的,应该是组件再components节点下注册的名称 --><!-- 写死了 --><!-- <components is="Left"></components> --><!-- 动态绑定 --><components :is="comName"></components></div></div>
</template><script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'export default {data(){return {// comName表示要进行展示的组件名字comName:Left}},components:{Left,Right,}
}
</script><style lang="less">
.app-container {padding: 1px 20px 20px;background-color: #efefef;
}
.box {display: flex;
}
</style>

4. 使用 keep-alive 保持状态

keep-alive可以把内部的组件进行缓存,而不是销毁组件.

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的<keep-alive> 组件保持动态组 件的状态。

      <keep-alive><components :is="comName"></components></keep-alive>

5. keep-alive 对应的生命周期函数:被缓存的时候才可以使用

当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。

当组件被激活时,会自动触发组件的 activated 生命周期函数。 

  1.当组件第一次被创建的时候,既会执行created生命周期,也会执行activated生命周期

  2. 但是,当组件被激活的时候,只会触发activated生命周期,不会触发created,因为组件没有重新被创建

6. keep-alive 的 include/exclude 属性:指定哪一些组件可以/不被被缓存

include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:

include:指定要进行缓存的组件 这里指定就缓存Left组件

exclude:指定不进行缓存的

 include和exclude不能同时使用

7.了解组件注册名称和组件声明时name区别

当提供了name属性之后,组件的名称,就是name属性的值

对比

   1.组件的"注册名称" components:{ Left,Right,} 的主要应用常见是:以标签的形式,把注册好的组件,渲染和使用到页面结构之中

   2.组件声明时候的"name"名称的主要应用场景"结合<keep-alive>标签实现组件缓存功能;以及再调试工具中看到组件的name名称

2.插槽

1. 什么是插槽<slot>:占位符

插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的 部分定义为插槽。

可以把插槽认为是组件封装期间,为用户预留的内容的占位符。

2. 体验插槽的基础用法

在封装组件时,可以通过<slot> 元素定义插槽,从而为用户预留内容占位符。

2.1 没有预留插槽的内容会被丢弃

如果在封装组件时没有预留任何<slot> 插槽,则用户提供的任何自定义内容都会被丢弃

2.2 后备内容:如果用户输入内容则会将默认内容进行覆盖

封装组件时,可以为预留的<slot> 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何 内容,则后备内容会生效。

3. 具名插槽:v-slot

如果在封装组件时需要预留多个插槽节点,则需要为每个<slot> 插槽指定具体的 name 名称。这种带有具体 名称的插槽叫做“具名插槽”。

vue官方规定:每一个slot插槽,都要有一个name名称

如果省略了slot的name属性,则有一个默认名称叫做default

  默认情况下,在使用组件的时候,提供的内容会被填充到名字为default的插槽中

3.1 为具名插槽提供内容

在向具名插槽提供内容的时候,我们可以在一个<template> 元素上使用 v-slot 指令,并以 v-slot 的参数的 形式提供其名称。

1.如果要把内容填充到指定名称的插槽中,需要使用v-slot这个指令

2.v-slot:后面要跟上插槽的名字

  3.v-slot:指令不能直接用在元素身上,必须用在template标签上,或者组件

4.template这个标签,它是一个虚拟标签,只起到包裹性质作用,但是,不会被渲染为任何实际性质的html元素

5.v-slot简写形式为:#

        <template v-slot:default><!-- 如果再Left组件中没有定义插槽,则下面的p标签会被忽略 --><!-- 默认情况下,在使用组件的时候,提供的内容会被填充到名字为default的插槽中 --><p>这是在Left组件的内容中声明的p标签</p></template>

3.2 具名插槽的简写形式:"#"

跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:

    <Article><template #title><h3>一首歌</h3></template><template #content> <div><p>啊,大海,全是水。</p><p>啊,蜈蚣,全是腿。</p><p>啊,辣椒,净辣嘴。</p></div></template><template #author><div>作者</div></template></Article>
<template><div class="article-container"><!-- 文章标题 --><div class="header-box"><slot name="title"></slot></div><!-- 文章的内容 --><div class="content-box"><!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” --><slot name="content"></slot></div><!-- 文章的作者 --><div class="footer-box"><slot name="author"></slot></div></div>
</template>

4. 作用域插槽

在封装组件的过程中,可以为预留的 <slot> 插槽绑定 props 数据这种带有 props 数据的 叫做“作用 域插槽”。

4.1 使用作用域插槽

可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据。

在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” 

      <!-- 这个obj得到的是一个对象 --><!-- 用scope接收作用域插槽中的数据 --><template #content="scope"> <div><p>啊,大海,全是水。</p><p>啊,蜈蚣,全是腿。</p><p>啊,辣椒,净辣嘴。</p><!-- 拿到的是:{ "msg": "hello vue.js" } --><p>{{ scope }}</p></div></template><!-- 文章的内容 --><div class="content-box"><!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” --><slot name="content" msg="hello vue.js"></slot></div>

4.2 解构插槽 Prop

作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。

      <!-- <template #content="scope">  --><template #content="{msg,user}"> <div><p>啊,大海,全是水。</p><p>啊,蜈蚣,全是腿。</p><p>啊,辣椒,净辣嘴。</p><!-- 拿到的是:{ "msg": "hello vue.js" } --><p>{{ user.name }}</p></div></template><div class="content-box"><!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” --><!-- msg="hello vue.js"--表示写入一个固定的值 --><!-- :user="userinfo"---表示动态绑定一个属性 --><slot name="content" msg="hello vue.js" :user="userinfo"></slot></div>

5.购物车案例改造

5.1 基于slot插槽改造购物车案例--在App组件中直接获得Counter组件

5.2 基于slot插槽改造购物车案例--在App组件中直接获得Counter组件中的数值

3.自定义指令

1. 什么是自定义指令

vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。

2. 自定义指令的分类

3. 私有自定义指令:directives 节点

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令

4. 使用自定义指令

在使用自定义指令时,需要加上 v- 前缀

<template><div class="app-container"><h1 v-color>App 根组件</h1></div>
</template><script>export default {// 私有自定义指令的节点directives:{// v-color--(v-)是固定写法,实际名字为:color    // 定义名为color的自定义指令,指向一个配置对象color:{// 当指令第一次被绑定到元素的时候,会立即触发bind函数// 形参中el表示当前指令所绑定到的那个DOM对象bind(el){// console.log('触发了v-color的bind 函数')el.style.color='red'}}}
}
</script>

5. 为自定义指令动态绑定参数值

在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值

<h1 v-color="color">App 根组件</h1>data(){return {color:'blue'}},

6. 通过 binding 获取指令的参数:bind(el,binding){}

在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值:

<h1 v-color="color">App 根组件</h1>data(){return {color:'blue'}},// 私有自定义指令的节点directives:{// v-color--(v-)是固定写法,实际名字为:color    // 定义名为color的自定义指令,指向一个配置对象color:{// 当指令第一次被绑定到元素的时候,会立即触发bind函数// 形参中el表示当前指令所绑定到的那个DOM对象bind(el,binding){// console.log('触发了v-color的bind 函数')// el.style.color='red'// 当自定义指令中动态绑定了值// binding---是一个对象// console.log(binding);el.style.color=binding.value}}}

7.单独在属性值后面添加属性值

8. update 函数

bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发update 函 数会在每次 DOM 更新时被调用。

9. 函数简写

如果binding 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:

10. 全局自定义指令:Vue.directive()【写在main.js中】

全局共享的自定义指令需要通过“Vue.directive()”进行声明

1.参数1:字符串,表示全局变量自定义指令的名字

2.参数2:对象,用l来接收指令的参数值

// 全局自定义指令
/*Vue.directive('color',{bind(el,binding){el.style.color=binding.value},update(el,binding){el.style.color=binding.value}
})*/Vue.directive('color',function(el,binging){el.style.color=binding.value
})

总结

五、ESLint:代码约束

1.了解eslintrc.js配置文件中rules规则

2.初步了解ESLint的语法规则

当报错的时候,去官网查找:ESLint - Pluggable JavaScript linter - ESLint中文

3.配置VsCode

3.1 ESlint

    // ESLint组件的配置"editor.codeActionsOnSave": {"source.fixAll": true},

3.2 prettier

    "eslint.alwaysShowStatus":true,"prettier.trailingComma": "none","prettier.semi":false,// 每行文字个数超出此限制见过会被迫换行"prettier.printWidth":300,// 使用单引号替换双引号"prettier.singleQuote": true,"prettier.arrowParens": "avoid",// 设置.vue文件中,HTML代码的格式化插件"vetur.format.defaultFormatter.html": "js-beautify-html","vetur.ignoreProjectWarning": true,"vetur.format.defaultFormatterOptions": {"prettier": {"trailingComma":"none","semi":false,"singleQuote":true,"arrowParens":"avoid","printWidth":300},"js-beautify-html": {"wrap_attributes": "false"}},

3.3 在电脑上配置一个文件

文件内容为:

 {"semi": false, "singleQuote": true, "printWidth": 300}

在setting.json文件中加上

"prettier.configPath": "C:\\Users\\小林\\.prettierrc",

4.配置默认格式化文档的方式

在vue文件下

在js文件下 

 出现保存自动加逗号,则在setting.json文件中将

5.演示axios的基本使用并发现问题

5.1 在装axios包的时候出现错误

则应该修改为:

npm i axios -S --legacy-peer-deps

 5. 2 在components文件夹中起名字为Left-vue和Right-vue的两个组件

   【注意点:一定要在文件名后面加上-vue要不然报错】

5.3 如果不想将名字修改为-vue,则修改.eslintrc.js文件

module.exports = {root: true,env: {node: true},extends: ['plugin:vue/essential','@vue/standard'],parserOptions: {parser: '@babel/eslint-parser'},rules: {'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off','no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',// 在方法的形参 () 之前,是否必须有空格'space-before-function-paren': ['warn', 'never'],'vue/multi-word-component-names': 'off'}
}

6.Right.vue和Left.vue

<template><div class="right-container"><h3>Right组件</h3><button @click="postInfo">发起 POST 请求</button></div></template><script>
import axios from 'axios'
export default {methods: {async postInfo() {const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', age: 20 })console.log(res)}}
}
</script><style lang="less" scoped>
.right-container {background-color: pink;min-height: 200px;flex:1;
}
</style>
<template><div class="left-container"><h3>Left组件</h3><button @click="getInfo">发起 GET 请求</button></div>
</template><script>import axios from 'axios'export default {methods: {async getInfo() {const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/get')console.log(res)}}}
</script><style lang="less" scoped>
.left-container {background-color: orange;min-height: 200px;flex:1;
}
</style>

7.把axios挂载到Vue的原型上并配置请求根路径

把axios挂载到Vue的原型上

把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!

 配置请求根路径

main.js文件

import Vue from 'vue'
import App from './App.vue'import axios from 'axios'
Vue.config.productionTip = false// 全局配置 axios 的请求根路径
axios.defaults.baseURL = 'http://www.liulongbin.top:3006'// 将axios属性挂载在Vue的原型上
// Vue.prototype.axios = axios
Vue.prototype.$http = axios// 今后,在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx
// 但是,把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!new Vue({render: (h) => h(App)
}).$mount('#app')

六、路由

1.前端路由的概念与原理

1. 什么是路由:路由(英文:router)就是对应关系

3. SPA (单页面开发)与前端路由

4. 什么是前端路由:#Hash 地址(锚链接)与组件之间的对应关系

5. 前端路由的工作方式

① 用户点击了页面上的路由链接

② 导致了 URL 地址栏中的 Hash 值发生了变化

前端路由监听了到 Hash 地址的变化

④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

前端路由,指的是 Hash 地址与组件之间的对应关系

6. 手动实现简易的前端路由

步骤1:通过 <component> 标签,结合 comName 动态渲染组件。示例代码如下:

步骤2:在 App.vue 组件中,为 <a>链接添加对应的hash值

步骤3:在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:

  created(){// 只要当前App组件一被创建,就立即监听window对象的onhashchange事件// onhashchange---是拿到当前的哈希地址window.onhashchange=()=>{// location.hash---拿到的是根地址后面的具体地址===#/hmoe   #/movieconsole.log('监听到了hash地址的变化',location.hash);switch(location.hash){case '#/home':this.comName='home'breakcase '#/movie':this.comName = 'movie'breakcase '#/about':this.comName = 'about'break}}

2.vue-router的基本使用

1. 什么是 vue-router

2. vue-router 安装和配置的步骤

① 安装 vue-router 包

② 创建路由模块

③ 导入并挂载路由模块

④ 声明路由链接和占位符

2.1 在项目中安装 vue-router

2.2 创建路由模块:导入,调用,创建,共享(router/index.js

在 src 源代码目录下,新建 router/index.js 路由模块

// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)// 创建路由的实例对象
const router=new VueRouter()// 将创建好的实例对象向外暴露
export default router

2.3 导入并挂载路由模块(在 src/main.js)

在 src/main.js 入口文件中,导入并挂载路由模块。

import Vue from 'vue'
import App from './App.vue'
// 导入路由模块,目的:拿到路由的实例对象
import router from '@/router/index.js'// 导入 bootstrap 样式
import 'bootstrap/dist/css/bootstrap.min.css'
// 全局样式
import '@/assets/global.css'Vue.config.productionTip = falsenew Vue({render: h => h(App),// 在VUe项目中,想要把路由用起来,必须把路由实例对象,通过下面的方式进行挂载// router:路由的实例对象router:router
}).$mount('#app')

2.4 路由的基本用法

在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下,名字叫做index.js的文件

2.5 声明路由占位符

在 src/App.vue 组件中,使用 vue-router 提供的 <router-view> 声明路由占位符

<template><div class="app-container"><h1>App2 组件</h1><a href="#/home">首页</a><a href="#/movie">电影</a><a href="#/about">关于</a><hr /><!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 --><!-- 它的作用很单纯:占位符 --><router-view></router-view></div>
</template>

2.6 使用router-link替代a链接

    <!-- 当安装和配置了vue-router后,就可以使用router-link来替代普通的a链接 -->//<a href="#/home">首页</a><router-link to="/home">首页</router-link>//<a href="#/movie">电影</a><router-link to="movie">电影</router-link>//<a href="#/about">关于</a><router-link to="/about">关于</router-link>

3. 声明路由的匹配规则

在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。

// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'// 导入想要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)// 创建路由的实例对象
const router=new VueRouter({// routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系// routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]routes:[{path:'/home',component:Home},{path:'/movie',component:Movie},{path:'/about',component:About}]
})// 将创建好的实例对象向外暴露
export default router

3.vue-router的常见用法

1. 路由重定向:链接的强制跳转(redirect 属性)【在index.js文件中写的对应关系】

路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。 通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

// 创建路由的实例对象
const router=new VueRouter({// routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系// routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]routes:[// 重定向的路由规则{path:'/',redirect:'/home'},// 路由规则{path:'/home',component:Home},{path:'/movie',component:Movie},{path:'/about',component:About}]
})

2. 嵌套路由:实现组件的嵌套展示

3.1 声明子路由链接和子路由占位符 (在About组件中写的)

子级路由链接

子级路由占位符

<template><div class="about-container"><h3>About 组件</h3><!-- 子级路由链接 --><router-link to="/about/tab1">tab1</router-link><router-link to="/about/tab2">tab2</router-link><hr><!-- 子级路由占位符 --><router-view></router-view></div>
</template>

3.2 通过 children数组 属性声明子路由规则

在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则:

使用children属性中包裹的子路由path路径中不能加”/“

父路由的path必须加”/“ 


import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'// 创建路由的实例对象
const router=new VueRouter({// routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系// routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]routes:[// 重定向的路由规则{path:'/',redirect:'/home'},// 路由规则{path:'/home',component:Home},{path:'/movie',component:Movie},{path:'/about',component:About,children:[// 子路由规则{path:'tab1',component:Tab1},{path:'tab2',component:Tab2}]}]
})

3.3 默认子路由

1.使用redirect属性

// 创建路由的实例对象
const router=new VueRouter({// routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系// routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]routes:[// 重定向的路由规则{path:'/',redirect:'/home'},// 路由规则{path:'/home',component:Home},{path:'/movie',component:Movie},{path:'/about',component:About,redirect:'/about/tab1',children:[// 子路由规则{path:'tab1',component:Tab1},{path:'tab2',component:Tab2}]}]
})

2. 默认子路由

默认子路由:如果children数组中,某一个路由规则path值为空字符串,则这条路由规则,叫做“默认子路由”

    <!-- 子级路由链接 --><router-link to="/about/">tab1</router-link>// 创建路由的实例对象
const router=new VueRouter({// routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系// routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]routes:[// 重定向的路由规则{path:'/',redirect:'/home'},// 路由规则{path:'/home',component:Home},{path:'/movie',component:Movie},{path:'/about',component:About,redirect:'/about/tab1',children:[// 子路由规则// 默认子路由:如果children数组中,某一个路由规则path值为空字符串,则这条路由规则,叫做“默认子路由”// {path:'tab1',component:Tab1},{path:'',component:Tab1},{path:'tab2',component:Tab2}]}]
})

4. 动态路由匹配

4.1 动态路由的概念

动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。 在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下

4.2 $route.params 参数对象

在动态路由渲染出来的组件中,可以使用 this.$route.params.id名 对象访问到动态匹配的参数值。

注意:在hash地址中,/后面的参数项,叫做”路径参数“

在路由”参数对象“中,需要使用this.$route.parms来访问路径参数

4.3 使用 props 接收路由参数

为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参(index.js)

    // props:true---表示为Movie这个组件开启自定义属性// 可以为路由规则开启props传参,从而方便的拿到动态参数的值{path:'/movie/:id',component:Movie,props:true},// 接收props数组props:['id']<h3>Movie 组件---{{ id }}</h3>

4.4 query和fullPath

注意1:在hash地址中,/后面的参数项,叫做”路径参数“

在路由”参数对象“中,需要使用this.$route.parms来访问路径参数

注意2:在hash地址中,?后面的参数项,叫做”查询参数“

在路由”参数对象“中,需要使用this.$route.query来访问查询参数

注意3:在this.$route中,path只是路径部分,fullPath是完整的地址

例如:

/movie/2?name=zs&age=20  是fullPath的值

/movie/2  是fullPath的值

<router-link to="/movie/2?name=zs&age=20">雷神</router-link>

5. 声明式导航 & 编程式导航

5.1 vue-router 中的编程式导航 API

5.2 this.$router.push('hash地址):增加一条记录

调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

  <button @click="gotoLK">跳转到”洛基“页面</button>methods:{gotoLK(){// 通过编程式导航API,导航跳转到指定的页面this.$router.push('/movie/1')}}

5.3 this.$router.replace('hash地址'):替换掉当前的历史记录

调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

push 和 replace 的区别:

⚫ push 会增加一条历史记录

⚫ replace 不会增加历史记录,而是替换掉当前的历史记录

<button @click="gotoLK2">通过replace跳转到”洛基“页面</button>gotoLK2(){// replace---替换当前的页面,没办法回之前的页面this.$router.replace('/movie/1')}

5.4  this.$router.go

调用 this.$router.go() 方法,可以在浏览历史中前进和后退。

<button @click="goback">后退</button>goback(){// 返回前一个页面// 如果后退的层数超过上限,则原地不动// this.$router.go(-1);this.$router.go(-2);}

5.5 $router.go 的简化用法

<!-- 在行内使用编程式导航跳转的时候,this必须要省略,否则报错 --> <!-- back()--后退一层 --><button @click="$router.back()">back 后退</button><!-- forward()--前进一层 --><button @click="$router.forward()">forward 前进</button>

6. 导航守卫:以控制路由的访问权限

6.1 全局前置守卫:全局生效

每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行 访问权限的控制:

6.2 守卫方法的 3 个形参

全局前置守卫的回调函数中接收 3 个形参


// 创建路由的实例对象
const router=new VueRouter()// 为router实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发beforEach指定的function回调函数
router.beforeEach(function(to,from,next){// to-表示要访问的路由信息对象// from-表示将要离开的路由信息对象// next()函数表示放行,如果被调用就会报错next()
})

6.3 next 函数的 3 种调用方式

当前用户拥有后台主页的访问权限,直接放行:next()

当前用户没有后台主页的访问权限,强制其跳转到登录页面:next('/login')

当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)

6.4 控制后台主页的访问权限

// 为router实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发beforEach指定的function回调函数
router.beforeEach(function(to,from,next){// to-表示要访问的路由信息对象// from-表示将要离开的路由信息对象// next()函数表示放行,如果被调用就会报错// 分析:// 1. 要拿到用户将要访问的 hash 地址// 2. 判断 hash 地址是否等于 /main。// 2.1 如果等于 /main,证明需要登录之后,才能访问成功// 2.2 如果不等于 /main,则不需要登录,直接放行  next()// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值// 3.1 如果有 token,则放行// 3.2 如果没有 token,则强制跳转到 /login 登录页if (to.path === '/main') {// 要访问后台主页,需要判断是否有 tokenconst token = localStorage.getItem('token')if (token) {//表示拿到token值next()} else {// 没有登录,强制跳转到登录页next('/login')}}//表示不是要进入后台主页else {next()}
})

4.后台管理案例

1. 案例效果

2. 案例用到的知识

2.1 配置路由

1.安装vue-router的包

2.在src中创建一个router的文件夹,在此文件夹中创建一个index.js

3.在main.js中导入路由

index.js

import Vue from 'vue'
import VueRouter from 'vue-router'// 将插件装上
Vue.use(VueRouter)const router=new VueRouter()export default router

main.js


// 导入路由模块
import router from '@/router/index.js'new Vue({render: h => h(App),router:router
}).$mount('#app')

2.2 基于路由渲染登录组件

1.在inedx.js文件中导入需要进行链接的hash地址和组件

2.在App.vue中输入占位符

3.将进入“/”地址页面的时候,强制跳转到login页面(redirect)

{path:'/',redirect:'/login'}

index.js


// 导入需要的组件
import Login from '@/components/MyLogin.vue'const router=new VueRouter({// 路由规则,建立hash地址与组件的对应关系routes:[// 当进入/地址的时候强制跳转到login页面{path:'/',redirect:'/login'},{path:'/login',component:Login},]
})

App.vue

<template><div><!-- 路由占位符 --><router-view></router-view></div>
</template>

2.3 模拟登录功能

1.给MyLogin组件输入data数据

2.分别给登录名和登录密码加上v-molde双向数据绑定

3.判断输入的是否与设置的一致

3.1 如果登录成功则记录token并且跳转页面

3.2 如果登录失败,则移除token

MyLogin.vue

<!-- 给登录名进行数据的双向绑定:v-modle --><input type="text" class="form-control ml-2" id="username" placeholder="请输入登录名称" autocomplete="off" v-model.trim="username">
<input type="password" class="form-control ml-2" id="password" placeholder="请输入登录密码" v-model.trim="passward">
<button type="button" class="btn btn-secondary mr-2" @click="reset">重置</button>
<button type="button" class="btn btn-primary" @click="login">登录</button><script>
export default {name: 'MyLogin',data(){return {username:'',passward:''}},methods:{// 重置按钮reset(){this.username='',this.passward=''},login(){if(this.username==='admin' && this.passward==='666666'){// 登录成功// 1.存储tokenlocalStorage.setItem('token','Bearer xxxx')// 2.跳转到后台主页this.$router.push('/home')}else{//登录失败// 清除tokenlocalStorage.removeItem('token')}}}
}
</script>

2.4 实现后台主页的基础布局

1.在index.js文件中声明hash地址与组件的对应关系

// 导入需要的组件
import Home from '@/components/MyHome.vue'// 将插件装上
Vue.use(VueRouter)const router=new VueRouter({// 路由规则,建立hash地址与组件的对应关系routes:[// 后台主页的路由规则{path:'/home',component:Home}]
})

2.在MyHome.vue组件中编写页面的形式

<template><div class="home-container"><!-- 头部区域 --><MyHeader></MyHeader><!-- 页面主体区 --><div class="home-main-box"><!-- 左侧边栏 --><MyAside></MyAside><!-- 右侧内容主题区域 --><div class="home-main-body">123</div></div></div>
</template><script>
// 头部区域组件
import MyHeader from './subcomponents/MyHeader.vue'
// 左侧边栏组件
import MyAside from './subcomponents/MyAside.vue'export default {name: 'MyHome',// 注册组件components: {MyHeader,MyAside,},
}
</script><style lang="less" scoped>
.home-container {height: 100%;display: flex;flex-direction: column;.home-main-box {height: 100%;display: flex;.home-main-body {padding: 15px;flex: 1;}}
}
</style>

2.5 退出登录并控制访问权限

1.在退出登录组件中添加带点击事件的时候,删除token

<button type="button" class="btn btn-light" @click="loginout">退出登录</button>methods:{loginout(){// 1.清空tokenlocalStorage.removeItem('token')// 2.跳转到登录页面this.$router.push('/login')}}

2.因为当未登录的时候可以访问home组件,所以要在index.js文件中使用全局守卫保护数据

// 全局前置守卫
router.beforeEach(function(to,from,next){if(to.path==='/home'){// 如果要访问的页面是home,则先进行判断是否已经登录const token=localStorage.getItem('token')if(token){next()}else{next('/login')}}else{next()}
})

2.6 实现子路由的嵌套展示

1.给左侧添加<router-link>链接,并添加to属性【MyAside组件】

    <!-- 左侧边栏列表 --><ul class="user-select-none menu"><li class="menu-item"><router-link to="/home/users">用户管理</router-link></li><li class="menu-item"><router-link to="home/rights">权限管理</router-link></li><li class="menu-item"><router-link to="/home/goods">商品管理</router-link></li><li class="menu-item"><router-link to="/home/orders">订单管理</router-link></li><li class="menu-item"><router-link to="/home/setting">系统设置</router-link></li></ul>

2.在右侧组件中添加占位符【MyHome组件】

      <!-- 右侧内容主题区域 --><div class="home-main-body"><router-view></router-view></div>

3.给地址为“/home”的地址添加子路由嵌套【children添加子路由】

const router=new VueRouter({// 路由规则,建立hash地址与组件的对应关系routes:[// 当进入/地址的时候强制跳转到login页面{path:'/',redirect:'/login'},{path:'/login',component:Login},// 后台主页的路由规则{path:'/home',component:Home,children:[// children中的path路径中不能加“/”{ path: 'users', component: Users },{ path: 'rights', component: Rights },{ path: 'goods', component: Goods },{ path: 'orders', component: Orders },{ path: 'settings', component: Settings },]}]
})

2.7 点击进入用户详情页面

1.先将data数渲染到页面

      <tbody><!-- 使用循环拿到data中的数据进行渲染 --><tr v-for="item in userlist" :key="item.id"><td>{{ item.id }}</td><td>{{ item.name }}</td><td>{{ item.age }}</td><td>{{ item.position }}</td><td><a href="#">详情</a></td></tr></tbody><script>
export default {name: 'MyUser',data() {return {// 用户列表数据userlist: [{ id: 1, name: '嬴政', age: 18, position: '始皇帝' },{ id: 2, name: '李斯', age: 35, position: '丞相' },{ id: 3, name: '吕不韦', age: 50, position: '商人' },{ id: 4, name: '赵姬', age: 48, position: '王太后' }]}}
}
</script>

2.当点击“详情”的时候进行跳转

          <td><!-- 阻止默认行为 --><a href="#" @click.prevent="gotoDetail">详情</a></td>

3.右边的“详情”应该跟左边的“用户管理”是兄弟,所以要设置路由规则,跟其平级【在index.js中】


// 详情页
import UserDetail from '@/components/user/MyUserDetail.vue'// 后台主页的路由规则{path:'/home',component:Home,children:[// children中的path路径中不能加“/”{ path: 'users', component: Users },{ path: 'rights', component: Rights },{ path: 'goods', component: Goods },{ path: 'orders', component: Orders },{ path: 'settings', component: Settings },// 用户详情页的路由规则{path:'userinfo',component:UserDetail}]}

4.补充gotoDetail函数

  methods:{gotoDetail(){// console.log('ok');this.$router.push('/home/userinfo')},}

5. 设置后退按钮功能【在MyUserDetaile】

<button type="button" class="btn btn-light btn-sm" @click="$router.back()">后退</button>

2.8 升级用户详情页面的路由规则

1.因为如果单纯写路径不够完整,应该将id传入

          <td><!-- 阻止默认行为 --><!-- 往gotoDetail中加入要进行跳转的id --><a href="#" @click.prevent="gotoDetail(item.id)">详情</a></td>methods:{gotoDetail(id){// console.log('ok');this.$router.push('/home/userinfo/'+id)},}

2.给路由规则添加动态绑定的id

      // 用户详情页的路由规则{path:'userinfo/:id',component:UserDetail}

3.在第1个页面拿到第一个页面应该出现的组件

2.9 当登录进去后要直接跳转到users页面【redirect】

    // 后台主页的路由规则{path:'/home',component:Home,// 添加重定向,强制从登录页面一进来直接进入/home/users页面redirect:'/home/users',children:[// children中的path路径中不能加“/”{ path: 'users', component: Users },{ path: 'rights', component: Rights },{ path: 'goods', component: Goods },{ path: 'orders', component: Orders },{ path: 'settings', component: Settings },// 用户详情页的路由规则{path:'userinfo/:id',component:UserDetail,props:true}]}

2.10 如何控制多页面的权限【多写一个数组存放】

// 全局前置守卫
router.beforeEach(function(to,from,next){const pathArr=['/home','/home/users','.home/rights']if(pathArr.indexOf(to.path)!==-1){// 如果要访问的页面是home,则先进行判断是否已经登录const token=localStorage.getItem('token')if(token){next()}else{next('/login')}}else{next()}
})

总结

5. 完整的页面项目:Headline | 黑马头条 - 移动端

1.创建(vue create demo-toutiao)并梳理项目结构

1.先将自动生成的App组件中生成的代码删除

<template><div><h1>App 根组件</h1></div>
</template><script>
export default {name: 'App'
}
</script><style lang="less" scoped></style>

2.将views和components文件夹中的所有文件删除,并且将router文件夹中的index.js文件中的router中的内容清除

import Vue from 'vue'
import VueRouter from 'vue-router'// 把VueRouter安装为Vue的插件
Vue.use(VueRouter)// 路由规则的数组
const routes = []// 创建路由实例对象
const router = new VueRouter({routes
})export default router

2.初始化-安装和配置Vant组件库

Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)

1.使用Vant步骤:安装,配置,使用

npm i vant@latest-v2 -S --legacy-peer-deps

2.导入组件【在main.js文件中导入】

// 导入并安装Vant组件库
import Vant from 'vant'
import 'vant/lib/index.css'Vue.use(Vant)

3.使用Tabbar组件并开启路由模式【因为实现了路由切换所以放在view文件夹下面】

1.初始化页面

<template>
<div><h1>Home 组件</h1>
</div>
</template><script>
export default {name: 'Home'
}
</script><style lang="less" scoped></style>
<template><div><h1>User 组件</h1></div>
</template><script>
export default {name: 'User'
}
</script><style lang="less" scoped></style>

2.在App组件中创建占位符

3.将Tabbar组件放入【放在App组件中】

<template><div><!-- 路由占位符 --><!-- Tabbar区域 --><van-tabbar v-model="active"><van-tabbar-item icon="home-o" >首页</van-tabbar-item><van-tabbar-item icon="user-o" >我的</van-tabbar-item></van-tabbar></div>
</template><script>
export default {data () {return {active: 0}}
}
</script>

4.添加路由模式

<template><div><!-- 路由占位符 --><!-- Tabbar区域 --><van-tabbar v-model="active"><van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item><van-tabbar-item icon="user-o" to="/user">我的</van-tabbar-item></van-tabbar></div>
</template><script>
export default {data () {return {active: 0}}
}
</script>

4.通过路由展示对应的Tabbar页面

1.将页面上定一个占位符【在App组件中定义】

    <!-- 路由占位符 --><router-view></router-view>

2.将组件和hash地址的对应关系写在router文件加下index.js文件中

import Vue from 'vue'
import VueRouter from 'vue-router'// 导入需要的组件
import Home from '@/views/Home/Home.vue'
import User from '@/views/User/User.vue'Vue.use(VueRouter)const routes = [// 定义首页的路由规则{ path: '/', component: Home },// 定义我的路由规则{ path: '/user', component: User }
]const router = new VueRouter({routes
})export default router

5.使用Navbar导航栏组件

1.导入组件中的的模块【应该放在Home组件中】

<template><div><van-nav-bartitle="标题"left-text="返回"right-text="按钮"left-arrow
/></div>
</template>

2.设置标题为固定定位【:fixed="true"】

<template><div><van-nav-bar title="标题" :fixed="true"/></div>
</template>

3.将被标题遮挡住的内容进行显示【分别给div加上下边框】

6.覆盖Navbar的默认样式

1.使用调试工具进行修改

 2.注意点:

7.了解获取列表数据的API接口

8.封装utils目录下的request模块【使用axios】

1.安装axios包

 npm i axios -S -legacy-peer-deps

2.配置axois【多创建一个request.js文件】

import axios from 'axios'// request 就是小axios
const request = axios.create({// 指定请求的根路径baseURL: 'https://applet-base-api-t.itheima.net'
})export default request

9.在Home组件中封装initArticleList方法【当进入Home页面的时候自动请求数据显示】

1.先在Home组件中导入request.js文件

// 导入request.js
import reuqest from '@/utils/request.js'

2.先在data中声明数据

  data () {return {// 页码值page: 1,// 每一页显示多少条数据limit: 10}},

3.封装函数

  methods: {// 封装获取文章列表数据的方法async initArticleList () {// 发起GET请求,获取文章的列表数据// 获取到的对象是Promise,所以要使用await和asyncconst { data: res } = await reuqest.get('/articles', {// 请求参数params: {_page: this.page,_limit: this.limit}})console.log(res)}}

4.调用该函数【created要写在函数的声明前】

  created () {this.initArticleList()},

10.文章列表--封装articleAPI模块

1.先在User组件中声明一个函数,当进入User页面的时候,获取5条数据

<script>// 导入
import request from '@/utils/request.js'export default {data () {return {page: 1,limit: 5}},created () {this.initArticleList()},name: 'User',methods: {async initArticleList () {const { data: res } = await request.get('/articles', {params: {_page: this.page,_limit: this.limit}})console.log(res)}}
}
</script>

2.因为重复写了调用接口的Promise所以直接写成一个js文件然后进行调用

3.在src文件夹下新建一个文件夹为api,然后再这个文件夹中新建一个articleAPI.js文件

4.因为获取到的是一个Promise对象,所以将这个Promise对象放在articleAPI.js文件中,如果需要则直接再外调用

 api/articleAPI.js

// 文章相关的API接口,都封装到这个模块中// 导入request组件
import request from '@/utils/request'// 向外按需导出一个API函数
/*   这是一个Promise对象request.get('/articles', {params: {_page: this.page,_limit: this.limit}})*/
export const getArticleListAPI = function (_page, _limit) {// console.log('调用了getArticleListAPI这个函数')return request.get('/articles', {params: {_page: _page,_limit: _limit}})
}

User.vue

<script>// 导入
// import request from '@/utils/request.js'// 按需导入API接口
import { getArticleListAPI } from '@/api/articleAPI.js'// 此时获取到的result是一个Promise对象
// const result = getArticleListAPI(1, 5)
// console.log(result)export default {data () {return {page: 1,limit: 5}},created () {this.initArticleList()},name: 'User',methods: {async initArticleList () {// const { data: res } = await Promise对象const { data: res } = await getArticleListAPI(this.page, this.limit)console.log(res)}}
}
</script>

Home.vue

<script>// 导入request.js
// import reuqest from '@/utils/request.js'// 按需导入API接口
import { getArticleListAPI } from '@/api/articleAPI'export default {name: 'Home',data () {return {// 页码值page: 1,// 每一页显示多少条数据limit: 10}},created () {this.initArticleList()},methods: {// 封装获取文章列表数据的方法async initArticleList () {// 发起GET请求,获取文章的列表数据// 获取到的对象是Promise,所以要使用await和asyncconst { data: res } = await getArticleListAPI(this.page, this.limit)console.log(res)}}
}
</script>

11.文章列表--封装ArticleInfo组件

1.当我们拿到一份数据要再页面上进行渲染,则应该将这份数据存放再data里面

2.创建一个ArticleInfo的组件【存放再components文件夹下的】

<template><div><h1>ArticleInfo 组件</h1></div>
</template><script>
export default {name: 'ArticleInfo'
}
</script><style lang="less" scoped></style>

3.在Home组件中导入,注册使用ArticleInfo组件

<template><div class="home-container"><!-- 导入,注册,并使用ArticleInfo组件 --><h1> {{ artlist.length }}</h1><!-- 3.使用 ArticleInfo组件 --><ArticleInfo></ArticleInfo></div>
</template>// 1.导入ArticleInfo组件
import ArticleInfo from '@/components/Article/ArticleInfo.vue'// 2.注册组件components: {ArticleInfo}

4.动态循环ArticleInfo组件

    <ArticleInfo v-for="item in artlist" :key="item.id"></ArticleInfo>

12.文章列表-为ArticleInfo组件封装props属性

1.在ArticleInfo组件中封装属性

ArticleInof组件

          <!-- 标题 --><span>{{title}}</span><div class="label-box"><span>作者{{ author}}&nbsp;&nbsp; {{cmtCount}}评论 &nbsp;&nbsp; 发布日期 {{ time }}</span><script>
export default {name: 'ArticleInfo',// 自定义属性props: {// 标题title: {type: String,default: ''},// 作者名字author: {type: String,default: ''},// 评论数cmtCount: {// 通过数组形式,为当前属性定义多个可能的类型type: [Number, String],default: 0},// 发布日期time: {type: String,default: ''}}
}
</script>

Home组件

    <!-- 导入,注册,并使用ArticleInfo组件 --><!-- 3.使用 ArticleInfo组件 --><ArticleInfo v-for="item in artlist" :key="item.id":title="item.title":author="item.aut_name":cmtCount="item.comm_count":time="item.pubdate"></ArticleInfo>

13.文章列表--为ArticleInfo组件封装cover属性(封面图片)

1.定义cover属性

    // 封面的信息对象cover: {type: Object,// 通过default函数,返回cover属性的默认值default: function () {//这个return的对象就是cover属性的默认值return { type: 0 }}}

2.动态添加属性

3.在ArticleInfo组件进行判断是显示几张照片

4.给图片动态绑定

14.上拉加载更多--了解List组件的基本使用

Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)

15.上拉加载更多--初步使用List组件

1.先将代码封装在<van-list></van-list>

2.初始化默认值和onLoad函数

      // 是否正在加载下一页数据,如果loading为true 则不会反复触发load事件// 每当下一页数据请求回来之后,千万要记得,把loading从true改为false// 因为刚刚进入页面的时候,不需要触发onLoad事件,所以应该将loading初始化为true,才不会进行数据请求loading: true,// 所有数据是否加载完毕了,如果没有更多数据了,一定要把finished改为truefinished: false// 只要onLoad被调用,就应该请求下一页数据onLoad () {console.log('触发了load事件')}

3.因为初始化loading为true,当第一页数据渲染完之后,就应该将loading改为false,才可与进行访问第二页数据

    async initArticleList () {// 发起GET请求,获取文章的列表数据// 获取到的对象是Promise,所以要使用await和asyncconst { data: res } = await getArticleListAPI(this.page, this.limit)// console.log(res)this.artlist = res// 当第一页渲染完,应该将loading改为false,等一下才可以访问第二页this.loading = false},

16.上拉加载更多--实现上拉加载更多的效果

1.上拉触发事件

    // 只要onLoad被调用,就应该请求下一页数据onLoad () {// console.log('触发了load事件')// 1.让页码值+1this.page++// 2.重新请求接口获取数据this.initArticleList()}

2.上面的写法会将第一页的数据进行覆盖,所以要使用将旧数据中的数据记录下来

    async initArticleList () {// 发起GET请求,获取文章的列表数据// 获取到的对象是Promise,所以要使用await和asyncconst { data: res } = await getArticleListAPI(this.page, this.limit)// console.log(res)// this.artlist = res// 如果上拉加载更多,那么应该是// this.artlist=[旧数据在前,新数据在后]this.artlist = [...this.artlist, ...res]// 当第一页渲染完,应该将loading改为false,等一下才可以访问第二页this.loading = false},

3.页数的判断条件

    // 封装获取文章列表数据的方法async initArticleList () {// 发起GET请求,获取文章的列表数据// 获取到的对象是Promise,所以要使用await和asyncconst { data: res } = await getArticleListAPI(this.page, this.limit)// console.log(res)// this.artlist = res// 如果上拉加载更多,那么应该是// this.artlist=[旧数据在前,新数据在后]this.artlist = [...this.artlist, ...res]// 当第一页渲染完,应该将loading改为false,等一下才可以访问第二页this.loading = falseif (res.length === 0) {// 证明没有下一页数据了,直接把finished改为true,表示数据加载完了this.finished = true}

过程:

1.在触发o'n'Load的时候先让页码值+1

2.重新请求接口获取数据

3.对获取回来的数据进行拼接

4.判断是否有下一页

17.下拉刷新--实现下拉刷新的功能

1.将下拉刷新的模块导入

2.初始化onRefresh和onRefresh函数

      // 是否下拉刷新refreshing: false// 下拉刷新的处理函数onRefresh () {// console.log('触发了下拉刷新')// 1.让页码值+1this.page++// 2.重新请求接口获取数据this.initArticleList()}

3.在initArticleList函数内判断是要上拉刷新还是下拉刷新(因为数据的拼接顺序不一样)

      if (isRefresh) {// 证明是下拉刷新:新数据在前,旧数据在后this.artlist = [...res, ...this.artlist]} else {// 如果上拉加载更多,那么应该是// this.artlist=[旧数据在前,新数据在后]this.artlist = [...this.artlist, ...res]}// 2.重新请求接口获取数据// 只有当下拉刷新的时候才需要在调用该函数的时候传参// async initArticleList (isRefresh) {this.initArticleList(true)

4.当我们下拉请求数据后应该将isLoading改为false,要不然没办法进行第二次数据请求

      if (isRefresh) {// 证明是下拉刷新:新数据在前,旧数据在后this.artlist = [...res, ...this.artlist]this.isLoading = false} else {// 如果上拉加载更多,那么应该是// this.artlist=[旧数据在前,新数据在后]this.artlist = [...this.artlist, ...res]}

5.判断数据是否已经渲染完毕

    <van-pull-refresh v-model="isLoading" :disabled="finished" @refresh="onRefresh"><van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" ><!-- 导入,注册,并使用ArticleInfo组件 --><!-- 3.使用 ArticleInfo组件 --><ArticleInfo v-for="item in artlist" :key="item.id":title="item.title":author="item.aut_name":cmtCount="item.comm_count":time="item.pubdate":cover="item.cover"></ArticleInfo></van-list>
</van-pull-refresh>

18.定制主题-说明Vant定制主题的核心原理【less变量的覆盖】

Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)

1.在main.js文件中导入less文件

// 切记:为了能够覆盖默认的less变量,这里一定要把后缀名改为.less
import 'vant/lib/index.less'

2.修改样式变量


// 这个文件是 vue-cli 创建出来的项目的配置文件
// 在 vue.config.js 这个配置文件中,可以对整个项目的打包、构建进行全局性的配置// webpack 在进行打包的时候,底层用到了 node.js
// 因此,在 vue.config.js 配置文件中,可以导入并使用 node.js 中的核心模块const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,css: {loaderOptions: {less: {modifyVars: {// 直接覆盖变量// 或者可以通过 less 文件覆盖(文件路径为绝对路径)hack: 'true; @import "your-less-file-path.less";'}}}}
})

3.通过theme.less定制主题【在src文件夹根目录下创建一个theme.less文件】

// 在theme.less文件中,覆盖Vant官方的less 变量值
@blue:#007bff;// 覆盖Navbar的less样式
@nav-bar-background-color:@blue;

4.导入less文件【在vue.config.js文件中导入】


// 这个文件是 vue-cli 创建出来的项目的配置文件
// 在 vue.config.js 这个配置文件中,可以对整个项目的打包、构建进行全局性的配置// webpack 在进行打包的时候,底层用到了 node.js
// 因此,在 vue.config.js 配置文件中,可以导入并使用 node.js 中的核心模块const path = require('path')
// __dirname--当前的根目录
const themePath = path.join(__dirname, './src/theme.less')const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,css: {loaderOptions: {less: {modifyVars: {// 直接覆盖变量// 或者可以通过 less 文件覆盖(文件路径为绝对路径)//  ../    ./    theme.less// 从盘符开始的路径,叫做绝对路径   C:\\Users\liulongbin\\theme.less// hack: 'true; @import "绝对路径";'(外面是使用模板字符串)hack: `true; @import "${themePath}";`}}}}
})

打包发布

1.npm run build【生成dist文件夹】

2.查看vue-cli文档

 3.在vue.config.js文件中添加

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({publicPath: '',transpileDependencies: true
})

4.重新run一下

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

1.ES6模块化

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

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

3. 什么是 ES6 模块化规范

ES6 模块化规范中定义:

⚫ 每个 js 文件都是一个独立的模块

⚫ 导入其它模块成员使用 import 关键字

⚫ 向外共享模块成员使用 export 关键字

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

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

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

5. ES6 模块化的基本语法:默认导出【export default 默认导出的成员】

let n1=10;
let n2=20;
function show(){}// 外界只能访问到n1和show,访问不到n2
export default{n1,show
}

注意事项

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

5. ES6 模块化的基本语法:默认导入【import 接收名称 from '模块标识符'】

// 记得加上后缀名
import m1 from './01.默认导出.js'console.log(m1);

注意事项

默认导入时的接收名称可以任意名称,只要是合法的成员名称即可:(数字开头报错)

5.2 按需导出:【export 按需导出的成员】

export let s1 = 'aaa'
export let s2 = 'ccc'
export function say() {}export default {a: 20
}

5.2 按需导入:【import { s1 } from '模块标识符'】

import {s1, s2 , say} from './03.按需导出.js'console.log(s1)
console.log(s2)
console.log(say)

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

① 每个模块中可以使用多次按需导出(但是默认导出只能有一个)

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

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

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

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

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

2.Promise

1. 回调地狱:多层回调函数的相互嵌套

回调地狱的缺点:

⚫ 代码耦合性太强,牵一发而动全身,难以维护

⚫ 大量冗余的代码相互嵌套,代码的可读性变差

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. 基于回调函数按顺序读取文件内容

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

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

3.1 then-fs 的基本使用

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

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

import thenFs from 'then-fs'// .then()中的失败回调函数可以不写
// thenFs.readFile('./files/1.txt', 'utf8')--返回的是一个Promise对象【是异步的,所以每一次输出的数值顺序不一样】
thenFs.readFile('./files/1.txt', 'utf8').then((r1) => {console.log(r1)})
thenFs.readFile('./files/2.txt', 'utf8').then((r2) => {console.log(r2)})
thenFs.readFile('./files/3.txt', 'utf8').then((r3) => {console.log(r3)})

3.2 .then() 方法的特性

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

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

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

import thenFs from 'then-fs'thenFs.readFile('./files/1.txt', 'utf8').then((r1) => {console.log(r1)//将第二个Promise对象作为第一个对象成功的回调函数的return返回值返回出去,然后再用.then()进行接收return thenFs.readFile('./files/2.txt', 'utf8')}).then((r2) => {console.log(r2)return thenFs.readFile('./files/3.txt', 'utf8')}).then((r3) => {console.log(r3)})

3.4 通过 .catch 捕获错误

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

如果不希望前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前(放在最前面)

import thenFs from 'then-fs'thenFs.readFile('./files/11.txt', 'utf8')// 把.catch()放再最前面,则就算出现错误,但是错误后面的then()不受到影响,还是可以继续执行.catch((err) => {console.log(err.message)}).then((r1) => {console.log(r1)//将第二个Promise对象作为第一个对象成功的回调函数的return返回值返回出去,然后再用.then()进行接收return thenFs.readFile('./files/2.txt', 'utf8')}).then((r2) => {console.log(r2)return thenFs.readFile('./files/3.txt', 'utf8')}).then((r3) => {console.log(r3)})

3.5 Promise.all() 方法【等所有的异步操作全部结束后才会执行下一步的 .then 操作】

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

注意:数组中 Promise 实例的顺序, 就是最终结果的顺序!

import thenFs from 'then-fs'const promiseArr = [thenFs.readFile('./files/3.txt', 'utf8'),thenFs.readFile('./files/2.txt', 'utf8'),thenFs.readFile('./files/1.txt', 'utf8'),
]// 数组中 Promise 实例的顺序, 就是最终结果的顺序
Promise.all(promiseArr).then(result => {console.log(result)//333,222,111
})

3.6 Promise.race() 方法【只要任何一个异步操作完成,就立即执行下一步的 .then 操作

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

import thenFs from 'then-fs'const promiseArr = [thenFs.readFile('./files/3.txt', 'utf8'),thenFs.readFile('./files/2.txt', 'utf8'),thenFs.readFile('./files/1.txt', 'utf8'),
]// 数组中 Promise 实例的顺序, 就是最终结果的顺序
Promise.race(promiseArr).then(result => {console.log(result)//输出结果取决于哪一个文件读取较快
})

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

方法的封装要求:

方法的名称要定义为 getFile

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

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

4.1 getFile 方法的基本定义 

4.2 创建具体的异步操作

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

function getFile(fpath) {// return new Promise--创建了形式上的异步return new Promise(function (resolve, reject) {fs.readFile(fpath, 'utf8', (err, dataStr) => {})})
}

4.3 获取 .then 的两个实参

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

4.4 调用 resolve(读取成功回调函数) 和 reject(读取失败回调函数) 回调函数

Promise 异步操作的结果,可以调用 resolve 或 reject 回调函数进行处理

import fs from 'fs'function getFile(fpath) {// return new Promise--创建了形式上的异步return new Promise(function (resolve, reject) {fs.readFile(fpath, 'utf8', (err, dataStr) => {// 如果读取失败,则调用失败的回调函数if (err) return reject(err)// 如果读取成功,则调用成功的回调函数resolve(dataStr)})})
}getFile('./files/11.txt').then((r1) => {console.log(r1)}).catch((err) => console.log(err.message))

3.async/await

1. 什么是 async/await

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

.then 链式调用的优点: 解决了回调地狱的问题

.then 链式调用的缺点: 代码冗余、阅读性差、 不易理解

2. async/await 的基本使用

import thenFs from 'then-fs'async function getAllFile() {// thenFs.readFile--Promise 对象// 被await修饰过的Promise对象就变成了一个普通变量// 当函数内部有await修饰过的对象,构造函数外部要加上asyncconst 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)
}getAllFile()

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

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

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

import thenFs from 'then-fs'console.log('A')
async function getAllFile() {//同步执行,因为下面是异步的,所以先跳出这个函数console.log('B')// thenFs.readFile--Promise 对象// 被await修饰过的Promise对象就变成了一个普通变量// 当函数内部有await修饰过的对象,构造函数外部要加上asyncconst 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')
//输出结果为A B C 111 222 333 D

4.EventLoop

1. JavaScript 是单线程的语言:同一时间只能做一件事情

JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。

2. 同步任务和异步任务

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

① 同步任务(synchronous)

⚫ 又叫做非耗时任务,指的是在主线程上排队执行的那些任务

⚫ 只有前一个任务执行完毕,才能执行后一个任务

② 异步任务(asynchronous)

⚫ 又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行

⚫ 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数

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

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

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

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

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

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

4. EventLoop 的基本概念

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

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

 

5.宏任务和微任务(异步任务的划分)

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

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

2. 宏任务和微任务的执行顺序:宏任务-->微任务(交替执行)

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

3. 去银行办业务的场景

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

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(function(){console.log('7');new Promise(function(resolve){console.log('8');resolve();}).then(function(){console.log('9');})
})

6.API接口案例

1. 案例需求

2. 主要的实现步骤

3. 搭建项目的基本结构

4. 创建基本的服务器


//搭建一个express服务器
import express from 'express'
const app=express();app.listen(20,()=>{console.log('server running at http://127.0.0.1');
})

5. 创建 db 数据库操作模块

//数据库操作模块
import mysql from 'mysql2'const pool=mysql.createPool({host:'127.0.0.1',port:3306,database:'my_db_01',user:'root',password:'111111'
})// 将pool以Promise的形式向外暴露
export default pool.promise()

6. 创建 user_ctrl 模块

//查询数据库中的数据
import db from '../db/index.js'// 使用ES6的按需导出语法,将getAllUser方法导出去
async function getAllUser(req,res){// db.query()--输入查询语句,并且返回的是一个Promise对象// 索引为0是我们要的数据const [rows]= await db.query('select id,username,nickname from ev_users')// console.log(rows);//将数据发送给客户端res.send({status:0,message:'获取用户列表数据成功',data:rows})
}getAllUser();

7. 创建 user_router 模块

//因为要使用路由对象,所以要导入express
import { express } from "express";
//因为要使用到user_ctrl中的函数,所以要导入
import { getAllUser } from '../controller/user_ctr.js'//创建路由对象
const router=new express.Router();
//为路由挂载一个get
router.get('/user',getAllUser);//将路由向外暴露
export default router

8. 导入并挂载路由模块


//搭建一个express服务器
import express from 'express'//导入路由模块
import userRouter from './router/user_router.js'const app=express();//路由挂载
app.use('./api',userRouter);app.listen(20,()=>{console.log('server running at http://127.0.0.1');
})

9. 使用 try…catch 捕获异常

//查询数据库中的数据
import db from '../db/index.js'// 使用ES6的按需导出语法,将getAllUser方法导出去
async function getAllUser(req,res){// db.query()--输入查询语句,并且返回的是一个Promise对象// 索引为0是我们要的数据try{const [rows]= await db.query('select id,username,nickname from ev_users')// console.log(rows);//将数据发送给客户端res.send({status:0,message:'获取用户列表数据成功',data:rows})// 使用catch要获取错误}catch(err){res.send({status:1,message:'获取用户数据失败',desc:err.message})}
}getAllUser();

总结

vue2[黑马程序员]相关推荐

  1. 黑马程序员 oc对象的方法成员变量

    -----------黑马程序员 IOS培训.Android培训.Java培训.期待与您交流---------------- #import <Foundation/Foundation.h&g ...

  2. python那么慢为什么还有人用-Python执行效率慢,为什么还这么火?【黑马程序员】...

    稍微了解python的同学,都知道python比起java这类编译型语言来说执行效率比较低,可是为什么python依然这么火呢? Python是一门解释型的动态语言,由于语言的解释执行的过程和动态类型 ...

  3. python实训项目-黑马程序员上海校区Python21期Django项目实训

    黑马程序员上海中心 月薪一万只是起点 关注 晚上十点,一名名Python学生正在酣畅淋漓地撸代码,手指不间断地敲击着键盘,发出机械而清脆的声音. 各个小组在经过为期4天的django项目小组开发,终于 ...

  4. 黑马程序员:从零基础到精通的前端学习路线

    黑马程序员:从零基础到精通的前端学习路线 随着互联网的深入发展,前端开发工程师一跃成为市场上非常抢手的人才.很多同学,包括以前做UI的.Java的.或者对于IT完全零基础的同学都想学习前端.下图是网上 ...

  5. 黑马程序员——c语言学习心得——函数传递二维数组

    黑马程序员--c语言学习心得--函数传递二维数组 -------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 一.定义指针的时候一定要初始化.    变量 ...

  6. 黑马程序员Linux系统开发视频之创建守护进程模型

    黑马程序员Linux系统开发视频之创建守护进程模型 1.创建子进程,父进程退出   所有工作在子进程中进行形式上脱离了控制终端 2.在子进程中创建新会话   setsid()函数   使子进程完全独立 ...

  7. 黑马 程序员——Java基础---流程控制

    黑马程序员--Java基础---流程控制 ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------ 一.概述 Java提供了两种基本的流程控制结构:分支结构 ...

  8. 黑马程序员的课程不如兄弟连兄弟会好

    [黑马程序员的课程不如兄弟连兄弟会好 兄弟连兄弟会it开发培训 www.itxdh.net 企鹅群:499956522 高端人才培养就到[兄弟连兄弟会it开发培训]纯免费的高端IT人才培养] 职场中的 ...

  9. 【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)

    黑马程序员C++教程 文章目录 4 类和对象(类属性[成员属性],类函数[成员函数]) 4.1 封装 4.1.1 封装的意义(三种权限:public公共.protected保护.private私有)( ...

最新文章

  1. RAC 实例不能启动 ORA-1589 signalled during ALTER DATABASE OPEN
  2. redis之intset
  3. Spring boot的静态资源映射
  4. LDAP-轻量级目录访问协议(统一认证)
  5. 直通BAT必考题系列:7种JVM垃圾收集器特点,优劣势、及使用场景
  6. 电影票房数据查询服务高性能与高可用实践
  7. django学习随笔:ManagementUtility
  8. 【codevs1078】最小生成树,prim算法
  9. latex 数学公式
  10. Helm 3 完整教程(二十):在 Helm 模板中定义和使用变量
  11. libvirt(virsh命令总结)
  12. 京东商品类目查询接口
  13. Java工程师学习指南(完结篇)
  14. 记录一个小程序 input输入框格式手机号方法
  15. 达梦数据库建表语句之create table as select 注意事项
  16. 计算机就是三角函数,三角函数计算器
  17. 网页版MSDOS的实现网站
  18. SoftICE初使用
  19. 乐字节最全面向对象深入1
  20. 测绘资质专业类别该如何区分并选择合适的专业

热门文章

  1. 一套松下FP-XH六轴PLC设备程序,此程序已经实际设备上批量应用
  2. 个人博客系统项目设计及结果展示图
  3. 小程序上传文字和图片到服务器并保存在数据库
  4. Scrapy-Redis 爬取快代理免费
  5. 企业微信群消息关键字提醒如何设置
  6. java扫描仪快捷键
  7. 初学oracle--join的整体实例说明
  8. linux下nginx安装与配置说明
  9. 渲染的艺术:建筑效果图渲染的5个成功要素
  10. 前端页面优化之完善篇