Vue全家桶学习笔记_零基础入门到入坑:Vue篇
什么是Vue,什么又是框架为了便于理解,我将Vue概括为:1.Vue是一个主要关注视图层的渐进式框架,即Vue可以只运用到局部代码。2.并且Vue实现了MVVM双向绑定模式M:模型层,指js对象V:视图层,指DOMVM:链接视图和数据的中间件3.而且兼顾了React的虚拟DOM和Angular的模块化开发至于什么是框架,我的理解是:自动生成各种文件配置及相关代码的,并且提供了许多更简洁高效的开发方
前言
这是个人的学习笔记,用于自己复习的同时也希望能帮助到有需要的人
由于是初学,加上Vue的知识也不少,难免有错误和不足,希望大家谅解,也欢迎批评指正
什么是Vue,什么又是框架
为了便于理解,我将Vue概括为:
1.Vue是一个主要关注视图层的渐进式框架,即Vue可以只运用到局部代码。
2.并且Vue实现了MVVM双向绑定模式
M:模型层,指js对象
V:视图层,指DOM
VM:链接视图和数据的中间件
3.而且兼顾了React的虚拟DOM和Angular的模块化开发
至于什么是框架,我的理解是:
自动生成各种文件配置及相关代码的,并且提供了许多更简洁高效的开发方式的东西
另外,Vue读作view,而不是 微优易
================================================
完善的准备
需要安装下载的东西都写在这里了,根据需要自行节选阅读
Vue 引入
两种引入方式:
本地引入:去官网复制一份代码到本地然后js引入即可
CDN引入:加上以下代码
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
接下来的内容是暂时不会用到的,不过提前准备好呢也没有坏处(要是觉得麻烦就先跳过)
npm 安装
Node.js的包管理工具,经常用到
1.搜索Node.js去它的官网下载,然后安装即可(自动配置环境变量)
2.win+r输入cmd打开命令提示符(或者打开编译器终端)输入:
node -v
npm -v
检测版本号
如果出现版本号则说明安装成功
cnpm镜像加速器 安装
由于npm下载国外资源的时候可能会比较慢,所以我们还得专门弄一个“中国版”(镜像加速器)的:
npm install cnpm -g
以后每当遇到npm下载卡顿的时候,就可以用ctrl+c终止下载,然后用cnpm替代npm即可体验极速下载
Vue-Cli脚手架 安装
Vue-Cli才是真正开始Vue,新建项目自动配置上万个文件让萌新感受一下什么才叫大前端
cnpm install vue-cli -g
webpack 安装
代码打包工具,自动将工程代码转为ES5并且压成一行代码
cnpm install webpack -g
webpack-cli 安装
webpack都装了,那webpack顺便一起吧
npm install webpack-cli -g
axios 安装
其实也就是Vue版的ajax
npm install axios
vue-router 安装
路由
这个要在项目目录下安装
进入项目目录下,输入
npm install vue-router --save-dev
devtools 安装
这是一个浏览器插件,用于vue代码的调试,只需要在浏览器上安装就行了
开始使用Vue
创建Vue对象 以及 mustache语法
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
let vm = new Vue({
//绑定id为app的DOM元素,这也确定了Vue对象的作用范围
el:"#app",
//以键值对的形式存放数据
data:{
mes:"HelloWorld!"
},
//类似的,存放方法
methods: {
helloWorld: function(){
//访问对象的属性
alert(this.mes);
}
}
});
</script>
相应的html部分为:
其中{{}}就是mustache语法,括号里面可以写多个变量,也可以填表达式(不推荐这么做)
mustache语法不能用于标签的内部!
(不能出现<div {{name}}></div>这种或者其他类似的形式)
<div id="app">
<!-- {{}}会动态的去接收值
这就是mustache语法 -->
<h1>{{mes}}</h1>
<!-- 使用v-on:绑定事件,也可以简写为@ -->
<div v-on:click="helloWorld">点击我试试</div>
</div>
运行效果:
另外,vue3.0之后,建议将Vue对象中的data写为函数的形式:
data(){
return{
mes:"HelloWorld!"
}
},
这样写的话,多个与之关联的html元素都会有自己独立的作用域,避免了污染
循环v-for
记得每次都要引入Vue的js文件
为了方便阅读,后面就不再提醒了
<script>
let vm2 = new Vue({
el: "#app2",
data(){
return{
items: ["牛逼","天秀","妙绝"]
}
}
})
</script>
<ul id="app2">
<li v-for="i in items">{{i}}</li>
</ul>
运行结果,可以看到动态的创建了三个
v-for还可以支持多个参数
<div id="app">
<div v-for="(i, index) in arr">{{index}}----{{i}}</div>
<div v-for="(i, index, key) in arr">{{index}}----{{i}}----{{key}}</div>
<div v-for="(i, index, key) in obj">{{index}}----{{i}}----{{key}}</div>
</div>
从结果上看,第一个参数是值,第二个参数是键,在对象中还会有第三个参数是下标
分支v-if
<script>
let vm1 = new Vue({
el:"#app1",
data:{
num: 3
}
});
</script>
<div id="app1">
<div v-if="num == 1">num是1</div>
<div v-else-if="num === 2">num是2</div>
<div v-else>num为其他值{{num}}</div>
</div>
运行结果
单次变化v-once
<script>
let vm = new Vue({
el: '#app',
data(){
return{
msg: "hhhh"
}
},
methods: {}
})
</script>
<div id="app">
<div>变化:{{msg}}</div>
<div v-once>不会变化:{{msg}}</div>
</div>
什么叫做单次变化呢?
首先来看看结果
我们在浏览器的控制台上对值进行一个修改,此时我们就发现:
v-text和v-html
v-text其实还没有mustache语法{{}}好用…
但是还是要了解
相比之下,v-html就显得有用多了
<script>
let vm = new Vue({
el: '#app',
data(){
return{
msg:"<div style=\"color: red;\">123</div>"
}
},
})
</script>
<div id="app">
<div>{{msg}}</div>
<div v-text="msg"></div>
<div v-html="msg"></div>
</div>
运行结果也可想而知
预设v-pre
这个说实话应该猜都能猜到作用了,其实就是<pre></pre>的vue版本
这里我也不给vue对象的代码了(因为给了也没用)
<div id="app">
<div v-pre>预设不处理:{{msg}}</div>
</div>
有什么用呢?大概就是…基本上没什么用…
解析判定v-cloak
vue对象的data数据可能会来自服务器,那么网络不畅的时候就可能导致数据没有及时渲染到页面,就会出现满屏的{{}}这种mustache语法,让用户看了觉得十分诡异,为了判定Vue对象到底有没有解析完毕,就出现了v-cloak
<script>
let vm = new Vue({
el: 'app',
data(){
return{
msg:"hhhh"
}
}
})
</script>
<div id="app">
<div v-cloak>啊哈!{{msg}}</div>
</div>
请求成功和请求失败的状态:
没错,v-cloak就是在成功时消失,失败时保留
那么我们就可以利用css的属性选择器搞一手操作了
<style>
[v-cloak]{
display: none;
}
</style>
这样一来,失败时就不会显示mustache语法了
动态绑定v-bind
之前说过了,mustache不能用在标签的内部,也就是不能用在左右箭头的里面,那么问题来了:
对于这样的vue对象
<script>
let vm = new Vue({
el: '#app',
data(){
return{
myColor: "color:red;",
someOnesColor: "blue"
}
}
})
</script>
我却偏要这么写html(甚至还报错了):
很明显这个mycolor是没有解析成color:red;的(被当作了css)
此时我们就需要用到v-bind了(第二种写法是个语法糖)
<div id="app">
<div v-bind:style="myColor">啊哈</div>
<div :style="myColor">啊哈</div>
<div :style="{color: someOnesColor,}">啊哈</div>
</div>
这里提一句,如果返回多个值,也可以考虑用数组或者对象的形式发送/接受,
中间可以利用函数进行处理(当然不用也行)
diff算法
另外,vue或react中,一个dom对象默认有一个key值,需要用v-bind绑定一个唯一标志(不要用下标,因为下标会在排序等变化值发生改变),
这样一来在基于diff算法的动态变化(增删改排序)时,能够做到正确匹配每个元素
事件绑定v-on
<script>
let vm = new Vue({
el: '#app',
data(){
return{}
},
methods: {
sayhi(){
alert("好啊你真敢点我");
},
saywow(){
alert("这么说你很勇哦");
}
}
})
</script>
其中@是一个语法糖
<div id="app">
<div v-on:click="sayhi">点我一下你试试</div>
<div @click="saywow">这个彬彬就是逊啦</div>
</div>
运行结果
事件对象参数
另外,涉及到参数的一种情况
<script>
let vm = new Vue({
el: '#app',
methods:{
clickEvent1(args){
console.log(args);
},
clickEvent2(args){
console.log(args);
}
}
})
</script>
注意绑定的事件,都不传参数,但是一个加了括号一个没加括号
<div id="app">
<button @click="clickEvent1()">按钮1号</button>
<button @click="clickEvent2">按钮2号</button>
</div>
可见分别输出undefined和事件对象
所以如果不加括号时会自动传一个事件对象
那么问题来了,如果需要传参数的同时有需要事件对象该如何操作呢?
<script>
let vm = new Vue({
el: '#app',
methods:{
clickEvent3(num1, num2, event){
console.log(num1);
console.log(num2);
console.log(event);
}
}
})
</script>
注意,第三个参数前面加了一个$符号,表示传递事件对象,这样一来我们就可以在传递其他参数的通时传递事件对象了
<div id="app">
<button @click="clickEvent3(123, 456, $event)">按钮3号</button>
</div>
当然,事件对象参数的位置可以随意,但是约定俗成还是放末尾比较好
修饰符
还记得事件冒泡和事件捕获吧
<script>
let vm = new Vue({
el: '#app',
methods:{
fatherClick(){
console.log("我是父元素");
},
sonClick(){
console.log("我是子元素");
}
}
})
</script>
写了点style方便观察(懒得写class)
<div id="app" @click="fatherClick" style="background-color: red;height: 300px;text-align: center;">
<button @click="sonClick" style="margin-top:150px;">冒泡:OoOoOooOOOo</button>
</div>
当我们点击子元素时,触发子元素事件之后会触发父元素事件
这是因为子元素时父元素的一部分,所以点击子元素也相当于点击了父元素的一部分,也就触发了事件冒泡(事件触发自底向上)
在以前我们会采用在子元素的函数中利用事件对象的内置函数stopPropagation来阻止冒泡
sonClick(event){
event.stopPropagation();
console.log("我是子元素");
}
但是现在我们有了修饰符,允许我们在标签中写:
<button @click.stop="sonClick" style="margin-top:150px;">冒泡:OoOoOooOOOo</button>
另外,不只有事件绑定v-on才有修饰符,其他vue关键词也有
懂了概念和作用,那么使用修饰符也是得心应手,下面是一些常用的事件修饰符,这里不再赘述:
修饰符 | 作用 |
---|---|
.trim | 清除首尾空格 |
.stop | 阻止事件冒泡 |
.prevent | 阻止默认行为 |
.self | 只有元素本身能触发自己的事件 |
.once | 只能触发一次 |
.capture | 将事件冒泡调整为事件捕获(自顶向下触发) |
.keycode: | 监听键盘 |
.enter | 监听键盘是否按下enter键 |
.up(上下左右空格之类依葫芦画瓢) | 监听键盘是否按下↑ |
计算属性computed
虽然说mustache语法中的{{}}内可以进行一些如同字符串拼接的简单操作,但是遇到更多的计算时,不仅是视觉上会显得不美观,而且还存在无法正确运算的可能。
我们可以通过methods来解决这些问题,不过methods内的方法是动态的从而很容易被修改,存在被意外地污染的风险
这时候,为了防止这种事情发生,就出现了computed计算属性
最简单的使用大概就像以下这样
<script>
let vm = new Vue({
el: '#app',
data(){
return{
msg1: "hello",
msg2: "world"
}
},
computed:{
msgStrcat(){
return this.msg1 + " " + this.msg2
}
}
})
</script>
<div id="app">
{{msgStrcat}}
</div>
和函数有什么区别?
在mustache语法调用的时候不加(),因为这是一个属性而不是函数——这是形式上的区别,
那还有更多本质上的区别吗?
各位看官莫急,且看以下分析:
1.computed内的实际形式:
computed:{
msgStrcat:{
set(){
console.log("这是computed的set函数");
},
get(){
console.log("这是computed的get函数");
}
}
}
其中get和set也就是ES6中增加的内容,可以参考我的ES6学习笔记
2.被调用的时候:
<script>
let vm = new Vue({
el: '#app',
methods:{
strcat(){
console.log("这是methods的方法");
}
},
computed:{
msgStrcat:{
set(newValue){
console.log("这是computed的set");
},
get(){
console.log("这是computed的get函数");
}
}
}
})
</script>
然后是三次同时调用computed和methods
<div id="app">
<div>{{msgStrcat}}{{strcat()}}</div>
<div>{{msgStrcat}}{{strcat()}}</div>
<div>{{msgStrcat}}{{strcat()}}</div>
</div>
可以看见的是,computed只被触发了一次,这源于set的性质
没错!computed计算后的结果是放在缓存里面的,这样一来不仅避免了大量重复计算从而大大地提高了页面响应式加载的效率!
所以以后遇到那种几乎不变的值时,就可以考虑computed了(由于几乎不变,set也没什么用了…)
当然也别管那么多,以后遇到属性直接先莽computed!
显示v-show
本质是修改css中display的值
<script>
let vm = new Vue({
el: '#app',
data(){
return{
isShowed:true
}
}
})
</script>
<div id="app">
<div v-show="isShowed">显示</div>
<div v-show="!isShowed">没有显示</div></div>
</div>
和v-if的区别
v-show纵使不显示,那么标签也还在,只不过隐藏了
但是v-if是 移除或创建 标签
想一下vue为什么出现,不就是为了响应式动态加载避免频繁操作DOM吗,所以这里也是一个道理——在涉及到频繁切换的时候,我们就优先考虑v-show而不是v-if
双向绑定v-model
<script>
let vm = new Vue({
el: '#app',
data(){
return{
inputStr:""
}
}
})
</script>
<div id="app">
<input type="text" v-model="inputStr">
<div>您输入的是:{{inputStr}}</div>
</div>
双向绑定之后,两边就可以同步变化了
v-model的底层实现
<script>
let vm = new Vue({
el: '#app',
data(){
return{
msg: 666,
}
},
methods: {
valueChanged(event){
this.msg = event.target.value;
}
}
})
</script>
<div id="app">
<input type="text" :value="msg" @input="valueChanged">
<div>您输入的是:{{msg}}</div>
</div>
通过v-bind动态绑定和methods方法完成双向绑定
懂了这个原理,你就可以这么写:
<div id="app">
<input type="text" :value="msg" @input="msg = $event.target.value">
<div>您输入的是:{{msg}}</div>
</div>
组件化component
开始组件化开发
低耦合高内聚嘛
先来看看最简单的使用方式
<script>
// 创建
let vm = Vue.createApp({
data(){
return{}
}
});
// 定义全局组件
vm.component('button-count',{
data(){
return {
count: 0
}
},
template:`
<button @click="count ++;">你点击了这个按钮{{count}}次</button>
`
});
// 绑定
vm.mount('#app');
</script>
这里我们不再采用Vue2中使用new来创建Vue对象的形式,而是采用了Vue3中的createApp方法。
这里需要注意的是,引入的文件需要更改为:
<script src="https://unpkg.com/vue@next"></script>
接下来我们就可以这样写了
(组件化中标签的写法有一定的讲究)
组件命名要么像buttonCount,要么像button-count
<div id="app">
<button-count></button-count><br><br>
<button-count></button-count><br><br>
<button-count></button-count>
</div>
效果如下:
由于data部分我们使用的函数形式,所以能够返回独立的函数作用域,因此每个点击按钮的数值可以相互独立;
反之,如果不采用函数形式的data,那么所有值都会同步。
全局组件
我们之前使用的都是全局组件
在介绍它有什么特点之前,先来看看以下代码:
<script>
// 创建
let vm = Vue.createApp({
data(){
return{}
}
});
// 定义全局组件
vm.component('cool-title',{
template:`
<h1 style="color:red;background-color:pink;">炫酷的标题</h1>
`
})
vm.component('button-count',{
template:`
<cool-title></cool-title>
<button>嘿,兄贵快来点我</button>
`
});
// 绑定
vm.mount('#app');
</script>
在一个Vue对象内定义多个组件的时候,它们之间可以相互调用
上述代码中,第二个组件内就调用了第一个组件
效果如下:
局部组件
先看代码
<script>
// 定义局部组件
let title = {
data(){
return {
title: "炫酷的标题"
}
},
template:`
<h1 style="color:red;background-color:pink;">{{title}}</h1>
`
}
let button = {
template:`
<cool-title></cool-title>
<button>嘿,兄贵快来点我</button>
`
}
// 创建
let vm = Vue.createApp({
data(){
return{}
},
components:{
"cool-title":title,
"button-count":button
}
});
vm.mount('#app');
</script>
没错,是声明一个类似于对象的东西,最后在创建Vue对象实例的时候才塞到components内,
这个时候我们还要给他们自定义一个名字构成键值对
效果如下
没错,如果你仔细看了代码,你会发现第二个组件内调用了第一个组件,但是从效果上来看并没有起到作用。
这就是局部组件,只能在components中引入后才能使用
所以如果硬要使用,就得像这样写:
let button = {
components:{
"niubi-title":title
},
template:`
<niubi-title></niubi-title>
<button>嘿,兄贵快来点我</button>
`
}
其他形式:
这两种形式都是可以的,引入方式都一样
<script type="text/template" id="coolTitle">
<h1 style="color:red;background-color:pink;">酷炫的标题</h1>
<button>嘿,兄贵快来点我</button>
</script>
<template id="goodTitle">
<h1 style="color:red;background-color:pink;">酷炫的标题</h1>
<button>嘿,兄贵快来点我</button>
</template>
使用:
let button = {
components: {
},
template:`#coolTitle
`,
}
注意这时候对象中的template中就不能再书写其他东西了(包括前置空格),不然总是会出一些稀奇古怪的问题
组件通信
父组件向子组件传递props
props其实就是参数
<script>
let box = {
props: ['p'],
template:`
<h1 style="color:red;">{{p}}</h1> `
}
let vm = Vue.createApp({
data(){
return{}
},
components:{
"great-box":box,
}
})
vm.mount('#app')
</script>
<div id="app">
<great-box :p="6666"></great-box>
</div>
效果如下
约束
可以通过如下形式对参数传递的内容进行约束,
当内容不符合约束时,控制台会给出相应的警告
let box = {
props: {
p: String
},
template:`
<h1 style="color:red;">{{p}}</h1>
`
}
当然,也有更完善的约束形式:
let box = {
props: {
p: {type:String, required:true, default:'哈哈哈哈'}
},
template:`
<h1 style="color:red;">{{p}}</h1>
`
}
props命名规范
props中比如写一个
coolBrand,那么在html部分进行绑定时就要写成cool-brand,原因是html不区分大小写
no-props
不使用props传递内容
<script>
let vm = Vue.createApp({
data(){
return{
price: 5
}
},
components: {
"box": {
template:`
<h1>
<div>吴彦祖?哦,是镜子啊</div>
</h1>`
}
}
}).mount("#app");
</script>
<div id="app">
<h1>当前的价格是{{price}}</h1>
<box ababa="阿巴阿巴" style="width: 200px;height: 200px;background-color: #faa;"></box>
</div>
课件,最外层的h1继承了模板标签的各项属性
需要注意的是,在以下两种情况中,继承无法完成:
第一种:inheritAttrs:false
components: {
"box": {
inheritAttrs:false,
template:`
<h1>
<div>吴彦祖?哦,是镜子啊</div>
</h1>`
}
}
第二种:模板内最大父级元素不唯一 (并且不做处理时)
此处指的两个h1标签,都是最大的父级元素
components: {
"box": {
template:`
<h1>
<div>吴彦祖?哦,是镜子啊</div>
</h1>
<h1>
<div>古尔丹?哦,是镜子啊</div>
</h1>
`
}
}
在第二种情况中,如果还是想要继承,
可以使用用 v-bind:="$attrs" :
components: {
"box": {
// inheritAttrs:false,
template:`
<h1 :="$attrs">
<div>吴彦祖?哦,是镜子啊</div>
</h1>
<h1 :="$attrs">
<div>古尔丹?哦,是镜子啊</div>
</h1>
`
}
}
另外,我们还可以选择其中一部分属性继承
components: {
"box": {
// inheritAttrs:false,
template:`
<h1 :a="$attrs.ababa">
<div>吴彦祖?哦,是镜子啊</div>
</h1>
<h1 :style="$attrs.style">
<div>古尔丹?哦,是镜子啊</div>
</h1>
`
}
}
需要注意的是,这样具体到某一个属性的时候,v-bind需要加上一个名字(盲猜之前没加的时候都是自动把$attrs解构了)
子组件向父组件传递
子组件向父组件传递的方式是通过自定义事件
<script>
let box = {
template:`
<button style="color:red;" @click="boxClick">我是子组件,快来点我</button>
`,
methods: {
boxClick(){
console.log("子组件发出了信息");
//1.this.$emit使得boxClick方法可以作为事件触发
//(注意当做事件使用时,命名规则形如box-click)
this.$emit('boxClick');
}
}
}
let vm = Vue.createApp({
data(){
return{}
},
components:{
"great-box":box,
},
methods: {
response(){
console.log("父组件表示收到");
}
}
})
vm.mount('#app')
</script>
<body>
<div id="app" style="width: 200px;height: 200px;background-color: #faa;">
<!-- 2.把方法当事件用,绑定父组件中的一个方法 -->
<great-box @box-click="response"></great-box>
</div>
来一步一步分析:
1.子组件自定义一个方法,这个方法内部有一个this.$emit()是用于触发自定义事件的,它的存在使得这个函数可以作为一个事件来使用。其中值是作为事件时的名称,可以随意。
2.在模板标签上用1中所构成的自定义事件来绑定一个父组件的方法
3.触发子组件的自定义事件,则父组件的函数也会触发
效果:
$refs,$parent和$root
<script>
//这个box就是子组件对象
let box = {
template:`
<div style="background-color: #faa;width:200px;height:200px">
</div>
`
}
let vm = Vue.createApp({
components:{
"box":box
},
methods: {
getChildComponent(){
//this.$refs访问到所有具有ref属性的子组件对象
console.log('啊哈',this.$refs);
}
}
})
vm.mount('#app')
</script>
给父组件下的元素添加一个ref属性,就是给个名字
点击button调用方法,通过this.$refs访问到父组件下的具有ref属性的子组件对象
<div id="app">
<box ref="牛蛙牛蛙"></box>
<box ref="牛蛙牛蛙2"></box>
<box></box>
<button @click="getChildComponent">别来点我</button>
</div>
效果:
由于是对象,所以也可以通过这样的语句去进一步地访问:
console.log(this.$refs.牛蛙牛蛙);
至于$parent和$root,前者是访问最近的父级,后者是访问最远的父级,用法同上,不再赘述
插槽slot
简单使用
一个组件在一个项目中可能会被反复用。但是问题是,我们可能会根据使用该组件的环境来对其进行一个调整,可是组件无法更改,我们也不想重新再写一个组件。就这样,插槽出现了。
<script>
let box = {
template: `
<div style="background-color: #faa;width: 250px;height: 250px;float:left">
<h1>好消息好消息!</h1>
<slot></slot>
<p>机会有限</p>
<p>先到先得</p>
</div>
`
}
let vm = Vue.createApp({
data(){
return{}
},
methods: {},
components: {
'box': box
}
});
vm.mount('#app');
</script>
<div id="app">
<box></box>
<box></box>
<box></box>
</div>
如果我们要对其进行修改,那么就在组件标签中填入相应的内容,这些内容会依次匹配slot
<div id="app">
<box>
<div style="width: 200px;height: 50px;background-color: #fff;">
<h1>hhhhh</h1>
</div>
</box>
<box>
牛逼
</box>
<box>
<div style="font-size: 32px;color: #fff;">6666</div>
</box>
</div>
插入之后就是这样了
如果需要默认内容的话,只要在slot标签里面写好就行了
具名插槽
也就是具有名字的插槽的意思。前面提到的都是匿名插槽,内容会自动匹配模板中的slot标签,这也意味着可能出现匹配错误的情况。为了准确的引导匹配,我们可以在slot标签中加上一个name属性,然后在插入的标签上加上v-slot。
let box = {
template: `
<div style="background-color: #faa;width: 250px;height: 250px;float:left">
<h1>好消息好消息!</h1>
<slot name="dalao"></slot>
<p>机会有限</p>
<p>先到先得</p>
</div>
`
}
注意v-slot只能运用在template标签或者components中
<template v-slot:dalao>
<box>
<div style="font-size: 32px;color: #fff;">6666</div>
</box>
</template>
注意v-slot的值不加引号。另外,和v-on一样,v-slot可以简写为#
渲染作用域
什么是渲染作用域?看看下面的代码就知道了:
<script>
let box = {
data(){
return {
flag: false
}
},
template:`
<div style="background-color:green;width:200px;height:200px"></div>
`
}
let vm = Vue.createApp({
data(){
return{
flag: true
}
},
components: {
'box': box
}
})
vm.mount('#app')
</script>
上图中,子组件和父组件都有一个值为flag,但是分别为false和true。
那么假如像下图这样访问flag值,究竟会不会显示呢?
<div id="app">
<box v-show="flag"></box>
</div>
竟然是会显示的,解释是,在模板外访问flag值时,优先使用父级组件的值。
那么,什么又是在模板内访问flag值呢?
let box = {
data(){
return {
flag: false
}
},
//template里面使用就是模板内使用
template:`
<div v-show="flag" style="background-color:green;width:200px;height:200px"></div>
`
}
这样就会只访问组件自身的值
(如果自身没有,并不会向上访问父组件,反而会给出警告)
总结起来就是,在模板外使用访问父组件的值,内部访问子组件自身的值
那问题来了,如果要在模板外使用,却依旧需要子组件的值呢?
作用域插槽
作用域插槽就是为了解决上述问题而诞生的
实际上就是一个v-bind与slot结合使用的产物,如果想通了v-bind的作用的话可以不用刻意去学作用域插槽
<script>
let box = {
data(){
return {
datas: ['123','456','789']
}
},
//slot标签通过v-bind绑定data属性,并把datas赋值给data
//后面通过v-slot:dalao1=""来接收
template:`
<div style="background-color:green;width:300px;height:300px">
<h1>广告位招商啦~</h1>
<slot name="dalao1" :data="datas"></slot>
<h1>买得早的都抱富(婆)了</h1>
</div>
`
}
let vm = Vue.createApp({
data(){
return{}
},
components: {
'box': box,
}
})
vm.mount('#app')
</script>
这里dataReceiver类似于一个参数,可以自由命名,其data属性(就是在模板里面绑定那个data)保存了传递的值
(#dalao1是v-slot:的简写)
<div id="app">
<box></box>
<box>
<template #dalao1="dataReceiver">
<div style="font-size: 32px;color: #fff;">
<ul>
<li v-for="i in dataReceiver.data">{{i}}</li>
</ul>
</div>
</template>
</box>
</div>
效果如下
另外,如果不给slot一个name值,那么在使用v-slot时,写成v-slot:default=""就好了,这样就会自动匹配
组件进阶
动态组件
嘛…也没啥好说的,还是类似于一个模板
<script>
let vm = Vue.createApp({
data(){
return {
boxOne: 'cool-box',
boxTwo: 'handsome-box'
}
},
//就是一个组件,名字都写明白了,然后替换掉is属性绑定的那个组件
template:`
<component :is="boxOne"></component>
`
})
vm.component('cool-box',{
template: `
<h1>很炫酷的box</h1>
`
})
vm.component('handsome-box',{
template: `
<h1>十分帅气的box</h1>
`
})
vm.mount('#app')
</script>
异步组件
异步组件实际上也就是大家所想的那样
只是语法上要注意一下就是了
实现方式很多,只要保证结果是“在需要的时候才加载”就行了
<script>
let vm = Vue.createApp({
data(){
return{}
},
methods: {}
});
vm.component('box',Vue.defineAsyncComponent(()=>{
return new Promise((resolve, reject)=>{
resolve({
template: `
<div>hhhh</div>
`
})
})
}));
vm.mount('#app')
</script>
当然还有其他实现的形式:
vm.component('box2',
() => import('')
);
这种方法简洁一些,但实际上还是一个意思
组件生命周期
钩子函数
为什么叫钩子函数?大概就是说,钩在某个组件上的函数,这个函数会在组件创建到销毁过程中的某个阶段触发
使用方式类似于下面这样:
<script>
let vm = Vue.createApp({
beforeCreate(){
console.log('beforeCreate!!!!');
}
});
</script>
beforeCreate顾名思义就是创建之前执行的函数。
其他常用的生命周期钩子函数还有:
阶段 | 名称 | 作用 |
---|---|---|
初始化 | beforeCreate | 创建之前执行 |
初始化 | created | 创建之后执行 |
初始化 | beforeMount | 渲染(挂载)之前执行 |
初始化 | mounted | 渲染(挂载)之后执行 |
更新 | beforeUpdate | 数据更新之前执行 |
更新 | updated | 数据更新之后执行 |
销毁 | beforeUnMount | app.unmount(’#app’)(DOM销毁)之前执行 |
销毁 | unMounted | app.unmount(’#app’)(DOM销毁)之前执行 |
(unmount方法可以手动结束组件的生命周期,但是不建议操作组建的生命周期)
侦听器watch
侦听数据,当其发生变化时,触发相应的操作
<script>
let vm = Vue.createApp({
data(){
return{
price: 5
}
},
watch:{
//第一个参数是当前的值,第二个参数是之前的值
price(cur, pre){
console.log("price变化了")
console.log("现在的值是",cur)
console.log("以前的值是",pre)
}
}
}).mount("#app");
//注意,这里的挂载!
//之前都是单独写一个vm.mount("#app")
//只有这样写才能用vm.price访问
</script>
那么问题来了,这个和computed有什么区别呢?
通俗地说,区别在于:
watch支持异步,不缓存,并且可以接受两个参数;
computed不支持异步,默认缓存;
更多的区别在于:
watch默认为浅度观测(对多层嵌套,只观测其最外层)…
computed在调用时需要在模板中渲染,且默认为深度依赖(对多层嵌套,观测所有)…
分别什么时候用?
计算属性就用computed;
异步、复杂的操作 或者 一个值得变化会引起一堆值变化的情况 就用 watch
特效动画
(略)
两种使用特效动画的方式,
一种是通过动态修改class等来添加动画,这个就是css3的内容,不在话下;
另一种就是Vue封装的特效动画。
不过这个东西个人感觉相对次要,这里先不做笔记,以后再说
Vue高阶API
自定义指令
前面我们用到了v-if等指令,现在我们来自定义指令
比如我们来自定义一个叫做v-tianxiu的指令
<script>
let vm = Vue.createApp({
data(){
return{}
},
methods: {
},
directives:{
tianxiu: {
// 里面写生命周期钩子
mounted(el){
el.focus();
console.log('执行成功了');
}
}
}
})
//这样写也可以
//也可以写个局部的然后挂载到组件上
// vm.directive('tianxiu',{
// mounted(el){
// el.focus()
// console.log('执行成功')
// }
// }
vm.mount("#app")
</script>
这样一来,在组件挂载后就会执行这个v-tianxiu
也就是 自动聚焦+输出“执行成功了”
<div id="app">
<input type="text" placeholder="请输入一点东西" v-tianxiu>
</div>
自定义事件最多可以有四个参数
传送门teleport
就和c/c++的goto差不多一个意思
components: {
'box': {
template: `
<div class="box">这是box</div>
`
},
'container': {
template: `
<teleport to=".box">
<div class="mask">是container组件的部分</div>
</teleport>
<div class="container">这是container</div>
`
}
}
这里box和container是两个不相干的标签
<div id="app">
<box></box>
<container></container>
</div>
但是通过teleport传送,让container的部分传送到了box内
并且是放到box内原有的DOM元素之后
组件式API
概要
本来应该放到Vue高阶API中的,但是由于过于重要,所以单独提出来
之前我们在Vue2.x中的写法:
<script>
let vm = Vue.createApp({
data(){},
methods: {},
computed: {},
watch: {},
components: {},
directives: {}
})
vm.mount()
</script>
这样把,不同的内容分类放到不同的选项里面就是选项式API
但是如果后期业务量庞大起来,一个功能的相关内容会分布到各个位置,维护起来就十分麻烦。
所以Vue3.x推出了组件化API,把一个业务的内容放到一个地方
(以下图片来自网络)
这是选项式API
这是组件式API
接下来我们开始使用组件式API
其中,改动了不少生命周期函数(删了一些又增了一些)
新的生命周期钩子
setup()
取代了beforeCreate()和created(),
并且不建议再使用data()等(虽然可以并存)
<script>
let vm = Vue.createApp({
setup(props, context){
return {
msg: 'hello compositionAPI',
sayHi: ()=>{
console.log(this,'hhh');
}
}
},
template: `
<div>
<h1>{{msg}}</h1>
<button @click="sayHi">点击一下</button>
</div>
`
}).mount("#app")
</script>
这里注意setup是创建之前触发,此时this指向的是window
更多生命周期钩子变化如下:
选项式API | 组件式API |
---|---|
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
其中onRenderTracked是每次页面刷新后重新收集响应式依赖时,
然后onRenderTriggered是每次页面重新渲染时
需要注意,除了setup之外,其余的都要写到setup里面,比如:
const app = Vue.createApp({
setup(props, context){
let name = '张三丰'
const {onMounted} = Vue
onMounted(()=>{
console.log('挂载成功')
})
return {
name
}
},
}).mount("#app")
ref
在过去选项式API里面,data()中的数据会被处理从而能达到响应式的效果,
但是组件式API里面就不会自动处理
要是没了方便的响应式效果,那么框架也失去了一大亮点
所以我们需要一个东西来实现基础数据的响应式,于是乎ref就诞生了
<script>
let vm = Vue.createApp({
setup(props, context){
//解构赋值,从Vue对象里拿到ref
const {ref} = Vue
return {
//像这样处理基础数据
msg: ref('hello compositionAPI'),
sayHi: ()=>{
console.log(this,'hhh');
}
}
},
template: `
<div>
<h1>{{msg}}</h1>
<button @click="sayHi">点击一下</button>
</div>
`
}).mount("#app")
</script>
原理是通过proxy代理,**包装成proxy({value:…})**的形式
至于多次提到的proxy代理到底是什么,后面再细嗦
另外,需要注意,如果在代码中对msg的值进行修改,需要写成msg.value的形式
(在页面上不用这么写是因为底层会自动帮你转化为msg.value)
reactive
其实作用和ref差不多,就是约定俗成地,在对 数组对象等引用类数据 进行响应式处理地时候,我们不使用ref,而使用reactive
(其实ref也可以,处理引用类数据地时候,ref底层还是会自动使用reactive,不过这样一来效率就低多了,所以不推荐)
唯一需要注意的是,reactive没有value属性,访问值的时候需要这样写:
let {computed, ref, reactive} = Vue
let num1 = reactive({one: 1, two: 2})
let show = ()=>{
console.log(num1)//proxy代理
console.log(num1.value)//undefined
//把num1作为了一个对象
console.log(num1.one,num1.two)
}
用法都一样,这里就不再赘述了
readonly
顾名思义咯,只读
防止数据传递传着传着就变了
const {readonly} = Vue
const myName = readonly(ref('吴彦祖'))
toRefs
在解构赋值的时候比较常用
比如,解构赋值一个具有响应式能力的对象时,
解构后的数据就成为了普通的数据而不再具有响应式能力
如果我们希望其继续拥有响应式能力,那么就要使用到toRefs()
为了方便阅读,往后尽量只给出关键的代码
const {ref, toRefs, reactive} = Vue
let person = reactive({name:"古尔丹", job:"大酋长"})
//解构赋值后的name和job也会拥有响应式的能力
let {name, job} = toRefs(person)
原理还是proxy代理,把代理的对象拆开,给属性代理
toRef
本质还是把里面的东西丢给代理
相关语法如下:
const {ref, toRefs, reactive,toRef} = Vue
let age = toRef(person, 'age').value = 233
这里需要两个参数,第一个是对象名称,第二个是该对象的属性名称
toref返回一个proxy代理,也就有value值
和toRefs的差异大概也就和名字意义的差异一般
这里不得不说一下ref(),reactive(),toRef(),toRefs()的区别
ref()深拷贝,获取数据时需要加上.value
reactive()深拷贝,不需要加.value(ref是在reactive的基础上实现的)
toRef()浅拷贝,获取数据时需要加上.value
toRefs()浅拷贝,获取数据时需要加上.value
context
context上下文
就是setup的参数
setup(props, context){
const {ref, toRefs, reactive,toRef} = Vue
let {attrs, slots, emit} = context
console.log("这是attrs:",attrs)
console.log("这是slots:", slots)
console.log("这是emit:", emit)
console.log("props:",props)
},
attrs在前面no-props提到过,也就是继承的属性,
slots是插槽
emit在前面组件通信提到过,是发射的事件
props是v-bind绑定传过来的参数
computed
没错就是那个computed,现在又来了
不过这里需要引入一下
作用也没变,都是把东西放到缓存
let {computed, ref} = Vue
let num1 = ref(233)
let num2 = computed(()=>{
return num1.value * 10;
})
watch
还是侦听器,和以前一样
let vm = Vue.createApp({
setup(props, context){
let {computed, ref, reactive,watch} = Vue
let myName= ref('')
//监听myName
watch(myName,(cur, pre)=>{
console.log("现在的值:", cur)
console.log("过去的值:",pre)
})
return {
myName
}
},
template:`
<input type="text" placeholder="请输入你的姓名" v-model="myName">
<div>我的名字是:{{myName}}</div>
`,
}).mount("#app")
如果监听多个值的话,写成如下形式即可
watch([myName,myAge],([curName, curAge], [preName, preAge])=>{
console.log("现在的值:", curName, "和", curAge)
console.log("过去的值:",preName, "和", preAge)
})
另外,由于监听的对象不能是一个对象的属性,那么如果要监听对象的属性的话,需要这么写:
watch(()=>person.name,(cur,pre)=>{
console.log("")
})
用一个函数返回一手,就可以变成一般的数据类型了(当然你可以解构之类的)
watch还有第三个参数,再说这个之前先了解watch的性质:
1.默认惰性(浏览器打开不会自动触发,要等到相关组件开始活动才触发,比如输入框要输入后才触发)
2.可调度(可以选择是否有惰性以及是否深度监视)
(tip:浅度监视是只监视如一个对象Obj1,发生如同Obj1 = Obj2这种操作Obj1本身发生的变化,会忽视对象内部的变化;相反地,深度监视就是会监视对象的所有变化)
具体语法:
watch(()=>person.name, (cur, pre)=>{
console.log("现在的值:", cur)
console.log("过去的值:", pre)
},{
//是否立即执行(是否取消惰性)
immediate: true,
//是否深度监视
deep: false
})
watchEffect
还是侦听器,不过默认深度监视,没有惰性,并且不能查询变化以前的值
watchEffect(()=>{
console.log('watchEffect开始监听:')
console.log('myName:', myName.value)
console.log('myAge', myAge.value)
})
provide与inject
由于Vue是单向数据流,孙组件要用父组件内的数据的话,得先经过子组件,也就是:
父组件—>子组件----->孙组件
如果嵌套层级过多,那么数据传递效率就会相当慢
或者,两个毫不相干的组件之间传递数据的…
除了组件通信中提到的方法,这里还可以使用provide和inject
<script>
const grandson = {
template:`
<div style="background-color:red;">我是孙组件</div>
`
}
const son = {
components: {
'grandson': grandson
},
template: `
<div style="background-color:orange">
我是子组件
<grandson></grandson>
</div>
`
}
const father = Vue.createApp({
setup(props, context){
return {
fatherMsg: '我是father'
}
},
components: {
'son': son
},
template: `
<div style="background-color:yellow">
我是父组件
<son></son>
</div>
`
}).mount("#app")
</script>
如果需要跨越性传递数据:
使用provide向所有内部组件提供数据
setup(props, context){
const {ref, provide} = Vue
//注意这里是value
let fatherMsg = ref('我是father').value
provide('fatherMsg', fatherMsg)
return {
fatherMsg
}
使用inject接收数据
setup(props, context){
const {inject} = Vue
let grapaMsg = inject('fatherMsg', '第二个参数:默认值')
return {
grapaMsg
}
},
ref获取真实DOM
注意这个ref不是之前的ref,而是写在标签里面作为属性的ref
<div id="app">
<h1 ref="mark">我被ref标记了!</h1>
</div>
const app = Vue.createApp({
setup(props, context){
const {ref, onMounted} = Vue
//这样一来,mark在app被挂载之后就会获得真实DOM
//下面这个ref纯属是为了让mark获得响应式
let mark = ref('')
onMounted(()=>{
console.log(mark.value)
})
return {
mark
}
},
}).mount("#app")
后记
到这里Vue篇大概也算完结了,不过我也会继续修改补充完善内容。
考虑到使用频率,部分没有提及的Vue知识点会在后续的Vue-Cli篇中补充。
感谢各位的阅读。
更多推荐
所有评论(0)