1. Vue基本介绍
-1.1 什么是Vue
- 官网地址:vuejs.org/
- 渐进式框架
-1.2 Vue特点
- 响应式
- JQuery时代需要手动操作DOM
- 现代框架是数据驱动
- 组件化开发
- 模板语法
-1.3 相关技术栈
- Vue(基本语法、组件及组件之间通信)
- Vue-router
- Pinia
- 组件库(ElementUI-plus)
- Vite(官方推荐构建工具,解决webpack痛点)
- Vitest(测试框架)
- Vue Test Utils(单元测试)
- Nuxt.js(服务端渲染)
2. 搭建工程
2.1 包管理器
- 搭建工程首先需要一个包管理器
- npm
- pnpm
- yarn
- bun
- 搭建工程文档:vuejs.org/guide/quick…
2.2 项目结构
-
.vscode:这个文件夹通常包含了 Visual Studio Code 的配置文件,用来设置代码格式化、主题样式等。
-
node_modules:这个文件夹内包含项目所需的所有 node 包。当运行 npm install 或 yarn 时,所有在 package.json 中列出的依赖都会被安装到这个文件夹下。
-
public:用来存放静态资源的文件夹,这部分静态资源是不会经过构建工具处理的。例如 favicon 图标文件。
-
src:源码文件夹,我们的开发工作主要就是在这个目录下面。通常包括 Vue 组件、JavaScript 文件、样式文件等。
- assets:这个同样是静态资源目录,放在该目录下的静态资源在打包的时候会被构建工具处理。
- components:组件目录,存放各种功能组件
- App.vue:根组件
- main.js:入口 JS 文件
-
.eslintrc.cjs:ESLint 的配置文件,用于检查代码错误和风格问题,cjs 是 CommonJS 的配置文件格式。
-
.gitignore:Git 的配置文件,用于设置不需要加入版本控制的文件或文件夹。
-
.prettierrc.json:Prettier 的配置文件,Prettier 是一个代码格式化工具。
-
index.html:项目的入口 HTML 文件,Vite 将利用它来处理应用的加载。
-
jsconfig.json:JavaScript 的配置文件,用于告诉 VS Code 如何处理 JavaScript 代码,例如设置路径别名。
-
package-lock.json:锁定安装时的包的版本,确保其他人在 npm install 时,大家的依赖能保持一致。
-
package.json:定义了项目所需的各种模块以及项目的配置信息(例如项目的名称、版本、许可证等)。
-
README.md:项目的说明文件,通常包含项目介绍、使用方法、贡献指南等。
-
vite.config.js:Vite 的配置文件,用于定制 Vite 的构建和开发服务等。
2.3 VSCode插件
-
Vue VSCode Snippets:可以快速生成 Vue 代码的模板
-
Vue-Official
- 在 Vue2 时间,大家接触更多的是 Vetur,该插件主要是对 Vue 单文件组件提供高亮,语法支持和检测功能。
- 后面随着 Vue3 版本的发布,Vue 官方团队推荐使用 Volar 插件,该插件覆盖了 Vetur 所有的功能,并且支持 Vue3 版本,还支持 TS 的语法检测。
- 但是现在,无论是 Vetur、Volar、TypeScript Vue Plugin 已经成为历史了,目前官方推出了 Vue-Official,这个最新的插件将前面插件的所有功能都涵盖了。
2.4 Vite
-
官网:vitejs.dev/
-
官方推荐的构建工具,显著提升开发体验。
-
Vite 之所以能够提升开发体验,是因为它的工作原理和 Webpack 完全不同。Vite 压根儿就不打包,而是通过请求本地服务器的方式来获取文件。
-
常用的配置如下:
-
base:用于设置项目的基础路径。这对于部署到非根目录的项目特别有用。
-
server:配置开发服务器的选项,例如
- 端口(port)
- 自动打开浏览器(open)
- 跨源资源共享(cors)
- 代理配置(proxy)
- .....
-
build:包含构建过程的配置,例如
- 输出目录(outDir)
- 生产环境源码地图(sourcemap)
- 压缩(minify)
- 分块策略(rollupOptions)
- .....
-
css:用于配置 CSS 相关选项,如预处理器配置、模块化支持等。
-
esbuild:可以自定义 ESBuild 的配置,例如指定 JSX 的工厂函数和片段。
-
optimizeDeps:用于预构建依赖管理,可以指定需要预构建的依赖,以加速冷启动时间。
-
define:允许你定义在源码中全局可用的常量替换。
-
publicDir:设置公共静态资源目录,默认为 public。
-
3. 模板语法
所谓模板,是 Vue 中构建视图的地方。
模板的写法基本上和 HTML 是一模一样的,上手无难度。
不过这个之所以被称之为模板,就是因为这个和之前的模板引擎类似,提供了一些不同于纯 HTML 的特性。
3.1 文本插值
可以在模板里面使用双大括号,括号内部就可以绑定动态的数据。
<template>
<div>{{ name }}</div>
</template>
<script setup>
const name = 'Steve'
</script>
<style lang="scss" scoped></style>
3.2 原始HTML
有些时候,变量的值对应的是一段 HTML 代码,但是普通的文本插值只会将这段 HTML 代码原封不动的输出。 例如:
<template>
<div>{{ htmlCode }}</div>
</template>
<script setup>
const htmlCode = '<span style="color:red">this is a test</span>'
</script>
<style lang="scss" scoped></style>
如果想要让上面的 HTML 字符串以 HTML 的形式渲染出来,那么需要指令。
指令是带有 v- 前缀的特殊属性。Vue 提供了一部分内置指令,开发者还可以自定义指令。
Vue 中所有内置的指令:cn.vuejs.org/api/built-i…
这里我们需要用到 v-html 的指令,例如:
<template>
<div v-html="htmlCode"></div>
</template>
<script setup>
const htmlCode = '<span style="color:red">this is a test</span>'
</script>
<style lang="scss" scoped></style>
3.3 绑定属性
Vue 中的核心思想,就是将模板中所有的东西都通过数据来控制,除了普通文本以外,属性应该由数据来控制,这就是所谓的属性绑定。
例如:
<template>
<div v-bind:id="id">hello</div>
</template>
<script setup>
const id = 'my-id'
</script>
<style lang="scss" scoped></style>
属性的动态绑定用得非常的多,因此有一种简写形式,直接用一个冒号( : )表示该属性是动态绑定的
<template>
<div :id="id">hello</div>
</template>
<script setup>
const id = 'my-id'
</script>
<style lang="scss" scoped></style>
另外还有一种简写形式,这种形式 Vue3.4 以上版本才能用,如果动态绑定的属性和数据同名,那么可以直接简写:
<template>
<div :id>hello</div>
</template>
<script setup>
const id = 'my-id'
</script>
<style lang="scss" scoped></style>
在 HTML 中,有一类属性是比较特殊的,就是布尔类型属性,例如 disabled,针对这一类布尔属性,绑定的数据不同,会有不同的表现:
- 如果所绑定的数据是真值或者空字符串,该布尔值属性会存在
- 如果所绑定的数据是假值(null 和 undefined),该布尔值属性会被忽略
有些时候,如果想要绑定多个属性,那么这个时候可以直接绑定成一个对象:
<template>
<div v-bind="attrObj">hello</div>
</template>
<script setup>
const attrObj = {
id: 'container',
class: 'wrapper'
}
</script>
<style lang="scss" scoped></style>
3.4 使用JS表达式
目前为止,模板可以绑定数据,但是目前数据是什么,模板中就渲染什么。
但是实际上模板中是可以对要渲染的数据进行一定处理的,通过 JavaScript 表达式来进行处理。
<template>
<div>{{ number + 1 }}</div>
<div>{{ ok ? '晴天' : '雨天' }}</div>
<div>{{ message.split('').reverse().join('') }}</div>
<div :id="`list-${id}`">{{ id + 100 }}</div>
</template>
<script setup>
const number = 1
const ok = true
const message = 'hello'
const id = 1
</script>
<style lang="scss" scoped></style>
这里有一个关键点,就是你要区分什么是表达式,什么是语句
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}
<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}
有一个简单的判断方法:看是否能够写在 return 后面。如果能够写在 return 后面,那么就是表达式,如果不能那么就是语句。
例如函数调用,其实就是一个表达式
return test();
{{ test() }}
3.5 模板沙盒化
模板中可以使用表达式,这些表达式都是沙盒化的,沙盒的意义主要在于安全,这里在模板中能够访问到全局对象,但是由于沙盒的存在,对能够访问到的全局对象进行了限制,只能访问 部分的全局对象
<template>
<div>{{ Math.random() }}</div>
</template>
<script setup></script>
<style lang="scss" scoped></style>
但是如果是不在上述列表中的,则无法访问到:
<template>
<div>{{ Math.random() }}</div>
<div>{{ Test.a }}</div>
</template>
<script setup>
window.Test = {
a: 1
}
</script>
<style lang="scss" scoped></style>
在上面的例子中,我们尝试给 window 挂载一个新的全局对象,然后在模板中进行访问,但是会报错:Cannot read properties of undefined (reading 'a')
如果真的有此需求,需要在 window 上挂载一个全局对象供模板访问,可以使用 app.config.globalProperties,例如:
// main.js
// import './assets/main.css'
import { createApp } from 'vue'
// 引入了根组件
import App from './App.vue'
// 挂载根组件
const app = createApp(App)
// 在这里新增全局对象属性
app.config.globalProperties.Test = {
a: 'Hello, Global Object!'
}
app.mount('#app')
4. 响应式基础
4.1 使用ref
可以使用 ref 创建一个响应式的数据:
<template>
<div>{{ name }}</div>
</template>
<script setup>
import { ref } from 'vue'
// 现在的 name 就是一个响应式数据
let name = ref('Bill')
console.log(name)
console.log(name.value)
setTimeout(() => {
name.value = 'Tom'
}, 2000)
</script>
<style lang="scss" scoped></style>
ref 返回的响应式数据是一个对象,我们需要通过 .value 访问到内部具体的值。模板中之所以不需要 .value,是因为在模板会对 ref 类型的响应式数据自动解包。
ref 可以持有任意的类型,可以是对象、数组、普通类型的值、Map、Set...
对象的例子:
<template>
<div>{{ Bill.name }}</div>
<div>{{ Bill.age }}</div>
</template>
<script setup>
import { ref } from 'vue'
// 现在的 name 就是一个响应式数据
let Bill = ref({
name: 'Biil',
age: 18
})
setTimeout(() => {
Bill.value.name = 'Biil2'
Bill.value.age = 20
}, 2000)
</script>
<style lang="scss" scoped></style>
数组的例子:
<template>
<div>{{ arr }}</div>
</template>
<script setup>
import { ref } from 'vue'
// 现在的 name 就是一个响应式数据
let arr = ref([1, 2, 3])
setTimeout(() => {
arr.value.push(4, 5, 6)
}, 2000)
</script>
<style lang="scss" scoped></style>
第二个点,ref 所创建的响应式数据是具备深层响应式,这一点主要体现在值是对象,对象里面又有嵌套的对象:
<template>
<div>{{ Bill.name }}</div>
<div>{{ Bill.age }}</div>
<div>{{ Bill.nested.count }}</div>
</template>
<script setup>
import { ref } from 'vue'
// 现在的 name 就是一个响应式数据
let Bill = ref({
name: 'Biil',
age: 18,
nested: {
count: 1
}
})
setTimeout(() => {
Bill.value.name = 'Biil2'
Bill.value.age = 20
Bill.value.nested.count += 2
}, 2000)
</script>
<style lang="scss" scoped></style>
如果想要放弃深层次的响应式,可以使用 shallowRef,通过 shallowRef 所创建的响应式,不会深层地递归将对象每一层转为响应式,而只会将 .value 的访问转为响应式:
const state = shallowRef({ count: 1});
// 这个操作不会触发响应式更新
state.value.count += 2;
// 只针对 .value 值的更改会触发响应式更新
state.value = { count: 2}
具体示例:
<template>
<div>{{ Bill.name }}</div>
<div>{{ Bill.age }}</div>
<div>{{ Bill.nested.count }}</div>
</template>
<script setup>
import { shallowRef } from 'vue'
let Bill = shallowRef({
name: 'Biil',
age: 18,
nested: {
count: 1
}
})
// 下面的更新不会触发视图更新
setTimeout(() => {
Bill.value.name = 'Biil2'
Bill.value.age = 20
Bill.value.nested.count += 2
}, 2000)
// 下面的更新会触发视图更新
setTimeout(() => {
Bill.value = {
name: 'Biil3',
age: 30,
nested: {
count: 3
}
}
}, 4000)
</script>
<style lang="scss" scoped></style>
响应式数据的更新,带来了 DOM 的自动更新,但是这个 DOM 的更新并非是同步的,这意味着当响应式数据发生修改后,我们去获取 DOM 值,拿到的是之前的 DOM 数据:
<template>
<div id="container">{{ count }}</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
let count = ref(1)
let container = null
setTimeout(() => {
count.value = 2 // 修改响应式状态
console.log('第二次打印:', container.innerText)
}, 2000)
// 这是一个生命周期钩子方法
// 会在组件完成初始渲染并创建 DOM 节点后自动调用
onMounted(() => {
container = document.getElementById('container')
console.log('第一次打印:', container.innerText)
})
</script>
<style lang="scss" scoped></style>
如果想要获取最新的 DOM 数据,可以使用 nextTick,这是 Vue 提供的一个工具方法,会等待下一次的 DOM 更新,从而方便后面能够拿到最新的 DOM 数据。
<template>
<div id="container">{{ count }}</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
let count = ref(1)
let container = null
setTimeout(async () => {
count.value = 2 // 修改响应式状态
// 等待下一个 DOM 更新周期
await nextTick()
// 这个时候再打印就是最新的值了
console.log('第二次打印:', container.innerText)
}, 2000)
// 这是一个生命周期钩子方法
// 会在组件完成初始渲染并创建 DOM 节点后自动调用
onMounted(() => {
container = document.getElementById('container')
console.log('第一次打印:', container.innerText)
})
</script>
<style lang="scss" scoped></style>
如果不用 async await,那么就是通过回调的形式:
setTimeout(() => {
count.value = 2 // 修改响应式状态
// 等待下一个 DOM 更新周期
nextTick(() => {
// 这个时候再打印就是最新的值了
console.log('第二次打印:', container.innerText)
})
}, 2000)
当然还是推荐使用 async await,看上去代码的逻辑更加清晰一些。
4.2 使用 reactive
<template>
<div>{{ state.count1 }}</div>
<div>{{ state.nested.count2 }}</div>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({
count1: 0,
nested: {
count2: 0
}
})
setTimeout(()=>{
state.count1++
state.nested.count2 += 2;
},2000);
</script>
<style lang="scss" scoped></style>
Vue 中的响应式底层是通过 ProxyAPI 来实现的,但是这个 ProxyAPI 只能对对象进行拦截,无法对原始值进行拦截。
这里就会产生一个问题:如果用户想要把一个原始值转为响应式,该怎么办?
两种方案:
- 让用户自己处理,用户需要将自己想要转换的原始值包装为对象,然后再使用 reactive API 🙅
- 框架层面来处理,多提供一个 API,这个 API 可以帮助用户简化操作,将原始值也能转为响应式数据 🙆
ref 的背后其实也调用了 reactive API
- 原始值:Object.defineProperty
- 复杂值:reactive API
reactive 还有一个相关的 API shallowReactiveAPI,是浅层次的,不会深层次去转换成响应式
<template>
<div>{{ state.count1 }}</div>
<div>{{ state.nested.count2 }}</div>
</template>
<script setup>
import { shallowReactive } from 'vue'
const state = shallowReactive({
count1: 0,
nested: {
count2: 0
}
})
setTimeout(()=>{
state.count1++
},2000);
setTimeout(()=>{
state.nested.count2++
},4000)
</script>
<style lang="scss" scoped></style>
4.3 使用细节
先说最佳实践:尽量使用 ref 来作为声明响应式数据的主要 API.
4.3.1 reactive局限性
-
使用 reactvie 创建响应式数据的时候,值的类型是有限的
- 只能是对象类型(object、array、map、set)
- 不能够是简单值(string、number、boolean)
-
第二条算是一个注意点,不能够去替换响应式对象,否则会丢失响应式的追踪
let state = reactive({count : 0});
// 下面的这个操作会让上面的对象引用不再被追踪,从而导致上面对象的响应式丢失
state = reactive({count : 1})
- 对解构操作不友好,当对一个 reactvie 响应式对象进行解构的时候,也会丢失响应式
let state = reactive({count : 0});
// 当进行解构的时候,解构出来的是一个普通的值
let { count } = state;
count++; // 这里也就是单纯的值的改变,不会触发和响应式数据关联的操作
// 另外还有函数传参的时候
// 这里传递过去的也就是一个普通的值,没有响应式
func(state.count)
4.3.2 ref解包细节
所谓 ref 的解包,指的是自动访问 value,不需要再通过 .value 去获取值。例如模板中使用 ref 类型的数据,就会自动解包。
4.3.2.1 ref作为reactvie对象属性
这种情况下也会自动解包
<template>
<div></div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const name = ref('Bill')
const state = reactive({
name
})
console.log(state.name) // 这里会自动解包
console.log(name.value)
</script>
<style lang="scss" scoped></style>
如果 ref 作为 shallowReactive 对象的属性,那么不会自动解包
<template>
<div></div>
</template>
<script setup>
import { ref, shallowReactive } from 'vue'
const name = ref('Bill')
const state = shallowReactive({
name
})
console.log(state.name.value) // 不会自动解包
console.log(name.value)
</script>
<style lang="scss" scoped></style>
因为对象的属性是一个 ref 值,这也是一个响应式数据,因此 ref 的变化会引起响应式对象的更新
<template>
<div>
<div>{{ state.name.value }}</div>
</div>
</template>
<script setup>
import { ref, shallowReactive } from 'vue'
const name = ref('Bill')
const state = shallowReactive({
name
})
setTimeout(() => {
name.value = 'Tom'
},2000)
</script>
<style lang="scss" scoped></style>
【练习】下面的代码:
- 为什么 Bill 渲染出来有双引号?
- 为什么 2 秒后界面没有渲染 Smith ?
<template>
<div>{{ obj.name }}</div>
</template>
<script setup>
import { ref, shallowReactive } from 'vue'
const name = ref('Bill') // name 是一个 ref 值
const obj = shallowReactive({
name
})
setTimeout(() => {
obj.name = 'John'
}, 1000)
setTimeout(() => {
name.value = 'Smith'
}, 2000)
</script>
<style lang="scss" scoped></style>
答案:
- 因为使用的是 shallowReactive,shallowReactive 内部的 ref 是不会自动解包的
- 1秒后,obj.name 被赋予了 John 这个字符串值,这就使得和原来的 ref 数据失去了联系
如果想要渲染出 Smith,修改如下:
import { ref, shallowReactive } from 'vue'
const name = ref('Bill') // name 是一个 ref 值
const obj = shallowReactive({
name
})
setTimeout(() => {
obj.name.value = 'John'
}, 1000)
setTimeout(() => {
name.value = 'Smith'
}, 2000)
下面再来看一个例子:
<template>
<div>{{ obj.name.value }}</div>
</template>
<script setup>
import { ref, shallowReactive } from 'vue'
const name = ref('Bill');
const stuName = ref('John');
const obj = shallowReactive({name})
// 注意这句代码,意味着和原来的 name 这个 Ref 失去关联
obj.name = stuName;
setTimeout(()=>{
name.value = 'Tom';
}, 2000)
setTimeout(()=>{
stuName.value = 'Smith';
}, 4000)
</script>
<style lang="scss" scoped></style>
4.3.2.2 数组和集合里面使用 ref
如果将 ref 数据作为 reactvie 数组或者集合的一个元素,此时是不会自动解包的
// 下面这些是官方所给的例子
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
<template>
<div></div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const name = ref('Bill')
const score = ref(100)
const state = reactive({
name,
scores: [score]
})
console.log(state.name) // 会自动解包
console.log(state.scores[0]) // 不会自动解包
console.log(state.scores[0].value) // 100
</script>
<style lang="scss" scoped></style>
4.3.2.3 在模板中的自动解包
在模板里面,只有顶级的 ref 才会自动解包。
<template>
<div>
<div>{{ count }}</div>
<div>{{ object.id }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0) // 顶级的 Ref 自动解包
const object = {
id: ref(1) // 这就是一个非顶级 Ref 不会自动解包
}
</script>
<style lang="scss" scoped></style>
上面的例子,感觉非顶级的 Ref 好像也能够正常渲染出来,仿佛也是自动解包了的。
但是实际情况并非如此。
<template>
<div>
<div>{{ count + 1 }}</div>
<div>{{ object.id + 1 }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0) // 顶级的 Ref 自动解包
const object = {
id: ref(1) // 这就是一个非顶级 Ref 不会自动解包
}
</script>
<style lang="scss" scoped></style>
例如我们在模板中各自加 1 就会发现上面因为已经解包出来了,所以能够正常的进行表达式的计算。
但是下面因为没有解包,意味着 object.id 仍然是一个对象,因此最终计算的结果为 [object Object]1
因此要访问 object.id 的值,没有自动解包我们就手动访问一下 value
<template>
<div>
<div>{{ count + 1 }}</div>
<div>{{ object.id.value + 1 }}</div>
</div>
</template>
5. 响应式常用API
- ref 相关:toRef、toRefs、unRef
- 只读代理:readonly
- 判断相关:isRef、isReactive、isProxy、isReadonly
- 3.3新增API:toValue
5.1 ref相关
toRef:基于响应式对象的某一个属性,将其转换为 ref 值
import { reactive, toRef } from 'vue'
const state = reactive({
count: 0
})
const countRef = toRef(state, 'count')
// 这里其实就等价于 ref(state.count)
console.log(countRef)
console.log(countRef.value)
import { reactive, isReactive, toRef } from 'vue'
const state = reactive({
count: {
value: 0
}
})
console.log(isReactive(state)) // true
console.log(isReactive(state.count)) // true
const countRef = toRef(state, 'count')
// 相当于 ref(state.count)
console.log(countRef)
console.log(countRef.value)
console.log(countRef.value.value)
toRefs:将一个响应式对象转为一个普通对象,普通对象的每一个属性对应的是一个 ref 值
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
message: 'hello'
})
const stateRefs = toRefs(state)
console.log(stateRefs) // {count: RefImpl, message: RefImpl}
console.log(stateRefs.count.value)
console.log(stateRefs.message.value)
unRef: 如果参数给的是一个 ref 值,那么就返回内部的值,如果不是 ref,那么就返回参数本身
这个 API 实际上是一个语法糖: val = isRef(val) ? val.value : val
import { ref, unref } from 'vue'
const countRef = ref(10)
const normalValue = 20
console.log(unref(countRef)) // 10
console.log(unref(normalValue)) // 20
5.2 只读代理
接收一个对象(不论是响应式的还是普通的)或者一个 ref,返回一个原来值的只读代理。
import { ref, readonly } from 'vue'
const count = ref(0)
const count2 = readonly(count) // 相当于创建了一个 count 的只读版本
count.value++;
count2.value++; // 会给出警告
在某些场景下,我们就是希望一些数据只能读取不能修改
const rawConfig = {
apiEndpoint: 'https://api.example.com',
timeout: 5000
};
// 例如在这个场景下,我们就期望这个配置对象是不能够修改的
const config = readonly(rawConfig)
5.3 判断相关
isRef 和 isReactive
import { ref, shallowRef, reactive, shallowReactive, isRef, isReactive } from 'vue'
const obj = {
a:1,
b:2,
c: {
d:3,
e:4
}
}
const state1 = ref(obj)
const state2 = shallowRef(obj)
const state3 = reactive(obj)
const state4 = shallowReactive(obj)
console.log(isRef(state1)) // true
console.log(isRef(state2)) // true
console.log(isRef(state1.value.c)) // false
console.log(isRef(state2.value.c)) // false
console.log(isReactive(state1.value.c)) // true
console.log(isReactive(state2.value.c)) // false
console.log(isReactive(state3)) // true
console.log(isReactive(state4)) // true
console.log(isReactive(state3.c)) // true
console.log(isReactive(state4.c)) // false
isProxy: 检查一个对象是否由 reactive、readonly、shallowReactive、shallowReadonly 创建的代理
import { reactive, readonly, shallowReactive, shallowReadonly, isProxy } from 'vue'
// 创建 reactive 代理对象
const reactiveObject = reactive({ message: 'Hello' })
// 创建 readonly 代理对象
const readonlyObject = readonly({ message: 'Hello' })
// 创建 shallowReactive 代理对象
const shallowReactiveObject = shallowReactive({ message: 'Hello' })
// 创建 shallowReadonly 代理对象
const shallowReadonlyObject = shallowReadonly({ message: 'Hello' })
// 创建普通对象
const normalObject = { message: 'Hello' }
console.log(isProxy(reactiveObject)) // true
console.log(isProxy(readonlyObject)) // true
console.log(isProxy(shallowReactiveObject)) // true
console.log(isProxy(shallowReadonlyObject)) // true
console.log(isProxy(normalObject)) // false
5.4 3.3新增API
toValue
import { ref, toValue } from 'vue'
const countRef = ref(10)
const normalValue = 20
console.log(toValue(countRef)) // 10
console.log(toValue(normalValue)) // 20
toValue 相比 unref 更加灵活一些,它支持传入 getter 函数,并且返回函数的执行结果
import { ref, toValue } from 'vue'
const countRef = ref(10)
const normalValue = 20
const getter = ()=>30;
console.log(toValue(countRef)) // 10
console.log(toValue(normalValue)) // 20
console.log(toValue(getter)) // 30