- 简介
- 环境搭建
- 计数器案例
- 命令式和声明式编程
- MVVM模型
- template属性
- data属性
- methods属性
- this的指向
- VSCode代码片段
- Musache语法
- 基本指令
- 重要指令
- v-if
- v-show
- v-for
- 数组更新检测
- VNode
- 计算属性-computed
- Watch
- 综合案例_书籍购物车
- v-model
- 组件化开发
- 组件通信
- 插槽
- 动态组件
- Webpack的代码分包&异步组件
- $refs的使用
- 生命周期
- 组件的v-model
- Vue3过渡&动画实现
- animate.css动画
- gsap库
- Mixin
- extends
- Options API的弊端
- Composition API
- computed
- 侦听数据的变化_watchEffect
- 侦听数据的变化_watch
- 生命周期钩子
- Provide和Inject
- Composition API练习
- jsx
- VueRouter
- Vuex的状态管理
- nexttick
简介
组件化
组件化开发最最重要的一点,就是复用.
类型检测
为什么一定要有类型检测呢?
简而言之,就是错误发现越早越好.
- JavaScript的类型错误只有在运行阶段才能发现
技术栈
vue项目需要掌握的技术栈
学习方法
什么是渐进式框架
一点点引入和使用
vue的本质
本质就是一个JavaScript库,就当做一个JS文件引入就好了
基本思路
传入一个对象,返回一个对象,将返回的对象挂在到dom元素上面
链式调用
链式调用更简单,更常用.
调试工具
[shell-chrome.rar - 快捷方式.lnk](..\ae_文本文件\shell-chrome.rar - 快捷方式.lnk)
环境搭建
CDN方式引入
什么是CDN?
个人理解,有点像P2P的下载模式,有点就近转发的意思.
引入
<script src="https://unpkg.com/vue@next"></script>
通过vue.js文件引入
下载
登录网址, https://unpkg.com/browse/vue@3.1.5/dist/ ,如下图所示下载vue.global.js文件,这个文件并不是源码文件,而是经过打包之后的文件.
引入
如下图引入:
计数器案例
原生JS实现
原生JS实现计数器-增加事件监听.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>
<div id="counter"></div>
<button id="increase" >+</button>
<button id="decrease">-</button>
<script>
//aa-get the counter and button elements
let counter = document.querySelector('#counter')
let increase = document.querySelector('#increase')
let decrease = document.querySelector('#decrease')
//bb-display number in the counter div
let num = 100
counter.innerHTML = num
//cc-bind the button click event
increase.addEventListener('click',()=>{
num++
counter.innerHTML = num
})
decrease.addEventListener('click',()=>{
num--
counter.innerHTML = num
})
</script>
</body>
</html>
原生JS实现计数器-在元素中绑定onclick属性和script标签中增加onclick属性.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>
<div id="counter"></div>
<button id="increase" onclick="increase()" >+</button>
<button id="decrease">-</button>
<script>
//aa-get the counter and button elements
let counter = document.querySelector('#counter')
let increase = document.querySelector('#increase')
let decrease = document.querySelector('#decrease')
//bb-display number in the counter div
let num = 100
counter.innerHTML = num
//cc-bind the button click event
//ca-bind onclick in the button div
function increase(){
num++
counter.innerHTML = num
}
//cb-bind onclick in the script query
decrease.onclick = function(){
num --
counter.innerHTML = num
}
</script>
</body>
</html>
VUE实现计数器
vue实现计数器.html
基本结构
数据绑定mustache语句
事件绑定
data的value为啥是个函数
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<div id="app"></div>
<script src="./vue/vue.js"></script>
<script>
Vue.createApp({
template:`
<h2>{{counter}}</h2>
<button @click='increase'>+1</button>
<button @click='decrease'>-1</button>
`,
data:function(){
return{
counter: 10
}
},
methods: {
increase(){
this.counter++
},
decrease(){
this.counter--
}
},
}).mount('#app')
</script>
</body>
</html>
vue实现计数器 and es5的写法 and 箭头函数的写法.html
es5和es6的写法的对比
箭头函数的写法
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<div id="app"></div>
<script src="./vue/vue.js"></script>
<script>
Vue.createApp({
template:`
<h2>{{counter}}</h2>
<button @click='increase'>+1</button>
<button @click='decrease'>-1</button>
`,
data:function(){
return{
counter: 10
}
},
methods: {
//es6的写法
//increase(){
// this.counter++
//},
//es5的写法--OK
increase:function(){
this.counter++
}
,
//箭头函数--Not OK!!!
decrease:()=>{
this.counter--
}
},
}).mount('#app')
</script>
</body>
</html>
命令式和声明式编程
声明式编程
理解
命令式编程和声明式编程的区别
不恰当的比喻:一个是手把手的教,一个是发个命令就好了.
MVVM模型
MVC模型
MVVM模型
vue的模式类似MVVM模型
template属性
挂载
template中的内容会被挂载到对应的元素下面,并且其中的内容会被覆盖掉
分离式写法_script标签
分离式写法.html
关键代码
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<div id="app"></div>
<script type='x-template' id='main'>
<h2>{{counter}}</h2>
<button @click='increase'>+1</button>
<button @click='decrease'>-1</button>
</script>
<script src="./vue/vue.js"></script>
<script>
Vue.createApp({
template:`#main`,
data:function(){
return{
counter: 10
}
},
methods: {
increase(){
this.counter++
},
decrease(){
this.counter--
}
},
}).mount('#app')
</script>
</body>
</html>
分离式写法_template标签
template中的内容分离式写法_使用template标签.html
核心代码
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<div id="app"></div>
<template id='main'>
<div>
<h2>{{counter}}</h2>
<button @click='increase'>+1</button>
<button @click='decrease'>-1</button>
</div>
</template>
<script src="./vue/vue.js"></script>
<script>
Vue.createApp({
template:`#main`,
data:function(){
return{
counter: 10
}
},
methods: {
increase(){
this.counter++
},
decrease(){
this.counter--
}
},
}).mount('#app')
</script>
</body>
</html>
template标签的特点
https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/template
普通div其实也是可以实现挂载的,vue也会将其挂载上去,只不过div会被浏览器解析器渲染上去,显示出来,从而导致内容重复多出来.
template中的根元素
vue2和vue3中template中根元素个数的区别.html
vue3中可以有多个根元素
vue2中只能有一个根元素
<!-- vue3 是允许template中有多个根元素 -->
<template id="my-app">
<a v-bind:href="link">百度一下</a>
<a :href="link">百度一下</a>
</template>
<!-- vue2 template模板中只能有一个根元素 -->
<template id="my-app">
<div>
<a v-bind:href="link">百度一下</a>
<a :href="link">百度一下</a>
</div>
</template>
data属性
vue2和vue3中的区别
methods属性
this的指向
普通函数
普通函数的this指向.html
this的指向
this永远指向的是调用他的那个对象
fun7()其实是window.fun7()的省略写法
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<script>
/**
* this的指向
* 1-函数调用的时候,this就是指向这个window对象
* 2-对象进行调用的时候,就是指向这个对象。
*/
function fun7(){
console.log(this);
console.log(this.name);
console.log("I'm fun7");
}
fun7();
console.log('----------------');
var obj2 = {
name:"Bruce",
age:12,
say:function(){
console.log(this);
console.log(this.name);
console.log("I'm fun8");
}
}
obj2.say();
</script>
</body>
</html>
强制改变普通函数函数定义时候的this.html
强制改变普通函数定义时候的this
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<script>
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
_this = this
setInterval(function () {
_this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);//=>3
setTimeout(() => console.log('s2: ', timer.s2), 3100);//=>3
</script>
</body>
</html>
箭头函数
箭头函数中this的指向.html
普通函数this永远指向调用他的对象
箭头函数this的指向定义时候的this
箭头函数没有自己的this,他会从里头向外头寻找,直到找到有this为止,这里的this是windows对象.
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<script>
var obj2 = {
name:"Bruce",
age:12,
say:function(){
console.log(this);
//=>{name: "Bruce", age: 12, say: ƒ, run: ƒ}
},
run:()=>{
console.log(this);
//=>Window {window: Window, self: Window, document: document, name: "", location: Location, …}
},
}
obj2.say();
obj2.run()
</script>
</body>
</html>
为什么vue的methods不要使用箭头函数?
计数器.html
我们想要操作的对象是什么?
我想要操作的对象是什么,我们想要通过这个this.counter对象拿到这个data中的counter,但是如果传入的this是Windows对象,我们通过这个windows对象是拿不到这个counter的.所以不推荐使用箭头函数.
箭头函数没有自己this,一般在定义的时候,会到自己的上级作用域寻找this,这里的上级作用域就是windows所在的作用域,一般来说都是这个windows作用域.
普通函数和箭头函数中的this
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<div id="app"></div>
<script src="./vue/vue.js"></script>
<script>
Vue.createApp({
template:`
<h2>{{counter}}</h2>
<button @click='increase'>+1</button>
<button @click='decrease'>-1</button>
`,
data:function(){
return{
counter: 10
}
},
methods: {
increase(){
console.log(this);
//=>Proxy {increase: ƒ, decrease: ƒ, …}
this.counter++
},
decrease:()=>{
console.log(this);
//=>Window {window: Window, self: Window, document: document, name: "", location: Location, …}
this.counter--
}
}
}).mount('#app')
</script>
</body>
</html>
总结
使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。
使用function定义的函数中this指向是随着调用环境的变化而变化的
//使用function定义的函数
function foo(){
console.log(this);
}
var obj = { aa: foo };
foo(); //Window
obj.aa() //obj { aa: foo }
明显使用箭头函数的时候,this的指向是没有发生变化的。
//使用箭头函数定义函数
var foo = () => { console.log(this) };
var obj = { aa:foo };
foo(); //Window
obj.aa(); //Window
VSCode代码片段
- 赋值自己需要的代码
<!DOCTYPE html>
<html lang="zh">
<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">
{{msg}}
</div>
<script src="vue/vue.js"></script>
<script>
Vue.createApp({
data:function(){
return{
msg: 'hello vue'
}
}
}).mount('#app')
</script>
</body>
</html>
- 登录这个网站
https://snippet-generator.app/
- 将生成的代码片段拷贝下来
"create vue app": {
"prefix": "vueapp",
"body": [
"<!DOCTYPE html>",
"<html lang=\"zh\">",
"<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\">",
" {{msg}}",
" </div>",
" <script src=\"vue/vue.js\"></script>",
" <script>",
" Vue.createApp({",
" data:function(){",
" return{",
" msg: 'hello vue'",
" }",
" }",
" }).mount('#app')",
" </script>",
"</body>",
"</html>"
],
"description": "create vue app"
}
- 打开vscode
![Honeycam 2021-07-21 18-54-06](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-21 18-54-06.gif)
Musache语法
正确用法
mustache语法.html
基本使用
简单的表达式
也可以是methods中的函数
既然可以是表达式,当然也可以是三元表达式了
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 1.mustache的基本使用 -->
<h2>{{message}}</h2>
<!-- 2.是一个表达式 -->
<h2>{{counter * 10}}</h2>
<h2>{{ message.split(" ").reverse().join(" ") }}</h2>
<!-- 3.也可以调用函数 -->
<!-- 可以使用computed(计算属性) -->
<h2>{{getReverseMessage()}}</h2>
<!-- 4.三元运算符 -->
<h2>{{ isShow ? "哈哈哈": "" }}</h2>
<button @click="toggle">切换</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World",
counter: 100,
isShow: true
}
},
methods: {
getReverseMessage() {
return this.message.split(" ").reverse().join(" ");
},
toggle() {
this.isShow = !this.isShow;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
错误用法
musache错误用法.html
两者都是赋值语句,不是表达式
<!-- 错误用法 -->
<!-- var name = "abc" -> 赋值语句 -->
<h2>{{var name = "abc"}}</h2>
<h2>{{ if(isShow) { return "哈哈哈" } }}</h2>
基本指令
v-once
v-once修饰的html元素,只渲染一次,以后都是不变,相当于一个原始的参照系.
v-once.html
代码理解
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<div v-once>
<h2>原始数值:{{counter}}</h2>
</div>
<h2>当前数值{{counter}}</h2>
<button @click="increment">+1</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
counter: 100,
}
},
methods: {
increment() {
this.counter++;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出
![Honeycam 2021-07-22 08-11-24](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 08-11-24.gif)
v-text
v-text就是其修饰的html元素中添加内容,作用和mustache类似,不过没有mustache语法灵活.
v-text.html
关键代码
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-text="message"></h2>
<h2>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
v-html
v-html.html
关键代码
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<div>{{msg}}</div>
<div v-html="msg"></div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
msg: '<span style="color:red; background: blue;">哈哈哈</span>'
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出
v-pre
v-pre.html
关键代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-pre>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出
v-cloak
cloak就是斗篷,遮盖的的意思,这个指令的作用是什么,主要为了显示效果,比如说网络很卡,浏览器很卡,这个{{message}}中的内容还没有渲染进来,网页页面会显示mustache语法的原始内容,我们加上这个遮盖之后,就是什么都不显示,然后等到这个里面的message内容渲染完成之后,才将其显示,目的是为了更好的用户体验.
v-cloak.html
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-cloak>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
cloak单词的意思
重要指令
v-bind
属性绑定
v-bind是用来绑定属性,实现动态属性.mustache是用来绑定内容,实现动态内容.
v-bind的基本使用.html
v-bind的基本使用
v-bind的语法糖
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 1.v-bind的基本使用 -->
<a v-bind:href="link">百度一下</a>
<!-- 2.v-bind提供一个语法糖 : -->
<a :href="link">百度一下</a>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
link: "https://www.baidu.com"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
属性绑定_对象形式
属性绑定_对象形式.html
key-value结构
错误写法
key可以加上引号,也可以不加引号.但是value一定不能加引号,因为value加上引号,就变成了字符串.
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<!-- 正确写法:key-value结构 -->
<!-- 对象语法: {key: value} -->
<h2 :class="{'active': isActive}">呵呵呵呵</h2>
<h2 :class="{active: isActive}">呵呵呵呵</h2>
<!-- 错误写法 -->
<h2 :class="{active: 'isActive'}">呵呵呵呵</h2>
<button @click="toggle">切换</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
isActive: true,
};
},
methods: {
toggle() {
this.isActive = !this.isActive;
},
},
};
Vue.createApp(App).mount("#app");
</script>
</body>
</html>
输出
![Honeycam 2021-07-22 10-58-26](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 10-58-26.gif)
多值情况
属性绑定_多值情况.html
多个键值对
默认class和动态的class结合
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.active {
color: red;
}
.title {
background-color: yellowgreen;
}
</style>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<button @click="toggle">切换</button>
<!-- 也可以有多个键值对 -->
<div :class="{active: isActive, title: true}">多个键值对</div>
<!-- 默认的class和动态的class结合 -->
<div class="abc cba" :class="{active: isActive, title: true}">
默认的class和动态的class结合
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
isActive: true,
};
},
methods: {
toggle() {
this.isActive = !this.isActive;
}
},
};
Vue.createApp(App).mount("#app");
</script>
</body>
</html>
对象放到一个单独的属性中
将对象放到一个单独的属性中.html
将对象放到一个单独的属性中
<!DOCTYPE html>
<html lang="zh">
<head>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<!-- 将对象放到一个单独的属性中 -->
<h2 class="abc cba" :class="classObj">将对象放到一个单独的属性中</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
classObj: {
active: true,
title: true
}
};
}
};
Vue.createApp(App).mount("#app");
</script>
</body>
</html>
将对象放到methods中返回
将返回的对象放到一个methods(computed)方法中.html
将对象放到一个methods返回
<!DOCTYPE html>
<html lang="zh">
<head>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<!-- 将返回的对象放到一个methods(computed)方法中 -->
<h2 class="abc cba" :class="getClassObj()">将对象放到一个methods(computed)方法中返回</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
methods: {
getClassObj() {
return {
active: true,
title: true
}
}
},
};
Vue.createApp(App).mount("#app");
</script>
</body>
</html>
属性绑定_数组形式
数组形式绑定属性.html
基本用法
嵌入三元表达式
嵌入对象
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 基本用法 -->
<div :class="['abc', title]">哈哈哈哈</div>
<!-- 数组中可以嵌入三元表达式 -->
<div :class="['abc', title, isActive ? 'active': '']">哈哈哈哈</div>
<!-- 数组中可以嵌入对象 -->
<div :class="['abc', title, {active: isActive}]">哈哈哈哈</div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World",
title: "cba",
isActive: true
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
样式绑定_对象形式
v-bind绑定样式.html
基本使用
基本使用_简单拼接
绑定data属性中的object对象
方法中返回的一个对象
短横线需要加引号,驼峰不需要加引号
<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<!-- :style="{cssPropertyName: cssPropertyValue}" -->
<div :style="{color: finalColor, 'font-size': '30px'}">'font-size'加了引号</div>
<div :style="{color: finalColor, fontSize: '30px'}">fontSize不加引号</div>
<div :style="{color: finalColor, fontSize: finalFontSize + 'px'}">finalFontSize是data中的值,和后面的'px'拼起来</div>
<!-- 绑定一个data中的属性值, 并且是一个对象 -->
<div :style="finalStyleObj">绑定一个data中的属性</div>
<!-- 方法中返回的一个对象 -->
<div :style="getFinalStyleObj()">methods中返回的一个对象</div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World",
finalColor: 'red',
finalFontSize: 50,
finalStyleObj: {
'font-size': '50px',
fontWeight: 700,
backgroundColor: 'red'
}
}
},
methods: {
getFinalStyleObj() {
return {
'font-size': '50px',
fontWeight: 700,
backgroundColor: 'red'
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
样式绑定_数组形式
样式绑定_数组形式.html
数组中嵌套对象
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<div :style="[style1Obj, style2Obj]">哈哈哈</div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
style1Obj: {
color: 'red',
fontSize: '30px'
},
style2Obj: {
textDecoration: "underline"
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
属性名称绑定
属性名称绑定.html
属性名称的绑定
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<div :[name]="value">哈哈哈</div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
name: "classs",
value: "content"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
属性名和属性值绑定
属性名和属性值绑定.html
和属性值绑定的区别
没有冒号:
多个属性名和属性值的键值对的绑定
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-bind="info">同时绑定多个属性名和属性值</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: {
name: "zhuo",
age: 18,
height: 1.88
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
v-bind(same)
-
缩写:
:
-
预期:
any (with argument) | Object (without argument)
-
参数:
attrOrProp (optional)
-
修饰符:
.camel
- 将 kebab-case attribute 名转换为 camelCase。
-
用法:
动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。
在绑定
class
或style
attribute 时,支持其它类型的值,如数组或对象。可以通过下面的教程链接查看详情。在绑定 prop 时,prop 必须在子组件中声明。可以用修饰符指定不同的绑定类型。
没有参数时,可以绑定到一个包含键值对的对象。注意此时
class
和style
绑定不支持数组和对象。 -
示例:
<!-- 绑定 attribute --> <img v-bind:src="imageSrc" /> <!-- 动态 attribute 名 --> <button v-bind:[key]="value"></button> <!-- 缩写 --> <img :src="imageSrc" /> <!-- 动态 attribute 名缩写 --> <button :[key]="value"></button> <!-- 内联字符串拼接 --> <img :src="'/path/to/images/' + fileName" /> <!-- class 绑定 --> <div :class="{ red: isRed }"></div> <div :class="[classA, classB]"></div> <div :class="[classA, { classB: isB, classC: isC }]"> <!-- style 绑定 --> <div :style="{ fontSize: size + 'px' }"></div> <div :style="[styleObjectA, styleObjectB]"></div> <!-- 绑定一个全是 attribute 的对象 --> <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div> <!-- prop 绑定。"prop" 必须在 my-component 声明 --> <my-component :prop="someThing"></my-component> <!-- 通过 $props 将父组件的 props 一起传给子组件 --> <child-component v-bind="$props"></child-component> <!-- XLink --> <svg><a :xlink:special="foo"></a></svg> </div>
.camel
修饰符允许在使用 DOM 模板时将v-bind
property 名称驼峰化,例如 SVG 的
viewBox
property:<svg :view-box.camel="viewBox"></svg>
在使用字符串模板或通过
vue-loader
/vueify
编译时,无需使用.camel
。
v-on
基本使用
v-on的基本使用.html
click事件
mousemove事件
<!DOCTYPE html>
<html lang="zh">
<head>
<style>
.area {
width: 200px;
height: 200px;
background: red;
}
</style>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<!-- 完整写法: v-on:监听的事件="methods中方法" -->
<button v-on:click="btn1Click">按钮1</button>
<div class="area" v-on:mousemove="mouseMove">div</div>
<!-- 语法糖 -->
<button @click="btn1Click">按钮1</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
methods: {
btn1Click() {
console.log("按钮1发生了点击");
},
mouseMove() {
console.log("鼠标移动");
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
绑定一个对象
v-on通过绑定一个对象从而实现绑定多个事件.html
v-on绑定一个对象实现绑定多个事件
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.area {
width: 200px;
height: 200px;
background: red;
}
</style>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<!-- 绑定一个对象 -->
<div class="area" v-on="{click: btn1Click, mousemove: mouseMove}"></div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
methods: {
btn1Click() {
console.log("按钮1发生了点击");
},
mouseMove() {
console.log("鼠标移动");
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
传递参数
v-on如何向vue的methods中传递参数.html
默认传入event事件
传入其他参数的同时传入event参数
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 默认传入event对象, 可以在方法中获取 -->
<button @click="btn1Click">按钮1</button>
<!-- $event可以获取到事件发生时的事件对象 -->
<button @click="btn2Click($event, 'zhuo', 18)">按钮2</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
},
methods: {
btn1Click(event) {
console.log(event);
},
btn2Click(event, name, age) {
console.log(name, age, event);
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
修饰符
stop修饰符
阻止事件冒泡的按钮.html
关键代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<div @click="divClick">
<button @click='btnClick'>没有阻止冒泡的按钮</button><br>
<button @click.stop="btnClick">阻止冒泡按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
methods: {
divClick() {
console.log("divClick");
},
btnClick() {
console.log('btnClick');
},
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
![Honeycam 2021-07-22 12-57-11](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 12-57-11.gif)
[按键]修饰符
输入框实现enter键上屏的效果.html
关键代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<input type="text" @keyup.enter="enterKeyup">
<h2>{{content}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
content:''
}
},
methods: {
enterKeyup(event) {
this.content = event.target.value
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
![Honeycam 2021-07-22 13-02-02](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 13-02-02.gif)
v-if
渲染原理
怎么理解这个惰性?当条件为false时,在dom的元素就会完全的删除掉,而不是display:none.
基本使用
v-if的基本使用.html
基本使用
<!DOCTYPE html>
<html lang="zn">
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-if="isShow">哈哈哈哈</h2>
<button @click="toggle">切换</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
isShow: true
}
},
methods: {
toggle() {
this.isShow = !this.isShow;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出
![Honeycam 2021-07-22 20-11-57](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 20-11-57.gif)
多个条件
v-if多个条件的使用.html
关键代码
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<input type="text" v-model="score">
<h2 v-if="score > 90">优秀</h2>
<h2 v-else-if="score > 60">良好</h2>
<h2 v-else>不及格</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
score: 95
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出
![Honeycam 2021-07-22 20-14-40](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 20-14-40.gif)
template和v-if的结合使用
为什么需要和v-if结合使用
v-if不结合使用.html
关键代码和输出效果
多了一个外层的div,现在就是不想要这个div
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 和div结合使用 -->
<div v-if="isShowHa">
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
</div>
<!-- 和template结合使用 -->
<template v-else>
<h2>呵呵呵呵</h2>
<h2>呵呵呵呵</h2>
<h2>呵呵呵呵</h2>
</template>
<button @click='toggle'>toggle</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
isShowHa: true
}
},
methods: {
toggle(){
this.isShowHa = !this.isShowHa
}
},
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
v-if结合使用例子
template和v-if的结合使用.html
关键代码
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<template v-if="isShowHa">
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
</template>
<template v-else>
<h2>呵呵呵呵</h2>
<h2>呵呵呵呵</h2>
<h2>呵呵呵呵</h2>
</template>
<button @click='toggle'>toggle</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
isShowHa: true
}
},
methods: {
toggle(){
this.isShowHa = !this.isShowHa
}
},
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
![Honeycam 2021-07-22 20-18-42](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 20-18-42.gif)
v-show
基本使用
v-show的基本使用.html
关键代码
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-show="isShow">哈哈哈哈</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
isShow: true
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
v-show和v-if的区别
2,3,4都是很好理解,第1个怎么理解?首先要理解一点就是v-show是通过display:none来控制显示不显示的,而template这个标签一旦被渲染,这个标签就是不存在了,我在对这个标签使用css修饰已经没有任何意义了.
v-show和v-if的区别.html
关键代码
重要区别
v-show通过修改css属性来实现隐藏显示,而v-if直接就是干掉整个元素.
![Honeycam 2021-07-22 20-32-44](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 20-32-44.gif)
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-if="isShow">哈哈哈哈</h2>
<h2 v-show="isShow">呵呵呵呵</h2>
<button @click='toggle'>toggle</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
isShow: true
}
},methods: {
toggle(){
this.isShow = !this.isShow
}
},
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
v-show和v-if如何选择
v-for
基本使用
v-for的使用.html
遍历数组
括号的中两个参数分别是value和index
v-for中传递参数的括号可以不加,但是建议加上去
v-for的in也可以使用of
遍历对象
遍历对象的时候,括号中的参数分别是value,key和index
遍历数字
遍历数字的时候,括号中的参数分别是num和index
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<h2>遍历数组</h2>
<ul>
<!-- 遍历数组 -->
<li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li>
</ul>
<h2>遍历对象</h2>
<ul>
<!-- 遍历对象 -->
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
</ul>
<h2>遍历数字</h2>
<ul>
<!-- 遍历数字 -->
<li v-for="(num, index) in 10">{{num}}-{{index}}</li>
</ul>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
movies: [
"星际穿越",
"盗梦空间",
"大话西游",
"教父",
"少年派"
],
info: {
name: "zhuo",
age: 18,
height: 1.88
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
v-for和template搭配使用
v-for和template搭配使用.html
v-for和template搭配使用
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<ul>
<template v-for="(value, key) in info">
<li>{{key}}</li>
<li>{{value}}</li>
<li class="divider"></li>
</template>
</ul>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: {
name: "why",
age: 18,
height: 1.88
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
搭配绑定key
数组更新检测
vue已经自动帮我们侦听了数组,所以数组的改变就会触发新的视图.实际上可以这样理解,vue已经自动帮我们把这个数组进行了数据的双向绑定.
push()方法
数组的更新检测.html
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>电影列表</h2>
<ul>
<li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li>
</ul>
<input type="text" v-model="newMovie">
<button @click="addMovie">添加电影</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
newMovie: "",
movies: [
"星际穿越",
"盗梦空间",
"大话西游",
"教父",
"少年派"
]
}
},
methods: {
addMovie() {
this.movies.push(this.newMovie);
this.newMovie = "";
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
![Honeycam 2021-07-22 23-12-19](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-22 23-12-19.gif)
VNode
计算属性-computed
为什么会有计算属性?
有些数据通过mustache语法显示在界面上面,但是在mustache中又进行了简单的表达式计算.这个用来写单独的项目是可以的.但是现在我们想要写出通用的组件,就必须让这个mustache中的数据更加纯粹,于是就必须对这些数据进行解耦,那么感觉我就是对原来的数据进行一下二次封装,然后就这些二次封装后的数据在渲染到界面上面.这个二次封装的数据就是计算属性.
基本使用
计算属性.html
计算属性的来源和目的地
- 来源:计算属性数据的来源肯定是来自data中的元数据,
- 目的地:mustache展示界面.
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{fullName}}</h2>
<h2>{{result}}</h2>
<h2>{{reverseMessage}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant",
score: 80,
message: "Hello World"
}
},
computed: {
fullName() {
return this.firstName + " " + this.lastName;
},
result() {
return this.score >= 60 ? "及格": "不及格";
},
reverseMessage() {
return this.message.split(" ").reverse().join(" ");
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
使用methods实现的数据二次封装和计算属性的区别
methods和计算属性的区别.html
计算属性
methods实现类似计算属性的功能
区别
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h1>计算属性</h1>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h1>使用methods实现</h1>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant"
}
},
computed: {
// 计算属性是有缓存的, 当我们多次使用计算属性时, 计算属性中的运算只会执行一次.
// 计算属性会随着依赖的数据(firstName)的改变, 而进行重新计算.
fullName() {
console.log("computed的fullName中的计算");
return this.firstName + " " + this.lastName;
}
},
methods: {
getFullName() {
console.log("methods的getFullName中的计算");
return this.firstName + " " + this.lastName;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
methods和计算属性的区别_修改元数据.html
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<button @click="changeFirstName">修改firstName</button>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant"
}
},
computed: {
fullName() {
console.log("computed的fullName中的计算");
return this.firstName + " " + this.lastName;
}
},
methods: {
getFullName() {
console.log("methods的getFullName中的计算");
return this.firstName + " " + this.lastName;
},
changeFirstName() {
this.firstName = "Coder"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
描述
methods修改了三次,计算属性只是修改了一次.所以计算属性的更省.
![Honeycam 2021-07-23 19-58-29](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-23 19-58-29.gif)
修改计算属性
修改计算属性_没有效果的例子.html
修改流程
按钮绑定click事件去修改,通过methods方法去修改计算属性中的计算属性.
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<button @click="changeFullName">修改fullName</button>
<h2>{{fullName}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant"
}
},
computed: {
// fullName 的 getter方法
fullName() {
return this.firstName + " " + this.lastName;
},
},
methods: {
changeFullName() {
this.fullName = "Coder Why";
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
描述
没有丝毫效果
![Honeycam 2021-07-23 20-14-51](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-23 20-14-51.gif)
修改计算属性_成功例子.html
计算属性的全写形式
语法糖和全写形式
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<button @click="changeFullName">修改fullName</button>
<h2>{{fullName}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant"
}
},
computed: {
// fullName的getter和setter方法
fullName: {
get: function() {
console.log('计算属性被__获取了');
return this.firstName + " " + this.lastName;
},
set: function(newValue) {
console.log('计算属性被__修改了');
let names = newValue.split(" ");
this.firstName = names[0];
this.lastName = names[1];
}
}
},
methods: {
changeFullName() {
this.fullName = "Coder Why";
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
描述
getter方法用上展示,没有getter方法,无法在mustache中渲染显示出来.
setter方法用于修改,没有setter方法,无法修改计算属性
![Honeycam 2021-07-23 20-36-17](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-23 20-36-17.gif)
Watch
基本使用
监听输入的内容.html
监听器的内容
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>监听输入的内容</h2>
<input type="text" v-model="question">
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
// 侦听question的变化时, 去进行一些逻辑的处理(JavaScript, 网络请求)
question: "Hello World",
}
},
watch: {
// question侦听的data中的属性的名称
// newValue变化后的新值
// oldValue变化前的旧值
question: function(newValue, oldValue) {
console.log("新值: ", newValue, "旧值", oldValue);
this.displayInput();
}
},
methods: {
displayInput() {
console.log(`你输入的内容是:${this.question}`);
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
![Honeycam 2021-07-23 22-55-28](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-23 22-55-28.gif)
深度监听
反例_语法糖形式
无法深度监听的一个例子.html
info对象
监听的部分代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info.name}}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeInfoName">改变info.name</button>
<button @click="changeInfoNbaName">改变info.nba.name</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: { name: "why", age: 18, nba: {name: 'kobe'} }
}
},
watch: {
// 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听)
info(newInfo, oldInfo) {
console.log("newValue=", newInfo);
console.log("oldValue=", oldInfo);
}
},
methods: {
changeInfo() {
this.info = {name: "kobe"};
},
changeInfoName() {
this.info.name = "kobe";
},
changeInfoNbaName() {
this.info.nba.name = "james";
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
描述
改变整个info对象是能够监听到的,
改变info.name无法监听到,
改变info.nba.name无法监听到.
![Honeycam 2021-07-24 06-54-22](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-24 06-54-22.gif)
反例_全写形式
无法深度监听的例子2.html
监听部分的代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info.name}}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeInfoName">改变info.name</button>
<button @click="changeInfoNbaName">改变info.nba.name</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: { name: "why", age: 18, nba: {age: 18} }
}
},
watch: {
info: {
handler: function(newInfo, oldInfo) {
console.log(newInfo);
console.log(oldInfo);
},
}
},
methods: {
changeInfo() {
this.info = {name: "kobe"};
},
changeInfoName() {
this.info.name = "kobe";
},
changeInfoNbaName() {
this.info.nba.age = 20;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
正例
能够深度监听的例子.html
关键代码:开启深度监听
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info.name}}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeInfoName">改变info.name</button>
<button @click="changeInfoNbaName">改变info.nba.name</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: { name: "why", age: 18, nba: {age: 18} }
}
},
watch: {
info: {
handler: function(newInfo, oldInfo) {
console.log('newInfo VVV');
console.log(newInfo);
console.log('oldInfor VVV');
console.log(oldInfo);
},
deep:true
}
},
methods: {
changeInfo() {
this.info = {name: "kobe"};
},
changeInfoName() {
this.info.name = "kobe";
},
changeInfoNbaName() {
this.info.nba.age = 20;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
改变info.nba.Age的效果
现在的情况是可以监听到这个info里面对象的变化,但是监听不到里面具体内容的变化,只能检测到改变之后的内容.
![Honeycam 2021-07-24 09-12-32](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-24 09-12-32.gif)
改变info.name
虽然info.name被改变了,mustache中的内容也改变了,但是这个watch只能检测到info.name的变化,无法检测到内容的变化,无法知道以前的info.name的值
![Honeycam 2021-07-24 09-17-12](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-24 09-17-12.gif)
改吗info
改变info可以检测到变化,也可以检测其新旧的内容,只有当整个对象发生变化的时候,才能够检测到其内容.
![](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-24 09-22-18.gif)
![](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-24 07-13-40.gif)
立即执行
有时候我们希望这个watch在启动的时候就是自动监听一次.
立即监听的例子.html
核心代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info.name}}</h2>
<button @click="changeInfo">改变info</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: { name: "why", age: 18, nba: {name: 'kobe'} }
}
},
watch: {
// 深度侦听/立即执行(一定会执行一次)
info: {
handler: function(newInfo, oldInfo) {
console.log('newInfo VVV');
console.log(newInfo);
console.log('oldInfo VVV');
console.log(oldInfo);
},
deep: true, // 深度侦听
immediate: true // 立即执行
}
},
methods: {
changeInfo() {
this.info = {name: "kobe"};
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
描述
刷新一下立马执行一次,也就是相当于有个初始化执行.
![Honeycam 2021-07-24 09-35-14](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-24 09-35-14.gif)
针对对象中某个属性的监听
针对info.name的监听.html
针对info.name的监听
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info}}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeInfoName">改变info.name</button>
<button @click="changeInfoNbaAge">改变info.age</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: { name: "why", age: 18 }
}
},
watch: {
"info.name": function(newName, oldName) {
console.log('newName='+newName,' oldName='+oldName);
},
},
methods: {
changeInfo() {
this.info = {name: "kobe"};
},
changeInfoName() {
this.info.name = "kobe";
},
changeInfoNbaAge() {
this.info.age = 100
},
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
描述
- 改变info: 有效果,因为整个对象都发生了变化,所以里面的name属性当然也发生了变化,所以能够检测到
- 改变info.name:很显然有效果
- 改变info.age: 发现虽然年龄由18-->100发生了变化,但是没有检测到
![Honeycam 2021-07-24 20-34-41](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-24 20-34-41.gif)
create()创建监听器
在create声明周期函数里面创建监听器.html
关键代码
this.$watch()的三个参数
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info.name}}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeInfoName">改变info.name</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: { name: "why", age: 18 },
}
},
methods: {
changeInfo() {
this.info = {name: "kobe" , age:100};
},
changeInfoName() {
this.info.name = "james";
},
},
created() {
this.$watch("info", function(newInfo, oldInfo) {
console.log('newInfo VVV');
console.log(newInfo);
console.log('oldInfo VVV');
console.log(oldInfo);
}, {
deep: true,
immediate: true
})
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
综合案例_书籍购物车
按钮禁用
按钮禁用
<button v-bind:disabled='book.count<=1'>-</button>
![](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-27 09-48-17.gif)
问题_多个tbody标签
问题
多个tbody标签
没有tr标签
v-for直接写在tbody里面了
解决
增加tr标签,v-for写在tr标签里面
问题_有时能完全删除,有时不能
问题
问题_有时能够完全删除,有时不能删除
![Honeycam 2021-07-27 10-19-00](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-27 10-19-00.gif)
问题_代码部分问题
__因为我传递的是数组中的book的id,而当我删除这个id的时候,这个id是固定不变的,比如说当我删除最后一个的时候,id=4,然后现在数组中只有一个元素,我传递过来的id等于4,即使id-1=3,也没有下标从三开始的元素,所以无法删除.
答案
解决_传递变化的index来解决问题
输出效果
![Honeycam 2021-07-27 10-28-26](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-27 10-28-26.gif)
最终
index.html
增加数量
减少数量
移出书籍
总价格
<!DOCTYPE html>
<html lang="zh">
<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">
<link rel="stylesheet" href="style.css">
<title>书籍购物车</title>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<table>
<thead>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</thead>
<tbody>
<tr v-for="(book, index) in books" :key="book.id">
<td>{{index+1}}</td>
<td>{{book.name}}</td>
<td>{{book.date}}</td>
<td>{{formatPrice(book.price)}}</td>
<td>
<button
@click='decrease($event,book.id)'
v-bind:disabled='book.count<=1'>-</button>
{{book.count}}
<button @click='increase($event,book.id)'>+</button>
</td>
<td>
<button @click='remove($event,index)'>移除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价为:{{formatPrice(totalPrice)}}</h2>
</template>
<script src="../js/vue.js"></script>
<script src="./index.js"></script>
</body>
</html>
index.js
Vue.createApp({
template:'#my-app',
data() {
return {
books: [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
}
]
}
},
computed:{
//总价格使用计算属性
totalPrice(){
let totalPrice = 0
for(let book of this.books){
totalPrice += book.count*book.price
}
return totalPrice
}
},
methods: {
//增加数量
increase(event,id){
this.books[id-1].count++
},
//减少数量
decrease(event,id){
this.books[id-1].count--
},
//移出书籍
remove(event,id){
console.log(id);
this.books.splice(id,1)
},
//用来给代码加上rmb符号
formatPrice(price) {
return "¥" + price;
}
},
}).mount('#app')
index.css
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th, td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
.counter {
margin: 0 5px;
}
输出效果
![Honeycam 2021-07-27 12-05-16](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-27 12-05-16.gif)
v-model
原始方法实现v-model
原始方法实现v-model.html
h2中的数据和input显示的数据绑定
监听input输入事件
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 1.v-bind value的绑定 2.监听input事件, 更新message的值 -->
<input type="text" :value="message" @input="inputChange">
<h2>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
},
methods: {
inputChange(event) {
this.message = event.target.value;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
![Honeycam 2021-07-27 19-11-44](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-27 19-11-44.gif)
v-model语法糖实现
v-model本质上是上面方法的语法糖.
v-model语法糖实现数据双向绑定.html
message数据的双向绑定
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<input type="text" v-model="message">
<h2>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
绑定基本组件
绑定textarea
v-model绑定textarea.html
关键代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 1.绑定textarea -->
<label for="intro">
自我介绍<br>
<textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea>
</label>
<h2>intro: {{intro}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
intro: "Hello World",
}
},
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
绑定单选框
绑定单选框.html
关键代码
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 2.checkbox -->
<!-- 2.1.单选框 -->
<label for="agree">
<input id="agree" type="checkbox" v-model="isAgree"> 同意协议
</label>
<h2>isAgree: {{isAgree}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
isAgree: false,
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
绑定多选框
绑定多选框.html
多选框
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- 多选框 -->
<span>你的爱好: </span>
<label for="basketball">
<input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
</label>
<label for="football">
<input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
</label>
<label for="tennis">
<input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
</label>
<h2>hobbies: {{hobbies}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
hobbies: ["basketball"],
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
绑定单选按钮
绑定单选按钮.html
绑定单选按钮
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- radio -->
<span>你的爱好: </span>
<label for="male">
<input id="male" type="radio" v-model="gender" value="male">男
</label>
<label for="female">
<input id="female" type="radio" v-model="gender" value="female">女
</label>
<h2>gender: {{gender}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
gender: "",
}
},
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
绑定下拉框
绑定下拉框.html
下拉框
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<!-- select -->
<span>喜欢的水果: </span>
<select v-model="fruit" multiple size="2">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>fruit: {{fruit}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
fruit: "orange"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
修饰符
lazy修饰符
lazy修饰符修饰的输入框.html
lazy模式
普通模式和lazy模式的对比
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
普通模式<input type="text" v-model="message1">
<h2>{{message1}}</h2>
<hr>
lazy模式<input type="text" v-model.lazy="message2">
<h2>{{message2}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message1: "Hello World",
message2: "Hello World"
}
},
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
普通模式
实时输出.
lazy模式
先输入,只有按下enter键后,输入的内容才会显示其上.
![Honeycam 2021-07-28 17-22-42](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-28 17-22-42.gif)
number修饰符
number修饰符.html
对比
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>普通的没有number修饰符</h2>
<input type="text" v-model="message1">
<h2>{{message1}}</h2>
<button @click="showType1">查看类型</button>
<hr>
<h2>有number修饰符</h2>
<input type="text" v-model.number="message2">
<h2>{{message2}}</h2>
<button @click="showType2">查看类型</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message1: "",
message2: ""
}
},
methods: {
showType1() {
console.log(this.message1, typeof this.message1);
},
showType2() {
console.log(this.message2, typeof this.message2);
},
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出效果
![Honeycam 2021-07-28 17-35-25](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-28 17-35-25.gif)
trim修饰符
trim修饰符能够去掉输入字符串前面和后面的空格.
trim修饰符.html
有trim和没有trim的对比
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<h2>普通模式,没有trim修饰符</h2>
<input type="text" v-model="message1">
<button @click="showResult1">查看结果</button>
<hr>
<h2>trim模式,有trim修饰符</h2>
<input type="text" v-model.trim="message2">
<button @click="showResult2">查看结果</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message1: "",
message2: ""
}
},
methods: {
showResult1() {
console.log(this.message1);
},
showResult2() {
console.log(this.message2);
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
输出
![Honeycam 2021-07-28 17-51-47](..\ad_images\vu_vue3.0LearnNote.assets\Honeycam 2021-07-28 17-51-47.gif)
组件化开发
全局组件
注册一个全局组件.html
组件的注册逻辑
从template中拿到id=component-a的组件内容,然后注册名为component-a的组件
自定义组件的使用
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<component-a></component-a>
</template>
<template id="component-a">
<h2>{{title}}</h2>
<button @click="btnClick">按钮点击</button>
</template>
<script src="../js/vue.js"></script>
<script>
const app = Vue.createApp({
template: "#my-app",
});
// 使用app注册一个全局组件app.component()
// 全局组件: 意味着注册的这个组件可以在任何的组件模板中使用
app.component("component-a", {
template: "#component-a",
data() {
return {
title: "我是标题",
}
},
methods: {
btnClick() {
alert('clicked!')
},
},
});
app.mount("#app");
</script>
</body>
</html>
多个组件
多个全局组件注册.html
两个组件的注册逻辑
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<component-a></component-a>
<hr>
<component-b></component-b>
</template>
<template id="component-a">
<h1>组件一号</h1>
<h2>{{title}}</h2>
<p>{{desc}}</p>
<button @click="btnClick">按钮点击</button>
</template>
<template id="component-b">
<h1>组件二号</h1>
<input type="text" v-model="message"/>
<h2>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
};
const app = Vue.createApp(App);
// 使用app注册一个全局组件app.component()
app.component("component-a", {
template: "#component-a",
data() {
return {
title: "我是标题",
desc: "我是内容, 哈哈哈哈哈",
};
},
methods: {
btnClick() {
console.log("按钮的点击");
},
},
});
app.component("component-b", {
template: "#component-b",
data() {
return {
message: "Hello World",
};
},
});
app.mount("#app");
</script>
</body>
</html>
输出
组件的命名
组件命名方法.html
注册组件大驼峰,引用组件下划线
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<component-name></component-name>
</template>
<template id="component-c">
<h2>ComponentC</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
};
const app = Vue.createApp(App);
// 使用app注册一个全局组件app.component()
app.component('ComponentName', {
template: "#component-c"
})
app.mount("#app");
</script>
</body>
</html>
局部组件
局部组件.html
局部组件的注册逻辑
全局组件和局部组件对比
<!DOCTYPE html>
<html lang="zh">
<body>
<div id="app"></div>
<template id="my-app">
<component-a></component-a>
</template>
<template id="component-a">
<h2>我是组件A</h2>
</template>
<script src="../js/vue.js"></script>
<script>
//组件A的对象内容引入
const ComponentA = {
template: "#component-a"
}
const App = {
template: '#my-app',
components: {
// key: 组件名称 value: 组件对象
ComponentA: ComponentA
},
data() {
return {
message: "Hello World"
}
}
}
const app = Vue.createApp(App);
app.mount('#app');
</script>
</body>
</html>
基于Vue CLI组件化开发
关于Vue CLI的使用,在webpack的学习笔记中有详细的创建过程.
使用Vue CLI创建项目
命令行
vue create 03_learn_component_2
输出
自此一个使用vue脚手架的创建的就是已经创建好了
创建一个总的组件
为了便于学习,不需要每次都是创建一个新的项目,我们现在把这个src
文件夹的文件除了main.js
,其他的文件都是删除,然后新建一个文件夹,用来存放第一个组件.
安装下面这款插件:
在App.vue中输入vbase之后,会自动生成如下代码:
App.vue
<template>
<div id="app">
<div class="myheader">
<h2>Header</h2>
<h2>Navebar</h2>
</div>
<div class="main">
<h2>Banner</h2>
<ul>
<li>product info 1</li>
<li>product info 2</li>
<li>product info 3</li>
<li>product info 4</li>
<li>product info 5</li>
</ul>
</div>
<div class="footer">
<h2>Footer</h2>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
main.js
import { createApp } from 'vue'
import App from './01_组件的拆分和嵌套/App.vue'
createApp(App).mount('#app')
输出
组件拆分
在分别创建另外三个组件MyHeader.vue
MyMain.vue
MyFooter.vue
,然后将App.vue
中组件分别拆分到三个组件当中.
拆分过后需要在App.vue中引入其他组件:
App.vue中引入其他组件
三个步骤
导入,注册和使用
<template>
<div id="app">
<my-header></my-header>
<my-main></my-main>
<my-footer></my-footer>
</div>
</template>
<script>
import MyHeader from './MyHeader.vue'
import MyMain from './MyMain.vue'
import MyFooter from './MyFooter.vue'
export default {
components:{
MyHeader,
MyMain,
MyFooter
}
}
</script>
<style scoped>
</style>
输出
进一步组件拆分
将MyMain.vue
进一步拆分成两个组件
MyMain.vue
组件注册错误
<template>
<div class="main">
<my-main-banner></my-main-banner>
<my-main-product-list></my-main-product-list>
</div>
</template>
<script>
import MyMainBanner from "./MyMainBanner.vue";
import MyMainProductList from "./MyMainProductList.vue";
export default {
components: {
MyMainBanner,
MyMainProductList,
},
};
</script>
<style lang="scss" scoped>
</style>
输出OK了
组件的CSS作用域
App.vue
<template>
<div>
<h2>this is App.vue</h2>
<hello-vue></hello-vue>
</div>
</template>
<script>
import HelloVue from "./HelloVue.vue";
export default {
components: {
HelloVue,
},
};
</script>
<style scoped>
h2{
color: green;
}
</style>
HelloVue.vue
<template>
<h2>this HelloVue.vue</h2>
</template>
<script>
export default {
}
</script>
<style scoped>
/* h2{
color:red
} */
</style>
输出
去掉HelloVue.vue的注释
<template>
<h2>this HelloVue.vue</h2>
</template>
<script>
export default {
}
</script>
<style scoped>
h2{
color:red
}
</style>
输出
紫色被注释掉了
去掉去掉App.vue的style标签的scoped
<template>
<div>
<h2>this is App.vue</h2>
<hello-vue></hello-vue>
</div>
</template>
<script>
import HelloVue from "./HelloVue.vue";
export default {
components: {
HelloVue,
},
};
</script>
<style >
h2{
color: green;
}
</style>
输出
达到了期望的效果
总结
上面的实验说命令,这个当这个子组件没有自己的样式时候,父组件的样式会作用于子组件.我们希望这个父组件的样式就是仅仅作用域父组件,而作用域子组件,一般就是:尽量不适用html标签来作用样式,使用类名,实际开发就是使用的类
组件通信
组件通信中使用最为广泛的就是父子组件间的通信:
父传子
简而言之
子组件注册属性,然后父组件使用属性,也就是给这些属性赋值.
属性形式_字符串数组
逻辑图
子组件注册属性:ShowMsg.vue
<template>
<div>
<h2>{{name}} : {{age}}</h2>
</div>
</template>
<script>
export default {
props:['name','age']
}
</script>
<style lang="scss" scoped>
</style>
父组件给属性赋值:App.vue
<template>
<div>
<show-msg name="zhuo" age=10></show-msg>
</div>
</template>
<script>
import ShowMsg from './ShowMsg.vue'
export default {
components:{
ShowMsg
}
}
</script>
<style scoped>
</style>
输出
除了直接赋值的方式也可以使用v-bind动态绑定属性:
属性形式_对象
ShowMsg.vue
<template>
<div>
<h2>{{name}} : {{age}}</h2>
</div>
</template>
<script>
export default {
props:{
name:String,
age:Number
}
}
</script>
<style lang="scss" scoped>
</style>
App.vue
<template>
<div>
<show-msg name="zhuo" age=10></show-msg>
<show-msg :name="name" :age=age></show-msg>
</div>
</template>
<script>
import ShowMsg from './ShowMsg.vue'
export default {
components:{
ShowMsg
},
data() {
return {
name:'bing',
age:12
}
},
}
</script>
<style scoped>
</style>
输出
required属性
default属性
发现没传给这个age属性赋值,于是就使用默认值.
其他写法
为什么对象的默认值必须通过一个工厂函数获取?
非Prop的Attribute
单根结点
传递一个没有定义的属性怎么样
如何把属性绑定到目标的标签上面
如何去掉这个子组件根元素的属性
多根结点
子传父
注册事件_数组形式
计数器案例_无参数传递
代码逻辑
父组件App.vue
<template>
<div>
<h2>current number:{{counter}}</h2>
<counter-operation @add="addOne" @sub="subOne"></counter-operation>
</div>
</template>
<script>
import CounterOperation from './CounterOperation.vue'
export default {
components:{
CounterOperation
},
data() {
return {
counter:0
}
},
methods: {
addOne(){
this.counter++
},
subOne(){
this.counter--
}
},
}
</script>
<style scoped>
</style>
子组件:CounterOperation.vue
<template>
<div>
<button @click="addOne">+1</button>
<button @click="subOne">-1</button>
</div>
</template>
<script>
export default {
emits:['add','sub'],
methods: {
addOne(){
console.log("+1");
this.$emit('add')
},
subOne(){
console.log("-1");
this.$emit('sub')
}
}
}
</script>
<style lang="scss" scoped>
</style>
输出
计数器案例_传递参数
代码逻辑
父组件App.vue
<template>
<div>
<h2>current number:{{counter}}</h2>
<counter-operation
@add="addOne"
@sub="subOne"
@addN="addN">
</counter-operation>
</div>
</template>
<script>
import CounterOperation from './CounterOperation.vue'
export default {
components:{
CounterOperation
},
data() {
return {
counter:0
}
},
methods: {
addOne(){
this.counter++
},
subOne(){
this.counter--
},
addN(num){
console.log(num);
this.counter += num
}
},
}
</script>
<style scoped>
</style>
子组件CounterOperation.vue
<template>
<div>
<h2>current number:{{counter}}</h2>
<counter-operation
@add="addOne"
@sub="subOne"
@addN="addN">
</counter-operation>
</div>
</template>
<script>
import CounterOperation from './CounterOperation.vue'
export default {
components:{
CounterOperation
},
data() {
return {
counter:0
}
},
methods: {
addOne(){
this.counter++
},
subOne(){
this.counter--
},
addN(num){
console.log(num);
this.counter += num
}
},
}
</script>
<style scoped>
</style>
输出效果
注册事件_对象形式
上面的例子中都是使用数组形式>来注册发送给父组件的事件,其实也是可以对象的形式>,对象的形式常用来进行参数检查.
子组件CounterOperation.vue
对象形式注册事件
主要是为了检查参数
<template>
<div>
<button @click="addOne">+1</button>
<button @click="subOne">-1</button>
<input type="number" v-model.number="num">
<button @click="emitAddN">+N</button>
</div>
</template>
<script>
export default {
// emits:['add','sub','addN'],
emits:{
add:null,
sum:null,
addN:num =>{
console.log(num);
if(num > 10){
return true
}else{
return false
}
}
},
data() {
return {
num:0
}
},
methods: {
addOne(){
console.log("+1");
this.$emit('add')
},
subOne(){
console.log("-1");
this.$emit('sub')
},
emitAddN(){
console.log("+n");
this.$emit('addN',this.num)
}
}
}
</script>
<style lang="scss" scoped>
</style>
输出效果
当我们传递过去的参数小于10的时候就会出现警告,只有大于10的时候,才能够正常运行
![Honeycam 2021-09-01 11-43-26](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-01 11-43-26.gif)
组件通信案例
输出效果
![Honeycam 2021-09-01 13-09-10](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-01 13-09-10.gif)
代码逻辑
父组件:App.vue
<template>
<div>
<tab-control :titles="titles"></tab-control>
</div>
</template>
<script>
import TabControl from './TabControl.vue'
export default {
components:{
TabControl
},
data() {
return {
titles:['衣服','鞋子','帽子']
}
}
}
</script>
<style scoped>
</style>
子组件:TabControl.vue
<template>
<div class="tab-control">
<div class="tab-control-item"
:class="{active:currentIndex === index}"
v-for='(title,index) in titles'
:key="title"
@click="itemClick(index)">
<span>{{title}}</span>
</div>
</div>
</template>
<script>
export default {
data(){
return{
currentIndex:0
}
},
props:{
titles:{
type:Array,
default(){
return ['title1','title2','title3']
}
}
},
methods: {
itemClick(index){
this.currentIndex = index
}
},
}
</script>
<style scoped>
.tab-control {
display: flex;
}
.tab-control-item {
flex: 1;
text-align: center;
}
.tab-control-item.active {
color: red;
}
.tab-control-item.active span {
border-bottom:5px red solid;
padding: 5px 10px;
}
</style>
非父子组件之间的通信
Provide/Inject
背景
代码逻辑:爷爷和孙子
通信
父组件:App.vue
<template>
<div>
<home></home>
</div>
</template>
<script>
import Home from './Home.vue'
export default {
components:{
Home
},
provide:{
name:'bing',
age:100
}
}
</script>
<style lang="scss" scoped>
</style>
儿子组件:Home.vue
<template>
<div>
<home-content></home-content>
</div>
</template>
<script>
import HomeContent from "./HomeContent.vue"
export default {
components:{
HomeContent
}
}
</script>
<style lang="scss" scoped>
</style>
孙子组件:HomeContent.vue
<template>
<div>
this is homeContent--{{name}}--{{age}}
</div>
</template>
<script>
export default {
inject:['name','age']
}
</script>
<style lang="scss" scoped>
</style>
长辈组件和子孙组件间的通信逻辑图
App.vue
<template>
<div>
<home></home>
</div>
</template>
<script>
import Home from './Home.vue'
export default {
components:{
Home
},
provide:{
name:'bing',
age:100
}
}
</script>
<style lang="scss" scoped>
</style>
HomeContent.vue
<template>
<div>
this is homeContent--{{name}}--{{age}}
</div>
</template>
<script>
export default {
inject:['name','age']
}
</script>
<style lang="scss" scoped>
</style>
问题_长辈组件在自己中是否使用provide中的数据
很显然是不能使用
问题_长辈组件如何拿到自己data中的数据信息然后传给子孙组件
问题背景
很显然,没有拿到.
原因分析
this的指向很是关键,我们知道在一个函数中,this的指向就是这个函数作用域,,这里的this就是foo()作用域里面的this.
而,中this也不是指向这个provide这个对象,他的this应该继续向上找,在这里,在这个script标签中,即,而这个this是没有定义的,所以报错
解决方案
把provide写成函数形式,就是把这个作用域现在这个函数里面.
App.vue
<template>
<div>
<home></home>
</div>
</template>
<script>
import Home from './Home.vue'
console.log(this);
export default {
components:{
Home
},
// provide:{
// name:'bing',
// age:100,
// namesLength:this.names.length
// },
provide(){
return {
name:'bing',
age:100,
namesLength:this.names.length
}
},
data(){
return{
names:['Alice','Bruce','Celina']
}
}
}
</script>
<style lang="scss" scoped>
</style>
子孙组件:HomeContent.vue
<template>
<div>
this is homeContent--{{name}}--{{age}}--{{namesLength}}
</div>
</template>
<script>
export default {
inject:['name','age','namesLength']
}
</script>
<style lang="scss" scoped>
</style>
输出
问题_长辈组件传给子孙组件的是动态数据吗
上面的例子中,尽管我们给这个数组增加了内容,数组的长度发生了变化,但是这个传递被子孙组件的数据依然没有改变,其实很好理解,这个就是在第一次就是当成一个普通的值赋了过去.
解决方案_动态传递数据
使用计算属性.
输出效果
数组的长度增加,这个传递给子孙组件的数据长度也是增加
![Honeycam 2021-09-01 16-14-40](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-01 16-14-40.gif)
长辈组件:App.vue
关键代码
<template>
<div>
<home></home>
<button @click="addName">array push</button>
</div>
</template>
<script>
import Home from './Home.vue'
import {computed} from 'vue'
export default {
components:{
Home
},
// provide:{
// name:'bing',
// age:100,
// namesLength:this.names.length
// },
provide(){
return {
name:'bing',
age:100,
namesLength:computed(()=>this.names.length)
}
},
data(){
return{
names:['Alice','Bruce','Celina']
}
},
methods: {
addName(){
console.log(this.names);
this.names.push('bing')
}
},
}
</script>
<style lang="scss" scoped>
</style>
警告
injected property "namesLength" is a ref
and will be auto-unwrapped and no longer needs `.value` in the next minor release.
To opt-in to the new behavior now, set `app.config.unwrapInjectedRef = true`
(this config is temporary and will not be needed in the future.)
警告的原因:
子孙组件:HomeContent.vue
<template>
<div>
this is homeContent--{{name}}--{{age}}--{{namesLength}} //警告出现,提示会自动解包
this is homeContent--{{name}}--{{age}}--{{namesLength.value}}//手动解包
</div>
</template>
<script>
export default {
inject:['name','age','namesLength']
}
</script>
<style lang="scss" scoped>
</style>
Mitt全局事件总线
https://github.com/developit/mitt
https://github.com/scottcorgan/tiny-emitter
安装mitt库
npm install mitt -D
背景
现在我想要在[]中监听[]的事件
代码逻辑
输出效果
About.vue
<template>
<div>
this is about.vue
<button @click="btnClick">about click</button>
</div>
</template>
<script>
import emitter from './utils/eventBus'
export default {
methods: {
btnClick(){
console.log('About.vue is clicked')
emitter.emit('aboutClicked',{name:'bing',age:20})
}
},
}
</script>
<style lang="scss" scoped>
</style>
HomeContent.vue
<template>
<div>
this is home-content
<hr>
</div>
</template>
<script>
import emitter from './utils/eventBus'
export default {
created() {
emitter.on('aboutClicked',(info)=>{
console.log(info);
})
},
}
</script>
<style lang="scss" scoped>
</style>
多事件监听
About.vue
发送两个事件
<template>
<div>
this is about.vue
<button @click="btnClick">about click</button>
<button @click="btnClick1">about click</button>
</div>
</template>
<script>
import emitter from './utils/eventBus'
export default {
methods: {
btnClick(){
emitter.emit('aboutClicked',{name:'bing',age:20})
},
btnClick1(){
emitter.emit('aboutClicked1',{name:'bing1',age:201})
}
},
}
</script>
<style lang="scss" scoped>
</style>
HomeContent.vue
监听所有事件
打印事件名称和传递过来的参数
<template>
<div>
this is home-content
<hr>
</div>
</template>
<script>
import emitter from './utils/eventBus'
export default {
created() {
// emitter.on('aboutClicked',(info)=>{
// console.log(info);
// })
emitter.on('*',(eventName,info) => {
console.log(eventName);
console.log(info);
})
},
}
</script>
<style lang="scss" scoped>
</style>
输出效果
![Honeycam 2021-09-01 20-16-40](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-01 20-16-40.gif)
事件取消
插槽
比如说上面的NavBar的共性就是都是具有三个部分,左中右,不同就是左中右三个区域可以显示三个不同的内容.
插槽的基本使用
代码逻辑
有插入的内容显示插入的内容,没有插入的内容显示默认的内容
App.vue
<template>
<div>
<my-slot-cpn>
<h4>我是插入插槽中的内容</h4>
</my-slot-cpn>
<my-slot-cpn>
<button>我是插入插槽中的按钮</button>
</my-slot-cpn>
<my-slot-cpn></my-slot-cpn>
</div>
</template>
<script>
import MySlotCpn from './MySlotCpn.vue'
export default {
components:{
MySlotCpn
}
}
</script>
<style lang="scss" scoped>
</style>
MySlotCpn.vue
<template>
<div>
<h3>组件开始VVVVVV</h3>
<slot>我是插槽中的默认内容</slot>
<h3>组件结束YYYYYY</h3>
<hr>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
多个插槽
一个内容插入多个插槽
我把一个元素插入多个插槽,发现是一个元素每个插槽都会被插入一次.
三个元素插入三个插槽
我们三个元素插入三个插槽,是这个三个元素作为一个整体插入三个插槽,因此显示了9个,我们其实有点希望三个分别插入三个插槽,这个需要下面的具名插槽来实现.
具名插槽
代码逻辑
通过名字来达到分别填入的效果
App.vue
<template>
<div>
<my-slot-cpn>
<template v-slot:slot1>
<h6>我是插入 第一个 插槽中的内容</h6>
</template>
<template v-slot:slot2>
<button>我是想插入 第二个 插槽中的按钮</button>
</template>
<template v-slot:slot3>
<h5>我是想插入 第三个 插槽的内容</h5>
</template>
</my-slot-cpn>
<my-slot-cpn></my-slot-cpn>
</div>
</template>
<script>
import MySlotCpn from './MySlotCpn.vue'
export default {
components:{
MySlotCpn
}
}
</script>
<style lang="scss" scoped>
</style>
MySlotCpn.vue
<template>
<div>
<h3>组件开始VVVVVV</h3>
<slot name="slot1"><h5>我是插槽中的默认内容1</h5></slot>
<slot name="slot2"><h5>我是插槽中的默认内容2</h5></slot>
<slot name="slot3"><h5>我是插槽中的默认内容3</h5></slot>
<h3>组件结束YYYYYY</h3>
<hr>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
动态具名插槽
代码逻辑
先把三个插槽的名字通过父传子的形式传递过去,然后再使用时候使用其对应的名字就是可以了.
具名插槽的缩写
渲染作用域
作用域插槽
代码逻辑
App.vue
<template>
<div>
<show-name :names="names"></show-name>
<hr>
**v-slot="slotProps" and 默认插槽的省略写法,省略template**
<show-name-slot :names="names" v-slot="slotProps">
<button>{{slotProps.index}}-{{slotProps.item}}</button>
</show-name-slot>
<hr>
**v-slot="slotProps 默认插槽的省略写法"**
<show-name-slot :names="names" >
<template v-slot="slotProps">
<button>{{slotProps.index}}-{{slotProps.item}}</button>
</template>
</show-name-slot>
<hr>
**v-slot:default="slotProps" 默认插槽的使用**
<show-name-slot :names="names" >
<template v-slot:default="slotProps">
<button>{{slotProps.index}}-{{slotProps.item}}</button>
</template>
</show-name-slot>
<hr>
**v-slot:juming="slotProps" 具名插槽的使用**
<show-name-slot :names="names" >
<template v-slot:juming="slotProps">
<button>{{slotProps.index}}-{{slotProps.item}}</button>
</template>
</show-name-slot>
</div>
</template>
<script>
import ShowName from './ShowName.vue'
import ShowNameSlot from './ShowNameSlot.vue'
export default {
components:{
ShowName,
ShowNameSlot
},
data() {
return {
names:['Alice','Bruce','Celina','Dora']
}
},
}
</script>
<style scoped>
</style>
ShowName.vue
<template>
<div>
<ul>
<li
v-for="(name,index) in names"
:key="index">
{{index}}--{{name}}</li>
</ul>
</div>
</template>
<script>
export default {
props:{
names:{
type:Array,
default:()=>[]
}
}
}
</script>
<style scoped>
</style>
ShowNameSlot.vue
<template>
<div>
<template v-for="(item,index) in names">
<slot :item='item' :index='index'>
</slot>
<slot name='juming' :item='item' :index='index'></slot>
</template>
</div>
</template>
<script>
export default {
props:{
names:{
type:Array,
default:()=>[]
},
}
}
</script>
<style scoped>
</style>
总结
动态组件
按钮切换案例
输出效果
![Honeycam 2021-09-02 17-53-01](C:/Users/zhuoss/Pictures/gif/Honeycam 2021-09-02 17-53-01.gif)
App.vue
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{active:currentTab === tab}"
@click="btnClick(tab)">
{{tab}}
</button>
</div>
</template>
<script>
export default {
data() {
return {
tabs:['home','about','category'],
currentTab:'home'
}
},
methods: {
btnClick(tab){
this.currentTab = tab
}
},
}
</script>
<style scoped>
.active {
color: red;
}
</style>
按钮切换案例-动态组件
输出效果
![Honeycam 2021-09-02 18-02-30](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 18-02-30.gif)
代码逻辑图
App.vue
关键代码
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{active:currentTab === tab}"
@click="btnClick(tab)">
{{tab}}
</button>
<component :is="currentTab"></component>
</div>
</template>
<script>
import Home from './Home.vue'
import About from './About.vue'
import Category from './Category.vue'
export default {
components:{
Home,
About,
Category
},
data() {
return {
tabs:['home','about','category'],
currentTab:'home'
}
},
methods: {
btnClick(tab){
this.currentTab = tab
}
},
}
</script>
<style scoped>
.active {
color: red;
}
</style>
Home.vue
<template>
<div>
<h3>Home.vue</h3>
</div>
</template>
<script>
export default {
name:'home'
}
</script>
<style scoped>
</style>
About.vue
<template>
<div>
<h3>About.vue</h3>
</div>
</template>
<script>
export default {
name:'about'
}
</script>
<style scoped>
</style>
Category.vue
<template>
<div>
<h3>Category.vue</h3>
</div>
</template>
<script>
export default {
name:'category'
}
</script>
<style scoped>
</style>
动态组件传递参数&发送事件
参数传递
输出效果
![Honeycam 2021-09-02 19-17-44](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 19-17-44.gif)
代码逻辑图
App.vue
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{active:currentTab === tab}"
@click="btnClick(tab)">
{{tab}}
</button>
<component :is="currentTab"
name='bing'
:age='13'>
</component>
<hr>
<home name='bingbing' :age='12'></home>
</div>
</template>
<script>
import Home from './Home.vue'
import About from './About.vue'
import Category from './Category.vue'
export default {
components:{
Home,
About,
Category
},
data() {
return {
tabs:['home','about','category'],
currentTab:'home'
}
},
methods: {
btnClick(tab){
this.currentTab = tab
}
},
}
</script>
<style scoped>
.active {
color: red;
}
</style>
Home.vue
<template>
<div>
<h3>Home.vue</h3>
<h4>{{name}}--{{age}}</h4>
</div>
</template>
<script>
export default {
name:'home',
props:{
name:{
type:String,
default:''
},
age:{
type:Number,
default:0
}
}
}
</script>
<style scoped>
</style>
如何传递的参数由默认的字符串类型转换成数字类型
监听事件
和上面的参数传递一样,都是写在component的属性里面.
App.vue
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{active:currentTab === tab}"
@click="btnClick(tab)">
{{tab}}
</button>
<component :is="currentTab"
name='bing'
:age='13'
@homeClick='homeClick'>
</component>
<hr>
<home name='bingbing' :age='12'></home>
</div>
</template>
<script>
import Home from './Home.vue'
import About from './About.vue'
import Category from './Category.vue'
export default {
components:{
Home,
About,
Category
},
data() {
return {
tabs:['home','about','category'],
currentTab:'home'
}
},
methods: {
btnClick(tab){
this.currentTab = tab
},
homeClick(){
console.log('home.vue is clicked');
}
},
}
</script>
<style scoped>
.active {
color: red;
}
</style>
Home.vue
<template>
<div @click="divClick">
<h3>Home.vue</h3>
<h4>{{name}}--{{age}}</h4>
</div>
</template>
<script>
export default {
name:'home',
props:{
name:{
type:String,
default:''
},
age:{
type:Number,
default:0
}
},
emits:['homeClick'],
methods: {
divClick(){
this.$emit('homeClick')
}
},
}
</script>
<style scoped>
</style>
keep-alive(缓存组件)
背景案例
在About页面增加一个按钮计数器.
输出效果
现在我们在About页面添加按钮计数器,然后切换页面我们发现这个计数的数值就是清零了.清零的原因是这个当我们切换界面的时候,这个界面实际上被销毁了,也就是生命周期结束了.
我们其实有点希望这个数值就是保存下来.
![Honeycam 2021-09-02 19-36-06](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 19-36-06.gif)
App.vue
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{active:currentTab === tab}"
@click="btnClick(tab)">
{{tab}}
</button>
<component :is="currentTab"
name='bing'
:age='13'
@homeClick='homeClick'>
</component>
</div>
</template>
<script>
import Home from './Home.vue'
import About from './About.vue'
import Category from './Category.vue'
export default {
components:{
Home,
About,
Category
},
data() {
return {
tabs:['home','about','category'],
currentTab:'home'
}
},
methods: {
btnClick(tab){
this.currentTab = tab
},
homeClick(){
console.log('home.vue is clicked');
}
},
}
</script>
<style scoped>
.active {
color: red;
}
</style>
About.vue
<template>
<div>
<h3>About.vue</h3>
<button @click="increase">{{counter}}</button>
</div>
</template>
<script>
export default {
name:'about',
data() {
return {
counter:0
}
},
methods: {
increase(){
this.counter ++
}
},
}
</script>
<style scoped>
</style>
基本使用案例
输出效果
现在即使是切换界面,这个计数器按钮的数值仍然能够保持不变
![Honeycam 2021-09-02 19-42-05](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 19-42-05.gif)
仅仅是增加了一行代码
App.vue
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{active:currentTab === tab}"
@click="btnClick(tab)">
{{tab}}
</button>
<keep-alive>
<component :is="currentTab"
name='bing'
:age='13'
@homeClick='homeClick'>
</component>
</keep-alive>
</div>
</template>
<script>
import Home from './Home.vue'
import About from './About.vue'
import Category from './Category.vue'
export default {
components:{
Home,
About,
Category
},
data() {
return {
tabs:['home','about','category'],
currentTab:'home'
}
},
methods: {
btnClick(tab){
this.currentTab = tab
},
homeClick(){
console.log('home.vue is clicked');
}
},
}
</script>
<style scoped>
.active {
color: red;
}
</style>
keep-alive的属性
include
输出效果
about界面的计数器是能够保持不变的,category界面的计数器不能
![Honeycam 2021-09-02 19-51-57](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 19-51-57.gif)
App.vue
关键代码
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{active:currentTab === tab}"
@click="btnClick(tab)">
{{tab}}
</button>
<keep-alive include="about">
<component :is="currentTab"
name='bing'
:age='13'
@homeClick='homeClick'>
</component>
</keep-alive>
</div>
</template>
<script>
import Home from './Home.vue'
import About from './About.vue'
import Category from './Category.vue'
export default {
components:{
Home,
About,
Category
},
data() {
return {
tabs:['home','about','category'],
currentTab:'home'
}
},
methods: {
btnClick(tab){
this.currentTab = tab
},
homeClick(){
console.log('home.vue is clicked');
}
},
}
</script>
<style scoped>
.active {
color: red;
}
</style>
Webpack的代码分包&异步组件
为什么需要分包
如何分包
默认的打包情况
现在我们想要把自己写的某些代码也是单独打包,怎么办呢?
JS中的代码分包
代码逻辑图
代码的引用逻辑
main.js
返回的是一个promise对象,然后调用then()方法,在then方法里面传入一个回调函数
import { createApp } from 'vue'
import App from './12_异步组件的使用/App.vue'
//以前的引入方式和使用方法
// import {sum} from './12_异步组件的使用/utils/math'
// console.log(sum(100,200));
//现在的引入方式和使用方法
import('./12_异步组件的使用/utils/math').then(res => {
console.log(res.sum(200,300));
})
createApp(App).mount('#app')
Vue组件中实现异步组件(代码分包)
工厂函数形式
新旧导入组件的对比
App.vue
关键代码
<template>
<div>
<home></home>
</div>
</template>
<script>
import Home from './Home.vue'
//以前的导入方式
// import AsyncCategory from './AsyncCategory.vue'
//现在的导入方式
////首先导入一个vue中的函数
import {defineAsyncComponent} from 'vue'
const AsyncCategory = defineAsyncComponent(() => import('./AsyncCategory.vue'))
export default {
components:{
Home,
AsyncCategory
}
}
</script>
<style scoped>
</style>
对象形式
其实使用对象形式主要是为了更多的属性.
导入型的对比
对象形式中常用的属性
异步组件和suspense
输出效果
![Honeycam 2021-09-02 23-07-11](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 23-07-11.gif)
App.vue
关键代码
<template>
<div>
<suspense>
<template #default>
<async-category></async-category>
</template>
<template #fallback>
<home></home>
</template>
</suspense>
</div>
</template>
<script>
import Home from './Home.vue'
//现在的导入方式-工厂函数形式
////首先导入一个vue中的函数
import {defineAsyncComponent} from 'vue'
const AsyncCategory = defineAsyncComponent(() => import('./AsyncCategory.vue'))
export default {
components:{
Home,
AsyncCategory,
}
}
</script>
<style scoped>
</style>
$refs的使用
vue中的DOM操作
不推荐使用document.getElement...方法或jQuery等等来操作Vue中的dom元素,因为vue中已经帮我们封装好了操作dom元素的函数,我在使用以前的方法,就是显得很笨拙.
获取元素
输出效果
![Honeycam 2021-09-02 23-20-37](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 23-20-37.gif)
App.vue
关键代码
<template>
<div>
<h2 ref="titleH2">我是被获取的元素</h2>
<button @click="getElement">获取自己中的元素</button>
</div>
</template>
<script>
export default {
methods: {
getElement(){
console.log(this.$refs.titleH2);
}
},
}
</script>
<style scoped>
</style>
获取组件
输出效果
![Honeycam 2021-09-02 23-43-49](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-02 23-43-49.gif)
代码的数据流
也就是说通过$ref不仅仅可以或这个组件,还可以获取这个组件里面的东西.
App.vue
<template>
<div>
<h2 ref="titleH2">我是被获取的元素</h2>
<button @click="getElement">获取自己中的元素</button>
<hr>
<home ref="homeCpn"></home>
<button @click="getCpn">获取自己中的元素</button>
</div>
</template>
<script>
import Home from './Home.vue'
export default {
components:{
Home
},
methods: {
getElement(){
console.log(this.$refs.titleH2);
},
getCpn(){
console.log(this.$refs.homeCpn);
console.log(this.$refs.homeCpn.$el);
console.log(this.$refs.homeCpn.name);
this.$refs.homeCpn.printName()
}
},
}
</script>
<style scoped>
</style>
Home.vue
<template>
<div>
<h2>我是被获取的组件</h2>
</div>
</template>
<script>
export default {
data(){
return{
name:'我是home组件中的数据'
}
},
methods: {
printName(){
console.log('执行了home组件中的函数')
}
},
}
</script>
<style scoped>
</style>
$parent
和$root
生命周期
生命周期流程
缓存组件的生命周期
首先看看缓存组件和非缓存组件的区别
这里的About是缓存组件,Category是非缓存组件,
缓存组件的只会一行一次created(),然后就什么都不执行了,
而非非缓存组件created()和unmounted()只要切换就是会执行.
我们现在希望这个非缓存组件且能够频繁执行某些生命周期函数.
![Honeycam 2021-09-03 08-18-27](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 08-18-27.gif)
期望的输出效果
![Honeycam 2021-09-03 08-33-10](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 08-33-10.gif)
About.vue
关键代码
<template>
<div>
<h3>About.vue</h3>
<button @click="increase">{{counter}}</button>
</div>
</template>
<script>
export default {
name:'about',
data() {
return {
counter:0
}
},
methods: {
increase(){
this.counter ++
}
},
created() {
console.log('about is created')
},
unmounted() {
console.log('about is unmounted')
},
activated() {
console.log('about is actived')
},
deactivated() {
console.log('about is deactived')
},
}
</script>
<style scoped>
</style>
组件的v-model
基本使用
输出效果
![Honeycam 2021-09-03 09-11-32](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 09-11-32.gif)
代码逻辑图
App.vue
<template>
<div>
<h2>普通元素的v-model</h2>
<input v-model="msg">
<!-- <input v-bind:value="msg" @input="msg = $event.target.value"> -->
<h3>{{msg}}</h3>
<!-- ------------------------------------------------------------------------------------------ -->
<hr>
<h2>组件的v-model</h2>
<my-input v-model="msg"></my-input>
<!-- 等价于 -->
<!-- <my-input :modelValue="msg" @update:modelValue="msg = $event"></my-input> -->
</div>
</template>
<script>
import MyInput from './MyInput.vue'
export default {
components:{
MyInput
},
data() {
return {
msg:''
}
},
}
</script>
<style scoped>
</style>
MyInput.vue
<template>
<div>
<input type="text" :value="modelValue" @input="sendInputEvent">
<h3>{{modelValue}}</h3>
</div>
</template>
<script>
export default {
props:{
modelValue:String
},
emits:['update:modelValue'],
methods: {
sendInputEvent(event){
this.$emit("update:modelValue", event.target.value);
}
},
}
</script>
<style scoped>
</style>
优化
上面的例子,虽然实现了组件v-model的基本使用,但是还存在一些小的问题,比如''我们这里的双向绑定,竟然绑定的是属性,这显然是不合逻辑的,因为这里面一般都是存放从父组件传递过来的数据,我们希望这些数据保持原样,子组件最好不要修改.这个就是时候我们就希望把这些数据copy一份,然后再次基础上进行操作,从而也降低了组件间的耦合性,于是就想到了计算属性.
代码逻辑
MyInput.vue
双向绑定
计算属性间接属性的值
给父组件发送触发事件
<template>
<div>
<input type="text" v-model="valueFromPar" >
<h3>{{valueFromPar}}</h3>
</div>
</template>
<script>
export default {
props:{
modelValue:String
},
emits:['update:modelValue'],
computed:{
valueFromPar:{
set(value){
this.$emit("update:modelValue", value);
},
get(){
return this.modelValue
}
}
}
}
</script>
<style scoped>
</style>
绑定多个v-model
代码逻辑图
普通标签上面不支持绑定多个v-model
![Honeycam 2021-09-03 18-01-57](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 18-01-57.gif)
App.vue
<template>
<div>
<span>msg: </span><input v-model="msg" >
<h3>{{msg}}</h3>
<span>msg2: </span><input v-model="msg2" >
<h3>{{msg2}}</h3>
<hr>
<h2>组件的v-model</h2>
<my-input v-model="msg" v-model:modelValue2="msg2"></my-input>
</div>
</template>
<script>
import MyInput from './MyInput.vue'
export default {
components:{
MyInput
},
data() {
return {
msg:'msg',
msg2:'msg2'
}
},
}
</script>
<style scoped>
</style>
MyInput.vue
<template>
<div>
<span>msg: </span><input type="text" v-model="valueFromPar" >
<h3>{{valueFromPar}}</h3>
<span>msg2: </span><input type="text" v-model="valueFromPar2" >
<h5>{{valueFromPar2}}</h5>
</div>
</template>
<script>
export default {
props:{
modelValue:String,
modelValue2:String
},
emits:['update:modelValue','update:modelValue2'],
computed:{
valueFromPar:{
set(value){
this.$emit("update:modelValue", value);
},
get(){
return this.modelValue
}
},
valueFromPar2:{
set(value){
this.$emit("update:modelValue2", value);
},
get(){
return this.modelValue2
}
}
}
}
</script>
<style scoped>
</style>
Vue3过渡&动画实现
认识动画
案例_hello world的显示和隐藏
没有动画效果
输出效果
![Honeycam 2021-09-03 21-09-26](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 21-09-26.gif)
App.vue
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<h2 v-if="isShow">hello world</h2>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
</style>
加入动画效果
输出效果
![Honeycam 2021-09-03 21-14-40](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 21-14-40.gif)
App.vue
transition标签将要显示或隐藏的内容包裹起来
根据其的名字来设置css样式
可以省略的代码
将这个元素完全显示出来他的opacity默认就是1,所以可以省略不写,也是可以的.
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<transition name="hw">
<h2 v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.hw-enter-from,
.hw-leave-to{
opacity:0
}
.hw-enter-to,
.hw-leave-from{
opacity:1
}
.hw-enter-active,
.hw-leave-active{
transition:opacity 2s ease;
}
</style>
transition组件的原理
过渡动画class
class的添加或删除的时机
class的name命名规则
animation动画
输出效果
![Honeycam 2021-09-03 21-31-16](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 21-31-16.gif)
App.vue
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw">
<h2 class='hello' v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.hello {
display: inline-block;
}
.hw-enter-active {
animation:bounce 2s ease
}
.hw-leave-active {
animation:bounce 2s ease reverse
}
@keyframes bounce {
0% {
transform: scale(0)
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
</style>
transition的属性
type属性
duration属性
mode属性(常用多个元素切换)
存在的问题
两个元素进行切换的时候,发现前面一个元素还没有离开,后面的元素就是进来了,然后前面的元素完全离开,后面的元素才会过来占位,有种一卡一卡的感觉.
![Honeycam 2021-09-03 21-45-06](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 21-45-06.gif)
App.vue
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw">
<h2 class='hello' v-if="isShow">hello world</h2>
<h2 class='hello' v-else>I'am computer</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.hello {
display: inline-block;
}
.hw-enter-from,
.hw-leave-to{
opacity:0
}
.hw-enter-to,
.hw-leave-from{
opacity:1
}
.hw-enter-active,
.hw-leave-active{
transition:opacity 1s ease;
}
</style>
改进后的输出效果
![Honeycam 2021-09-03 21-47-28](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 21-47-28.gif)
App.vue
mode的模式
先让前面的元素离开,然后再进来.就是这种丝滑的感觉.
我们由此可知前面的卡卡的感觉,先让后面的元素进来,等待前面的元素离去,就是in-out了
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw" mode="out-in">
<h2 class='hello' v-if="isShow">hello world</h2>
<h2 class='hello' v-else>I'am computer</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.hello {
display: inline-block;
}
.hw-enter-from,
.hw-leave-to{
opacity:0
}
.hw-enter-to,
.hw-leave-from{
opacity:1
}
.hw-enter-active,
.hw-leave-active{
transition:opacity 1s ease;
}
</style>
appear属性
下面的例子是一个组件的例子和普通元素差不多.
输出效果
![Honeycam 2021-09-03 21-57-11](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-03 21-57-11.gif)
App.vue
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw" mode="out-in" appear="true">
<component :is="isShow?'home':'about'"></component>
</transition>
</div>
</template>
<script>
import About from './pages/About.vue'
import Home from './pages/Home.vue'
export default {
components:{
About,
Home
},
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.hello {
display: inline-block;
}
.hw-enter-from,
.hw-leave-to{
opacity:0
}
.hw-enter-to,
.hw-leave-from{
opacity:1
}
.hw-enter-active,
.hw-leave-active{
transition:opacity 1s ease;
}
</style>
animate.css动画
https://www.dowebok.com/demo/2014/98/
介绍
如何使用
安装animate.css
命令行输入
npm install animate.css
输出效果
导入animate.css
基本使用
输出效果
![Honeycam 2021-09-04 10-54-42](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 10-54-42.gif)
App.vue
关键代码
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw">
<h2 class='title' v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.title {
text-align: center;
}
.hw-enter-active {
animation: bounceInDown 2s ease
}
.hw-leave-active {
animation: bounceOutDown 2s ease
}
</style>
自定义过渡class_使用animate中的类
输出效果
和上面的例子中输出效果一样,只不过这里使用了不同的方式
![Honeycam 2021-09-04 10-54-42](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 10-54-42.gif)
代码逻辑
App.vue
关键代码
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw"
enter-active-class="animate__animated animate__bounceInDown"
leave-active-class="animate__animated animate__bounceOutDown">
<h2 class='title' v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.title {
text-align: center;
}
</style>
小修改
有时候我们对第三方库的动画效果进行小小的修改,比如说添加个reverse.
输出效果
![Honeycam 2021-09-04 11-51-29](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 11-51-29.gif)
App.vue
关键代码
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw"
enter-active-class="animate__animated animate__bounceInDown"
leave-active-class="animate__animated animate__bounceOutDown">
<h2 class='title' v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.title {
text-align: center;
}
.animate__bounceOutDown {
animation-direction: reverse;
}
</style>
自定义过渡class_使用自己定义的类
输出效果
![Honeycam 2021-09-04 11-31-27](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 11-31-27.gif)
关键代码
App.vue
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr>
<transition name="hw"
enter-active-class="animate__animated animate__bounceInDown"
leave-active-class="animate__animated my__animate__drop">
<h2 class='title' v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.title {
text-align: center;
}
.my__animate__drop {
animation:hinge 2s ease;
}
</style>
gsap库
https://www.npmjs.com/package/gsap
介绍
如何使用
安装gsap库
命令行
npm install gsap
JavaScript钩子
输出效果
![Honeycam 2021-09-04 12-09-38](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 12-09-38.gif)
App.vue
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr />
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@afterLeave="afterLeave"
>
<h2 class="title" v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow: true,
};
},
methods: {
beforeEnter() {
console.log("beforeEnter");
},
enter() {
console.log("enter");
},
afterEnter() {
console.log("afterEnter");
},
beforeLeave() {
console.log("beforeLeave");
},
leave() {
console.log("leave");
},
afterLeave() {
console.log("afterLeave");
},
},
};
</script>
<style scoped>
.title {
text-align: center;
}
</style>
基本使用gsap
参考网址: https://greensock.com/get-started/
输出
![Honeycam 2021-09-04 12-30-31](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 12-30-31.gif)
代码逻辑
App.vue
<template>
<div>
<button @click="isShow = !isShow">toggle</button>
<hr />
<transition
@enter="enter"
@leave="leave"
>
<h2 class="title" v-if="isShow">hello world</h2>
</transition>
</div>
</template>
<script>
import gsap from 'gsap'
export default {
data() {
return {
isShow: true,
};
},
methods: {
enter(el,done) {
console.log("enter");
gsap.from(el,{
scale:0,
x:200,
onComplete:done
})
},
leave(el,done) {
console.log("leave");
gsap.to(el,{
scale:0,
x:200,
onComplete:done
})
},
},
};
</script>
<style scoped>
.title {
text-align: center;
}
</style>
gsap实现数字增长效果
输出
![Honeycam 2021-09-04 12-43-58](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 12-43-58.gif)
App.vue
关键代码
<template>
<div>
<input type="number" step=100 v-model="counter">
<hr>
<h2>{{showCounter.toFixed(0)}}</h2>
</div>
</template>
<script>
import gsap from 'gsap'
export default {
data() {
return {
counter:0,
showCounter:0
};
},
watch:{
counter(newValue){
gsap.to(this,{
duration:1,
showCounter:newValue
})
}
}
};
</script>
<style scoped>
.title {
text-align: center;
}
</style>
列表的过渡
App.vue
<template>
<div>
<button @click="addNum">添加数字</button>
<button @click="removeNum">删除数字</button>
<transition-group tag="p" name="why">
<span v-for="item in numbers" :key="item" class="item">
{{item}}
</span>
</transition-group>
</div>
</template>
<script>
export default {
data() {
return {
numbers:[0,1,2,3,4,5,6,7,8,9],
}
},
methods: {
addNum() {
this.numbers.splice(this.randomIndex(), 0, this.numCounter)
},
removeNum() {
this.numbers.splice(this.randomIndex(), 1)
},
randomIndex() {
return Math.floor(Math.random() * this.numbers.length)
},
},
computed:{
numCounter(){
return this.numbers.length
}
}
}
</script>
<style scoped>
.item {
margin-right: 10px;
display: inline-block;
}
.why-enter-from,
.why-leave-to {
opacity: 0;
transform: translateY(30px);
}
.why-enter-active,
.why-leave-active {
transition: all 1s ease;
}
</style>
输出效果与不足
现在的情况是添加数字和删除数字都是具有动画的,而剩余在数组中的其他数字占位却显得很生硬,
![Honeycam 2021-09-04 14-20-39](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 14-20-39.gif)
尝试在App.vue添加如下代码:
App.vue添加的代码
<style scoped>
.why-move {
transition: transform 1s ease;
}
</style>
输出效果
现在的情况是添加数字时,无论是添加的数字还是移动的数字都是具有动画的,而删除数字时,删除的数字有动画,但是移动的数字很生硬.原因是因为当一个数字被删除时,这个动画还没有执行完成,这个元素仍然是标准流中的元素,所以会占位.我们现在希望这个删除的时候就是不要占位,因此可以将其删除时候的属性设置为absolute,脱离标准流.
![Honeycam 2021-09-04 14-26-39](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 14-26-39-1630736842322.gif)
在App.vue继续增加如下代码
<style scoped>
.why-leave-active {
position: absolute;
}
</style>
输出效果
输出效果还可以,无论删除或增加数字,以及由此引起的数字的移动,都是具有动画的.
![Honeycam 2021-09-04 14-31-32](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 14-31-32.gif)
完整App.vue代码
<template>
<div>
<button @click="addNum">添加数字</button>
<button @click="removeNum">删除数字</button>
<transition-group tag="p" name="why">
<span v-for="item in numbers" :key="item" class="item">
{{item}}
</span>
</transition-group>
</div>
</template>
<script>
export default {
data() {
return {
numbers:[0,1,2,3,4,5,6,7,8,9],
}
},
methods: {
addNum() {
this.numbers.splice(this.randomIndex(), 0, this.numCounter)
},
removeNum() {
this.numbers.splice(this.randomIndex(), 1)
},
randomIndex() {
return Math.floor(Math.random() * this.numbers.length)
},
},
computed:{
numCounter(){
return this.numbers.length
}
}
}
</script>
<style scoped>
.item {
margin-right: 10px;
display: inline-block;
}
.why-enter-from,
.why-leave-to {
opacity: 0;
transform: translateY(30px);
}
.why-enter-active,
.why-leave-active {
transition: all 1s ease;
}
.why-leave-active {
position: absolute;
}
.why-move {
transition: transform 1s ease;
}
</style>
列表的交错过渡
代码逻辑图
App.vue
HTML5中数据传递的方式
数组的过滤
<template>
<div>
<input v-model="keyword">
<transition-group tag="ul" name="why" :css="false"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave">
<li v-for="(item, index) in showNames" :key="item" :data-index="index">
{{item}}
</li>
</transition-group>
</div>
</template>
<script>
import gsap from 'gsap';
export default {
data() {
return {
names: ["abc", "cba", "nba", "why", "lilei", "hmm", "kobe", "james"],
keyword: ""
}
},
computed: {
showNames() {
return this.names.filter(item => item.indexOf(this.keyword) !== -1)
}
},
methods: {
beforeEnter(el) {
el.style.opacity = 0;
el.style.height = 0;
},
enter(el, done) {
gsap.to(el, {
opacity: 1,
height: "1.5em",
delay: el.dataset.index * 0.5,
onComplete: done
})
},
leave(el, done) {
gsap.to(el, {
opacity: 0,
height: 0,
delay: el.dataset.index * 0.5,
onComplete: done
})
}
}
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-04 15-27-32](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 15-27-32.gif)
数字洗牌
用到了了一个第三方库,首先安装这个第三方库,在命令行输入:
npm install lodash
然后再App.vue中使用:
App.vue
<template>
<div>
<button @click="addNum">添加数字</button>
<button @click="removeNum">删除数字</button>
<button @click="shuffleNum">数字洗牌</button>
<transition-group tag="p" name="why">
<span v-for="item in numbers" :key="item" class="item">
{{item}}
</span>
</transition-group>
</div>
</template>
<script>
import _ from 'lodash';
export default {
data() {
return {
numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
numCounter: 10
}
},
methods: {
addNum() {
this.numbers.splice(this.randomIndex(), 0, this.numCounter++)
},
removeNum() {
this.numbers.splice(this.randomIndex(), 1)
},
shuffleNum() {
this.numbers = _.shuffle(this.numbers);
},
randomIndex() {
return Math.floor(Math.random() * this.numbers.length)
}
},
}
</script>
<style scoped>
.item {
margin-right: 10px;
display: inline-block;
}
.why-enter-from,
.why-leave-to {
opacity: 0;
transform: translateY(30px);
}
.why-enter-active,
.why-leave-active {
transition: all 1s ease;
}
.why-leave-active {
position: absolute;
}
.why-move {
transition: transform 1s ease;
}
</style>
输出效果
![Honeycam 2021-09-04 15-32-27](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-04 15-32-27.gif)
Mixin
认识Mixin
关键就是把组件中相同的代码逻辑进行抽取.
Mixin的基本使用
export default和export两种不同的导出方式
代码逻辑图
mixins
>demoMixin.js
export const demoMixin = {
data() {
return {
msg:'this is mixin message',
msg2:'this is mixin message2'
}
},
methods: {
mixinFunc(){
console.log('this is mixin function')
},
mixinAndApp(){
console.log('mixinAndApp function in the demoMixin.js');
}
},
created() {
console.log('mixin created')
},
}
App.vue
<template>
<div>
<h4>demoMixin中的msg--{{msg}}</h4>
<button @click="mixinFunc">mixFunc</button>
<hr>
<h4>demoMixin和App.vue中都有的msg2--{{msg2}}</h4>
<button @click="mixinAndApp">mixinAndApp</button>
<hr>
<h4>App.vue中的appData--{{appData}}</h4>
<button @click="appFunc">appFunc</button>
</div>
</template>
<script>
import {demoMixin} from './mixins/demoMixin'
export default {
mixins:[demoMixin],
data() {
return {
msg2:'this is App.vue message2',
appData:"this is App.vue appData"
}
},
methods: {
mixinAndApp(){
console.log('mixinAndApp function but in the App.vue');
},
appFunc(){
console.log('appFunc function in the App.vue');
}
},
created() {
console.log('App.vue is created');
},
}
</script>
<style scoped>
</style>
输出效果
Mixin的合并规则
全局混入Mixin
代码逻辑
main.js
import { createApp } from 'vue'
import App from './02_mixin的全局混入/App.vue'
const app = createApp(App)
app.mixin({
data() {
return {
msgInMain:'msg data in main.js'
}
},
})
app.mount('#app')
extends
Options API的弊端
Composition API
setup函数的参数
props
通过props拿到父组件传递过来的数据
父组件:App.vue
<template>
<div>
App
<hr>
<home paraFromParent="hello my son"></home>
</div>
</template>
<script>
import Home from './Home.vue'
export default {
components:{
Home
}
}
</script>
<style scoped>
</style>
子组件:Home.vue
<template>
<div>
Home
</div>
</template>
<script>
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props) {
console.log(props.paraFromParent); //=>hello my son
console.log(this); //=>undefined
// console.log(this.paraFromParent);//=>报错
}
}
</script>
<style scoped>
</style>
输出
- setup()不能使用this
- 父组件传递过来的数据通过props拿到.
context
非prop的attribute
代码的逻辑
父组件:App.vue
<template>
<div>
App
<hr>
<home
paraFromParent="hello my son"
class="helloHome"
id="helloHome_1"
></home>
</div>
</template>
<script>
import Home from './Home.vue'
export default {
components:{
Home
}
}
</script>
<style scoped>
</style>
子组件:Home.vue
<template>
<div>
Home
</div>
</template>
<script>
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props,context) {
console.log(props.paraFromParent); //=>hello my son
console.log(context.attrs)Proxy //=>{class: "helloHome", id: "helloHome_1", __vInternal: 1}
console.log(context.attrs.class)//=>helloHome
console.log(context.attrs.id)//=>helloHome_1
}
}
</script>
<style scoped>
</style>
子组件:Home.vue
解构赋值
<template>
<div>
Home
</div>
</template>
<script>
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props,{attrs,slots,emit}) {
console.log(props.paraFromParent)
console.log(attrs)
console.log(slots)
console.log(emit)
}
}
</script>
<style scoped>
</style>
setup函数的返回值
代码逻辑图
setup的返回值
setup的返回值可以在template中使用.
存在的问题
虽然数字能够绑定到mustache语法中,但是数据不是动态绑定的.
Home.vue
<template>
<div>
Home
<h3>msg: {{msg}}</h3>
<h4>counter: {{counter}}</h4>
<button @click="increment">+</button>
</div>
</template>
<script>
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props,{attrs,slots,emit}) {
let counter = 100
const increment = () => {
counter++;
console.log(counter);
};
return {
msg:'I am Home data msg',
counter,
increment
}
}
}
</script>
<style scoped>
</style>
补充
在setup中的函数也要返回
![Honeycam 2021-09-05 17-00-34](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-05 17-00-34.gif)
Reactive API_动态绑定数据
代码逻辑图
Home.vue
关键代码
<template>
<div>
Home
<h4>counter: {{dyCounter.counter}}</h4>
<button @click="increment">+</button>
</div>
</template>
<script>
import {reactive} from 'vue'
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props,{attrs,slots,emit}) {
const dyCounter = reactive({
counter:100
})
const increment = () => {
dyCounter.counter++;
};
return {
dyCounter,
increment
}
}
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-05 17-20-06](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-05 17-20-06.gif)
Ref API
代码逻辑图
Home.vue
关键代码
自动解包功能
自动解包功能,本来这里应该填写
counter.value
,vue帮我们做了自动解析,所以这里填写counter
也是可以的.
<template>
<div>
Home
<h4>counter: {{counter}}</h4>
<button @click="increment">+</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props,{attrs,slots,emit}) {
let counter = ref(100)
const increment = () => {
counter.value ++
}
return {
counter,
increment
}
}
}
</script>
<style scoped>
</style>
自动解包的层数
代码的逻辑图
Home.vue
<template>
<div>
Home
<h4>counter: {{info}}</h4>
<h4>info.counter: {{info.counter}}</h4>
<h4>info.counter.value: {{info.counter.value}}</h4>
<button @click="increment">+</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props,{attrs,slots,emit}) {
let counter = ref(100)
const info = {
counter
}
const increment = () => {
info.counter.value ++
}
return {
info,
increment
}
}
}
</script>
<style scoped>
</style>
readonly
关键代码
Home.vue
<template>
<div>
Home
<h4>rCounter: {{rCounter}}</h4>
<button @click="increment">+</button>
</div>
</template>
<script>
import {ref, readonly} from 'vue'
export default {
props:{
paraFromParent:{
type:String,
required:true
}
},
setup(props,{attrs,slots,emit}) {
let counter = ref(100)
let rCounter = readonly(counter)
const increment = () => {
//正确使用方式
//counter.value ++
rCounter.value ++//=>Set operation on key "value" failed: target is readonly.
}
return {
rCounter,
increment
}
}
}
</script>
<style scoped>
</style>
输出
Reactive判断的API
toRefs
使用结构赋值存在的问题
App.vue
<template>
<div>
<h3>{{name}}--{{age}}</h3>
<button @click="changAge">changName</button>
</div>
</template>
<script>
import {reactive} from 'vue'
export default {
setup() {
const {name,age} = reactive({name:'Alice',age:18})
const changAge = ()=>{
age ++
}
return {
name,
age,
changAge
}
}
}
</script>
<style scoped>
</style>
输出
- 发现使用解构赋值之后,这个age就不是动态数据了.
使用toRefs解决解构赋值的问题
App.vue
关键代码
toRefs一般和reactive搭配使用
toRefs()里面传入的一定是reactive类型的对象.
<template>
<div>
<h3>{{name}}--{{age}}</h3>
<button @click="changAge">changName</button>
</div>
</template>
<script>
import {reactive,toRefs} from 'vue'
export default {
setup() {
const info = reactive({name:'Alice',age:18})
let {name,age} = toRefs(info)
const changAge = ()=>{
// info.age ++ //有作用
// age ++ //没有作用
age.value ++ //有作用
console.log(info.age);
}
return {
name,
age,
changAge
}
}
}
</script>
<style scoped>
</style>
输出
- toRefs在这两者之间建立了联系,两者之中只要有一个改变,另外一个也会改变.
![Honeycam 2021-09-06 21-54-49](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-06 21-54-49.gif)
toRef
App.vue
解构赋值的单独使用
<template>
<div>
<h3>{{name}}--{{age}}</h3>
<button @click="changAge">changName</button>
</div>
</template>
<script>
import {reactive,toRef} from 'vue'
export default {
setup() {
const info = reactive({name:'Alice',age:18})
let {name} = info
// let {age} = toRef(info,age) //没有作用
// let age = toRef(info,age)//没有作用
// let {age} = toRef(info,'age')//没有作用
let age = toRef(info,'age')//有作用
const changAge = ()=>{
info.age ++
console.log(info.age);
}
return {
name,
age,
changAge
}
}
}
</script>
<style scoped>
</style>
输出
ref其他的API
shallowRef&triggerRef
问题背景
App.vue
<template>
<div>
<h3>{{info.name}}--{{info.age}}</h3>
<button @click="changAge">changAge</button>
<hr>
<h4>{{info.friend.name}}--{{info.friend.age}}</h4>
<button @click="changFriendAge">changFriendAge</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
setup() {
const info = ref({name:'Alice',age:18,friend:{name:'Celina',age:20}})
const changAge = ()=>{
info.value.age ++
console.log('changAge--'+info.value.age);
}
const changFriendAge = ()=>{
info.value.friend.age ++
console.log('changFriendAge--'+info.value.friend.age);
}
return {
info,
changAge,
changFriendAge
}
}
}
</script>
<style scoped>
</style>
输出
- 无论是第一层''的age,还是第二层''的age都是能够被修改的.
目标需求
-
我们现在希望这个第一层''的age能够被修改,而第二层''的age是不能够被修改的.
-
这个时候我们就可以使用shallowRef''了.
-
shallRef一般搭配使用
使用shallowRef后没有反应的代码
App.vue
<template>
<div>
<h3>{{info.name}}--{{info.age}}</h3>
<button @click="changAge">changAge</button>
<hr>
<h4>{{info.friend.name}}--{{info.friend.age}}</h4>
<button @click="changFriendAge">changFriendAge</button>
</div>
</template>
<script>
import {ref,shallowRef} from 'vue'
export default {
setup() {
const info = shallowRef({name:'Alice',age:18,friend:{name:'Celina',age:20}})
const changAge = ()=>{
info.value.age ++
console.log('changAge--'+info.value.age);
}
const changFriendAge = ()=>{
info.value.friend.age ++
console.log('changFriendAge--'+info.value.friend.age);
}
return {
info,
changAge,
changFriendAge
}
}
}
</script>
<style scoped>
</style>
输出
- 虽然info.value.age的值和info.value.friend.age的值都是发生改变了,但是在mustache语法中的值没有改变.
shallowRef需要搭配triggerRef
搭配triggerRef的代码:App.vue
<template>
<div>
<h3>{{info.name}}--{{info.age}}</h3>
<button @click="changAge">changAge</button>
<hr>
<h4>{{info.friend.name}}--{{info.friend.age}}</h4>
<button @click="changFriendAge">changFriendAge</button>
</div>
</template>
<script>
import {ref,shallowRef, triggerRef} from 'vue'
export default {
setup() {
const info = shallowRef({name:'Alice',age:18,friend:{name:'Celina',age:20}})
const changAge = ()=>{
info.value.age ++
triggerRef(info)
console.log('changAge--'+info.value.age);
}
const changFriendAge = ()=>{
info.value.friend.age ++
// triggerRef(info)
console.log('changFriendAge--'+info.value.friend.age);
}
return {
info,
changAge,
changFriendAge
}
}
}
</script>
<style scoped>
</style>
输出
- 在第一层''的age,我们使用了'',所以数据是动态绑定的
- 在第二层''的age,我们没有使用'',所以数据无法动态更新
- 当我们再次点击第一个changeAge时,我们又再一次触发这个trigger,所以不仅第一层的age更新了,第二层的age也更新了.
![Honeycam 2021-09-07 09-39-31](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 09-39-31.gif)
customRef
App.vue
<template>
<div>
<input type="text" v-model="msg">
<h2>{{msg}}</h2>
</div>
</template>
<script>
import debounceRef from './hook/useDebounceRef'
export default {
setup(props) {
const msg = debounceRef('hello world')
return{
msg
}
}
}
</script>
<style scoped>
</style>
hook>useDebounceRef.js
import { customRef } from 'vue';
// 自定义ref
export default function(value, delay = 300) {
let timer = null;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
}
})
}
输出
- 输入延时的效果
- 防抖
![Honeycam 2021-09-07 12-33-48](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 12-33-48.gif)
computed
基本使用_传入一个getter函数
App.vue
<template>
<div>
<h2>{{fullName}}</h2>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref("Kobe");
const lastName = ref("Bryant");
// 1.用法一: 传入一个getter函数
// computed的返回值是一个ref对象
const fullName = computed(() => firstName.value + " " + lastName.value);
return {
fullName,
}
}
}
</script>
<style scoped>
</style>
输出
传入一个对象,包含getter和setter
代码逻辑
App.vue
<template>
<div>
<h2>{{fullName}}</h2>
<button @click="changName">changName</button>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref("Kobe");
const lastName = ref("Bryant");
const fullName = computed({
get:() => firstName.value + " " + lastName.value,
set(newValue){
let names = newValue.split(' ')
firstName.value = names[0]
lastName.value = names[1]
}
})
const changName = ()=>{
fullName.value = 'haha hehe'
}
return {
fullName,
changName
}
}
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-07 12-56-06](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 12-56-06.gif)
侦听数据的变化_watchEffect
watchEffect
App.vue
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changName</button>
<button @click="changeAge">changAge</button>
</div>
</template>
<script>
import {ref,watchEffect} from 'vue'
export default {
setup() {
const info = ref({name:'Alice',age:20})
const changeName = ()=>{
info.value.name = 'Celina'
}
const changeAge = () => {
info.value.age ++
}
watchEffect(
() => {
console.log('name: ',info.value.name+' age: ',info.value.age);
}
)
return {
info,
changeName,
changeAge
}
}
}
</script>
<style scoped>
</style>
输出
- watchEffect会监听所有的数据,当其中只要有一个数据发生变化时,就是输出
- 刚开始的时候会立即执行一次.
![Honeycam 2021-09-07 13-33-07](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 13-33-07.gif)
watchEffect的停止侦听
代码逻辑图
App.vue
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changName</button>
<button @click="changeAge">changAge</button>
</div>
</template>
<script>
import {ref,watchEffect} from 'vue'
export default {
setup() {
const info = ref({name:'Alice',age:20})
const stop = watchEffect(() => {
console.log('name: ',info.value.name+' age: ',info.value.age);
})
const changeName = ()=>{
info.value.name = 'Celina'
}
const changeAge = () => {
info.value.age ++
if(info.value.age > 25){
stop()
}
}
return {
info,
changeName,
changeAge
}
}
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-07 13-46-34](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 13-46-34.gif)
watchEffect清除副作用
App.vue
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changName</button>
<button @click="changeAge">changAge</button>
</div>
</template>
<script>
import {ref,watchEffect} from 'vue'
export default {
setup() {
const info = ref({name:'Alice',age:20})
const stop = watchEffect((onInvalidate) => {
onInvalidate(() => {
//在这里面清除额外的副作用
console.log('onInvaliddata');
})
console.log('name: ',info.value.name+' age: ',info.value.age);
})
const changeName = ()=>{
info.value.name = 'Celina'
}
const changeAge = () => {
info.value.age ++
}
return {
info,
changeName,
changeAge
}
}
}
</script>
<style scoped>
</style>
输出
- 每次输出都会执行onInvaliddata中的程序,
- 相当于清除的作用
![Honeycam 2021-09-07 13-58-52](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 13-58-52.gif)
App.vue
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changName</button>
<button @click="changeAge">changAge</button>
</div>
</template>
<script>
import {ref,watchEffect} from 'vue'
export default {
setup() {
const info = ref({name:'Alice',age:20})
const timer = setTimeout(() =>{
console.log('网络请求成功');
},2000)
const stop = watchEffect((onInvalidate) => {
onInvalidate(() => {
//在这里面清除额外的副作用
clearTimeout(timer)
console.log('清除上次的网络请求');
})
console.log('name: ',info.value.name+' age: ',info.value.age);
})
const changeName = ()=>{
info.value.name = 'Celina'
}
const changeAge = () => {
info.value.age ++
}
return {
info,
changeName,
changeAge
}
}
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-07 14-04-21](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 14-04-21.gif)
代码逻辑图
模拟网络请求的例子:App.vue
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changName</button>
<button @click="changeAge">changAge</button>
</div>
</template>
<script>
import {ref,watchEffect} from 'vue'
export default {
setup() {
const info = ref({name:'Alice',age:20})
const timer = setTimeout(() =>{
console.log('网络请求成功');
},3000)
const stop = watchEffect((onInvalidate) => {
onInvalidate(() => {
//在这里面清除额外的副作用
clearTimeout(timer)
console.log('清除上次的网络请求');
console.log('准备下次的网络请求...');
setTimeout(() =>{
console.log('网络再次请求成功');
},3000)
console.log('----');
})
console.log('name: ',info.value.name+' age: ',info.value.age);
})
const changeName = ()=>{
info.value.name = 'Celina'
}
const changeAge = () => {
info.value.age ++
}
return {
info,
changeName,
changeAge
}
}
}
</script>
<style scoped>
</style>
输出:模拟网络请求
![Honeycam 2021-09-07 14-08-50](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 14-08-50.gif)
setup中使用ref
App.vue
<template>
<div>
<h2 ref="titleRef">hello world</h2>
</div>
</template>
<script>
import {ref,watchEffect} from 'vue'
export default {
setup() {
const titleRef = ref(null)
watchEffect(() => {
console.log(titleRef.value);
},{
flush:'post'
})
return {
titleRef
}
}
}
</script>
<style scoped>
</style>
输出
几种输出比较
- 为什么打印了两次
- 其他补充:
侦听数据的变化_watch
侦听reactive对象
第一种传入方式''
App.vue
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changeName</button>
</div>
</template>
<script>
import {reactive,watch} from 'vue'
export default {
setup() {
const info = reactive({name:'Alice',age:19})
const changeName = () => {
info.name = 'Celina'
}
watch(() => info.name,(newValue,oldValue) =>{
console.log(oldValue,'--',newValue);
})
return {
info,
changeName
}
}
}
</script>
<style scoped>
</style>
输出
第二种传入方式''
App.vue
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changeName</button>
</div>
</template>
<script>
import {reactive,watch} from 'vue'
export default {
setup() {
const info = reactive({name:'Alice',age:19})
const changeName = () => {
info.name = 'Celina'
}
watch(info,(newValue,oldValue) =>{
console.log(oldValue,'--',newValue);
})
return {
info,
changeName
}
}
}
</script>
<style scoped>
</style>
输出
结构赋值
App.vue
关键代码
和上面的对比
<template>
<div>
<h2>{{info.name}}--{{info.age}}</h2>
<button @click="changeName">changeName</button>
</div>
</template>
<script>
import {reactive,watch} from 'vue'
export default {
setup() {
const info = reactive({name:'Alice',age:19})
const changeName = () => {
info.name = 'Celina'
}
watch(() => {
return {...info}
}
,(newValue,oldValue) =>{
console.log(oldValue,newValue);
})
return {
info,
changeName
}
}
}
</script>
<style scoped>
</style>
输出
侦听ref对象
App.vue
<template>
<div>
<h2>{{name}}</h2>
<button @click="changeName">changeName</button>
</div>
</template>
<script>
import {ref,watch} from 'vue'
export default {
setup() {
const name = ref('Alice')
const changeName = () => {
name.value = 'Celina'
}
watch(name,(newValue,oldValue) =>{
console.log(oldValue,'--',newValue);
})
return {
name,
changeName
}
}
}
</script>
<style scoped>
</style>
输出
侦听多个数据
App.vue
传入的值是一个数组
传入的值是一个数组,返回的新旧的值也是一对数组.
<template>
<div>
<h2 ref="title">{{info.name}}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
setup() {
// 1.定义可响应式的对象
const info = reactive({name: "why", age: 18});
const name = ref("why");
// 2.侦听器watch
watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
console.log(newInfo, newName,'---', oldInfo, oldName);
})
const changeData = () => {
info.name = "kobe";
}
return {
changeData,
info
}
}
}
</script>
<style scoped>
</style>
侦听数组
深层侦听
生命周期钩子
App.vue
<template>
<div>
<h2>{{name}}</h2>
<button @click="changeName">changName</button>
</div>
</template>
<script>
import {ref,onMounted, onUpdated} from 'vue'
export default {
setup(props) {
onMounted(() => {
console.log('App onMounted_1');
})
onMounted(() => {
console.log('App onMounted_2');
})
onUpdated(() =>{
console.log('App onUpdated');
})
const name = ref("Alice")
const changeName = () => {
name.value = 'Celina'
}
return {
name,
changeName
}
}
}
</script>
<style scoped>
</style>
输出
- 生命周期函数可以重复定义,其中执行的代码会叠加.
- 改变用户名称时,会执行onUpdated()
![Honeycam 2021-09-07 21-21-18](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 21-21-18.gif)
Provide和Inject
基本使用_父组件向子组件传递数据
数据传递过程图
App.vue
<template>
<div>
App
<hr>
<home></home>
</div>
</template>
<script>
import {ref,provide} from 'vue'
import Home from './Home.vue'
export default {
components:{
Home
},
setup() {
const msg = ref('I am msg form Father')
provide('msg',msg)
}
}
</script>
<style scoped>
</style>
Home.vue
<template>
<div>
Home
<h2>{{msg}}</h2>
</div>
</template>
<script>
import {inject} from 'vue'
export default {
setup() {
const msg = inject('msg')
return{
msg
}
}
}
</script>
<style scoped>
</style>
基本使用_动态数据传递
父组件:App.vue
<template>
<div>
<h1>App.vue</h1>
<input type="text" v-model="msg">
<hr>
<home></home>
</div>
</template>
<script>
import {ref,provide} from 'vue'
import Home from './Home.vue'
export default {
components:{
Home
},
setup() {
const msg = ref('I am msg form Father')
provide('msg',msg)
return{
msg
}
}
}
</script>
<style scoped>
</style>
Home.vue
<template>
<div>
<h1>Home.vue</h1>
<h2>{{msg}}</h2>
<input type="text" v-model="msg">
</div>
</template>
<script>
import {inject} from 'vue'
export default {
setup() {
const msg = inject('msg')
return{
msg
}
}
}
</script>
<style scoped>
</style>
输出
- 父组件能够传递数据到子组件
- 父组件的数据能够动态绑定到子组件中,也就修改父组件中的数据,子组件中的数据能够动态变化.
- 存在的问题,就是子组件修改数据,父组件中的数据也遭受到修改,违反了数据的单向流原则,不太友好
![Honeycam 2021-09-07 21-50-51](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 21-50-51.gif)
改进_数据单向流
针对上面出现的问题,修改子组件中的数据,父组件中的数据也会遭到修改.
父组件:App.vue
和上面代码对比
<template>
<div>
<h1>App.vue</h1>
<input type="text" v-model="msg">
<hr>
<home></home>
</div>
</template>
<script>
import {ref,provide,readonly} from 'vue'
import Home from './Home.vue'
export default {
components:{
Home
},
setup() {
const msg = ref('I am msg form Father')
provide('msg',readonly(msg))
return{
msg
}
}
}
</script>
<style scoped>
</style>
输出效果
![Honeycam 2021-09-07 21-57-11](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 21-57-11.gif)
Composition API练习
计数器案例
代码提取图
App.vue
<template>
<div>
<h2>{{counter}}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
import useCounter from './hooks/useCounter'
export default {
setup(){
const {counter,increment,decrement} = useCounter()
return {
counter,
increment,
decrement
}
}
}
</script>
<style scoped>
</style>
hooks
>useCounter.js
import {ref} from 'vue'
export default function(){
const counter = ref(0)
const increment = () => {
counter.value ++
}
const decrement = () => {
counter.value --
}
return {
counter,
increment,
decrement
}
}
useTitle案例
App.vue
<template>
<div>
</div>
</template>
<script>
import useTitle from './hooks/useTitle'
export default {
setup(){
const titleRef = useTitle('myTitle')
setTimeout(() => {
titleRef.value = 'youTitle'
},2000)
}
}
</script>
<style scoped>
</style>
hooks
>useTitle
import {ref,watch} from 'vue'
export default function(title = 'default title'){
const titleRef = ref(title)
document.title = title
watch(titleRef,(newTitle) => {
document.title = newTitle
})
return titleRef
}
输出
- 刷新的时候显示默认的title,即default title
- 然后迅速变换成myTitle
- 最后经过两秒过后,这个变成youTitle
![Honeycam 2021-09-07 22-36-31](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 22-36-31.gif)
综合案例
关键代码逻辑
App.vue
<template>
<div>
<h2>{{ counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<hr />
<p class="content"></p>
<div class="scroll">
<div class="scroll-x">scrollX: {{ scrollX }}</div>
<div class="scroll-y">scrollY: {{ scrollY }}</div>
</div>
<div class="mouse">
<div class="mouse-x">mouseX: {{ mouseX }}</div>
<div class="mouse-y">mouseY: {{ mouseY }}</div>
</div>
</div>
</template>
<script>
import useCounter from "./hooks/useCounter";
import useTitle from "./hooks/useTitle";
import useScrollPosition from './hooks/useScrollPosition'
import useMousePosition from './hooks/useMousePosition'
export default {
setup() {
const { counter, increment, decrement } = useCounter();
useTitle("myTitle");
const {scrollX, scrollY} = useScrollPosition()
const {mouseX,mouseY} = useMousePosition()
return {
counter,
increment,
decrement,
scrollX,
scrollY,
mouseX,
mouseY
};
},
};
</script>
<style scoped>
.content {
width: 3000px;
height: 5000px;
}
.scroll {
position: fixed;
right: 30px;
bottom: 30px;
}
.mouse {
position: fixed;
right: 30px;
bottom: 80px;
}
</style>
hooks
>useScrollPosition
import { ref } from 'vue';
export default function() {
const scrollX = ref(0);
const scrollY = ref(0);
document.addEventListener("scroll", () => {
scrollX.value = window.scrollX;
scrollY.value = window.scrollY;
});
return {
scrollX,
scrollY
}
}
hooks
>useMousePosition
import { ref } from 'vue';
export default function() {
const mouseX = ref(0);
const mouseY = ref(0);
window.addEventListener("mousemove", (event) => {
mouseX.value = event.pageX;
mouseY.value = event.pageY;
});
return {
mouseX,
mouseY
}
}
输出
![Honeycam 2021-09-07 22-49-14](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-07 22-49-14.gif)
代码整合优化
所有导入合并成一个单独的文件
jsx
基本使用
App.vue
- 非常简单,就是把template标签扔掉了,然后直接 在render()的返回值里面写html代码
<script>
export default {
render() {
return <h2>hello, I am render</h2>
}
}
</script>
<style scoped>
</style
输出
计数器案例
App.vue
<script>
export default {
data() {
return {
counter:0
}
},
render() {
const increment = () =>this.counter ++
const decrement = () =>this.counter --
return (
<div>
<h2>当前计数:{this.counter}</h2>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
)
}
}
</script>
<style scoped>
</style>
输出
引入子组件
基本使用
App.vue
<script>
import Home from './Home.vue'
export default {
render() {
return (
<div>
<h3>App.vue</h3>
<hr></hr>
<Home></Home>
</div>
)
}
}
</script>
<style scoped>
</style>
Home.vue
<template>
<div>
<h3>Home</h3>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
输出
使用插槽
App.vue
<script>
import Home from './Home.vue'
export default {
render() {
return (
<div>
<h3>App.vue</h3>
<hr></hr>
<Home>
{{default:props => <button>Button</button>}}
</Home>
</div>
)
}
}
</script>
<style scoped>
</style>
Home.vue
<script>
export default {
render(h) {
return(
<div>
<h3>Home.vue</h3>
{this.$slots.default ? this.$slots.default():<span>hahaha</span>}
</div>
)
},
}
</script>
<style scoped>
</style>
输出
- 当父组件传递插槽过来时,就是使用父组件的插槽
- 当父组件没有传递插槽过来时,就是使用默认的组件.
![Honeycam 2021-09-08 13-29-32](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-08 13-29-32.gif)
VueRouter
概念
路由器
前后端分离阶段
URL的hash
代码理解1
代码理解2
代码理解3
HTML5的History
代码理解1
e.preventDefault()
代码理解:history.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>
<div id="app">
<a href="/home">home</a>
<a href="/about">about</a>
<div class="content">Default</div>
</div>
<script>
const aEls = document.getElementsByTagName("a");
for (let aEl of aEls) {
aEl.addEventListener("click", e => {
e.preventDefault();
console.log("clicked");
})
}
</script>
</body>
</html>
输出
- 没有用
e.preventDefault();
时,浏览器地址会进行跳转 - 使用了
e.preventDefault();
,浏览器地址不会跳转,不会变化, - 问-为什么要阻止浏览器地址的跳转?答-因为浏览器地址跳转会引发网络请求,而每次引发网络请求,频繁的网络请求也是不好的.
![Honeycam 2021-09-09 11-06-18](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 11-06-18.gif)
history.pushState({}, "", href);
使用history方式跳转页面:history.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>
<div id="app">
<a href="/home">home</a>
<a href="/about">about</a>
<div class="content">Default</div>
</div>
<script>
const contentEl = document.querySelector('.content');
const changeContent = () => {
switch(location.pathname) {
case "/home":
contentEl.innerHTML = "Home";
break;
case "/about":
contentEl.innerHTML = "About";
break;
default:
contentEl.innerHTML = "Default";
}
}
const aEls = document.getElementsByTagName("a");
for (let aEl of aEls) {
aEl.addEventListener("click", e => {
e.preventDefault();
const href = aEl.getAttribute("href");
history.pushState({}, "", href);
changeContent();
})
}
</script>
</body>
</html>
代码理解
输出
- 实现了即使改变url的部分内容,不用网络请求
![Honeycam 2021-09-09 11-15-35](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 11-15-35.gif)
history.pushState({}, "", href);
这个过程类似入栈,出栈的过程,所以可以前进后退.
![Honeycam 2021-09-09 11-21-36](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 11-21-36.gif)
history.replaceState({},'',href)
使用history中的
history.replaceState({},'',href)
:history.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>
<div id="app">
<a href="/home">home</a>
<a href="/about">about</a>
<div class="content">Default</div>
</div>
<script>
const contentEl = document.querySelector('.content');
const changeContent = () => {
switch(location.pathname) {
case "/home":
contentEl.innerHTML = "Home";
break;
case "/about":
contentEl.innerHTML = "About";
break;
default:
contentEl.innerHTML = "Default";
}
}
const aEls = document.getElementsByTagName("a");
for (let aEl of aEls) {
aEl.addEventListener("click", e => {
e.preventDefault();
const href = aEl.getAttribute("href");
// history.pushState({}, "", href);
history.replaceState({},'',href)
changeContent();
})
}
</script>
</body>
</html>
输出
- 这个
history.replaceState({},'',href)
就不是history.pushState({}, "", href);
的出栈入栈了,而是直接替换,所以无法前进和后退''.
![Honeycam 2021-09-09 11-26-29](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 11-26-29.gif)
认识vue-router
安装vue-router
路由的使用步骤
代码结构图
App.vue
<template>
<div>
<h3>App</h3>
<hr>
<!-- vue-router中的超链接,类似a标签 -->
<router-link to="/home">home</router-link>
<router-link to="/about">about</router-link>
<hr>
<!-- 子组件显示的区域 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
router
>index.js
import {createRouter,createWebHashHistory, createWebHistory} from 'vue-router'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
//配置映射关系
const routes = [
{path:'/home',component:Home},
{path:'/about',component:About}
]
//创建一个路由对象router
const router = createRouter({
routes,
history:createWebHashHistory()
})
export default router
Home.vue
<template>
<div>
Home
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
About.vue
<template>
<div>
About
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
代码逻辑图
输出
![Honeycam 2021-09-09 12-51-20](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 12-51-20.gif)
存在的问题
- 刚进来的时候没有默认的页面,会有一个警告
![Honeycam 2021-09-09 13-10-32](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 13-10-32.gif)
路由的默认路径
router
>index.js
import {createRouter,createWebHashHistory} from 'vue-router'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
//配置映射关系
const routes = [
{path:'/',redirect:'/home'},
{path:'/home',component:Home},
{path:'/about',component:About}
]
//创建一个路由对象router
const router = createRouter({
routes,
history:createWebHashHistory()
})
export default router
输出
- 以前的默认打开地址栏''
- 现在的默认打开地址栏
![Honeycam 2021-09-09 13-15-15](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 13-15-15.gif)
router-linker
active-class&exact-active-class属性
输出
![Honeycam 2021-09-09 14-19-40](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 14-19-40.gif)
两者的区别
- 当点击Home时,两个属性都会被激活
- 当点击Home中的
消息
或商品
时,只会激活其中一个属性
![Honeycam 2021-09-09 20-06-44](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 20-06-44.gif)
路由懒加载
分包_魔法注释
懒加载,肯定要分包
index.js
import {createRouter,createWebHistory} from 'vue-router'
// import Home from '../pages/Home.vue'
// import About from '../pages/About.vue'
//配置映射关系
const routes = [
{path:'/',redirect:'/home'},
// {path:'/home',component:Home},
// {path:'/about',component:About}
{path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
{path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')}
]
//创建一个路由对象router
const router = createRouter({
routes,
history:createWebHistory()
})
export default router
代码逻辑图
路由的其他属性
动态路由_参数传递
发送参数
接受参数
index.js
传递参数的关键代码
import {createRouter,createWebHistory} from 'vue-router'
//配置映射关系
const routes = [
{path:'/',redirect:'/home'},
{path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
{path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
{
path:'/user/:userName',
component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')}
]
//创建一个路由对象router
const router = createRouter({
routes,
history:createWebHistory()
})
export default router
App.vue
发送参数的关键代码
<template>
<div>
<h3>App</h3>
<hr>
<!-- vue-router中的超链接,类似a标签 -->
<router-link to="/home" >Home</router-link>
<router-link to="/about" >About</router-link>
<router-link to="/user/Bruce/Celina" >User</router-link>
<hr>
<!-- 子组件显示的区域 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
输出
- 通过在
/user
后面添加/Alice
,可以传递参数
![Honeycam 2021-09-09 16-13-48](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 16-13-48.gif)
子组件接受参数:User.vue
接受参数的关键代码
<template>
<div>
User
</div>
</template>
<script>
export default {
created() {
console.log(this.$route)
},
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-09 16-23-29](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 16-23-29.gif)
子组件接受指定的参数:User.vue
关键代码
<template>
<div>
User
</div>
</template>
<script>
export default {
created() {
console.log(this.$route)
console.log(this.$route.params.userName)
},
}
</script>
<style scoped>
</style>
输出
User.vue
关键代码
<template>
<div>
User : {{$route.params.userName}}
</div>
</template>
<script>
export default {
created() {
console.log(this.$route)
console.log(this.$route.params.userName)
},
}
</script>
<style scoped>
</style>
输出
如何在setup()中的拿到动态路由传递的参数
代码逻辑图
User.vue
关键代码
<template>
<div>
User : {{$route.params.userName}}
</div>
</template>
<script>
import { useRoute } from 'vue-router'
export default {
created() {
console.log(this.$route)
},
setup() {
const route = useRoute();
console.log(route.params.userName);
}
}
</script>
<style scoped>
</style>
输出
多参数传递
路由配置文件 : index.js
传递多个参数的关键配置
import {createRouter,createWebHistory} from 'vue-router'
//配置映射关系
const routes = [
{path:'/',redirect:'/home'},
{path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
{path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
{
path:'/user/:userName/:age',
component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')}
]
//创建一个路由对象router
const router = createRouter({
routes,
history:createWebHistory()
})
export default router
父组件 : App.vue
传递多个参数
<template>
<div>
<h3>App</h3>
<hr>
<!-- vue-router中的超链接,类似a标签 -->
<router-link to="/home" >Home</router-link>
<router-link to="/about" >About</router-link>
<router-link to="/user/Alice/19" >User</router-link>
<hr>
<!-- 子组件显示的区域 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
子组件 : User.vue
<template>
<div>
User : {{$route.params.userName}}
Age : {{$route.params.age}}
</div>
</template>
<script>
import { useRoute } from 'vue-router'
export default {
created() {
console.log(this.$route)
},
// setup() {
// const route = useRoute();
// console.log(route.params.userName);
// }
}
</script>
<style scoped>
</style>
输出
NotFound
映射表配置文件:
route
>index.js
关键代码
import {createRouter,createWebHistory} from 'vue-router'
//配置映射关系
const routes = [
{path:'/',redirect:'/home'},
{path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
{path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
{
path:'/user/:userName/:age',
component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')
},
{
path:'/:pathMatch(.*)',
component:() => import(/* webpackChunkName:"chunk-notFound" */ '../pages/NotFound.vue')
}
]
//创建一个路由对象router
const router = createRouter({
routes,
history:createWebHistory()
})
export default router
输出
$route.params.pathMatch
NotFound.vue
<template>
<div>
Not Found
<h4>{{$route.params.pathMatch}}</h4>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
输出
匹配规则加*
路由的嵌套
路由配置文件: index.js
关键代码
import {createRouter,createWebHistory} from 'vue-router'
//配置映射关系
const routes = [
{path:'/',redirect:'/home'},
{
path:'/home',
component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue'),
children:[
{
path:'msg',
component:() => import('../pages/HomeMsg.vue')
},
{
path:'pro',
component:() => import('../pages/HomeShops.vue')
}
]
},
{path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
{
path:'/user/:userName/:age',
component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')
},
{
path:'/:pathMatch(.*)',
component:() => import(/* webpackChunkName:"chunk-notFound" */ '../pages/NotFound.vue')
}
]
//创建一个路由对象router
const router = createRouter({
routes,
history:createWebHistory()
})
export default router
Home.vue
关键代码
<template>
<div>
<table border="1">
<div>Home</div>
<router-link to="/home/msg">消息</router-link>
<router-link to="/home/pro">商品</router-link>
<router-view></router-view>
</table>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
HomeMsg.vue
<template>
<div>
<table border="1">
HomeMsg
</table>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
HomeShops.vue
<template>
<div>
<table border="1">
HomeShops
</table>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-09 19-57-56](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 19-57-56.gif)
编程式控制路由
App.vue
关键代码
<template>
<div>
<table border="1">
<h3>App</h3>
<!-- vue-router中的超链接,类似a标签 -->
<router-link to="/home" >Home</router-link>
<router-link to="/about" >About</router-link>
<router-link to="/user/Alice/19" >User</router-link><br>
<button @click="jumpToHome">Home</button>
<button @click="jumpToAbout">About</button>
<button @click="jumpToUser">User</button>
<!-- 子组件显示的区域 -->
<router-view></router-view>
</table>
</div>
</template>
<script>
import {useRouter} from 'vue-router'
export default {
setup() {
const router = useRouter()
const jumpToHome = () => {
router.push('/home')
}
const jumpToAbout = () => {
router.push('/about')
}
const jumpToUser = () => {
router.push('/user/Alice/19')
}
return {
jumpToHome,
jumpToAbout,
jumpToUser
}
}
}
</script>
<style scoped>
</style>
输出
![Honeycam 2021-09-09 20-31-29](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-09 20-31-29.gif)
router-link的v-slot
v-slot的基本使用
custom的作用和如何通过插槽给内部传值
navigate导航函数
![Honeycam 2021-09-10 17-14-47](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-10 17-14-47.gif)
isActive
![Honeycam 2021-09-10 17-16-56](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-10 17-16-56.gif)
router-view的v-slot
动态添加路由
添加一级路由
添加二级路由
动态删除路由
路由导航守卫
https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html
登录守卫功能
Vuex的状态管理
批量引入Vuex中的数据_mapState
setup中如何使用mapState
Home.vue
<template>
<div class="home">
<table border="1">
<h5>{{ name }}</h5>
<h5>{{ age }}</h5>
<h5>{{ counter }}</h5>
</table>
</div>
</template>
<script>
import { useStore, mapState } from "vuex";
import { computed } from "vue";
export default {
setup() {
const store = useStore();
const storeStateFns = mapState(["name", "age", "counter"]);
// console.log(storeStateFns);//=>{name: ƒ, age: ƒ, counter: ƒ}
const storeState = {};
Object.keys(storeStateFns).forEach((fnKey) => {
const fn = storeStateFns[fnKey].bind({ $store: store });
storeState[fnKey] = computed(fn);
});
return {
...storeState,
};
},
};
</script>
<style></style>
setup中使用mapState的封装
hooks>useState.js
import { useStore, mapState } from "vuex";
import { computed } from "vue";
export function useState(mapper) {
const store = useStore();
const storeStateFns = mapState(mapper);
const storeState = {};
Object.keys(storeStateFns).forEach((fnKey) => {
const fn = storeStateFns[fnKey].bind({ $store: store });
storeState[fnKey] = computed(fn);
});
return storeState;
}
Home.vue
<template>
<div class="home">
<table border="1">
<h5>{{ name }}</h5>
<h5>{{ age }}</h5>
<h5>{{ counter }}</h5>
</table>
</div>
</template>
<script>
import { useState } from "../hooks/useState";
export default {
setup() {
const storeState = useState(["name", "age", "counter"]);
return {
...storeState,
};
},
};
</script>
<style></style>
Vuex的计算属性_getters
使用state中的数据
使用getters中的数据
getters返回一个函数
index.js
import { createStore } from "vuex";
export default createStore({
state() {
return {
counter: 10,
name: "Alice",
age: 19,
height: 180,
books: [
{ name: "Alice", price: 10, count: 1 },
{ name: "Bruce", price: 9, count: 1 },
{ name: "Celina", price: 11, count: 1 },
],
};
},
getters: {
totalBooksPrice(state) {
return function (disCount) {
let totalPrice = 0;
for (const book of state.books) {
totalPrice += book.price * book.count * disCount;
}
return totalPrice;
};
},
},
mutations: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
},
actions: {},
modules: {},
});
Home.vue
<template>
<div class="home">
<table border="1">
<h2>{{ $store.getters.totalBooksPrice(0.8) }}</h2>
</table>
</div>
</template>
<script>
export default {
setup() {},
};
</script>
<style></style>
批量拿到getters中的数据
Home.vue
<template>
<div class="home">
<table border="1">
<h2>{{ $store.getters.totalBooksPrice }}</h2>
<h2>{{ $store.getters.info }}</h2>
<hr />
<h2>{{ totalBooksPrice }}</h2>
<h2>{{ info }}</h2>
</table>
</div>
</template>
<script>
import { mapGetters, useStore } from "vuex";
import { computed } from "vue";
export default {
setup() {
const store = useStore();
const gettersFns = mapGetters(["totalBooksPrice", "info"]);
const getters = {};
Object.keys(gettersFns).forEach((key) => {
const fn = gettersFns[key].bind({ $store: store });
getters[key] = computed(fn);
});
return {
...getters,
};
},
};
</script>
<style></style>
封装批量拿到getters中的数据
hooks>useGetters.js
import { mapGetters, useStore } from "vuex";
import { computed } from "vue";
export function useGetters(mapper) {
const store = useStore();
const gettersFns = mapGetters(mapper);
const getters = {};
Object.keys(gettersFns).forEach((key) => {
const fn = gettersFns[key].bind({ $store: store });
getters[key] = computed(fn);
});
return getters;
}
mapState 和mapGetters的综合封装
图解
hooks>useMapper.js
import { useStore } from "vuex";
import { computed } from "vue";
export function useMapper(mapper, mapFunc) {
const store = useStore();
const gettersFns = mapFunc(mapper);
const getters = {};
Object.keys(gettersFns).forEach((key) => {
const fn = gettersFns[key].bind({ $store: store });
getters[key] = computed(fn);
});
return getters;
}
hooks>useState.js
import { mapState } from "vuex";
import { useMapper } from "./useMapper";
export function useState(mapper) {
return useMapper(mapper, mapState);
}
hooks>useGetters.js
import { useMapper } from "./useMapper";
import { mapGetters } from "vuex";
export function useGetters(mapper) {
return useMapper(mapper, mapGetters);
}
hooks>index.js
import { useGetters } from "./useGetters";
import { useState } from "./useState";
export { useGetters, useState };
引用useGetters的文件:Home.vue
<template>
<div class="home">
<table border="1">
<h2>{{ $store.getters.totalBooksPrice }}</h2>
<h2>{{ $store.getters.info }}</h2>
<hr />
<h2>{{ totalBooksPrice }}</h2>
<h2>{{ info }}</h2>
</table>
</div>
</template>
<script>
// import { useGetters } from "../hooks/useGetters";
import { useGetters } from "../hooks/index";
export default {
setup() {
return {
...useGetters(["totalBooksPrice", "info"]),
};
},
};
</script>
<style></style>
Mutation
无参数传递_计数器
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</table>
</div>
</template>
<script>
export default {
name: "App",
components: {},
methods: {
increment() {
this.$store.commit("increment");
},
decrement() {
this.$store.commit("decrement");
},
},
};
</script>
<style></style>
有参数传递_计数器
图解
传递普通参数
传递对象类型参数
两种提交风格
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<hr />
<button @click="increment_10">+10</button>
</table>
</div>
</template>
<script>
export default {
name: "App",
components: {},
methods: {
increment() {
this.$store.commit("increment");
},
decrement() {
this.$store.commit("decrement");
},
increment_10() {
this.$store.commit("incrementN", { step: 10 });
},
},
};
</script>
<style></style>
store>index.js
import { createStore } from "vuex";
export default createStore({
state() {
return {
counter: 10,
name: "Alice",
age: 19,
height: 180,
books: [
{ name: "Alice", price: 10, count: 1 },
{ name: "Bruce", price: 9, count: 1 },
{ name: "Celina", price: 11, count: 1 },
],
};
},
getters: {
totalBooksPrice(state) {
let totalPrice = 0;
for (const book of state.books) {
totalPrice += book.price * book.count;
}
return totalPrice;
},
info(state) {
return state.name + state.age;
},
},
mutations: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
incrementN(state, payload) {
state.counter += payload.step;
},
},
actions: {},
modules: {},
});
常量类型
图解
store>index.js
import { createStore } from "vuex";
import { INCREMENT_N } from "./mutation-types";
export default createStore({
state() {
return {
counter: 10,
name: "Alice",
age: 19,
height: 180,
books: [
{ name: "Alice", price: 10, count: 1 },
{ name: "Bruce", price: 9, count: 1 },
{ name: "Celina", price: 11, count: 1 },
],
};
},
getters: {
totalBooksPrice(state) {
let totalPrice = 0;
for (const book of state.books) {
totalPrice += book.price * book.count;
}
return totalPrice;
},
info(state) {
return state.name + state.age;
},
},
mutations: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
// incrementN(state, payload) {
// state.counter += payload.step;
// },
[INCREMENT_N](state, payload) {
state.counter += payload.step;
},
},
actions: {},
modules: {},
});
store>mutation-types.js
export const INCREMENT_N = "incrementN";
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<hr />
<button @click="increment_10">+10</button>
</table>
</div>
</template>
<script>
import { INCREMENT_N } from "./store/mutation-types";
export default {
name: "App",
components: {},
methods: {
increment() {
this.$store.commit("increment");
},
decrement() {
this.$store.commit("decrement");
},
// 第一种提交风格
increment_10() {
this.$store.commit(INCREMENT_N, { step: 10 });
},
// 第二种提交风格
// increment_10() {
// this.$store.commit({
// type: "incrementN",
// step: 10,
// });
// },
},
};
</script>
<style></style>
mapMutation
图解
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<hr />
<button @click="incrementN(10)">+10</button>
</table>
</div>
</template>
<script>
import { INCREMENT_N } from "./store/mutation-types";
import { mapMutations } from "vuex";
export default {
name: "App",
components: {},
setup() {
const mutations = mapMutations(["increment", "decrement", INCREMENT_N]);
return {
...mutations,
};
},
};
</script>
<style></style>
actions
基本使用
- 在组件中调用store中actions的函数.
- 在actions中调用mutations中的方法.
- 真正的数据修改发生在mutations的方法里面.
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
</table>
</div>
</template>
<script>
export default {
name: "App",
components: {},
methods: {
increment() {
console.log("1.在App.vue中调用store中的actions的incrementAction()");
this.$store.dispatch("incrementAction");
},
},
};
</script>
<style></style>
index.js
import { createStore } from "vuex";
export default createStore({
state() {
return {
counter: 10,
name: "Alice",
age: 19,
height: 180,
books: [
{ name: "Alice", price: 10, count: 1 },
{ name: "Bruce", price: 9, count: 1 },
{ name: "Celina", price: 11, count: 1 },
],
};
},
getters: {},
mutations: {
increment(state) {
console.log("3.真正调用mutations中的increment");
state.counter++;
},
},
actions: {
incrementAction(context) {
console.log("2.在actions中调用mutations中的increment");
context.commit("increment");
},
},
modules: {},
});
基本使用-异步请求-一秒后加一的计数器
图解
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
</table>
</div>
</template>
<script>
export default {
name: "App",
components: {},
methods: {
increment() {
this.$store.dispatch("incrementAction");
},
},
mounted() {
this.$store.dispatch("getBannerDataAction");
},
};
</script>
<style></style>
store>index.js
import { createStore } from "vuex";
import axios from "axios";
export default createStore({
state() {
return {
counter: 10,
banners: [],
};
},
getters: {},
mutations: {
increment(state) {
state.counter++;
},
addBannerData(state, payload) {
state.banners = payload;
},
},
actions: {
incrementAction(context) {
setTimeout(() => {
context.commit("increment");
}, 1000);
},
getBannerDataAction(context) {
axios.get("http://123.207.32.32:8000/home/multidata").then((res) => {
context.commit("addBannerData", res.data.data.banner.list);
});
},
},
modules: {},
});
axios异步请求
图解
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
</table>
</div>
</template>
<script>
export default {
name: "App",
components: {},
methods: {
increment() {
this.$store.dispatch("incrementAction");
},
},
mounted() {
this.$store.dispatch("getBannerDataAction");
},
};
</script>
<style></style>
store>index.js
import { createStore } from "vuex";
import axios from "axios";
export default createStore({
state() {
return {
counter: 10,
banners: [],
};
},
getters: {},
mutations: {
increment(state) {
state.counter++;
},
addBannerData(state, payload) {
state.banners = payload;
},
},
actions: {
incrementAction(context) {
context.commit("increment");
},
getBannerDataAction(context) {
axios.get("http://123.207.32.32:8000/home/multidata").then((res) => {
context.commit("addBannerData", res.data.data.banner.list);
});
},
},
modules: {},
});
context的类型-context的解构传参
图解
参数传递
actions的分发操作的中风格
图解
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
</table>
</div>
</template>
<script>
export default {
name: "App",
components: {},
methods: {
increment() {
// 风格一
// this.$store.dispatch("incrementAction", { msg: "我是被传递的参数" });
// 风格二
this.$store.dispatch({
type: "incrementAction",
msg: "我是被传递的参数",
});
},
},
mounted() {
this.$store.dispatch("getBannerDataAction");
},
};
</script>
<style></style>
actions的辅助函数
methods中使用
- 使用方法和mapMutation一样
setup中使用
图解
App.vue
<template>
<div class="app">
<table border="1">
<h2>{{ $store.state.counter }}</h2>
<button @click="incrementAction">+1</button>
<button @click="add">+1</button>
</table>
<table border="1">
<button @click="getBannerDataAction">获取网络数据</button>
<button @click="getData">获取网络数据</button>
</table>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
name: "App",
components: {},
setup() {
const actions = mapActions(["incrementAction", "getBannerDataAction"]);
const actions2 = mapActions({
add: "incrementAction",
getData: "getBannerDataAction",
});
return {
...actions,
...actions2,
};
},
};
</script>
<style></style>
actions中的函数的返回值
为什么会出现这样的需求?
比如说我们在axios请求之后要将数据返回给原来的组件,这个时候就要return
图解
store>index.js
import { createStore } from "vuex";
import axios from "axios";
export default createStore({
state() {
return {
counter: 10,
banners: [],
};
},
getters: {},
mutations: {
increment(state) {
state.counter++;
},
addBannerData(state, payload) {
state.banners = payload;
console.log(payload);
},
},
actions: {
incrementAction(context) {
context.commit("increment");
},
getBannerDataAction(context) {
return new Promise((resolve, reject) => {
axios
.get("http://123.207.32.32:8000/home/multidata")
.then((res) => {
context.commit("addBannerData", res.data.data.banner.list);
resolve("我是从store>index.js发来的标志位");
})
.catch((err) => {
reject(err);
});
});
},
},
modules: {},
});
App.vue
<template>
<div class="app">
<table border="1"></table>
</div>
</template>
<script>
import { useStore } from "vuex";
import { onMounted } from "vue";
export default {
name: "App",
components: {},
setup() {
const store = useStore();
onMounted(() => {
const promise = store.dispatch("getBannerDataAction");
promise
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
});
return {};
},
};
</script>
<style></style>
module的基本使用
图示
module的命名空间
背景问题
图解
输出
- 当我点击按钮式,根模块和子模块中的数据都会被修改,
- 这是因为根模块和子模块中的increment都会被调用
![Honeycam 2021-09-17 19-41-04](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-17 19-41-04.gif)
App.vue
<template>
<div class="app">
<table border="1">
<h2>rootStore: {{ $store.state.counter }}</h2>
<h2>homeStore: {{ $store.state.home.counter }}</h2>
<button @click="increment">+1</button>
</table>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
name: "App",
components: {},
setup() {
const mutations = mapMutations(["increment"]);
return {
...mutations,
};
},
};
</script>
<style></style>
store>index.js
import { createStore } from "vuex";
import homeModule from "./modules/home";
import userModule from "./modules/user";
const store = createStore({
state: {
counter: 0,
},
getters: {},
mutations: {
increment(state) {
state.counter++;
},
},
actions: {},
modules: {
home: homeModule,
user: userModule,
},
});
export default store;
store>modules>home.js
const homeModule = {
state: {
counter: 0,
},
getters: {},
mutations: {
increment(state) {
state.counter++;
},
},
actions: {},
};
export default homeModule;
解决方案-加上命名空间namespaced
图解
- 仅仅只要加上namespaced:true这一行代码就可以了
输出
![Honeycam 2021-09-17 20-39-29](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-17 20-39-29.gif)
调用子模块的中的函数
图解
- mapMutations中传入两个参数,第一个参数传入模块,第二个参数传入需要的函数
输出
- 这次就是单单修改home模块中的数据,root模块中的数据没有修改
![Honeycam 2021-09-17 20-54-26](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-17 20-54-26.gif)
App.vue
<template>
<div class="app">
<table border="1">
<h2>rootStore: {{ $store.state.counter }}</h2>
<h2>homeStore: {{ $store.state.home.counter }}</h2>
<button @click="increment">+1</button>
</table>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
name: "App",
components: {},
setup() {
const mutationsHome = mapMutations("home", ["increment"]);
return {
...mutationsHome,
};
},
};
</script>
<style></style>
module修改或派发根组件
module中getters和actions中的函数参数列表
module的辅助函数
createNamespacedHelpers
App.vue
<template>
<div class="app">
<table border="1">
<h2>rootStore: {{ $store.state.counter }}</h2>
<h2>homeStore: {{ $store.state.home.counter }}</h2>
<button @click="increment">+1</button>
</table>
</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
const homeMapMutations = createNamespacedHelpers("home").mapMutations;
export default {
name: "App",
components: {},
setup() {
// 原来的方式
// const mutationsHome = mapMutations("home", ["increment"]);
// 现在的方式
const mutationsHome = homeMapMutations(["increment"]);
return {
...mutationsHome,
};
},
};
</script>
<style></style>
mapState的关于模块的重新封装
图解
useState.js
import { createNamespacedHelpers } from "vuex";
import { useMapper } from "./useMapper";
export function useState(moduleName, mapper) {
let mapperFn = null;
if (typeof moduleName === "string" && moduleName.length > 0) {
mapperFn = createNamespacedHelpers(moduleName).mapState;
}
return useMapper(mapper, mapperFn);
}
useGetters.js
import { useMapper, createNamespacedHelpers } from "./useMapper";
export function useGetters(mapper) {
let mapperFn = null;
if (typeof moduleName === "string" && moduleName.length > 0) {
mapperFn = createNamespacedHelpers(moduleName).mapGetters;
}
return useMapper(mapper, mapperFn);
}
nexttick
官方解释:将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。
背景问题
输出
- 现在我们点击一下按钮,希望获取这个文字的高度,但是我们发现,打印的是0,也就是初始的高度
- 第二次才打印上次按钮的高度,
- 我们现在希望,这个获取文字高度的执行,能够和点击按钮同步执行,也就是当这个dom元素变化之后在执行,
- 某种程度上来看,就是延迟到下一个周期执行,也就是nexttick的字面意思.
![Honeycam 2021-09-18 14-34-40](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-18 14-34-40.gif)
App_nextTick.vue
<template>
<div class="app">
<div class="msgStyle" ref="msgRef">
<p>{{ msg }}</p>
</div>
<button @click="addMsgContent">add</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "App",
components: {},
setup() {
const msg = ref("");
const msgRef = ref(null);
const addMsgContent = () => {
msg.value += "Hello World";
console.log(msgRef.value.offsetHeight);
};
return {
msg,
addMsgContent,
msgRef,
};
},
};
</script>
<style>
.msgStyle p {
width: 100px;
}
</style>
nextTick的基本使用
输出
- 现在就是做到了这个显示高度和DOM更新是同步的
![Honeycam 2021-09-18 14-39-52](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-18 14-39-52.gif)
和上面的对比
App_nextTick.vue
<template>
<div class="app">
<div class="msgStyle" ref="msgRef">
<p>{{ msg }}</p>
</div>
<button @click="addMsgContent">add</button>
</div>
</template>
<script>
import { ref, nextTick } from "vue";
export default {
name: "App",
components: {},
setup() {
const msg = ref("");
const msgRef = ref(null);
const addMsgContent = () => {
msg.value += "Hello World";
nextTick(() => {
console.log(msgRef.value.offsetHeight);
});
};
return {
msg,
addMsgContent,
msgRef,
};
},
};
</script>
<style>
.msgStyle p {
width: 100px;
}
</style>