vue2[黑马程序员]
一、前端工程化与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}} {{cmtCount}}评论 发布日期 {{ 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[黑马程序员]相关推荐
- 黑马程序员 oc对象的方法成员变量
-----------黑马程序员 IOS培训.Android培训.Java培训.期待与您交流---------------- #import <Foundation/Foundation.h&g ...
- python那么慢为什么还有人用-Python执行效率慢,为什么还这么火?【黑马程序员】...
稍微了解python的同学,都知道python比起java这类编译型语言来说执行效率比较低,可是为什么python依然这么火呢? Python是一门解释型的动态语言,由于语言的解释执行的过程和动态类型 ...
- python实训项目-黑马程序员上海校区Python21期Django项目实训
黑马程序员上海中心 月薪一万只是起点 关注 晚上十点,一名名Python学生正在酣畅淋漓地撸代码,手指不间断地敲击着键盘,发出机械而清脆的声音. 各个小组在经过为期4天的django项目小组开发,终于 ...
- 黑马程序员:从零基础到精通的前端学习路线
黑马程序员:从零基础到精通的前端学习路线 随着互联网的深入发展,前端开发工程师一跃成为市场上非常抢手的人才.很多同学,包括以前做UI的.Java的.或者对于IT完全零基础的同学都想学习前端.下图是网上 ...
- 黑马程序员——c语言学习心得——函数传递二维数组
黑马程序员--c语言学习心得--函数传递二维数组 -------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 一.定义指针的时候一定要初始化. 变量 ...
- 黑马程序员Linux系统开发视频之创建守护进程模型
黑马程序员Linux系统开发视频之创建守护进程模型 1.创建子进程,父进程退出 所有工作在子进程中进行形式上脱离了控制终端 2.在子进程中创建新会话 setsid()函数 使子进程完全独立 ...
- 黑马 程序员——Java基础---流程控制
黑马程序员--Java基础---流程控制 ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------ 一.概述 Java提供了两种基本的流程控制结构:分支结构 ...
- 黑马程序员的课程不如兄弟连兄弟会好
[黑马程序员的课程不如兄弟连兄弟会好 兄弟连兄弟会it开发培训 www.itxdh.net 企鹅群:499956522 高端人才培养就到[兄弟连兄弟会it开发培训]纯免费的高端IT人才培养] 职场中的 ...
- 【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)
黑马程序员C++教程 文章目录 4 类和对象(类属性[成员属性],类函数[成员函数]) 4.1 封装 4.1.1 封装的意义(三种权限:public公共.protected保护.private私有)( ...
最新文章
- RAC 实例不能启动 ORA-1589 signalled during ALTER DATABASE OPEN
- redis之intset
- Spring boot的静态资源映射
- LDAP-轻量级目录访问协议(统一认证)
- 直通BAT必考题系列:7种JVM垃圾收集器特点,优劣势、及使用场景
- 电影票房数据查询服务高性能与高可用实践
- django学习随笔:ManagementUtility
- 【codevs1078】最小生成树,prim算法
- latex 数学公式
- Helm 3 完整教程(二十):在 Helm 模板中定义和使用变量
- libvirt(virsh命令总结)
- 京东商品类目查询接口
- Java工程师学习指南(完结篇)
- 记录一个小程序 input输入框格式手机号方法
- 达梦数据库建表语句之create table as select 注意事项
- 计算机就是三角函数,三角函数计算器
- 网页版MSDOS的实现网站
- SoftICE初使用
- 乐字节最全面向对象深入1
- 测绘资质专业类别该如何区分并选择合适的专业