Vue学习笔记
Vue简介:
- Vue是一个JavaScript框架
- Vue可以简化Dom操作
- Vue响应式数据驱动
第一个Vue程序(失败)
-
官网:https://cn.vuejs.org
-
vscode如何运行html(因为对vscode不熟悉)
-
安装Live Server插件
-
鼠标右键选择Open with Live Server (此处使用默认浏览器打开,且自动刷新页面)
-
跟着黑马学的,敲完代码后显示不出效果,正常来讲应该显示Hello Vue!,但是我的浏览器显示{{message}},看到弹幕说是因为这个代码是vue2的,现在vue3不支持这个语法,不知道是不是真的,现在决定换一个教学视频学习了。
-
创建Vue工程(成功)
创建步骤:
-
检查Node.js的版本
Node.js的版本必须高于15.0,win+r进入命令提示符查询:输入
node -v
,检查Node.js的版本。 -
切换路径
进入一个文件夹,此文件夹即为新创建的Vue项目所存放的位置。
-
开始创建项目
位置切换到此文件夹后,输入
npm init vue@latest
,创建Vue项目。 -
输入项目名称
输入完名称后回车,下面所有选项都选择No,所以直接一路回车就行。
-
启动项目
依次执行上图中三个绿色文字的命令即可。
先把路径切换到项目文件夹:
然后输入
npm install
:最后输入
npm run dev
,看到如下界面即项目启动成功:可以访问上图网址,看到如下页面:
-
总结:
至此,一个Vue项目就创建完成了,注意:以上步骤中,出现
WARN
警告不需要理会,直接跳过就行,只要不出现红色的ERROR
错误都可以不用管!
开发环境
官方推荐的IDE配置是Visual Studio Code
+ Volar
扩展。
Vue的项目目录结构
.vscode --- VSCode工具的配置文件夹
node_modules --- Vue项目的运行依赖文件夹
public --- 资源文件夹(浏览器图标)
src --- 源码文件夹
.gitignore --- git忽略文件夹
index.html --- 入口HTML文件
package.json --- 信息描述文件
README.md --- 注释文件
vite.config.js --- Vue配置文件
文本插值
最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法(即双大括号):
<template>
<h1>模板语法</h1>
<p>{{ msg }}</p>
</template>
<script>
export default{
data(){
return{
msg:"神奇的语法"
}
}
}
</script>
原始HTML
双大括号会将数据插值为纯文本,而不是HTML。若想插入HTML,需要使用v-html
指令。
<template>
<p>纯文本:{{ rawHtml }}</p>
<p v-html="rawHtml"></p>
</template>
<script>
export default{
data(){
return{
rawHtml:"<a href='https://itbaizhan.com'>百战程序员</a>"
}
}
}
</script>
属性绑定
给一个div绑定id,class,name,title等属性,需要使用 v-bind
属性。
<template>
<div v-bind:class="myClass" v-bind:id="myId">测试</div>
</template>
<script>
export default{
data(){
return{
myClass:"appclass",
myId:'appid'
}
}
}
</script>
简写:
因为 v-bind
使用率很高,所以提供了特定的简写语法:(省略v-bind,直接写冒号:即可)
<!-- : 相当于 v-bind: -->
<template>
<div :class="myClass" :id="myId">测试</div>
</template>
同时绑定多个属性值:
直接v-bind=一个对象,不需要冒号,对象里面定义了id,class等属性。
<template>
<div v-bind="objectOfAttrs">测试</div>
</template>
<script>
export default{
data(){
return{
objectOfAttrs:{
class:'appclass',
id:'appid'
}
}
}
}
</script>
条件渲染
在Vue中,提供了4个条件渲染,这类似于JavaScript中的条件语句。
v-if
v-else
v-else-if
v-show
<template>
<h1>条件渲染</h1>
<p v-if="flag">你能看见我吗</p>
<p v-else>那你还是看看我吧</p>
<p v-if="type==='A'">A</p>
<p v-else-if="type==='B'">B</p>
<p v-else-if="type==='C'">C</p>
<p v-else>Not A/B/C</p>
<p v-show="flag">你能看见我吗</p>
</template>
<script>
export default{
data(){
return{
flag:true,
type:"F"
}
}
}
</script>
v-if
VS v-show
:
v-if
是真实的按条件渲染,因为它确保了在切换时,条件区块内的事件监听和子组件都会被销毁与重建。
v-if
也是惰性的,如果在初次渲染时条件值为false,则不会做任何事。条件区块只有当条件首次变为true时才被渲染。
相比之下,v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有CSS display
属性会被切换。
总的来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show
比较好;如果在运行时绑定条件很少改变,则 v-if
会更合适。
列表渲染
我们可以使用 v-for
指令基于一个数组来渲染一个列表。v-for
指令的值需要使用 item in items
形式的特殊语法,其中 items
是源数据的数组,而 item
是迭代项的别名,此处 in
也可以用 of
代替,item of items
。
<template>
<h1>列表渲染</h1>
<p v-for="item in names">{{ item }}</p>
</template>
<script>
export default{
data(){
return{
names:["百战程序员","尚学堂","IT"]
}
}
}
</script>
复杂数据渲染:
<template>
<h1>列表渲染</h1>
<div v-for="item in result">
<p>{{ item.id }}</p>
<p>{{ item.name }}</p>
<img :src="item.avator" alt="图片">
</div>
</template>
<script>
export default{
data(){
return{
result:[
{
"id":111,
"name":"你好",
"avator":"https://foter.com/finishing-thoughts-eggshell-vs-satin-paint"
},
{
"id":222,
"name":"你好你好",
"avator":"https://foter.com/three-amazing-paris-boutique-hotels-for-design-lovers"
}
]
}
}
}
</script>
v-for遍历对象:
<template>
<h1>列表渲染</h1>
<!-- 这里展示出来是Jason 20 -->
<div v-for="item in userInfo">
{{ item }}
</div>
<!-- 这里展示出来是Jason---name---0 20---age---1 -->
<div v-for="(value,key,index) in userInfo">
{{ value }}---{{ key }}---{{ index }}
</div>
</template>
<script>
export default{
data(){
return{
userInfo:{
name:"Jason",
age:20
}
}
}
}
</script>
通过Key管理状态:
为了给Vue一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,需要为每个元素对应的块提供一个唯一的 key
:
<template>
<div v-for="(name,index) in names" :key="index">
{{ name }}
</div>
</template>
<script>
export default{
data(){
return{
names:["百战程序员","尚学堂","IT"]
}
}
}
</script>
温馨提示:
key
在这里是一个通过v-bind
绑定的特殊attribute推荐在任何可行的时候为
v-for
提供一个key
key
绑定的值期望是一个基础类型的值,例如字符串或number类型
key的来源:
一般不使用 index
作为 key
的值,我们要确保每一条数据的唯一索引不会发生改变。(推荐使用 id
作为 key
)
事件处理
我们可以使用 v-on
指令(可简写为 @
)来监听DOM事件,并在事件触发时执行对应的JavaScript。
用法:v-on:click="methodName"
或 @click="methodName"
。
<template>
<button v-on:click="addCount">Add</button>
<p>{{ count }}</p>
</template>
<script>
export default{
data(){
return{
count:0
}
},
methods:{
addCount(){
// 读取到data里面的数据的方法:this.count
this.count++
}
}
}
</script>
事件传参
<template>
<h1>事件传参</h1>
<div @click="showName(name)" v-for="name in names">{{ name }}</div>
</template>
<script>
export default{
data(){
return{
names:["Jack","Tom","Jason"]
}
},
methods:{
// 实现一个功能就是,当鼠标点击名字时,就在控制台打印名字
showName(name){
console.log(name)
}
}
}
</script>
传递参数过程中获取 event
:
Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。
<template>
<h1>事件传参</h1>
<!-- 传递参数的时候一定要使用$event,$符号不能省略 -->
<div @click="showName(name,$event)" v-for="name in names">{{ name }}</div>
</template>
<script>
export default{
data(){
return{
names:["Jack","Tom","Jason"]
}
},
methods:{
// 实现一个功能就是,当鼠标点击名字时,就在控制台打印名字
showName(name,e){
console.log(name,e)
}
}
}
</script>
数组变化侦测
引起UI自动更新:
<template>
<h1>数组变化侦测</h1>
<button @click="addName">添加</button>
<p v-for="(item,index) in names" :key="index">{{ item }}</p>
</template>
<script>
export default{
data(){
return{
names:["Jack","Tom","Jason"]
}
},
methods:{
// 此方法功能:鼠标点击后,在names数组后面添加一个元素Salen
addName(){
//push引起UI自动更新
this.names.push("Salen")
}
}
}
</script>
不会引起UI自动更新:
<template>
<h1>数组变化侦测</h1>
<button @click="concatArray">合并数组</button>
<h3>nums1</h3>
<p v-for="(item,index) in nums1" :key="index">{{ item }}</p>
<h3>nums2</h3>
<p v-for="(item,index) in nums2" :key="index">{{ item }}</p>
</template>
<script>
export default{
data(){
return{
nums1:[1,2,3,4],
nums2:[5,6,7]
}
},
methods:{
//此方法功能:合并数组
concatArray(){
//不会引起UI自动更新
//this.nums1.concat(this.nums2)
//必须赋值给this.nums1才会引起UI自动更新
this.nums1=this.nums1.concat(this.nums2)
}
}
}
</script>
还有很多操作数组的方法 ,可访问Vue官网查看详情!
计算属性
computed
模板中的表达式虽然方便,但也只能做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。因此推荐使用计算属性来描述依赖响应式状态的复杂逻辑。
<template>
<h1>计算属性</h1>
<h3>{{ itbaizhan.name }}</h3>
<p>{{ itbaizhanContent }}</p>
</template>
<script>
export default{
data(){
return{
itbaizhan:{
name:"百战程序员",
content:["Java","Python","前端"]
}
}
},
//计算属性
computed:{
itbaizhanContent(){
return this.itbaizhan.content.length > 0 ? 'Yes' : 'No'
}
}
}
</script>
计算属性缓存VS方法:
我们在表达式中调用一个函数也会获得和计算属性相同的结果,但是他们是有着本质区别的。
重点区别:
计算属性:计算属性值会基于其响应式依赖被缓存。一个计算属性只会在代码发生变化时才会重新计算。
方法:方法调用总是会在重渲染发生时再次调用函数。即每次都会调用,影响程序性能。
Class绑定
Vue专门为 class
的 v-bind
用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
Class绑定对象:
<template>
<h1>Class绑定</h1>
<div :class="classObject">你好,世界!</div>
</template>
<script>
export default{
data(){
return{
classObject:{
'text':true,
'border':true
}
}
}
}
</script>
<style>
.text{
color: green;
}
.border{
background-color:antiquewhite
}
</style>
侦听器watch
我们可以使用 watch
选项在每次响应式属性发生变化时触发一个函数。
<template>
<h1>侦听器</h1>
<p>{{ message }}</p>
<button @click="updateMessage">修改</button>
</template>
<script>
export default{
data(){
return{
message:"Hello"
}
},
methods:{
updateMessage(){
this.message = "World"
}
},
// 侦听器
watch:{
//newValue:改变之后的数据
//oldValue:改变之前的数据
//函数名必须与侦听的数据对象名保持一致
message(newValue,oldValue){
//数据发生变化,自动执行的函数
console.log(newValue,oldValue)
}
}
}
</script>
表单输入绑定 v-model
在前端处理表单时,我们常常需要将表单输入框的内容同步给JavaScript中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model
指令帮我们简化了这一步骤。
v-model
:就是将用户在表单中输入的信息与JS对象进行同步绑定,实时获取用户输入的信息。
<template>
<h1>表单输入绑定</h1>
<form>
<input type="text" v-model="message">
<p>{{ message }}</p>
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
</form>
</template>
<script>
export default{
data(){
return{
message:'',
checked:false
}
}
}
</script>
修饰符:
v-model
也提供了修饰符:.lazy
、.number
、.trim
。
.lazy
:默认情况下,v-model
会在用户输入的同时实时获取用户输入的信息,可以通过 .lazy
修饰符来改变,达到只有当input失去焦点或者用户按下回车键时,才获取用户输入的信息。
<template>
<h1>表单输入绑定</h1>
<form>
<input type="text" v-model.lazy="message">
<p>{{ message }}</p>
</form>
</template>
<script>
export default{
data(){
return{
message:''
}
}
}
</script>
模板引用(其实就是获取DOM)
虽然Vue的声明性渲染模型抽象了大部分对DOM的直接操作,但在某些情况下,我们仍然需要直接访问底层DOM元素。要实现这一点,我们可以使用特殊的 ref
属性。挂载结束后引用都会被暴露在 this.$refs
之上。
<template>
<h1>模板引用</h1>
<p ref="hello">{{ message }}</p>
<button @click="getElement">获取DOM</button>
</template>
<script>
export default{
data(){
return{
message:"你好"
}
},
methods:{
getElement(){
console.log(this.$refs.hello)
this.$refs.hello.innerHTML = "世界"
}
}
}
</script>
组件组成结构
<template>
<div>组件基础组成</div>
</template>
<script>
export default{}
</script>
<style>
</style>
组件引用步骤
<script>
//第一步:引入组件
import MyComponent from './components/MyComponent.vue';
export default{
//第二步:注入组件
components:{
MyComponent
}
}
</script>
<template>
<!-- 第三步:显示组件 -->
<MyComponent/>
</template>
<style>
</style>
在style中
scoped
的作用是:让当前样式只在当前组件中生效。
组件传递数据props
props
Parents.vue:
<template>
<h2>父母</h2>
<Child :message1="message1" :age="age" :names="names"/>
</template>
<script>
import Child from './Child.vue';
export default{
data(){
return{
message1:"父亲有100万",
age:60,
names:['Tom','Jack','Hah']
}
},
components:{
Child
}
}
</script>
Child.vue:
<template>
<h2>儿子</h2>
<p>{{ message1 }}</p>
<p>{{ age }}</p>
<ul>
<li v-for="(name,index) in names" key="index">{{ name }}</li>
</ul>
</template>
<script>
export default{
props:["message1","age","names"]
}
</script>
注意事项:
props
传递数据,只能从父级传递到子级,不能反其道而行!
学到后面发现其实
props
是可以实现从子级到父级的传递的,通过函数携带参数实现传递,但是呢,这里我自己有点没学太懂,而且暂时感觉也没什么必要,因为可以使用$emit
实现从子级传递到父级。(后面如果发现这个传递数据的方式很有必要且无可替代,再补学和补笔记)
组件事件$emit
在组件的模板表达式中,可以直接使用 $emit
方法触发自定义事件,目的是组件之间传递数据。实现将数据从子级传递到父级。
Parents.vue:
<template>
<h1>父元素</h1>
<!-- 自定义事件的名字就是someEvent,在Child组件中已经定义好了 -->
<Child @someEvent="getChild"/>
<h3>子元素传递的数据:{{ message }}</h3>
</template>
<script>
import Child from './Child.vue'
export default{
data(){
return{
message:""
}
},
components:{
Child
},
methods:{
getChild(data){
console.log("出发了",data);
this.message = data;
}
}
}
</script>
Child.vue:
<template>
<h1>子元素</h1>
<button @click="clickEvent">传递数据</button>
</template>
<script>
export default{
data(){
return{
message:"你好"
}
},
methods:{
clickEvent(){
// console.log("点击了按钮");
//向父元素传递数据
//第一个参数为自定义事件的名字,第二个参数为传递的数据
this.$emit("someEvent",this.message)
}
}
}
</script>
温馨提示:
组件之间传递数据的方案:
① 父传子:
props
② 子传父:自定义事件
this.$emit
组件事件配合v-model
和侦听器使用:
实现一个在Search
组件的输入框中输入文字,在Main
组件中实时显示出来。
Search.vue:
<template>
<h3>搜索页</h3>
搜索内容:<input type="text" v-model="search">
</template>
<script>
export default{
data(){
return{
search:""
}
},
//侦听器
watch:{
search(newValue,oldValue){
//数据发生变化,自动执行的函数
this.$emit("searchEvent",this.search)
}
}
}
</script>
Main.vue:
<template>
<Search @searchEvent="getSearch"/>
<h3>{{ message }}</h3>
</template>
<script>
import Search from './Search.vue'
export default{
components:{
Search
},
data(){
return{
message:""
}
},
methods:{
getSearch(data){
this.message = data;
}
}
}
</script>
插槽 slot
插槽就像是一个容器,用于在组件中插入内容,插槽提供了一种定制组件内部内容的方式,让你可以在不修改组件结构的情况下,改变组件的外观和行为。可以在使用组件时,根据自己的需求,灵活地定制组件内部的部分内容,使得组件更加通用和可复用。
<slot>
元素是一个插槽出口,标示了父元素提供的插槽内容将在哪里被渲染。
一个简单的插槽代码,认识一下插槽:
App.vue:
<template>
<SlotBase>
<h2>你好</h2>
<div>hello</div>
</SlotBase>
</template>
<script>
import SlotBase from "./components/SlotBase.vue";
export default{
components:{
SlotBase
}
}
</script>
SlotBase.vue:
<template>
<slot></slot>
<h1>插槽基础知识</h1>
<slot></slot>
</template>
<script>
</script>
展示效果:
插槽渲染作用域:
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身就是在父组件模板中定义的。
App.vue:
<template>
<SlotTwo>
<!-- 就是这个message这个数据,要写在父元素的data中 -->
<h3>{{ message }}</h3>
</SlotTwo>
</template>
<script>
import SlotBase from "./components/SlotBase.vue";
import SlotTwo from "./components/SlotTwo.vue";
export default{
components:{
SlotBase,
SlotTwo
},
data(){
return{
//此处message写在这里
message:"SlotTwo"
}
}
}
</script>
SlotTwo.vue:
<template>
<h1>插槽续集</h1>
<slot></slot>
</template>
<script>
</script>
具名插槽 v-slot
:
给一个插槽里面每一个 template
取一个名字,在子元素中用 name='取的名字'
取出来。
App.vue:
<template>
<SlotTwo>
<!-- 这里用v-slot:给插槽取名字,注意中间是冒号:,不是等于= -->
<template v-slot:slot1>
<h3>{{ message }}</h3>
</template>
<template v-slot:slot2>
<h3>你好</h3>
</template>
</SlotTwo>
</template>
<script>
import SlotBase from "./components/SlotBase.vue";
import SlotTwo from "./components/SlotTwo.vue";
export default{
components:{
SlotBase,
SlotTwo
},
data(){
return{
message:"SlotTwo"
}
}
}
</script>
SlotTwo.vue:
<template>
<h1>插槽续集</h1>
<!-- 这里使用name="slot1"来展示插槽的内容 -->
<slot name="slot1"></slot>
<slot name="slot2"></slot>
</template>
<script>
</script>
v-slot
有对应的简写#
,因此<template v-slot:slot1>
可以简写为<template #slot1>
。其意思就是将这部分模板片段传入子组件的slot1插槽中。
生命周期函数
创建期: beforeCreate
created
挂载期: beforeMount
mounted
更新期: beforeUpdate
updated
销毁期: beforeUnmount
unmounted
动态组件
实现点击一个按钮,可以切换展示不同的组件。
使用 <component :is="...">
在多个组件间做切换。
App.vue:
<template>
<h1>动态组件</h1>
<component :is="tabComponent"></component>
<button @click="clickComponent">切换组件</button>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
export default{
components:{
ComponentA,
ComponentB
},
data(){
return{
tabComponent:"ComponentA"
}
},
methods:{
clickComponent(){
this.tabComponent = this.tabComponent == "ComponentA" ? "ComponentB" : "ComponentA";
}
}
}
</script>
ComponentA.vue:
<template>
<h1>ComponentA</h1>
</template>
<script>
</script>
ComponentB.vue:
<template>
<h1>ComponentB</h1>
</template>
<script>
</script>
组件保持存活
当使用 <component :is="...">
来在多个组件间做切换时,被切换掉的组件会被卸载。我们可以通过 <keep-alive>
组件强制使被切换掉的组件仍然保持“存活”的状态。
<template>
<h1>动态组件</h1>
<!-- 这里使用<keep-alive>将组件包裹起来 -->
<keep-alive>
<component :is="tabComponent"></component>
</keep-alive>
<button @click="clickComponent">切换组件</button>
</template>
异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue提供了 defineAsyncComponent
方法来实现此功能。
添加异步组件总共分为两个步骤:
第一步:引入异步组件
import { defineAsyncComponent } from 'vue'
第二步:把B组件路径引入进来
const ComponentB = defineAsyncComponent(()=>
import('./components/ComponentB.vue')
)
App.vue:
<template>
<h1>动态组件</h1>
<keep-alive>
<component :is="tabComponent"></component>
</keep-alive>
<button @click="clickComponent">切换组件</button>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
// import ComponentB from './components/ComponentB.vue'
//添加异步组件分为两步
//第一步:引入异步组件
import { defineAsyncComponent } from 'vue'
//第二步:把B组件路径引入进来
const ComponentB = defineAsyncComponent(()=>
import('./components/ComponentB.vue')
)
export default{
components:{
ComponentA,
ComponentB
},
data(){
return{
tabComponent:"ComponentA"
}
},
methods:{
clickComponent(){
this.tabComponent = this.tabComponent == "ComponentA" ? "ComponentB" : "ComponentA";
}
}
}
</script>
依赖注入 provide
和 inject
我们使用 props
只能实现父辈到子辈的数据传递,现在我们要实现爷爷辈到孙子辈的数据传递就需要使用 provide
来传递,使用 inject
来接收。
App.vue:
<template>
<h1>爷爷辈</h1>
<Parent/>
</template>
<script>
import Parent from './components/Parent.vue'
export default{
components:{
Parent
},
// 爷爷的财产直接传给孙子,使用provide
provide:{
message:"爷爷的财产!!!"
}
}
</script>
Child.vue:
<template>
<h1>孙子辈</h1>
{{ message }}
</template>
<script>
export default{
// 孙子辈直接接收爷爷传递的数据,使用inject
inject:['message']
}
</script>
Vue应用
应用实例:
每个Vue应用都是通过 createApp
函数创建一个新的应用实例。
import {createApp} from 'vue'
const app = createApp({
/* 根组件选项 */
})
根组件:
我们传入 createApp
的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。
import {createApp} from 'vue'
//从一个单文件组件中导入根组件
import App from './App.vue'
const app = createApp(App)
挂载应用:
应用实例必须在调用了 .mount()
方法后才会渲染出来,该方法接收一个容器参数,可以是一个实际的DOM元素,或是一个CSS选择器字符串。
app.mount('#app')
<div id="app"></div>
公共资源:
在 src
目录下的 assets
文件夹的作用就是存放公共资源,例如:图片、公共CSS、字体、图标等。