Vue3 学习笔记 (基础)
看视频学习的笔记,自用
bilibili 尚硅谷禹神
setup 一切的开始
- 组建中所用到的:数据、方法等都 应当 配置在 setup 中。没有写到 setup 中的函数或者属性在组件模板中无法访问。
- vue2 中的 data、computed等生命周期组件在 vue3 中可以写,但是不建议写
- setup 中没有 this
- 通常情况下 setup 组件是不能够返回 promise 的
- 当组件返回 promise 时,要确定被引用时,要使用
defineAsyncComponent
接收组件,并且使用 Suspense 来报过组件进行配合(后面文档有记载)
<script lang="ts">
export default {
setup() {
const name = '张三'
const age = 18
function sayHello() {
alert("hello world")
}
return {
name, // 抛出 name 属性
age, // 抛出 age 属性
sayHello // 抛出 sayHello 函数
}
}
}
</script>
<template>
<h1>人员信息</h1>
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<button @click="sayHello">sayHello</button>
</template>
ref 动态响应
ref 函数的作用是用于动态渲染。
- ref 会给普通的值类型属性更改为 RefImpl 对象,并且实现 get、set 两个函数。
- ref 会吧引用类型的对象更改为 Proxy 对象
使用 ref 时,除了在模板中,都需要操作实际的 value 否则无效
<script lang="ts">
import {ref} from "vue";
export default {
setup() {
const name = ref('张三') // 使用 ref 来定义一个属性
const age = ref(18) // 使用 ref 来定义一个属性
const job = ref({ // 使用 ref 来定义一个对象 ref 实际是通过 ES6 中的 Proxy 实现了这个对象的代理,进行了数据内部的处理
type: '前端工程师',
year: 2
})
function sayHello() {
alert("hello world")
}
function changeInfo() {
name.value = '李四' // 更改 name 属性的值
age.value = 30 // 更改 age 属性的值
}
function changeJob() {
const work = job.value // 获取到 job 的这个对象
work.type = '全栈' // 更改 job 中 type 这个属性
work.year = 10 // 更改 job 中 year 这个属性
}
return {
name,
age,
job,
sayHello,
changeInfo,
changeJob
}
}
}
</script>
<template>
<h1>人员信息</h1>
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<h1>工作信息</h1>
<h2>岗位类型: {{job.type}}</h2>
<h2>工作年限: {{job.year}}</h2>
<button @click="sayHello">sayHello</button>
<br><br>
<button @click="changeInfo">改变信息</button>
<br><br>
<button @click="changeJob">更换工作</button>
</template>
reactive 动态响应对象
- 定义一个 对象类型 的响应式数据(基本类型不要用 reactive)
- reactive 定义的响应式数据是深层响应的
- 基于 ES6 的Proxy 实现。通过代理对象操作源对对象内部数据进行操作
<template>
<h1>人员信息</h1>
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<h1>工作信息</h1>
<h2>岗位类型: {{job.type}}</h2>
<h2>工作年限: {{job.year}}</h2>
<button @click="sayHello">sayHello</button>
<br><br>
<button @click="changeInfo">改变信息</button>
<br><br>
<button @click="changeJob">更换工作</button>
</template>
<script lang="ts">
import {ref, reactive} from "vue";
export default {
setup() {
const name = ref('张三')
const age = ref(18)
// 使用 reactive 来初始化对象
const job = reactive({
type: '前端工程师',
year: 2
})
function sayHello() {
alert("hello world")
}
function changeInfo() {
name.value = '李四'
age.value = 30
}
function changeJob() {
// 直接更改属性,不需要再 .value 了
job.type = '全栈'
job.year = 10
}
return {
name,
age,
job,
sayHello,
changeInfo,
changeJob
}
}
}
</script>
Proxy 代理
Proxy 是 Es6 提供的对象代理函数。在在做对象时直接操作 Proxy 的代理,它会同步修改原来对象中的属性。使用代理可以完成对对象的原生监听
Proxy 参数 ( 源对象,操作对象)
如:
const person = {
name: '张三',
age: 18
}
const student = new Proxy(person, {})
student.name = '李四' // 修改代理中的name属性 相当于 person.name = '李四'
console.log(student) // Proxy {name: '李四', age: 18}
console.log(person) // {name: '李四', age: 18}
get / set / delete
const person = {
name: '张三',
age: 18
}
const student = new Proxy(person, {
get(target, propName: string): unknown {
console.log(`读取了 student 中的 ${propName} 属性`)
return Reflect.get(target, propName)
},
set(target, propName: string, value): boolean {
console.log(`写入了 student 中的 ${propName} 值为 ${value}`)
return Reflect.set(target, propName, value)
},
deleteProperty(target, propName: string): boolean {
console.log(`删除了 student 中的 ${propName} 属性`)
return Reflect.deleteProperty(target, propName)
}
})
student.name = '李四' // 修改代理中的name属性 相当于 person.name = '李四'
console.log(student.name) // 李四
// 读取了 student 中的 name 属性
console.log(student) // Proxy {name: '李四', age: 18}
Reflect.deleteProperty(student, 'age') // 删除 age 属性
console.log(JSON.stringify(student)) // Proxy {name: '李四'}
console.log(JSON.stringify(person)) // {name: '李四'}
computed 计算属性
需要从 vue 中进入 computed 函数
import { reactive, computed } from "vue";
<script lang="ts">
import { reactive, computed } from "vue";
export default {
setup() {
const person = reactive({
firstName: '张',
lastName: '三'
})
// 设置一个叫 fullName 的计算属性
Reflect.set(person, 'fullName', computed({
get() {
return person.firstName + '-' + person.lastName
},
set(value: string) {
const split = value.split('-')
person.firstName = split[0]
person.lastName = split[1]
}
}))
return {
person
}
}
}
</script>
<template>
<div>
姓: <input type="text" v-model="person.firstName">
名: <input type="text" v-model="person.lastName">
全名: <input type="text" v-model="person.fullName">
</div>
</template>
watch 监听
需要从 vue 中心如 watch 函数
注意:
- reactive 定义的对象在使用 watch 监听时无法正确的获得 oldVal
- vue3 目前版本中 watch 监听对象是默认深度监听的,且 deep 也无法关闭
watch 的三个参数
- 被监听的值/对象
- 监听触发回调函数。两个回调参数 ( 改变后的值/对象,改变前的值/对象 )
- 配置项。两个配置 {deep: boolean, immediate: boolean} ; deep: 深度监听; immediate: 初始触发监听
<script lang="ts">
import { ref, reactive, watch } from "vue";
export default {
setup() {
let sum = ref(0)
let msg = ref(0)
const person = reactive({
firstName: '',
lastName: '',
fullName: '',
job: {
year: 10
}
})
// 1. 普通监听
watch(sum, (newVal, oldVal) => {
console.log(newVal, oldVal)
msg.value = newVal
})
// 2. 普通监听
watch(msg, (newVal, oldVal) => {
console.log(newVal, oldVal)
sum.value = newVal
})
// 3. 组合监听
watch([sum, msg], (newVal, oldVal) => {
console.log(newVal, oldVal) // newVal: ['sum', 'msg'], oldVal: ['sum', 'msg']
}, { immediate: true }) // 初始触发 与 vue2 中 watch 里的 immediate 一致
// 4. 监听 reactive 生成对象中的某一个属性
watch(() => person.job.year, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// 5. 监视的是 reactive 对象中某个对象属性时,超过 1 层的属性对象将会无法监听,需要添加 deep:true 配置才有效
watch(() => person.job, (newVal, oldVal) => {
console.log(newVal, oldVal)
}, {deep: true})
// 6. 监听 ref 生成的对象
const person2 = ref({
firstName: '',
lastName: '',
fullName: '',
job: {
year: 10,
work: {
address: '南京'
}
}
})
// 当被监听的是一个由 ref 生成的对象时,那么监听时需要监听它实际的值
watch(person2.value, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
return {
person,
sum,
msg
}
}
}
</script>
<template>
<div>
sum: <input type="text" v-model="sum">
smg: <input type="text" v-model="msg">
姓: <input type="text" v-model="person.firstName">
名: <input type="text" v-model="person.lastName">
全名: <input type="text" v-model="person.fullName">
工作年限: <input type="text" v-model="person.job.year">
</div>
</template>
watchEffect 监听
<script lang="ts">
import { ref, reactive, watch, watchEffect } from "vue";
export default {
setup: function () {
const person = reactive({
firstName: '',
lastName: '',
fullName: '',
job: {
year: 10,
work: {
address: '南京'
}
}
})
// watchEffect 默认开启了 immediate: true
// watchEffect 不需要指定监视谁,它更类似于一个computed,在函数中用到的属性一旦发生改变,都将会触发这个函数
// watchEffect 更注重程序的执行过程,没有返回值
watchEffect(()=> {
const name = person.fullName
const address = person.job.work.address
console.log("watchEffect 被执行了")
})
return {
person,
}
}
}
</script>
<template>
<div>
姓: <input type="text" v-model="person.firstName">
名: <input type="text" v-model="person.lastName">
全名: <input type="text" v-model="person.fullName">
工作年限: <input type="text" v-model="person.job.year">
工作信息: <input type="text" v-model="person.job.work.address">
</div>
</template>
setup 进阶
setup 提供了两个参数
props 属性
-
context 上下文
属性有 attrs, emit, expose, slots
attrs: 值为对象,收录了所有 props 中没有声明但被传递的属性。相当于 Vue2 中的 $attrs
emit: 分发自定义时间的函数。相当于 Vue2 中的 $emit
expose:
slots: 收到的插槽内容。相当于 Vue2 中的 $slots
hook
hook 本质是一个函数,用于复用可用代码
hook 文件 .../usePoint.ts
import {reactive, onMounted, onUnmounted} from "vue"; export default function () { const point = reactive({ x: 0, y: 0 }) function onclick(event: MouseEvent) { point.x = event.pageX point.y = event.pageY } onMounted(() => window.addEventListener('click', onclick)) onUnmounted(() => window.addEventListener('click', onclick)) return point }
引入 hook 的文件
<script lang="ts"> import usePoint from "@/hooks/usePoint"; export default { setup: function () { const point = usePoint() return { point } } } </script> <template> <div> 鼠标位置 x {{point.x}} y {{point.y}} </div> </template>
toRef 函数
<script lang="ts">
import { reactive, toRef } from "vue";
export default {
setup: function () {
const person = reactive({
job: {
year: 10,
work: {
address: '南京'
}
}
})
// 使用 toRef 可以将Proxy中的属性与变量名进行桥接,更改被桥接的名字或者这个变量名时,两边都会进行同样的更改
let address = toRef(person.job.work, 'address')
return {
person,
address
}
}
}
</script>
<template>
<div>
我的地址: <input type="text" v-model="address">
person 中的地址: <input type="text" v-model="person.job.work.address">
</div>
</template>
toRefs 函数
<script lang="ts">
import { reactive, toRefs } from "vue";
export default {
setup: function () {
const person = reactive({
job: {
year: 10,
work: {
address: '南京'
}
}
})
// toRefs 可以将对象中的第一层全部 toRef 出来为一个新的对象
const obj = toRefs(person)
return {
person,
...toRefs(person) // 直接展开这个对象,那么即可以立即在模板中直接使用
}
}
}
</script>
<template>
<div>
我的地址: <input type="text" v-model="job.work.address">
person 中的地址: <input type="text" v-model="person.job.work.address">
</div>
</template>
不常用函数
readonly 只读函数
对象、值一旦使用了 readonly 函数,那么这个对象、值将不能再被修改。只能被读取
<script lang="ts">
import { reactive, readonly } from "vue";
export default {
setup: function () {
// 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
let person = reactive({
firstName: '',
lastName: '',
fullName: '',
job: {
year: 10,
work: {
address: '南京'
}
}
})
person = readonly(person)
return {
person,
}
}
}
</script>
<template>
<div>
<!-- 下面都无法更改到源数据-->
姓: <input type="text" v-model="person.firstName">
名: <input type="text" v-model="person.lastName">
全名: <input type="text" v-model="person.fullName">
工作年限: <input type="text" v-model="person.job.year">
工作信息: <input type="text" v-model="person.job.work.address">
<br>
<span>{{JSON.stringify(person)}}</span>
</div>
</template>
shallowReactive、shallowRef、shallowReadonly
浅响应式。相当于弱化了的 reactive 和 ref
- shallowReactive 只处理对象最外层的响应式
- shallowRef 只处理基本的数据类型响应式,不进行对象的响应式处理
- shallowReadonly 将对象的第一层设置为只读,其他可读
<script lang="ts">
import { shallowReactive, shallowRef } from "vue";
export default {
setup: function () {
// 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
const person = shallowReactive({
firstName: '',
lastName: '',
fullName: '',
job: {
year: 10,
work: {
address: '南京'
}
}
})
return {
person,
}
}
}
</script>
<template>
<div>
姓: <input type="text" v-model="person.firstName">
名: <input type="text" v-model="person.lastName">
全名: <input type="text" v-model="person.fullName">
工作年限: <input type="text" v-model="person.job.year">
工作信息: <input type="text" v-model="person.job.work.address">
<br>
<span>{{JSON.stringify(person)}}</span>
</div>
</template>
toRaw 回归原始
reactive 生成的对象一旦被 toRaw 以后,就不在是 Proxy 代理对象了。也不再具有动态响应的作用
<script lang="ts">
import { reactive, toRaw } from "vue";
export default {
setup: function () {
// 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
let person = reactive({
firstName: '',
lastName: '',
fullName: '',
job: {
year: 10,
work: {
address: '南京'
}
}
})
person = toRaw(person)
return {
person
}
}
}
</script>
<template>
<div>
<!-- 下面都无法更改到源数据-->
姓: <input type="text" v-model="person.firstName">
名: <input type="text" v-model="person.lastName">
全名: <input type="text" v-model="person.fullName">
工作年限: <input type="text" v-model="person.job.year">
工作信息: <input type="text" v-model="person.job.work.address">
<br>
<span>{{JSON.stringify(person)}}</span>
</div>
</template>
markRaw 标记原始
对象的某个属性被标记标记了原始以后,将不会被动态响应
<script lang="ts">
import { reactive, markRaw } from "vue";
export default {
setup: function () {
// 浅层次的 reactive 声明一个对象。这种声明方式只会考虑第一层数据的响应式。超过一层的数据改变时将不会改变源对象
let person = reactive({
firstName: '',
lastName: '',
fullName: '',
})
let job = {
year: 10,
work: {
address: '南京'
}
}
job = markRaw(job)
Reflect.set(person, 'job', job)
return {
person
}
}
}
</script>
<template>
<div>
姓: <input type="text" v-model="person.firstName">
名: <input type="text" v-model="person.lastName">
全名: <input type="text" v-model="person.fullName">
<!-- 下面都无法更改到源数据-->
工作年限: <input type="text" v-model="person.job.year">
工作信息: <input type="text" v-model="person.job.work.address">
<br>
<span>{{JSON.stringify(person)}}</span>
</div>
</template>
响应式数据的类型判断
isRef: 检查一个值是否为 ref 对象
isReactive: 检查一个对象是否由 reactive
创建的响应式代理
isReadonly: 检查一个对象是否有 readonly
创建的只读代理
isProxy: 检查一个对象是否有 reactive
或者 readonly
方法创建的代理
感觉很重要,但是用的不多
customRef 自定义 ref 的实现
这是一个官方的实现案例,延迟更新新效果
<script lang="ts">
import { customRef } from "vue";
import { NodeJS } from "timers";
export default {
setup: function () {
function myRef(value:string) {
let timer:NodeJS.Timeout = undefined
return customRef((track, trigger) => {
return {
get() {
track() // 1,告诉Vue跟踪这个值,使它每次变化都更新
return value
},
set(newValue:string) {
timer && clearTimeout(timer)
timer = setTimeout(()=>{
// 延迟更新
value = newValue
trigger() // 2. 通知Vue数据更新了
}, 500)
}
}
})
}
let name = myRef('你好啊')
return {
name
}
}
}
</script>
<template>
<div>
请输入: <input type="text" v-model="name">
<br>
输入内容为: {{name}}
<br>
</div>
</template>
provide/inject 后代组件传值
provide
// 父组件定义
import {provide} from 'vue';
export default({
name: 'HomeView',
setup() {
let heirloom = ref('双鱼玉佩')
// 通过 provide 将 car 传递给后代
provide('heirloom', heirloom)
return {
}
}
})
inject
<script lang="ts">
import {inject} from "vue";
export default {
setup: function () {
// 通过 inject 接收祖辈组件传值
const heirloom = inject('heirloom')
return {
heirloom
}
}
}
</script>
<template>
<div>
<br>
传家宝: {{heirloom}}
<br>
</div>
</template>
组件
teleport 传送组件
在这个组件内部写的所有组件都可以通过 teleport 的 to 属性传送到指定的位置去
如,这里传送到了 body 中
<script lang="ts">
import {inject, isRef, isReactive} from "vue";
export default {
setup: function () {
const car = inject('car')
console.log(isRef(car))
console.log(isReactive(car))
return {
car
}
}
}
</script>
<template>
<div>
<h1>Vue3</h1>
</div>
<teleport to="body">
<div>传家宝: {{car}}</div>
<div>传送到body去</div>
</teleport>
</template>
如果想要传送到指定的 div 中,那么需要先在 index.html 中实现定义好这个div
如:
<!DOCTYPE html>
<html lang="">
<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="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<div class="dialog"></div> <!-- 传到这个div 中 -->
<!-- built files will be auto injected -->
</body>
</html>
<script lang="ts">
import {inject, isRef, isReactive} from "vue";
export default {
setup: function () {
const car = inject('car')
console.log(isRef(car))
console.log(isReactive(car))
return {
car
}
}
}
</script>
<template>
<div>
<h1>Vue3</h1>
</div>
<teleport to=".dialog">
<div>传家宝: {{car}}</div>
<div>传送到body去</div>
</teleport>
</template>
Suspense 异步组件
用于需要从后台加载数据后才渲染的组件
<template>
<div class="home">
<Suspense>
<!-- suspense 提供了两个槽位
default 是默认槽位,是代码正确运行后让用户看到的结果
fallback 是代码异常或者加载缓慢时让用户看到的结果-->
<template v-slot:default>
<test-vue3></test-vue3>
</template>
<template v-slot:fallback>
<div>加载中</div>
</template>
</Suspense>
</div>
</template>
<script lang="ts">
// defineAsyncComponent 异步引入组件
import {defineAsyncComponent} from 'vue';
const TestVue3 = defineAsyncComponent(()=> import('@/components/TestVue3.vue'));
export default({
name: 'HomeView',
components: {
TestVue3
}
})
</script>
当组件是使用 defineAsyncComponent
来接收时,那么被使用的组件 setup 可以返回 promise
父组件
<template>
<div class="home">
<Suspense>
<!-- suspense 提供了两个槽位
default 是默认槽位,是代码正确运行后让用户看到的结果
fallback 是代码异常或者加载缓慢时让用户看到的结果-->
<template v-slot:default>
<test-vue3></test-vue3>
</template>
<template v-slot:fallback>
<div>加载中</div>
</template>
</Suspense>
</div>
</template>
<script lang="ts">
// defineAsyncComponent 异步引入组件
import {defineAsyncComponent} from 'vue';
const TestVue3 = defineAsyncComponent(()=> import('@/components/TestVue3.vue'));
export default({
name: 'HomeView',
components: {
TestVue3
}
})
</script>
子组件
<script lang="ts">
import {inject, isRef, isReactive} from "vue";
export default {
setup: function () {
const car = inject('car')
console.log(isRef(car))
console.log(isReactive(car))
return new Promise((resolve, reject) => {
setTimeout(()=> {
resolve(1)
}, 2000)
})
}
}
</script>
<template>
<h1>Vue3</h1>
</template>