Vue3学习01 Vue3核心语法
Vue3学习
- 1. Vue3
- 新的特性
- 2. 创建Vue3工程
- 2.1 基于 vue-cli 创建
- 项目文件说明
- 2.2 基于 vite 创建
- 具体操作
- 项目文件说明
- 2.3 简单案例(vite)
- 3. Vue3核心语法
- 3.1 OptionsAPI 与 CompositionAPI
- Options API 弊端
- Composition API 优势
- ⭐3.2 setup
- 小案例
- setup返回值
- setup 与 Options API 的关系
- setup语法糖
- 3.2 ref
- ref 创建:基本类型的响应式数据
- ref 创建:对象类型的响应式数据
- 3.3 reactive
- reactive 创建:对象类型的响应式数据
- 3.4 ref 对比 reactive
- 区别
- 使用原则
- 3.5 toRefs 与 toRef
- 3.6 computed
- 3.7 watch
- 情况一
- 情况二
- 情况三
- 情况四
- 情况五
- 3.8 watchEffect
- 3.9 标签中的ref属性
- 回顾TS
- vue文件路径 @
- 3.10 props使用
- reactive和泛型一起用
- 示例
- 3.11 生命周期
- 3.12 自定义hooks
1. Vue3
新的特性
-
Composition API
(组合API
):-
setup
-
ref
与reactive
-
computed
与watch
…
-
-
新的内置组件:
-
Fragment
-
Teleport
-
Suspense
…
-
-
其他改变:
-
新的生命周期钩子
-
data
选项应始终被声明为一个函数 -
移除
keyCode
支持作为v-on
的修饰符…
-
2. 创建Vue3工程
2.1 基于 vue-cli 创建
点击查看官方文档
备注:目前
vue-cli
已处于维护模式,官方推荐基于Vite
创建项目
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version## 安装或者升级你的@vue/cli
npm install -g @vue/cli## 执行创建命令
vue create vue_test## 随后选择3.x
## Choose a version of Vue.js that you want to start the project with (Use arrow keys)
## > 3.x
## 2.x## 启动
cd vue_test
npm run serve
具体步骤:
① 项目名字不可以大写英文
②
③ 进入文件夹运行
项目文件说明
- /src/main.js 入口文件
import { createApp } from 'vue'
/* 这里引入的不再是vue构造函数了,在vue2中 import Vue from 'vue'
vue3 引入的是一个名为 creatApp 的工厂函数
区别是 vue 构造函数需要通过new调用,工厂函数可以直接调用creatApp
*/import App from './App.vue'// 直接调用 createApp函数
// 把外壳组件 App 传递进去
// #app是页面上容器的 id
createApp(App).mount('#app')
/*
不简写:
const app = createApp(App) 创建应用实例对象app 类似vue2中的vm,但app更轻
app.mount('#app') 挂载
*//*
vue2写法:
new Vue({el: '#app', //容器
})或者
new Vue({render:(h)=>h(App)
}).$mount('#app)或者
const vm = new Vue({render:(h)=>h(App)
})vm.$mount('#app)*/
- vue3组件中可以没有 根标签
2.2 基于 vite 创建
vite
是新一代前端构建工具,官网地址:https://vitejs.cn。
vite
的优势如下:
轻量快速的热重载(HMR
热重载的意思是改代码之后局部刷新),能实现极速的服务启动。
对 TypeScript
、JSX
、CSS
等支持开箱即用。
真正的按需编译,不再等待整个应用编译完成。
webpack
构建 与 vite
构建对比图如下:
webpack 分析路由、模块=>进行打包=>服务器准备(8080)。所以我们每次执行npm run serve时都需要等待一段时间(等的就是打包的过程)
vite先打开服务器
具体操作
(点击查看官方文档)
## 1.创建命令
npm create vue@latest## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript? Yes
## 是否添加JSX支持
√ Add JSX Support? No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development? No
## 是否添加pinia环境
√ Add Pinia for state management? No
## 是否添加单元测试
√ Add Vitest for Unit Testing? No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality? Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting? No
具体步骤:
Need to install the following packages:
create-vue@3.10.2
Ok to proceed? (y) y
项目文件说明
.vscode/extensions.json 官方推荐的插件
public/favicon.ico 页签图标
src/ 源代码文件
.gitignore git的忽略文件
env.d.ts 该文件只有一一行/// <reference types="vite/client" />
且飘红,飘红的原因是当前没有 node_modules(没有依赖),在命令行 npm i 安装,重新打开 vs code就不红了。 .ts文件不认识 .jpg .txt,这个文件的目的就是让ts认识他们。
index.html 入口文件(与webpack区别)
package.json 、package-lock.json 包管理文件
REANME.md可以删除
tsconfig.app.json, tsconfig.json, tsconfig.node.json是ts的配置文件
vite.config.ts 整个工程的配置文件
src文件夹说明
入口文件 index.html把 /src/main.ts 引入
- main.ts
import './assets/main.css' // 引入样式import { createApp } from 'vue' // 创建应用(花盆)
import App from './App.vue' // 组件(植物的根)createApp(App).mount('#app')
//挂载到 index.html 的 app div里面,
总结:
Vite
项目中,index.html
是项目的入口文件,在项目最外层。- 加载
index.html
后,Vite
解析<script type="module" src="/src/main.ts"></script>
指向的typescript
。 Vue3
中是通过createApp
函数创建一个应用实例。
2.3 简单案例(vite)
注意:当前笔记及之后 都是使用 vite 创建的工程
这个案例证明了vue3可以写vue2的代码
① <script lang="ts">
这里写明是 ts
- /components/Person.vue
<template><div class="person"><h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="showTel">点击查看联系方式</button></div>
</template><script lang="ts">
export default {name: 'Person',data() {return {name: '张三',age: 18,tel: '11111111111111'}},methods: {changeName(){this.name = 'zhang-san'},changeAge(){this.age++},showTel(){alert(this.tel)}},
}
</script><style>
.person {background-color: skyblue;
}
button {margin: 0 5px;
}
</style>
- /App.vue
对person组件,①引用import ②注册components ③使用
<template><div class="haha"><h1>你好啊</h1><Person></Person></div>
</template><script lang="ts">import Person from './components/Person.vue'export default {name: 'App',components: {Person}}
</script><style>
.haha{background-color: pink;
}
</style>
3. Vue3核心语法
3.1 OptionsAPI 与 CompositionAPI
Vue2
的API
设计是Options
(配置)风格的。Vue3
的API
设计是Composition
(组合)风格的。
Options API 弊端
选项式/配置式写法
学习vue2就是在学习配置项 data、methods、components。
Options
类型的 API
,数据、方法、计算属性等,是分散在:data
、methods
、computed
中的,若想新增或者修改一个需求,就需要分别修改:data
、methods
、computed
,不便于维护和复用。
Composition API 优势
可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
⭐3.2 setup
setup
是Vue3
中一个新的配置项,值是一个函数,它是 Composition API
“表演的舞台”,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup
中。
小案例
- 下文案例的问题:数据不是响应式(之后会讲)
注意事项:
① setup中的函数没有维护 this。在setup函数中 console.log(this),值为undefined
② setup函数的时机:setup比vue2中的 beforeCreate时机早(之后会讲)
<template><div class="person"><h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="showTel">点击查看联系方式</button></div>
</template><script lang="ts">
export default {name: 'Person',// data() {// return {// name: '张三',// age: 18,// tel: '11111111111111'// }// },// methods: {// changeName(){// this.name = 'zhang-san'// },// changeAge(){// this.age++// },// showTel(){// alert(this.tel)// }// },setup() {// 数据// 如果这么简单的定义数据,那就不是响应式数据// 响应式:后续数据变化 页面上也变化let name = '张三'let age = 18let tel = '1388888'// 方法function changeName(){// this.name = '???'// 这样写是错的,vue在setup中没有维护this关键字 // console.log(this) undefinedname = 'zhang-san'}function changeAge(){age += 1}function showTel(){alert(tel)}// 返回数据和函数return {name,age,changeName,changeAge,showTel}}
}
</script><style>
.person {background-color: skyblue;
}
button {margin: 0 5px;
}
</style>
setup返回值
setup的返回值是对象,把数据、方法交出去
setup() {...return {name,age,changeName,changeAge,showTel}
}
setup的返回值如果是一个函数的话,会直接渲染页面内容
setup(){...return function() {'哈哈'}
}或者
setup(){...return () => '哈哈' //因为setup不维护this 所以可以直接简写为箭头函数
}
setup 与 Options API 的关系
面试题
① data 、methods 、setup可以同时存在
② Vue2
的配置(data
、methos
…)中可以访问到 setup
中的属性、方法。(因为setup是最早的生命周期,当data去读数据的时候,setup已经有数据了)
③ 但在setup
中不能访问到Vue2
的配置(data
、methos
…)。
④ 如果与Vue2
冲突,则setup
优先。
<template><div class="person"><h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><hr><h2>测试:data读到setup中的数据{{ b }}</h2><h2>测试:setup中无法读到data中的数据</h2></div>
</template><script lang="ts">
export default {name: 'Person',data() {return {// 测试a: 100,b: this.name,d: 900}},setup() {let name = '张三'let age = 18let tel = '1388888'// 测试// let s = d // Cannot find name 'd'. return {name,age}}
}
</script>
setup语法糖
setup
函数有一个语法糖,这个语法糖,可以让我们把setup
独立出去,代码如下:
<template><div class="person"><h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="showTel">点击查看联系方式</button></div>
</template><script lang="ts">
export default {name: 'Person'
}
</script><script lang="ts" setup>let name = '张三'let age = 18let tel = '1388888'// 方法function changeName(){ name = 'zhang-san'}function changeAge(){age += 1}function showTel(){alert(tel)}
</script>
扩展: 上述代码,还需要编写一个不写setup
的script
标签,(在开发者工具)去指定组件名字,比较麻烦,我们可以借助vite
中的插件简化。
-
第一步:
npm i vite-plugin-vue-setup-extend -D
(D是开发依赖的意思) -
第二步:
vite.config.ts
中引入: import VueSetupExtend from ‘vite-plugin-vue-setup-extend’
-
第三步:
<script setup lang="ts" name="Person">
这样就只用一个script标签了:
<template><div class="person"><h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="showTel">点击查看联系方式</button></div>
</template><script lang="ts" setup name="Person2222">let name = '张三'let age = 18let tel = '1388888'// 方法function changeName(){ name = 'zhang-san'}function changeAge(){age += 1}function showTel(){alert(tel)}
</script>
如果【不】添加这个插件,同时也只写一个script标签,开发者工具中的组件名字和文件名保持一致。
3.2 ref
上文案例中的数据方法:此时数据不是响应式的
vue2中,数据都存放在data里面,只要把数据放在data里面,数据就是响应式,会自动数据代理、数据劫持。
在vue3中,有两个东西定义响应式数据 :① ref ②reactive
ref 创建:基本类型的响应式数据
作用: 定义响应式变量。
语法: 先引入 import {ref} from 'vue'
哪个数据是响应式就用ref包起来 let xxx = ref(初始值)
。
ref() 返回值: 一个RefImpl
的实例对象,简称ref对象
或ref
,ref
对象的value
属性是响应式的。
注意点:
JS
中操作数据需要:xxx.value
,但模板中不需要.value
,直接使用即可。- 对于
let name = ref('张三')
来说,name
不是响应式的,name.value
是响应式的。
<script lang="ts" setup name="Person">// 引入refimport {ref} from 'vue'// 哪个数据是响应式就用ref包起来let name = ref('张三')let age = ref(18)let tel = '1388888'let address = '北京昌平区'console.log('name',name);console.log('age',age);console.log('tel',tel);console.log('address',address);
</script>
name、age是 RefImpl 的实例对象
目前先知道 下划线的属性都不是给我们用的,value才是给我们用的。而且模板(HTML)内自动帮我们解析了value,所以模板中使用的时候不需要 {{name.value}}使用,直接{{name}}就可以。但是在JS代码中需要写 name.value
ref 创建:对象类型的响应式数据
- 其实
ref
接收的数据可以是:基本类型、对象类型。 - 若
ref
接收的是对象类型,内部其实也是调用了reactive
函数。
<template><div class="car"><h2>一辆{{ car.brand }}车,价值{{ car.price }}万</h2><button @click="changePrice">修改汽车价格</button></div><hr><h2>游戏列表</h2><ul><li v-for="item in games" :key="item.id">{{item.name}}</li></ul><button @click="changeFirstGame">修改游戏</button>
</template><script lang="ts" setup name="Person">import {ref} from 'vue'let car = ref({brand: '奔驰', price: 100})console.log(car); // 这个对象let games = ref([{id:'001', name:'王者荣耀'},{id:'002', name:'原神'},{id:'003', name:'三国志'}])function changePrice(){car.value.price += 10}function changeFirstGame(){games.value[0].name = '星穹铁道'}
</script>
3.3 reactive
reactive 创建:对象类型的响应式数据
作用:定义一个响应式对象(基本类型不要用它,要用ref
,否则报错)
语法: 引入reactive import {reactive} from 'vue'
哪个对象想变成响应式就包裹住哪个let 响应式对象= reactive(源对象)
。
源对象可以是 对象、数组、函数
返回值: 一个Proxy
的实例对象,简称:响应式对象。
注意点:reactive
定义的响应式数据是 深层次 的。
<template><div class="car"><h2>一辆{{ car.brand }}车,价值{{ car.price }}万</h2><button @click="changePrice">修改汽车价格</button></div>
</template><script lang="ts" setup name="Person">import {reactive} from 'vue'let car = reactive({brand: '奔驰', price: 100})console.log(car); // 这个对象function changePrice(){car.price += 10}
</script>
打印这个car:
这个Proxy是window上就有的一个函数 Proxy。数据藏在 target 里面
3.4 ref 对比 reactive
宏观角度看:
1、ref
用来定义:基本类型数据、对象类型数据;
2、reactive
用来定义:对象类型数据。
区别
1、ref
创建的变量必须使用.value
(可以使用volar
插件自动添加.value
)。
2、reactive
重新分配一个新对象,会失去响应式(可以使用Object.assign
去整体替换)。
注意:在ref中整体替换就是可以的。因为 ref 的响应式体现在 car.value,这个值变了 响应式可以检测到。
使用原则
1、若需要一个基本类型的响应式数据,必须使用ref
。
2、若需要一个响应式对象,层级不深,ref
、reactive
都可以。
3、若需要一个响应式对象,且层级较深,推荐使用reactive
。
3.5 toRefs 与 toRef
作用:将一个响应式对象中的每一个属性,转换为ref
对象。
备注:toRefs
与toRef
功能一致,但toRefs
可以批量转换。
语法如下:
// 数据
let person = reactive({name:'张三', age:18, gender:'男'})// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
let {name,gender} = toRefs(person)// 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
let age = toRef(person,'age')
- 示例:
加入toRefs
<script lang="ts" setup name="Person">import {reactive, toRefs} from 'vue'let person = reactive({name: '张三',age: 18})let {name, age} = toRefs(person)function changeName() {name.value += '~'}function changeAge() {age.value += 1}
</script>
3.6 computed
计算属性是有缓存的、方法没有
使用方式:import {computed} from 'vue'
computed是一个函数,实参是函数,因为vue3中没有this,所以用箭头函数
必须要使用 set get才能对计算属性进行修改
<script lang="ts" setup name="Person">import {ref,computed} from 'vue'let firstName = ref('张')let lastName = ref('三')// 这么定义的fullName是一个计算属性,只读、不可以修改let fullName = computed(()=>{return firstName.value + '-' + lastName.value })// 上述的fullName是 ComputedRefImpl{}// 这么定义的fullName是一个计算属性,可读可写let fullName2 = computed({get(){return firstName.value + '-' + lastName.value },set(val){const [str1, str2] = val.split('-')firstName.value = str1lastName.value = str2}})function changeName(){fullName2.value = '李-四'}</script>
3.7 watch
watch作用:监视数据的变化(和Vue2
中的watch
作用一致)
特点:Vue3
中的watch
只能监视以下四种数据:
ref
定义的数据。(ref能定义基本类型和对象类型)reactive
定义的数据。- 一个函数,返回一个值(也就是一个
getter
函数)。- 一个包含上述内容的数组。
我们在Vue3
中使用watch
的时候,通常会遇到以下几种情况:
情况一
情况一:监视ref
定义的【基本类型】数据:直接写数据名即可,监视的是其value
值的改变。
使用:
引入watch: import {ref, watch} from 'vue'
。watch是函数,有很多参数,目前只看两个 watch(监视对象,回调)
回调函数的参数是(newValue, oldValue)因为setup中没有this(undefined),所以可以直接写成箭头函数。
- 示例
<template><div class="person"><h1>情况一:监视【ref】定义的【基本类型】数据</h1><h2>当前求和为:{{sum}}</h2><button @click="changeSum">点我sum+1</button></div>
</template><script lang="ts" setup name="Person">// 引入watchimport {ref,watch} from 'vue'// 数据let sum = ref(0)// 方法function changeSum(){sum.value += 1}// 监视,情况一:监视【ref】定义的【基本类型】数据const stopWatch = watch(sum,(newValue,oldValue)=>{console.log('sum变化了',newValue,oldValue)if(newValue >= 10){stopWatch()}})
</script>
- 注意事项
① watch 里面的 sum(监视的ref的数据,不写value)
② 解除监视 :watch的返回值是一个函数,调用这个函数就可以结束监视
情况二
监视ref
定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
注意:
若修改的是
ref
定义的对象中的属性,newValue
和oldValue
都是新值,因为它们是同一个对象。若修改整个
ref
定义的对象,newValue
是新值,oldValue
是旧值,因为不是同一个对象了。
watch的第一个参数是:被监视的数据
watch的第二个参数是:监视的回调(新值,旧值)
watch的第三个参数是:配置对象(deep、immediate等等.....)
<template><div class="person"><h1>情况二:监视【ref】定义的【对象类型】数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改整个人</button></div>
</template><script lang="ts" setup name="Person">import {ref,watch} from 'vue'// 数据let person = ref({name:'张三',age:18})// 方法function changeName(){person.value.name += '~'}function changeAge(){person.value.age += 1}function changePerson(){person.value = {name:'李四',age:90}}/* 监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视deepwatch的第一个参数是:被监视的数据watch的第二个参数是:监视的回调watch的第三个参数是:配置对象(deep、immediate等等.....) */watch(person,(newValue,oldValue)=>{console.log('person变化了',newValue,oldValue)},{deep:true})</script>
- 注意事项 有些时候 newValue oldValue是一样的
若修改的是ref
定义的对象中的属性,newValue
和 oldValue
都是新值,因为它们是同一个对象。
情况三
监视reactive
定义的【对象类型】数据,且 默认开启了深度监视。且不能关闭,即不能通过deep:false关闭深度监视(隐式创建深层监听)。
<template><div class="person"><h1>情况三:监视【reactive】定义的【对象类型】数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改整个人</button><hr><h2>测试:{{obj.a.b.c}}</h2><button @click="test">修改obj.a.b.c</button></div>
</template><script lang="ts" setup name="Person">import {reactive,watch} from 'vue'// 数据let person = reactive({name:'张三',age:18})let obj = reactive({a:{b:{c:666}}})// 方法function changeName(){person.name += '~'}function changeAge(){person.age += 1}function changePerson(){Object.assign(person,{name:'李四',age:80})}function test(){obj.a.b.c = 888}// 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的watch(person,(newValue,oldValue)=>{console.log('person变化了',newValue,oldValue)})watch(obj,(newValue,oldValue)=>{console.log('Obj变化了',newValue,oldValue)})
</script>
情况四
监视ref
或reactive
定义的【对象类型】数据中的某个属性,注意点如下:
- 若该属性值不是【对象类型】,需要写成函数形式。
- 若该属性值是依然是【对象类型】,可直接写,也可写成函数,建议写成函数。
结论:监视的要是对象里的属性,那么最好写函数式(因为setup中没有this,所以直接写箭头函数),注意点:如果要同时① 监视对象属性的地址值,②需要关注对象内部,需要手动开启深度监视。
<template><div class="person"><h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeC1">修改第一台车</button><button @click="changeC2">修改第二台车</button><button @click="changeCar">修改整个车</button></div>
</template><script lang="ts" setup name="Person">import {reactive,watch} from 'vue'let person = reactive({name: '张三',age: 18,car: {c1: '奔驰',c2: '宝马'}})// 方法function changeName(){person.name += '~'}function changeAge(){person.age += 1}function changeC1(){person.car.c1 = '奥迪'}function changeC2(){person.car.c2 = '大众'}function changeCar(){// 这里可以直接赋值更换,因为这是person内部的一个属性person.car = {c1:'雅迪',c2:'爱玛'}}// 监视person对象里面的 name属性(不是对象类型)watch(()=>person.name,(newValue,oldValue)=>{console.log('person.name',newValue,oldValue);})
接上文代码,如果想监视 person.car(这个属性是一个对象属性),watch的第一个参数可以直接写 person.car ,但是 当执行函数changeCar的时候,无法监视。因为这段代码的意思是监视这个 person.car,当changeCar函数把整个car都换了的时候,就无法监视到原本的car。
watch(person.car,(newValue,oldValue)=>{console.log('person.car',newValue,oldValue);
})
因此如果需要监视原本 person.car 的地址值的时候,就需要用函数包裹起来(如下文代码)。但此时 修改第一辆第二辆车的时候又没法检测到了,因为地址值没有变化。
watch(()=>person.car,(newValue,oldValue)=>{console.log('person.car',newValue,oldValue);
})
怎么同时检测呢?加一个deep:true
watch(()=>person.car,(newValue,oldValue)=>{console.log('person.car',newValue,oldValue);
},{deep:true})
情况五
监视上述的多个数据
<template><div class="person"><h1>情况五:监视上述的多个数据</h1><h2>姓名:{{ person.name }}</h2><h2>年龄:{{ person.age }}</h2><h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeC1">修改第一台车</button><button @click="changeC2">修改第二台车</button><button @click="changeCar">修改整个车</button></div>
</template><script lang="ts" setup name="Person">import {reactive,watch} from 'vue'// 数据let person = reactive({name:'张三',age:18,car:{c1:'奔驰',c2:'宝马'}})// 方法function changeName(){person.name += '~'}function changeAge(){person.age += 1}function changeC1(){person.car.c1 = '奥迪'}function changeC2(){person.car.c2 = '大众'}function changeCar(){person.car = {c1:'雅迪',c2:'爱玛'}}// 监视,情况五:监视上述的多个数据watch([()=>person.name,person.car],(newValue,oldValue)=>{console.log('person.car变化了',newValue,oldValue)},{deep:true})</script>
3.8 watchEffect
-
官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
-
watch
对比watchEffect
-
都能监听响应式数据的变化,不同的是监听数据变化的方式不同
-
watch
:要明确指出监视的数据 -
watchEffect
:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
-
-
示例
需求:当水温达到60度,或水位达到80cm时,给服务器发请求。
为什么要引入watcheffect?因为如果同时检测的数据多了,每一个都要写进数据里很不方便。
<template><div class="person"><h2>需求:当水温达到60度,或水位达到80cm时,给服务器发请求</h2><h2>当前水温:{{temp}}℃</h2><h2>当前水位:{{height}}cm</h2><button @click="changeTemp">水温+10</button><button @click="changeHeight">水位+10</button></div>
</template><script lang="ts" setup name="Person">import {ref,watch,watchEffect} from 'vue'// 数据let temp = ref(10)let height = ref(0)// 方法function changeTemp(){temp.value += 10}function changeHeight(){height.value += 10}// 监视 -- watch实现
/* watch([temp,height],(value)=>{// 从value中获取最新的水温(newTemp)、最新的水位(newHeight)// 数组解构赋值let [newTemp,newHeight] = value // 逻辑if(newTemp >= 60 || newHeight >= 80){console.log('给服务器发请求')}}) */// 监视 -- watchEffect实现watchEffect(()=>{if(temp.value >= 60 || height.value >= 80){console.log('给服务器发请求')}})
</script>
3.9 标签中的ref属性
为什么要引入ref属性?在vue文件的 template(HTML)部分,可能会用 id 表示一个标签的唯一性。但是打包合并文件的时候,不同vue文件的HTML会合并到一起,如果多个组件用了同一个id名字(例如<div id="title">哈哈</div>
)会有冲突。所以要用ref属性。
用法:
① 引入import {ref} from 'vue'
② 通过 ref 创建一个东西, 用于存储其标记的内容 let title = ref()
③ HTML中 <h2 ref="title">北京</h2>
作用:
用于注册模板引用。
① 用在普通DOM
标签上,获取的是DOM
节点。
② 用在组件标签上,获取的是组件实例对象。
示例:
- 用在普通
DOM
标签上,获取的是DOM
节点
<template><div class="person"><h1>中国</h1><h2 ref="title2">北京</h2><h3>尚硅谷</h3><button @click="showLog">点我输出h2这个元素</button></div>
</template><script lang="ts" setup name="Person">import {ref} from 'vue'// 创建一个title2,用于存储ref标记的内容let title2 = ref()console.log(title2);function showLog(){console.log(title2.value) // <h2 data-v-4cadc14e>北京</h2>console.log(title2.value.innerHTML) // 北京}
</script>
控制台输出:<h2 data-v-4cadc14e>北京</h2>,输出的是DOM节点
注意这里的 data-v-4cadc14e 是因为 style 上加了 scoped 局部样式
- ref 用在组件标签上,获取的是组件实例对象。
注意 ref 如果用在组件标签上,肯定是父标签在其文件中使用,因为父标签中才会使用子组件。因此 父组件获取了子组件的实例对象。但是不能轻易获得子组件里面的数据,必须是子组件通过defineExpose暴露的数据才可以访问。
补充:vue3 中父组件中,只需① 引入组件(import)② 使用,不需要注册。因为是在setup中写的代码。
<!-- 父组件App.vue -->
<template><Person ref="ren"/><button @click="test">测试</button>
</template><script lang="ts" setup name="App">import Person from './components/Person.vue'import {ref} from 'vue'let ren = ref()function test(){console.log(ren.value.name)console.log(ren.value.age)}
</script><!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
<script lang="ts" setup name="Person">import {ref,defineExpose} from 'vue'// 数据let name = ref('张三')let age = ref(18)/****************************//****************************/// 使用defineExpose将组件中的数据交给外部defineExpose({name,age})
</script>
回顾TS
引入的时候,如果要引入这个: /types/index.js ,只用写到 /types,里面的 index.js会自动补上
vue文件路径 @
@ 表示在 src 文件夹
- 示例:接口、泛型、自定义类型
/src/types/index.js
// 定义一个接口,用于限制person对象的具体属性
export interface PersonInter {id: string,name: string,age: number
}// 一个自定义类型
// export type Persons = Array<PersonInter>
export type Persons = PersonInter[]
/src/components/Person.vue
//因为这里不是具体的值 所以要加一个type
import {type PersonInter,type Persons} from '@/types'// 定义一个person对象,要符合PersonInter接口规范
let person:PersonInter = {id:'1111', name:'张三',age:60}
console.log(person);// 定义一个数组,它里面的每一项都符合PersonInter规范
let personList:Array<PersonInter> = [{id:'1111', name:'张三',age:25},{id:'2222', name:'李四',age:20},{id:'3333', name:'王五',age:30}
]let personList2:Persons = [{id:'1111', name:'张三',age:25},{id:'2222', name:'李四',age:20},{id:'3333', name:'王五',age:30}
]
3.10 props使用
父组件要把数据给子组件
reactive和泛型一起用
import {reactive} from 'vue'import {type Persons} from '@/types'let personList = reactive<Persons>([{id:'1111', name:'张三',age:25},{id:'2222', name:'李四',age:20},{id:'3333', name:'王五',age:30}])
示例
父组件要把数据给子组件
- 接口
// 定义一个接口,限制每个Person对象的格式
export interface PersonInter {
id:string,
name:string,age:number
}// 定义一个自定义类型Persons
export type Persons = Array<PersonInter>
App.vue
(父组件)中代码:
<template><Person :list="persons"/>
</template><script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {reactive} from 'vue'import {type Persons} from './types'let persons = reactive<Persons>([{id:'e98219e12',name:'张三',age:18},{id:'e98219e13',name:'李四',age:19},{id:'e98219e14',name:'王五',age:20}])
</script>
Person.vue
中代码:
<template>
<div class="person">
<ul><li v-for="item in list" :key="item.id">{{item.name}}--{{item.age}}</li></ul>
</div>
</template><script lang="ts" setup name="Person">
import {defineProps} from 'vue'
import {type PersonInter} from '@/types'// 第一种写法:接收。defineProps返回值是一个对象
// const props = defineProps(['list'])// 第二种写法:接收+限制类型
// defineProps<{list:Persons}>()
// 写法是,defineProps可以传入一个泛型,泛型里面是规定传入数据的类型的,因为父组件不一定只传递一个数据,所以用对象传递// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
console.log(props)
</script>
- props 用于父组件给子组件传递数据
父组件:
<Son str="helloworld" :list="list"></Son>
这里传递的两个数据是不一样的 str实为一个字符串,list通过:也就是v-bind绑定了数据
子组件中接收数据:
通过defineProps,在模板中可以直接使用
但是在script中不能使用,如果想用的话, 就需要定义变量接收。defineProps返回值是一个对象,对象的每一个元素都是父元素传递来的数据
<script>import {defineProps} from 'vue' // 引入——其实可以不引入defineProps(['list'])// 通过defineProps接收数据const father_data = defineProps(['list'])<template>{{list}}
</template>
因为子组件在接收父组件的数据时,想做一些限制,比如限制类型,指定默认值,限制数据传递必要性,所以引入了第二种第三种写法
- 第二种写法补充说明
defineProps<{list:Persons}>()
。写法是,defineProps可以传入一个泛型,泛型里面是规定传入数据的类型的,因为父组件不一定只传递一个数据,所以用对象限制类型
- 子组件中如果想表明 父组件的数据可传可不传,就要加一个问号
defineProps<{list ? :Persons}>()
- 第三种写法补充说明
// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
withDefaults配置默认值,withDefaults 是一个函数。
第一个参数defineProps获取父组件传递的数据(这里用问号设置为可传可不传递,用泛型限制了传递过来的数据的类型);第二个参数是对象,设置每个属性的默认值,要用函数返回(function xxx(){return yyy}
简写为()=> yyy
)
- defineProps 和 withDefaults都可以不用引入。
definexxx就是宏函数,自动引入
3.11 生命周期
-
概念:
Vue
组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue
会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子 -
规律:
生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。
-
Vue2
的生命周期创建阶段:
beforeCreate
、created
(创建前、创建完毕)挂载阶段:
beforeMount
、mounted
(挂载前、挂载完毕)更新阶段:
beforeUpdate
、updated
(更新前、更新完毕)销毁阶段:
beforeDestroy
、destroyed
(销毁前、销毁完毕) -
Vue3
的生命周期创建阶段:
setup
挂载阶段:
onBeforeMount
、onMounted
更新阶段:
onBeforeUpdate
、onUpdated
卸载阶段:
onBeforeUnmount
、onUnmounted
-
常用的钩子:
onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前)
<template><div class="person"><h2>当前求和为:{{ sum }}</h2><button @click="changeSum">点我sum+1</button></div>
</template><!-- vue3写法 -->
<script lang="ts" setup name="Person">import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'// 数据let sum = ref(0)// 方法function changeSum() {sum.value += 1}console.log('setup')// 生命周期钩子// vue3在 挂载前,调用 onBeforeMount【里面的函数】onBeforeMount(()=>{console.log('挂载之前')})onMounted(()=>{console.log('挂载完毕')})onBeforeUpdate(()=>{console.log('更新之前')})onUpdated(()=>{console.log('更新完毕')})onBeforeUnmount(()=>{console.log('卸载之前')})onUnmounted(()=>{console.log('卸载完毕')})
</script>
子组件先挂载=>父组件再挂载, 从入口文件开始分析。App组件是最后挂载的
3.12 自定义hooks
什么是hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。
自定义hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂。
**使用:**src文件夹下新建一个hooks文件夹,里面根据功能创建文件 useDog.ts useSum.ts。ts文件向外暴露函数(hook本质是函数),而且函数内要返回值。
示例:
- useDog.ts
import axios from 'axios'
import {reactive} from 'vue'export default function (){// 数据let dogList = reactive(['https://images.dog.ceo/breeds/pembroke/n02113023_7292.jpg'])// 方法async function getDog() {try {let res = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')// console.log(res.data.message);dogList.push(res.data.message)} catch(err) {console.log(err);}}//给外部提供东西return {dogList,getDog}
}
- useSum.ts
import {ref} from 'vue'export default function(){let sum = ref(0)function add() {sum.value += 1} //给外部提供东西return {sum,add}
}
- 组件中使用
<template><div class="person"><h2>当前求和为:{{ sum }}</h2><button @click="add">点我加一</button><hr><img v-for="(dog, index) in dogList" :src="dog" :key="index"><button @click="getDog()">再来一只狗</button></div>
</template><script lang="ts" setup name="Person">import useSum from '@/hooks/useSum'import useDog from '@/hooks/useDog'const {sum,add} = useSum()const{dogList,getDog} = useDog()</script>
相关文章:

Vue3学习01 Vue3核心语法
Vue3学习 1. Vue3新的特性 2. 创建Vue3工程2.1 基于 vue-cli 创建项目文件说明 2.2 基于 vite 创建具体操作项目文件说明 2.3 简单案例(vite) 3. Vue3核心语法3.1 OptionsAPI 与 CompositionAPIOptions API 弊端Composition API 优势 ⭐3.2 setup小案例setup返回值setup 与 Opt…...
Spring Boot实现跨域的5种方式
Spring Boot实现跨域的5种方式 为什么会出现跨域问题什么是跨域非同源限制java后端实现CORS跨域请求的方式返回新的CorsFilter(全局跨域)重写WebMvcConfigurer(全局跨域)使用注解(局部跨域)手动设置响应头(局部跨域)使用自定义filter实现跨域 为什么会出现跨域问题 出于浏览器…...

Elasticsearch:从 ES|QL 到 PHP 对象
作者:来自 Elastic Enrico Zimuel 从 elasticsearch-php v8.13.0 开始,你可以执行 ES|QL 查询并将结果映射到 stdClass 或自定义类的 PHP 对象。 ES|QL ES|QL 是 Elasticsearch 8.11.0 中引入的一种新的 Elasticsearch 查询语言。 目前,它在…...

Stm32 HAL库 访问内部flash空间
Stm32 HAL库 访问内部flash空间 代码的部分串口配置申明文件main函数 在一些时候,需要存储一些数据,但是又不想接外部的flash,那我们可以知道,其实还有内部的flash可以使用, 需要注意的是内部flash,读写次数…...

线程池详解
线程池 什么是线程池:线程池就是管理一系列线程的资源池。 当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。 为什么要用线程池 / 线程池的好处: **降低资源消…...

mybatis(5)参数处理+语句查询
参数处理+语句查询 1、简单单个参数2、Map参数3、实体类参数4、多参数5、Param注解6、语句查询6.1 返回一个实体类对象6.2 返回多个实体类对象 List<>6.3 返回一个Map对象6.4 返回多个Map对象 List<Map>6.5 返回一个大Map6.6 结果映射6.6.1 使用resultM…...

数据应用OneID:ID-Mapping Spark GraphX实现
前言 说明 以用户实体为例,ID 类型包含 user_id 和 device_id。当然还有其他类型id。不同id可以获取到的阶段、生命周期均不相同。 device_id 生命周期通常指的是一个设备从首次被识别到不再活跃的整个时间段。 user_id是用户登录之后系统分配的唯一标识ÿ…...

第6章 6.2.3 : readlines和writelines函数 (MATLAB入门课程)
讲解视频:可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。 MATLAB教程新手入门篇(数学建模清风主讲,适合零基础同学观看)_哔哩哔哩_bilibili 在MATLAB的文本数据处理任务中,导入和导出文件是常…...

Matlab应用层生成简述
基础软件层 目前接触到的几款控制器,其厂商并没有提供simulink的基础软件库一般为底层文件被封装为lib,留有供调用API接口虽然能根据API接口开发基础软件库,但耗费时间过长得不偿失 应用层 所以可以将应用层封装为一个子系统,其…...

每日一题(leetcode1702):修改后的最大二进制字符串--思维
找到第一个0之后,对于后面的子串(包括那个0),所有的0都能调上来,然后一一转化为10,因此从找到的第一个0的位置开始,接下来是(后半部分子串0的个数-1)个1,然后…...

PHP自助建站系统,小白也能自己搭建网站
无需懂代码,用 自助建站 做企业官网就像做PPT一样简单,您可以亲自操刀做想要的效果! 自助建站是一款简单、快捷、高效的工具,可以帮助您制作响应式网站。我们的自助建站系统,将传统的编码工作转化为直观的拖拽操作和文…...

计算机视觉 | 基于 ORB 特征检测器和描述符的全景图像拼接算法
Hi,大家好,我是半亩花海。本项目实现了基于 ORB 特征检测器和描述符的全景图像拼接算法,能够将两张部分重叠的图像拼接成一张无缝连接的全景图像。 文章目录 一、随机抽样一致算法二、功能实现三、代码解析四、效果展示五、完整代码 一、随机…...
Scala - 函数柯里化(Currying)
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。 实例 首先我们定义一个函数: def add(x:Int,y:Int)xy 那么我们应用的时候,应该是这样用:add(1,2) 现在我们把这…...

Switch-case
Java switch case 语句 switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。 语法 switch case 语句语法格式如下: switch(expression){case value ://语句break; //可选case value ://语句break; //可选//你可以有任意数量…...

蓝桥杯-单片机基础16——利用定时计数中断进行动态数码管的多窗口显示
综合查阅了网络上目前能找到的所有关于此技能的代码,最终找到了下述方式比较可靠,且可以自定义任意显示的数值。 传统采用延时函数的方式实现动态数码管扫描,在题目变复杂时效果总是会不佳,因此在省赛中有必要尝试采用定时计数器中…...

2024/4/5—力扣—下一个排列
代码实现: 思路:两遍扫描 void swap(int *a, int *b) {int t *a;*a *b;*b t; }void reverse(int *nums, int l, int r) {while (l < r) {swap(nums l, nums r);l;r--;} }void nextPermutation(int *nums, int numsSize) {int i numsSize - 2;wh…...

xss.pwnfunction-Ugandan Knuckles
这个是把<>过滤掉了所以只能用js的事件 ?weya"onfocus"alert(1337)" autofocus"...

LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量
LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量 在电气化铁路运输中,受电弓滑板的健康状况对于保障列车安全行驶至关重要。受电弓滑板作为连接电网与列车的直接介质,其磨损情况直接影响到电能的有效传输及列车的稳定运行。精确、快速测量受电弓滑板磨损情…...

Linux第87步_阻塞IO实验
阻塞IO是“应用程序”对“驱动设备”进行操作,若不能获取到设备资源,则阻塞IO应用程序的线程会被“挂起”,直到获取到设备资源为止。 “挂起”就是让线程进入休眠,将CPU的资源让出来。线程进入休眠后,当设备文件可以操…...

C/C++基础----常量和基本数据类型
HelloWorld #include <iostream>using namespace std;int main() {// 打印cout << "Hello,World!" << endl;return 0; }c/c文件和关系 c和c是包含关系,c相当于是c的plus版本c的编译器也可以编译c语言c文件.cpp结尾.h为头文件.c为c语言…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋
随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...

恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...