VUE笔记B站黑马程序员
- webpack
- VUE
- 32.什么是VUE
- 33.vue的特性
- 34.MVVM
- 35.Vue基础语法-初步使用Vue
- 36Vue基础语法-体验Vue调试工具
- 37Vue基础语法-内容渲染指令
- 属性绑定指令
- 事件绑定指令
- 301事件对象event
- 事件修饰符
- v-if&&v-else&&v-else-if
- 列表渲染指令
- 使用key列表维护
- 过滤器
- 私有过滤器和全局过滤器
- 连续调用多个过滤器
- 品牌列表案例
- 什么是单页面应用程序
- Vite项目结构
- 组件化开发的思想
- template节点
- script节点
- script中的data节点
- script中的methods节点
- style节点
- 组件的注册
- 组件命名时的大小写问题
- 解决样式冲突的问题
- deep样式穿透
- 组件的props
- 以对象语法绑定HTML的class
- 以对象语法绑定内联的style
- 封装组件的案例
- props验证
- 计算属性
- 动态计算水果案例
- watch侦听器
- immedidate选项
- deep选项
- watch选项监听单个属性
- 计算属性vs侦听器
- 组件的运行过程
- 全部的生命周期函数
- 全局配置axios
- 什么是ref引用
- 动态组件
- 插槽
- 插槽的结构赋值
- 私有自定义指令
- 全局自定义指令
- update函数
- 自定义指令两个注意点:
- 指令的参数值
- table案例的一些问题
- 路由
- 前端路由工作方式
- 安装Vue-router
- 自定义Router高亮连接
- 动态路由的概念
webpack
05 webpack基础-隔行变色
{
}
06webpack基础-安装webpack
{
}
-S 是 --save的简写
-D 是 --save-dev的简写
07在项目中配置两个包
{
}
development 表示在开发阶段
production表示要上线的项目
none 表示‘无’
执行dev脚本后会在根目录下生成一个dist文件夹,里面包含了生成的main.js文件
,main.js文件大小取决于exports的参数
开发的是有一定要用development,因为最求的是打包的速度,而不是体积
上线的时候一定要用production,因为上线最求得是体积,而不是速度快
08指定webpack的entry和output
在webpack 4.X和5.X的版本中,有如下默认约定:
1.默认的打包入口为src -> index.js
2.默认的输出文件路径为 dist -> main.js
ps:可以在webpack.config.js中修改打包的默认约定
const path = require('path')
module.exports = {
mode : 'none', //mode 来指定构建模式 develoment 开发模式 production表示要上线的项目
//entry: //指定要处理那个文件
// __dirname表示该文件的绝对路径,拼接上要处理文件的相对路径
entry: path.join(__dirname,'./src/index1.js'),
// 指定生成的文件要放到哪里
output: {
//存放的目录
path: path.join(__dirname,'dist'),
// 生成的文件名
filename: 'bundle.js'
}
}
09安装和配置webpack-dev-server这个插件
在开发中使用插件自动打包
1.下载webpack-dev-server插件
npm install webpack-dev-server@3.11.2 -D
2.修改package.json ->scripts 的dev命令
"scripts": {
//添加serve参数
"dev": "webpack serve"
},
3.重新运行npm run dev命令
4.重新访问http://localhost:8080地址
10介绍webpack-dev-server工作原理
「wds」: Project is running at http://localhost:8080/
表示项目运行的地址
「wds」: webpack output is served from /
生成的js在根目录,但是不会在硬盘中显示,而是生成在内存中,因为内存比硬盘快,所以应当把index.html里的js引用为内存生成的js
/**index.html中**/
<script src="../main.js"></script>
11安装和配置html-webpack-plugin插件
在实际运行中需要进入http://localhost:8080/src/这个路径里面,可以使用html-webpack-plugin插件把src/index.html页面复制到项目根目录里面
下载
npm install html-webpack-plugin@5.3.2 -D
配置html-webpack-plugin
const path = require('path')
//导入html-webpack-plugin这个插件,得到插件构造函数
const HtmlPlugin = require('html-webpack-plugin')
//new 构造函数,创建插件的实例对象
const htmlPlugin = new HtmlPlugin({
//指定要复制哪个页面
template: './src/index.html',
//复制出来的路径以及存放路径
filename: './index.html'
})
module.exports = {
mode : 'none', //mode 来指定构建模式 develoment 开发模式 production表示要上线的项目
//entry: //指定要处理那个文件
// __dirname表示该文件的绝对路径,拼接上要处理文件的相对路径
entry: path.join(__dirname,'./src/index.js'),
plugins: [htmlPlugin],
//通过plugin节点,使htmlplugin插件生效
// 指定生成的文件要放到哪里
output: {
//存放的目录
path: path.join(__dirname,'dist'),
// 生成的文件名
filename: 'main.js',
}
}
表示将复制的页面放到主目录里面(内存)
12了解html-webpack-plugin插件的特性
如11所讲
13了解devServer中的常用选项
devServer
module.exports = {
mode : 'none', //mode 来指定构建模式 develoment 开发模式 production表示要上线的项目
//entry: //指定要处理那个文件
// __dirname表示该文件的绝对路径,拼接上要处理文件的相对路径
entry: path.join(__dirname,'./src/index.js'),
plugins: [htmlPlugin],
//通过plugin节点,使htmlplugin插件生效
// 指定生成的文件要放到哪里
output: {
//存放的目录
path: path.join(__dirname,'dist'),
// 生成的文件名
filename: 'main.js',
},
// devServer
devServer: {
open: true,//初次打包,自动打开浏览器
port:80,//指定端口
host: '127.0.0.2' //指定打开的地址
}
}
在http协议中,端口号80表示默认端口,则端口号在浏览器省略不显示
14loader的作用
loader(加载器)
在实际开发过程中,webpack处理不了后缀不是.js结尾的模块,webpack默认是处理不了的,需要调用loader加载器才能正常的打包,否则会报错!
VUE
32.什么是VUE
1.构建用户页面
用vue往html页面填充数据
2.框架
一套现成的解决方案,要遵守框架的规范
33.vue的特性
1.数据驱动视图
vue会监听数据的变化,从而重新渲染页面的结构DOM
OK:数据发生变化,页面重新渲染!
Note:数据驱动是 单向的数据绑定 \color{red}{单向的数据绑定} 单向的数据绑定
2.双向数据绑定
在网页中form负责采集数据,Ajax负责 “提交数据”
34.MVVM
MVVM 是 数据驱动视图 \color{red}{数据驱动视图} 数据驱动视图和 双向数据 \color{red}{双向数据} 双向数据绑定的核心原理,MVVM是值指的Model、View和View Model它把一个页面拆分成三部分
35.Vue基础语法-初步使用Vue
这里我自己按照视频上面的代码识别不了Vue对象,下面代码贴的开发文档的
<!-- 希望vue控制这个div,数据填充div内部 -->
<div id="app">{{ message }}</div>
<!-- "username"两边加空格让格式更清晰 -->
<!-- 通过cdn使用vue -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- 创建Vue的实例对象 -->
<script>
//这里使用的vue开发文档来创建https://cn.vuejs.org/guide/quick-start.html#using-vue-from-cdn
const { createApp } = Vue
createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
</script>
36Vue基础语法-体验Vue调试工具
Null
37Vue基础语法-内容渲染指令
1.指令的概念
指令(Directives)是vue为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。
vue中的指令按照不同的用途可以分为如下6大类:
① 内容渲染 \color{red}{内容渲染} 内容渲染指令
② 属性绑定 \color{red}{属性绑定} 属性绑定指令
③ 事件绑定 \color{red}{事件绑定} 事件绑定指令
④ 双向绑定 \color{red}{双向绑定} 双向绑定指令
⑤ 条件渲染 \color{red}{条件渲染} 条件渲染指令
⑥ 列表渲染 \color{red}{列表渲染} 列表渲染指令
①内容渲染指令(v-text , {{}} , v-html)
v
−
t
e
x
t
\color{pink}{v-text}
v−text
缺点:会覆盖元素内部原有的内容
<p v-text="username"></p>
{ { } } 插值表达式 \color{pink}{\{\{\}\}插值表达式} {{}}插值表达式
用来解决覆盖原有内容的问题,实际开发中用的最多
但插值表达式只能用于内容节点,不能用于属性节点
v − h t m l \color{pink}{v-html} v−html
可以渲染标签
v − o n c e \color{pink}{v-once} v−once
可以通过这个指令来一次性的插值,数据改变的时候,这个值不会更新
属性绑定指令
v − b i n d \color{pink}{v-bind} v−bind
vue规定可以将v-bind:简写为":"(冒号)
在vue中绑定内容可以动态拼接
<div :title="'box'+index">这是一个div</div>
事件绑定指令
v − o n \color{pink}{v-on} v−on
语法格式为 v-on :事件名称=“事件处理函数名称”
由于v-on在开发中频率很高,简写为 @ \color{red}{@} @
<div id="app">
<p>count的值是{{ count }}</p>
<!-- 语法格式为 v-on :事件名称="事件处理函数名称" -->
<button v-on:click="addCount">+1</button>
</div>
<script src="./vue.global.js"></script>
<script>
const { createApp } = Vue
createApp({
data() {
return {
count:0,
}
},
// methods的作用就是定义事件的处理函数
methods:{
addCount: function(){
//在实际开发中建议使用简写 addCount(){
count++
}
}
}).mount('#app')
</script>
由于业务逻辑代码只有一行所以也可以简写为
<button @click="message++">+1</button>
<button @click="message--">-1</button>
301事件对象event
可以使用一个形参来接收
add(e){
const nowBgc = e.target.style.background;
e.target.style.background = nowBgc ==='red' ? '' : 'red'
this.resu=nowBgc
console.log(nowBgc);
}
$event
e v e n t 是 v u e 提供的特殊变量,表示原生事件参数对象 e v e n t 。 event是vue提供的特殊变量,表示原生事件参数对象event。 event是vue提供的特殊变量,表示原生事件参数对象event。event可以解决事件对象event被覆盖的问题
事件修饰符
.prevent 阻止默认行为
----> 阻止a连接的跳转,阻止表单的提交
.stop 阻止事件冒泡
----> 内部盒子点击不会触发外层盒子
}}
.capture
---->从外部开始向内部触发
.once
---->只触发一次
.self
---->如果通过冒泡,则不触发
按键修饰符
表按下了enter键则执行submit方法 && 按下了esc键则执行clearInput方法
<input type="text"@keyup.enter="submit"@keyup.esc="clearInput">
双向绑定指令
v-model数据绑定指令
可以帮助辅助开发者不操作DOM的前提下,快速获取表单的数据
<p>用户名{{username}}</p>
<input type="text"@keyup.enter="submit"@keyup.esc="clearInput" v-model="username">
<p>选中的省份是:{{province}}</p>
<select v-model="province">
<option value="">请选择</option>
<option value="1">重庆</option>
<option value="2">广东</option>
<option value="3">北京</option>
<option value="4">上海</option>
</select>
注意:v-model指令只能配合表单元素一起使用,不能和p标签a标签不能输入的标签所使用
v-model指令的修饰符
为了方便对用户内容的输入处理,v-model提供了三个修饰符
.lazy 当对象失去焦点的时候更新,而不是实时更新
条件渲染指令
v
−
i
f
\color{pink}{v-if}
v−if
v
−
s
h
o
w
\color{pink}{v-show}
v−show
实现的效果
v-if会动态创建和移除DOM元素,从而控制元素在页面上的显示与隐藏
它有更高的性能消耗
v-show指令会动态的为元素添加或移除style="display:none"样式,来控制显示与隐藏
他会有更高的出事消耗开销
频繁的切换,使用v-show更好
反之,使用v-if更好
v-if&&v-else&&v-else-if
随机生成0.01~1的数
num:Math.random(),
相当于if–else
<p v-if="num>0.5">大于0.5</p>
<p v-else>小于0.5</p>
v-else-if相当于switch case语句
列表渲染指令
v − f o r \color{pink}{v-for} v−for
语法格式:(item,index)in items
item和index都是形参,可更改
<li v-for="(user,i) in list">索引:{{i}}姓名是:{{user.name}}</li>
使用key列表维护
问题:
当列表的数据变化时,默认情况下,vue会尽可能的复用已存在的DOM元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。
解决方案:给每一项给一个唯一的key 属性
key所绑定的值,只能是数字型或字符串型
<li v-for="(user,i) in list" :key="list.id">
过滤器
过滤器(Filter)常用于文本的格式化
“|”被称为管道符
在属性中添加|可以使用过滤器
<p>{{province | capitalize}}</p>
过滤器可以用在插值表达式和v-bind属性绑定
过滤器得通过与el节点平齐的filters节点来描述
filters: {
capitalize(str){
return str.charAt(0).toUpperCase()+str.slice(1)
}
}
私有过滤器和全局过滤器
私有过滤在不被控制的区域不会起作用
可以用下面的例子来声明全局过滤器
<script>
Vue.filter('capitalize',(str)=>{//str是形参
return str.charAt(0).toUpperCase()+str.slice(1)
})
</script>
其中,私有过滤器优先级高于全局过滤器
连续调用多个过滤器
可以调用多个过滤器
2.X的Vue是可以使用过滤器,但3.X则被剔除了
品牌列表案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- 导入 bootstrap 的样式表 -->
<!-- https://v4.bootcss.com/docs/components/forms/#switches -->
<link rel="stylesheet" href="./lib/bootstrap.css" />
<style>
:root {
font-size: 13px;
}
body {
padding: 8px;
}
</style>
</head>
<body>
<div id="app">
<!-- 卡片区域 -->
<div class="card">
<h5 class="card-header">添加品牌</h5>
<div class="card-body">
<!-- 添加品牌的表单 -->
<!-- vue在做表单提交的时候,需要用到一些自定义的验证规则,这个时候就需要阻止表单默认的提交方式。 -->
<form class="form-inline" @submit.prevent>
<div class="input-group mb-2 mr-sm-2">
<div class="input-group-prepend">
<div class="input-group-text">品牌名称</div>
</div>
<!-- 文本输入框 -->
<input type="text" class="form-control" v-model.trim="brandname" @keyup.esc="brandname=''"/>
</div>
<!-- 提交表单的按钮 -->
<button type="submit" class="btn btn-primary mb-2" @click="addNewBrand">添加品牌</button>
</form>
</div>
</div>
<!-- 品牌列表 -->
<table class="table table-bordered table-striped mt-2">
<thead>
<tr>
<th>#switches</th>
<th>品牌名称</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<!-- 表格的主体区域 -->
<tbody>
<!-- TODO:循环渲染表格的每一行数据 -->
<tr v-for="(item,index) in brandlist" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.brandname}}</td>
<td>
<div class="custom-control custom-switch">
<!-- //v-model双向绑定绑定初始值 -->
<input type="checkbox" class="custom-control-input" :id="item.id" v-model="item.state">
<label class="custom-control-label" :for="item.id" v-if="item.state">已开启</label>
<label class="custom-control-label" :for="item.id" v-else>已禁用</label>
</div>
</td>
<td>{{item.addtime | dateFormat}}</td>
<td>
<a href="#" @click.prevent="removeBrand(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<script src="../../vue-2.6.14/dist/vue.js"></script>
<script>
//创建全局的过滤器
Vue.filter('dateFormat', (dateStr)=>{
return dateStr
})
const vm = new Vue({
el: '#app',
data: {
brandname:'',
nextId:'5',
brandlist:[
{ id: 1,brandname: '宝马', state: true, addtime:new Date() },
{ id: 2,brandname: '奥迪', state: true, addtime:new Date() },
{ id: 3,brandname: '奔驰', state: true, addtime:new Date() },
{ id: 4,brandname: '山崎', state: true, addtime:new Date() },
]
},
methods: {
// 添加新的品牌数据
addNewBrand(){
if(!this.brandname)
return alert("品牌名不能为空!");
// 添加操作
this.brandlist.push({
id:this.nextId,
brandname:this.brandname,
state:true,
addtime:new Date()
})
this.nextId++,
this.brandname = ''
},
// 根据id删除对应的数据
removeBrand(id){
// 过滤旧数组,形成新数组
this.brandlist=this.brandlist.filter(x => x.id !==id )
}
},
})
</script>
</body>
</html>
什么是单页面应用程序
是指一个web网站中只有唯一一个html页面,所有的功能和交互都在这一个页面完成
单页面应用程序将所有的功能局限于一个web页面中,仅在该web页面初始化时加载相应的资源(HTML、Javascript和CSS)。
一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转。而是利用JavaScript动态地变换HTML的内容,从而实现页面与用户的交互
Vite项目结构
Vite根目录
{
}
node_modules 目录用来存放第三方依赖包
public 是公共的静态资源目录
srC是项目的源代码目录(程序员写的所有代码都要放在此目录下)
.gitignore是Git的忽略文件
index.html是SPA单页面应用程序中唯一的HTML页面
package.json 是项目的包管理配置文件
Vite src目录
{
}
assets目录用来存放项目中所有的静态资源文件(css、fonts等)
components目录用来存放项目中所有的自定义组件
App.vue是项目的根组件
index.css是项目的全局样式表文件
main.js是整个项目的打包入口文件
运行流程
{
}
App.vue用来编写待渲染的模板结构
index.html中需要预留一个el区域
main.js把App.vue 渲染到了index.html所预留的区域中
组件化开发的思想
根据封装的思想,把页面上可重用的部分封装为组件,从而方便项目的开发和维护。
例如:Ibootstrap就契合了组件化开发的思想。用户可以通过拖拽组件的方式,快速生成一个页面的布局结构。
Vue是完全支持组件化,开发的,其中后缀名为 . v u e \color{red}{.vue} .vue的文件为组件
template组件模板结构
script组件的javascript行为
css 组件的样式
template节点
template不会被渲染成DOM元素,只会起到包裹的作用
在vue3版本中<template>支持多个根节点
script节点
可以使用export default导出一个组件
可以通过name指定名称
<script>
export default{
name: 'MyApp1',
}
</script>
script中的data节点
指定组件的数据
<template>
<div>
<h1>这是一个vue组件</h1>
<h3>MyName:{{username}}</h3>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default{
name: 'MyApp1',
data(){
return {
username:'晓刘'
}
}
}
</script>
script中的methods节点
methods方法节点,与data同级
<template>
<div>
<h1>这是一个vue组件</h1>
<h3>MyName:{{username}}</h3>
<p>count的值是:{{count}}</p>
<button @click="add">+1</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default{
name: 'MyApp1',
data(){
return {
username:'晓刘',
count:0
}
},
methods: {
add(){
this.count++
}
},
}
</script>
style节点
style节点与script节点同级
......
<script>
......
</script>
<style>
h1{
color:red;
}
</style>
让style中支持less语法
①运行npm install less -D命令安装依赖包,从而提供less 语法的编译支持
②在<style>标签上添加lang="less"属性,即可使用less 语法编写组件的样式
<style lang="less">
h1{
color:red;
i{
color:blue;
}
}
</style>
组件的注册
组件的引用原则 先注册,后引用 \color{red}{先注册,后引用} 先注册,后引用
vue中注册组件的方式分为“全局注册”和“局部注册”两种,其中:
被全局注册的组件,可以在全局任何一个组件内使用
被局部注册的组件,只能在当前注册的范围内使用
全局注册:
main.js:
//导出需要被全局注册的组件
import Swiper from './Swiper.vue'
//调用app.component方法注册全局组件
const app = createApp(App)
// 调用createApp函数,创建spa应用实例
app.component('mySwiper',Swiper)
Swiper:
<template>
<h1>这是一个组件</h1>
</template>
<script>
export default{
name:'myswiper'
}
</script>
局部注册:
import Search from './test.vue';
export default{
components: {
'myTest': Search,
},
.....
}
如果某些组件在开发期间的使用频率很高,推荐进行全局注册;
如果某些组件只在特定的情况下会被用到,推荐进行局部注册。
组件命名时的大小写问题
短横线命名法
\color{pink}{短横线命名法}
短横线命名法的特点:
·必须严格按照短横线名称进行使用
帕斯卡命名法
\color{pink}{帕斯卡命名法}
帕斯卡命名法的特点:
·既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用
在实际开发中,建议使用帕斯卡命名法来命名,适用性更好
解决样式冲突的问题
style节点的scoped属性
快速选择相同的字符Ctrl+D
scoped的原理就是生成一个带有随机数的自定义属性
deep样式穿透
由于添加了scoped属性父组件不能给子组件进行样式声明,则需要
/
d
e
e
p
/
\color{red}{/deep/}
/deep/深度选择器
如上,
/
d
e
e
p
/
\color{red}{/deep/}
/deep/是vue2所特有的写法,在vue3推荐使用
:
d
e
e
p
(
X
X
X
)
\color{red}{:deep(XXX)}
:deep(XXX)来替代
组件的props
props的作用:父组件通过props 向子组件传递要展示的数据。
props的好处:提高了组件的复用性。
使用props
父组件:
<mytest title="kukud_"></mytest>
子组件:
<script>
export default{
props: ['title'],
......
......
},
......
</script>
封装者定义了props数组,相当于留了一个形参api
无法使用未声明的props
在子组件中无法使用未声明的props
以对象语法绑定HTML的class
使用 数组语法 \color{red}{数组语法} 数组语法动态绑定classs会导致模板结构臃肿,使用对象语法来简化
<template>
<div>
<h2 class="thin" :class="classObj">MyStyle组件</h2>
<button @click="classObj.italic=!classObj.italic">Togge italic</button>
<button @click="classObj.delete=!classObj.delete">Togge Delete</button>
</div>
</template>
<script>
export default{
name: 'MyApp1',
data(){
return {
// 是否倾斜
isItalic: false,
isDelete: false,
classObj :{
italic: false,
delete: false
}
}},
methods: {
}
}
</script>
<style lang="less" scoped>
.italic{
font-style: italic;
}
.thin{
font-weight: 200;
}
.delete{
text-decoration: line-through;
}
</style>
以对象语法绑定内联的style
:style的对象语法十分直观——看着非常像CSS,但其实是一个JavaScript对象。CSS property名可以用驼峰式(camelCase)或短横线分隔(kebab-case,记得用引号括起来)来命名:
<h2 class="thin" :class="classObj" :style="{fontSize: fsize + 'px',color: active}">MyStyle组件</h2>
封装组件的案例
Null 过时…
props验证
props多个可能的值
当类型不唯一的时候,可以用数组来表示可能的类型
<script>
export default {
name: 'MyHeader',
props:{
count: Number,
state: Boolean,
// 数组表示可能的类型,可以使Number,可以使Boolean类型
info: [Number,Boolean]
}
}
</script>
props必填项校验
可以用 r e q u i r e d \color{red}{required} required属性来表示该值是否必填
<script>
export default {
name: 'MyHeader',
props:{
count: {
type: Number,
required: true
}
......
}
}
</script>
props默认值
可以使用 d e f a u l t \color{red}{default} default属性来表示默认值
<script>
export default {
name: 'MyHeader',
props:{
count: {
type: Number,
default: 100
},
......
}
}
</script>
自定义验证函数
可以用自定义验证函数来判断
<script>
export default {
name: 'MyHeader',
props:{
count: {
valdata(value){
// 判断是否属于数组中的一个
return ['success','warning','denger'].indexOf(value) !== -1
}
}
}
}
</script>
计算属性
计算属性本质上就是一个function函数,它可以实时监听 data中数据的变化,并return一个计算后的新值,供组件渲染DOM时使用。
声明计算属性
计算属性以function函数的形式声明到computed选项中,且计算属性结果会被缓存,性能好
<script>
export default {
name: 'MyHeader',
props:{
......
},
data() {
return {
count: 5,
}
},
computed: {
plus(){
return this.count*2;
}
}
}
</script>
计算属性必须定义在computed节点中
计算属性必须是一个function函数
计算属性必须有返回值
计算属性必须当做普通属性使用
动态计算水果案例
<!-- 结算区域 -->
<div class="settle-box">
<!-- TODO: 1. 动态计算已勾选的商品的总数量 -->
<span>总数量:{{total}}</span>
<!-- TODO: 2. 动态计算已勾选的商品的总价 -->
<span>总价:{{amount}}</span>
<!-- TODO: 3. 动态计算按钮的禁用状态 -->
<button type="button" class="btn btn-primary" :disabled="isDisabled ? true : false">结算</button>
</div>
</div>
</template>
<script>
export default {
name: 'FruitList',
data() {
......
},
computed:{
// 动态计算
total(){
let t=0
this.fruitlist.forEach(x =>{
if(x.state==true){
t+=x.count
}
})
return t
},
amount(){
let a=0
//循环state属性为true的所有price单价*count数量
this.fruitlist.filter(x => x.state).forEach(x =>{
a+=x.price*x.count
})
return a
},
isDisabled(){
return this.total===0
}
},
methods: {
......
}
}
</script>
watch侦听器
异步编程async/await语法
javascript从设计之初就是单线程的语法,单线程的异步编程方式有诸多优点,比如无需考虑线程同步,资源竞争,线程切换开销的问题,为避免回调地狱设计了promise
fetch函数会返回一个promise(承诺在某个时间返回数据)
async关键字会将该函数标记为异步函数(返回值为promise对象的函数)
await语法等待promise完成后最终的结果
安装axios
终端输入:
cnpm i axios -S
<template>
<div>
<h3>Watch侦听器用法</h3>
<input type="text" class="form-control" v-model="username">
</div>
</template>
<script>
import axios from 'axios'
export default {
name:'MyWatch',
data(){
return{
username:''
}
},
watch:{
// 声明这是一个异步处理函数
async username(newVal,oldVal){
console.log(newVal,oldVal)
const {data:res} =await axios.get('https://www.escook.cn/api/finduser/'+newVal)
console.log(res);
}
}
}
</script>
<style lang="less" scoped>
</style>
immedidate选项
在组件第一次被渲染后立即被调用,加入immedidata: true
<script>
import axios from 'axios'
export default {
name:'MyWatch',
data(){
return{
username:'admin'
}
},
watch:{
// 声明这是一个异步处理函数
// async username(newVal,oldVal){
// console.log(newVal,oldVal)
// const {data:res} =await axios.get('https://www.escook.cn/api/finduser/'+newVal)
// console.log(res);
// }
username:{
async handler(newVal,oldVal){
const {data:res} =await axios.get('https://www.escook.cn/api/finduser/'+newVal)
console.log(res);
},
//这里立即出发watch组件
immediate:true
}
}
}
</script>
deep选项
当watch侦听的是一个对象,当对象中的属性发生了变化,则无法监听到,此时需要使用deep选项
<template>
<div>
<h3>Watch侦听器用法</h3>
<input type="text" class="form-control" v-model="info.username">
</div>
</template>
<script>
import axios from 'axios'
export default {
name:'MyWatch',
data(){
return{
username:'admin',
info:{
username:'admin'
}
}
},
watch:{
// 声明这是一个异步处理函数
// async username(newVal,oldVal){
// console.log(newVal,oldVal)
// const {data:res} =await axios.get('https://www.escook.cn/api/finduser/'+newVal)
// console.log(res);
// }
// username:{
// async handler(newVal,oldVal){
// const {data:res} =await axios.get('https://www.escook.cn/api/finduser/'+newVal)
// console.log(res);
// },
// //这里立即出发watch组件
// immediate:true
// }
info:{
async handler(newVal,oldVal){
const {data:res} =await axios.get('https://www.escook.cn/api/finduser/'+newVal)
console.log(res);
},
immediate:true,
deep:true
}
}
}
</script>
<style lang="less" scoped>
</style>
watch选项监听单个属性
如上笔记所示,如果对象中的每一个属性发生变化时,都会触发
<template>
<div>
.......
<input type="text" class="form-control" v-model="info.username">
</div>
</template>
<script>
import axios from 'axios'
export default {
.......
data(){
return{
.......
info:{
username:'admin'
}
}
},
watch:{
//这里改为属性,用单引号包裹
'info.username':{
async handler(newVal,oldVal){
const {data:res} =await axios.get('https://www.escook.cn/api/finduser/'+newVal)
console.log(res);
}
}
}
}
</script>
<style lang="less" scoped>
</style>
计算属性vs侦听器
计算属性和侦听器侧重的应用场景不同:
计算属性侧重于监听多个值的变化,最终计算并返回一个新值
侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值
组件的运行过程
{
}
vue框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。例如:
①当组件在内存中被创建完毕之后,会自动调用created函数
②当组件被成功的渲染到页面上之后,会自动调用mounted函数
③当组件被销毁完毕之后,会自动调用unmounted函数
当组件被重新渲染的时候,会自动调用updatad函数
.......
<script>
export default {
name:'lifeCycle',
created(){
console.log('created');
},
mounted() {
console.log('mounted');
},
updated(){
console.log('updated');
},
unmounted(){
console.log('unmounted');
}
}
</script>
.......
{
}
全部的生命周期函数
{
}
全局配置axios
在main.js 入口文件中,通过app.config.globalProperties全局挂载axios
{
}
什么是ref引用
ref用来辅助开发者在不依赖于jQuery的情况下,获取DOM元素或组件的引用。
每个vue的组件实例上,都包含一个$refs对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的$refs指向一个空对象。
相当于打一个锚点,并操作 \color{red}{相当于打一个锚点,并操作} 相当于打一个锚点,并操作
让文本框自动获得焦点的方法: . f o c u s ( ) \color{red}{.focus()} .focus()
Dom更新是异步的,因此获取引用对象的时候会出现未定义,因此需要延迟执行,比如方法
this.$nextTick(()=>{ ...... this.$refs.ipt.focus() })
动态组件
在vue3中,可以声明动态组件 c o m p o n e n t \color{red}{component} component来让组件动态的展示,其中 i s \color{red}{is} is参数可以指明现在的所展示的组件
<template>
<div>
<button @click="ckHome">首页</button>
<button @click="ckMovie">电影</button>
<hr>
<component :is="conName"></component>
</div>
</template>
<script>
import movie from './components/movie.vue';
import home from './components/home.vue';
export default {
name:'App',
components:{
movie,
home,
},
data() {
return {
conName:'home'
}
},
methods:{
ckHome(){
this.conName = 'home'
},
ckMovie(){
this.conName = 'movie'
}
}
}
</script>
<style>
</style>
但是有一个弊端,当组件被切换时,默认将会销毁旧组件,里面的值也会随之被销毁,解决办法就是在 c o m p o n e n t \color{red}{component} component外添加 < k e e p − a l i v e > \color{red}{<keep-alive>} <keep−alive>组件保持动态组件的状态
<template>
<div>
<button @click="ckHome">首页</button>
<button @click="ckMovie">电影</button>
<hr>
<keep-alive>
<component :is="conName"></component>
</keep-alive>
</div>
.......
</template>
插槽
如果没有预留插槽,那么自定义的任何内容都将被遗弃
具名插槽
如果在封装组件的时候预留了多个插槽并声明了具体的name名称
<slot name="xxx"></slot>
如果只有一个插槽,且未命名,则默认名称为 d e f a u l t \color{red}{default} default
向具名插槽传递
App.vue:
<template>
<div>
<button @click="ckHome">首页</button>
<button @click="ckMovie">电影</button>
<hr>
<keep-alive>
<component :is="conName">
<template v-slot:header>
<h1>滕王阁序</h1>
</template>
<template v-slot:body>
唧唧复唧唧,木兰当不知
《懒得打,乱编的》
</template>
<template v-slot:footer>
<h4>
王博
</h4>
</template>
</component>
</keep-alive>
</div>
</template>
<script>
import movie from './components/movie.vue';
import home from './components/home.vue';
export default {
name:'App',
components:{
movie,
home,
},
data() {
return {
conName:'home'
}
},
methods:{
ckHome(){
this.conName = 'home'
},
ckMovie(){
this.conName = 'movie'
}
}
}
</script>
<style>
</style>
movie.vue:
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot name="body"></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<script>
export default {
name:'move'
}
</script>
<style>
</style>
具名插槽 v − s l o t : \color{red}{v-slot:} v−slot:可以替换成 # \color{red}{\#} #
作用域插槽
相当于子传父
父组件 — App.vue:
<template v-slot:body="info">
唧唧复唧唧,木兰当不知
{{info}}
</template>
在插槽后面跟一个 = " i n f o " \color{red}{="info"} ="info"可以传递一个对象,info可以看做形参
子组件 — movie.vue:
<template>
<div>
......
<main>
<slot name="body" :info="da" :msg="ya"></slot>
</main>
......
</div>
</template>
<script>
export default {
name:'move',
data() {
return {
da:'这是一条数据',
ya:'这是第二条数据'
}
},
}
</script>
......
所显示:
插槽的结构赋值
在default之后可以直接指定数据,更加方便
私有自定义指令
vue 官方提供了v-for、v-model、v-if等常用的内置指令。除此之外vue还允许开发者自定义指令。
vue中的自定义指令分为两类,分别是:
私有自定义指令 \color{red}{私有自定义指令} 私有自定义指令&&& 全局自定义指令 \color{red}{全局自定义指令} 全局自定义指令
使用自定义指令必须以’v-’开头,声明不需要开头
这里使用了mounted钩子函数
{
<template>
<div>
directives!!
自动获得焦点
<h1>
<input type="text" v-focuss >
</h1>
<slot name="header"></slot>
</div>
</template>
<script>
export default {
name:'directives',
data() {
return {
}
},
directives:{
focuss:{
// 被绑定到的函数渲染后自动触发mounted这个函数
mounted(el) {
// 用focus获取焦点
el.focus();
},
}
}
}
</script>
<style lang="less" scoped>
</style>
}
全局自定义指令
在项目的main.js中:
{
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import './assets/bootstrap-3.4.1-dist/css/bootstrap.css'
const app = createApp(App)
app.directive('focus',{
mounted(el){
el.focus()
}
})
app.mount('#app')
}
}
update函数
在上面的z局部自定义函数中的代码有一个不足之处,当mounted 函数只在元素第一次插入DOM时被调用,当点击+1后,会失去焦点
当DOM更新时
m
o
u
n
t
e
d
函数
\color{red}{mounted函数}
mounted函数不会被触发。updated函数会在每次DOM更新完成后被调用。
添加update函数后
自定义指令两个注意点:
在vue2项目中,名称有所不同【mounted -> bind】【updated -> update】
mounted和updated的业务逻辑如果相同可以使用简写形式
app.directive('focus',(el)=>{
el.focus();
})
指令的参数值
在自定义指令中可以通过 = " X X X " \color{red}{="XXX"} ="XXX"来传递参数值
在vue自检中:
<input type="text" v-focus v-color="'red'">
main.js中:
app.directive('color',(el,binding)=>{
//第二个参数为形参
el.style.color=binding.value;
})
table案例的一些问题
input输入框change和blur事件区别
blur与change事件在绝大部分的情况下表现都非常相似,输入结束后,离开输入框,会先后触发change与blur,唯有两点例外。
1、没有进行任何输入时,不会触发change
在这种情况下失焦后,输入框并不会触发change事件,但一定会触发blur事件。在判断表单的修改状态时,这种差异会非常有用,通过change事件能轻易地找到哪些字段发生了变更以及其值的变更轨迹。
2、输入后值并没有发生变更
这种情况是指,在没有失焦的情况下,在输入框内进行的删除与输入操作,但最终的值与原值一样,这种情况下失焦后,keydown、input、keyup、blur都会触发,但change依旧不会触发。
路由
路由前导
后端路由
后端路由指的是:请求方式、请求地址与function 处理函数之间的对应关系。
SPA项目中的前端路由
SPA指的是一个web网站只有唯一的一个HTML页面,所有组件的展示与切换都在这唯一的一个页面内完成。
此时,不同组件之间的切换需要通过前端路由来实现。
前端路由
Hash地址与组中的联系
前端路由工作方式
用户点击了页面上的路由链接
导致了URL地址栏中的Hash值发生了变化
前端路由监听了到Hash地址的变化
前端路由把当前Hash地址对应的组件渲染都浏览器中
安装Vue-router
cnpm i vue-router@next -S
自定义Router高亮连接
后面为类名
linkActiveClass:‘XXXX’,
动态路由的概念
通过冒号(:)可以定义后面的参数是可变的
如/home/:XXX
参数值可以通过 $ r o u t e . p a r a m s \color{red}{route.params} route.params来获取XXX
也可通过prop传参
(1)在动态路由规则开启动态传参 props:true
(2)在子组件使用props数组进行接收