about
vue3是渐进式 JavaScript 框架,也是目前默认的vue版本,是现在和未来的主流。
安装
前提:已安装 16.0 或更高版本的 Node.js,安装教程:<cnblogs.com/Neeo/articles/11637320.html>
C:\Users\zhangkai>node -v
v16.14.2
C:\Users\zhangkai>npm -v
8.7.0
你可以直接通过cdn的形式在html中引入:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
hello world
如下示例主要通过一个简单示例,来快速上手一下vue3:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>{{ msg }}</p>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
// 1. 创建配置对象
const configObj = { // 配置对象
// 写法1
data(){
// 必须return一个对象
return {
msg: "hello world1",
}
},
// 写法2
// data: function () {
// return {
// msg: "hello world2",
// }
// }
}
// 2. 根据配置对象创建app应用
// createApp,创建出来一个应用,然后通过mount挂在到容器中,进行关联
Vue.createApp(configObj).mount('#app'); // 可以这么写
// 也可以通过变量接收,干点其它的,比如use其它组件
let app = Vue.createApp(configObj);
app.mount('#app')
</script>
</html>
vite
vite中文文档:https://cn.vitejs.dev/
关于node版本
Vite 需要 Node.js 版本 14.18+,16+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
下载安装
不需要下载安装!!!
使用vite+vue创建vue项目
这家伙依赖npm版本,不同版本命令不太一样:
# npm 6.x
npm create vite@latest my-vue-app --template vue
# npm 7+, 需要额外的加两个短横线:
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
来个示例:
# 首先确认下npm的版本
D:\tmp\studyvue3>npm -v
8.7.0
# 1. 创建vue项目
# 我的npm版本是7+,所以,我可以在命令行执行下面的命令,创建一个名为 v3d1 的vue项目
# 如果遇到有个 ok 什么什么process(y)的提示,输入y即可,没有这个提示的话,就算了
D:\tmp\studyvue3>npm create vite@latest v3d1 -- --template vue
Scaffolding project in D:\tmp\studyvue3\v3d1...
Done. Now run:
cd v3d1
npm install
npm run dev
# 2. 按照要求cd到项目根目录去,然后安装该项目的依赖
D:\tmp\studyvue3>cd v3d1
D:\tmp\studyvue3\v3d1>npm install
added 32 packages in 2s
# 3. 启动vue项目,默认监听的是本机5173端口
D:\tmp\studyvue3\v3d1>npm run dev
> v3d1@0.0.0 dev
> vite
VITE v3.1.3 ready in 309 ms
➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
额外的,如果你想指定端口启动,可以修改vue项目根目录下的vite.config.js
:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: "0.0.0.0",
port: 8822,
open: true, // 启动后是否自动在浏览器中打开
}
})
然后执行npm run dev
启动就行了,如果是在项目运行时修改的,会自动重启,超级快!
后续的代码,我基本上都会在这个项目中进行演示,毕竟不用来回手动刷新页面了。
自定义IP和端口
vite+vue项目中,可以有几种自定义端口的方式,我们一起来看看。
方式1: 通过命令行指定ip和端口
另外的指定端口的方式就是命令行了:
D:\tmp\studyvue3\v3d1>npm run dev -- --port 8882 --host 0.0.0.0
> v3d1@0.0.0 dev
> vite "--port" "8882" "--host" "0.0.0.0"
VITE v3.1.3 ready in 364 ms
➜ Local: http://localhost:8882/
➜ Network: http://192.168.221.1:8882/
➜ Network: http://192.168.233.1:8882/
➜ Network: http://192.168.8.113:8882/
方式2:修改vue项目根目录下的vite.config.js
文件
这个vite.config.js
文件位于项目根目录下,如果不存在就创建。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: "0.0.0.0",
port: 8822,
open: true, // 启动后是否自动在浏览器中打开
}
})
然后执行npm run dev
启动就行了。
模板语法
v-bind
v-bind
指令动态的绑定标签属性的内容,包括标签自带的属性和自定义属性,样式这些都可以。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 没有通过v-bind绑定的属性,就是普通属性,你写啥就是啥-->
<p><a href="url">百度</a></p>
<!-- 通过v-bind绑定后,就可以和数据进行绑定了,那么对应的属性也是动态的了-->
<p><a v-bind:href="url">百度</a></p>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
const configObj = { // 配置对象
// 写法1
data(){
// 必须return一个对象
return {
url:"https://www.baidu.com"
}
}
}
let app = Vue.createApp(configObj);
app.mount('#app')
</script>
</html>
v-html
模板语法会将数据解释为普通文本,而非HTML代码,有些情况下,需要输出真正的html,我们可以通过v-html
指令来做。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<div>{{ h2 }}</div>
<div v-html="h2">{{ h2 }}</div>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
const configObj = { // 配置对象
data(){
return {
"h2": "<h2>二级标题</h2>"
}
}
}
let app = Vue.createApp(configObj);
app.mount('#app')
</script>
</html>
在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用
v-html
,并且永远不要使用用户提供的 HTML 内容。
使用JavaScript表达式
模板语法中支持JavaScript表达式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h3>示例1</h3>
<p>age: {{ age }}</p>
<button @click="changeAge">使用方法</button>
<!-- 模板语法中支持JavaScript表达式的写法-->
<button @click="age-=1">使用JavaScript表达式</button>
<h3>示例2</h3>
<p>{{ name }}</p>
<p>{{ name.split("").reverse().join('') }}</p>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
const configObj = { // 配置对象
data(){
// 必须return一个对象
return {
age:1,
name: "张开"
}
},
methods: {
changeAge(){
this.age += 1
}
}
}
let app = Vue.createApp(configObj);
app.mount('#app')
</script>
</html>
v-once
v-once
将插值动作只执行一次,即数据再次发生改变时,该v-once
绑定的插值出的内容不再发生变化。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>{{ msg }}</p>
<p v-once>{{ age }}</p>
<p>{{ age }}</p>
<button @click="changeAge">点我</button>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
const configObj = { // 配置对象
data(){
return {
msg: "hello world1",
age: 1,
}
},
methods: {
changeAge(){
this.age += 1
}
}
}
let app = Vue.createApp(configObj);
app.mount('#app')
</script>
</html>
配置对象
data
methods
注意:在methods中,定义方法,请避免使用箭头函数形式,因为这会组织Vue绑定恰当的this指向。
计算属性:computed
vue提供计算属性(computed),用来创建一个变量保存data中的数据计算结果。
src/App.vue
:
<script>
export default {
data(){
return {
num1: 0,
num2: 0,
}
},
computed:{
// 虽然计算属性的定义看着是函数形式,但本质上还是一个属性,所以调用时不要加括号
// 这里先学简单的简写形式,对计算属性有个了解
// 常用写法1,这种方式用的还比较多
total(){
console.log('computed被调用了')
return parseFloat(this.num1) + parseFloat(this.num2)
}
// 常用写法2
// total: function () {
// return parseFloat(this.num1) + parseFloat(this.num2)
// }
},
methods:{
getTotal(){
console.log('getTotal方法被调用了')
return parseFloat(this.num1) + parseFloat(this.num2)
}
}
}
</script>
<template>
<div>
<h1>计算属性</h1>
<input type="text" v-model="num1">
<input type="text" v-model="num2">
<p>第一种方法:使用js计算,不推荐<span>{{ parseFloat(this.num1) + parseFloat(this.num2) }}</span></p>
<h3>注意通过F12观察函数和计算属性被调用几次,就知道为啥推荐使用计算属性不推荐使用函数了</h3>
<h4>函数</h4>
<p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
<p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
<p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
<h4>计算属性</h4>
<p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
<p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
<p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
</div>
</template>
<style>
</style>
通过示例,我们也能看出使用计算属性和使用方法二者的区别了:
- 对于方法调用,你使用了几次方法调用,这个方法就被调用了几次。
- 对于计算属性,无论使用了几次计算属性调用,计算属性的get方法只调用了一次,其原因就是计算属性内部是走了缓存,所以效率高。
- 另外,对于计算属性中用到的数据发生变化,那么计算属性也会同步发生更新。
计算属性的getter和setter
默认的,每个计算属性都有两个函数:
- getter函数,即在模板语法中使用该计算属性时,真正执行的函数。
- setter函数,当设置或者修改计算属性时,执行的函数,但是希望你永远不要用到这个函数,因为推荐你将计算函数当做只读属性来处理。所以,在上面的示例中,我们都是直接写的简写形式。
虽然不推荐使用setter函数,这里也来看看完整的计算属性怎么写:
<script>
export default {
data(){
return {
num1: 0,
num2: 0,
}
},
computed:{
// 计算属性的完整写法,每个计算属性都包含setter和getter函数
total: {
get(){ // getter函数的固定写法,不需要接收参数
console.log('计算属性total的getter函数被调用了')
return parseFloat(this.num1) + parseFloat(this.num2);
},
set(newValue){ // setter函数的固定写法,接收一个参数
console.log('计算属性total的setter函数被调用了', newValue, typeof newValue);
this.num1 += parseFloat(newValue);
this.num2 += parseFloat(newValue);
}
}
// 上面演示了计算属性的完整写法,但通常用得最多的还是下面的简写形式
// total(){
// console.log('computed被调用了')
// return parseFloat(this.num1) + parseFloat(this.num2)
// }
// 常用写法2
// total: function () {
// return parseFloat(this.num1) + parseFloat(this.num2)
// }
},
methods:{
getTotal(){
console.log('getTotal方法被调用了')
return parseFloat(this.num1) + parseFloat(this.num2)
}
}
}
</script>
<template>
<div>
<h1>计算属性</h1>
<input type="text" v-model="num1">
<input type="text" v-model="num2">
<p>第一种方法:使用js计算,不推荐<span>{{ parseFloat(this.num1) + parseFloat(this.num2) }}</span></p>
<h3>注意通过F12观察函数和计算属性被调用几次,就知道为啥推荐使用计算属性不推荐使用函数了</h3>
<h4>函数</h4>
<p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
<p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
<p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
<h4>计算属性</h4>
<p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
<p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
<p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
<br>
<button @click="this.total='1'">点我让每个num值加1</button>
</div>
</template>
<style>
</style>
让我们牢记官网的建议:
- 不要在计算函数中做异步请求或者更改dom。
- 一个计算属性的声明中描述的是如何根据其他值派生一个值。因此计算函数的职责应该仅为计算和返回该值。从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。
侦听器:watch
侦听器也叫监听器,或者叫做监听属性或者侦听属性,主要用来监听data中指定属性,当属性的值发生变化就调用相应的方法。
监听属性是一个对象,它的键是要监听的对象或者变量,值一般是函数,当侦听的data数据发生变化时,会自定执行的对应函数,这个函数在被调用时,vue会传入两个形参,第一个是变化前的数据值,第二个是变化后的数据值。
<script>
export default {
data(){
return {
num1: 0,
num2: 0,
total2: "", // 被监听的属性一定要存在,不存在监听谁啊,但属性值是什么不确定,后面根据监视给它赋值
}
},
computed:{
total(){
console.log('computed被调用了')
return parseFloat(this.num1) + parseFloat(this.num2)
}
},
watch: {
// 写法1,监听属性的简写形式,用的不多
// num1(newValue, oldValue){
// console.log('监听属性', newValue, oldValue)
// this.total2 = parseFloat(newValue) + parseFloat(this.num2)
// }
// 写法2,写起来相对复杂,但支持的姿势多,所以用的最多
num1: { // 这是个配置对象
// 配置对象的属性必须叫做handler,因为后面的函数名可以变
// 注意是handler,不是handle,小心写错了
// handler:function () {} // 通常不这么写
// 当数据发生改变的时候会自动调用handler
handler(newValue, oldValue){
console.log('监听属性', newValue, oldValue)
this.total2 = parseFloat(newValue) + parseFloat(this.num2)
},
//重要的:配置这个配置项的作用是无论监视到属性发生不发生变化,都要强制的执行一次回调
// 这里它解决了初次访问页面时,不显示值的问题
immediate: true
}
// 能同时监听多个属性,这里不再多表
}
}
</script>
<template>
<div>
<h1>监听属性</h1>
<input type="text" v-model="num1">
<input type="text" v-model="num2">
<p>计算属性computed</p>
{{ total }}
<p>监听属性watch</p>
{{ total2 }}
<br>
<button @click="this.num1+=1">点我让num1的值加1</button>
</div>
</template>
<style>
</style>
上例同时用了watch和computed两个,实际上,解决示例的需求,只需要computed就行了,使用watch是表示我(watch)也能行。
那么watch和computed的区别是啥呢?
- computed是计算属性,一般适用于没有这个值,但想要使用这个值,且这个值是根据已有的数据计算出来的,这样的场景。
- watch是监视属性,它要监视的属性必须存在,以及后期要修改的属性也必须存在。
- 通常能用computed的场景都可以使用watch,但是,能使用watch解决的问题,你用computed不一定能解决。
其次,我们比较computed和watch的时候,其实比较的是计算属性的get方法,而计算属性的set,仅仅是对计算的属性添加了监视,即当计算属性的值被修改时,会调用set,可以参考计算属性部分setter函数部分的示例。
最后,也是最重要的区别,computed中只能使用同步,而watch当中可以使用同步,也可以是异步,来看个简单示例:
<script>
export default {
data(){
return {
n2: "before"
}
},
computed:{
n1(){
let a = "before"
setTimeout(()=>{
a = "after"
}, 2000)
return a
}
},
watch: {
n2:{
// 注意是handler,不是handle,小心写错了
handler(newValue, oldValue){
setTimeout(()=>{
this.n2 = "after"
}, 2000)
},
// 必须加上immediate配置项
immediate: true
}
}
}
</script>
<template>
<div>
<h3>在computed中使用异步,不行</h3>
<p>{{ n1 }}</p>
<h3>在watch中使用异步,可以</h3>
<p>{{ n2 }}</p>
</div>
</template>
<style>
</style>
补充:在watch、computed、metheds中的this都指向vue的实例化对象。
扩展:
- vue控制的所有回调函数的this都是vue的实例化对象。
- 所有data/computed/method中的属性/方法都会成为vue的实例化对象的属性/方法。
- 模板中表达式读取数据,是读取的vue的实例化对象的属性,模板中调用的方法,调用的是vue的实例化对象的方法。
深度监听:deep
vue面试中也有一个常问的点,那就是深度监视/深度监听,什么意思呢?上面关于监听的例子都是监听data中第一季的某个属性值,那么如果这个属性是个对象,而我们想要监听这个对象中某个属性怎么办?按照上面的写法是做不到的,所以我们需要使用deep来声明下,深度监听。
下面的示例展示了监听、深度监听的几种常见写法,以及区别:
<script>
export default {
data(){
return {
obj1: "张开1",
obj2: { name: '张开2', age: 18, info:{addr:"北京"} },
obj3: { name: '张开3', age: 18, info:{addr:"北京"} },
obj4: { name: '张开4', age: 18, info:{addr:"北京"} },
}
},
watch: {
obj1: {
handler(newValue){
console.log('obj1', newValue);
this.obj1 = 'after 张开1'
},
// 也可以根据需要决定是否需要使用immediate
// immediate: true
},
// 默认的,监听器不会监听对象内部是否有变动
obj2:{
handler(newValue){
console.log('obj2', newValue);
this.obj1.info.addr = 'after 北京啦啦啦'
},
// 也可以根据需要决定是否需要使用immediate
// immediate: true
},
// 通过设置deep为true来开启深度监听,即监听器会递归的为对象中的每个属性,给其加上监听器
obj3:{
handler(newValue){
// 传过来的newValue是个完整更新后的对象
console.log('obj3', newValue);
// this.obj3.name = newValue.name
// this.obj3.info.addr = "after 上海"
},
// 也可以根据需要决定是否需要使用immediate
// immediate: true
// deep的值是布尔值,表示是否开启深度监听
deep: true
},
// 通过设置deep为true来开启深度监听,但如果我们只需要对对象中某一个属性进行监听,那么上面obj3那个监听器就有点浪费了
// 所以,可以通过下面这种字符串的形式对对象中指定属性进行监听,性能较好
"obj4.info.addr":{
handler(newValue){
// 传过来的newValue是个完整更新后的对象
console.log('obj4', newValue);
// this.obj3.info.addr = "after 上海"
},
// 也可以根据需要决定是否需要使用immediate
// immediate: true
// 无论是监听对象中的指定属性还是递归监听所有属性,都需要声明deep
deep: true
}
}
}
</script>
<template>
<div>
<p>默认的监听器监听data中顶层的属性</p>
<button @click="obj1='after zhangkai1'">name</button> {{ obj1 }}
<br>
<p>默认的监听器监听data中顶层的属性,对于data中属性是对象的,对于对象内部的属性变动不会监听,下面的示例虽然结果有变动,但是监听器的obj2部分代码没有执行</p>
<button @click="obj2.info.addr='after 北京'">addr</button> {{ obj2.info.addr }}
<br>
<p>通过设置deep为true来开启深度监听,即监听器会递归的为对象中的每个属性,给其加上监听器,慎用这种方式,比较消耗资源,因为它为对象中的每个属性都加上了监听器</p>
<button @click="obj3.name='after 张开3'">name</button> {{ obj3.name }}
<button @click="obj3.info.addr='after 北京'">addr</button> {{ obj3.info.addr }}
<br>
<p>通过字符串的形式,为对象中指定属性添加监听器,消耗较小,下面的示例虽然name值也变动了,但监听器代码没执行,而只有点击addr那个按钮时监听器才执行</p>
<button @click="obj4.name='after 张开4'">name</button> {{ obj4.name }}
<button @click="obj4.info.addr='after 北京'">addr</button> {{ obj4.info.addr }}
<br>
</div>
</template>
<style>
</style>
ref
vue中的ref属性通常有两个作用:
- ref应用于普通标签,则可以通过
this.$refs.ref值
来获取标签的原始dom对象。 - ref应用于组件,那么就可以通过ref来进行组件间通信。
ref应用于普通标签
<script>
export default {
data(){
return {}
},
methods: {
handleClick(){
// 注意,在标签定义ref时,属性名是ref,在通过this对象操作时,就要使用$refs了
console.log(this.$refs) // 获取所有具有ref属性的标签对象
console.log(this.$refs.input1) // 根据标签中的ref属性对应的值获取到该标签的原生dom对象
console.log(this.$refs.input1.value) // 然后就可以执行dom操作
this.$refs.input1.value = 123; // 能获取值,也就是能赋值
}
}
}
</script>
<template>
<div>
<h1>ref应用于普通标签</h1>
<!-- 直接在标签上填写ref和对应的值,这个值的名字任意起 -->
<input type="text" ref="input1">
<input type="text" ref="input2">
<button @click="handleClick">点我</button>
</div>
</template>
<style>
</style>
用法比较简单。
ref应用于组件间通信