Vue3
官网
https://cn.vuejs.org/guide/quick-start.html
再vue3中采用的是组合式API风格(组合式 API (Composition API))
通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup>
搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup>
中
下面是使用了组合式 API 与 <script setup>
改造后和上面的模板完全一样的组件:
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
<!-- 加上 setup 允许再script中直接编写组合式API -->
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
构建工具
create-vue 是Vue 官方新的脚手架工具,底层切换到了 vite,为开发提供极速响应
前提条件:
- 熟悉命令行
- 已安装 16.0 或更高版本的 Node.js
创建一个vue应用:npm init vue@latest
这一指令将会安装并执行 create-vue
通过npm run dev
启动项目后,浏览器显示以下图片表示创建成功,并运行了一个vue3的项目
vue3的项目目录和关键文件
创建Vue实例
vue2和vue3创建vue实例发生了很大的变化,对于vue2来说我们再min.js中是通过 new Vue({})去创建的,而再vue3中是通过createApp 去创建,虽然通过 .mount 去渲染组件,根据官方文档描述,应用实例必须在调用了 .mount()
方法后才会渲染出来
.mount()
方法应该始终在整个应用配置和资源注册完成后被调用。
main.js
文件如下:
import './assets/main.css'
// 如果要创建路由就通过 createRouter() 创建仓库就通过 createStore()
// 将创建实例进行了封装,保证每个实例的独立封闭性
import { createApp } from 'vue'
import App from './App.vue'
// mount 设置挂载点 #app 为app的盒子
createApp(App).mount('#app')
组合式API-setup选项
setup选项的写法和执行时机:
<script>
export default {
setup(){
},
beforeCreate(){
}
}
</script>
执行时机
在beforeCreate钩子之前执行
setup中写代码的特点:
- 在setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用
例如:
<script>
export default {
setup(){
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
// 必须return才可以
return {
message,
logMessage
}
}
}
</script>
不过这样总是觉得会很麻烦,所以在vue3中提供了语法糖,我们只需要在<script>
标签中添加setup
属性即可
例如:
<script setup>
// 定义一个变量
const message = 'this is message'
// 定义一个方法
const logMessage = ()=>{
console.log(message)
}
</script>
在vue3中 setup中是获取不到this
对象的,根据官网的说法是在原来的vue2中,原来的选项式api暴露了this大致意思是这样。
总结:
- setup选项的执行时机?
在beforeCreate钩子之前 自动执行 - setup写代码的特点是什么?
定义数据+函数 然后以对象方式return <script setup></script>
解决了什么问题?
经过语法糖的封装更简单的使用组合式API- setup中的this还指向组件实例吗?
不指向了,根据第一项在beforCreate之前就执行了,说明不存在组件实例了,指向undefined
组合式API - reactive 和 ref 函数
对于reactive() 和 ref() 来说都是vue3中新出的概念,都是用来表示响应式状态的。
reactive()
reactive: 主要用于创建一个包装对象的响应式对象。它接受一个普通对象,并返回一个代理对象,该代理对象会拦截对原始对象的访问。这使得在修改代理对象时,Vue 可以监听到变化。
<script setup>
import { reactive } from 'vue'
//reactive接收一个对象类型的数据,返回一个响应式对象,num就是一个响应式对象
// reactive() 返回的是一个原始对象的 Proxy 只有代理对象是响应式的,更改原始对象不会触发更新
const num = reactive({count:0})
const btnAdd = () =>{
num.count += 1
}
</script>
更多详细细节看官网:https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#reactive
reactive() 的局限性
-
有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。
-
不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
- 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
由于这些限制,我们建议使用 ref()
作为声明响应式状态的主要 API。
ref()
ref: 主要用于创建一个包装基本类型
或复杂类型
的响应式对象。它接受一个参数,并返回一个具有 .value
属性的对象,该属性包含传入的参数。ref 通常用于包装原始值,比如数字、字符串等。
Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构
为什么在脚本中要使用.value
,在模板中 不使用?
模板编译器在处理模板时,会自动解析为组件实例
在js类或对象里面 就需要显式this指定上下文
所以类比ref 他返回的是包裹的值,在模板层会解析,在js层需要加.value属性指向内部的原始值
import { ref } from 'vue';
// 接收简单类型 或 复杂类型,返回一个响应式的对象
// 本质:实在原有传入数据的基础上,外层包了一层对象,包成了复杂类型
// 底层: 包层复杂类型之后,在借助 reactive 实现的响应式
// 注意点:
// 1.脚本中访问数据,需要通过 .value
// 2.在模板中,.value不需要加,模板解析器自动帮我们解析了
const count = ref(0);
// 接收复杂类型 ,返回一个响应式对象
const message = ref({count:0,num:10})
console.log(count.value); // 访问值 0
console.log(message.value.count); // 访问值 0
<template>
<div>
{{ message.num }} --- {{ num.count }}
<button @click="btnAdd">点我+1</button>
</div>
</template>
组合式API - computed
计算属性基本思想和Vue2保持一致,组合式API下的计算属性只是修改了API写法
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
语法如下:
<script setup>
import { ref , reactive ,computed } from 'vue'
// reactive 接收一个对象,返回一个响应式对象
const num = reactive({count:0})
// 计算属性
const computedNum = computed(()=>{
return num.count + 1
})
</script>
<template>
<div>
<span>计算属性-{{ computedNum }}</span>
</div>
</template>
我们都知道,在vue2中 计算属性 是不能直接去修改值得,如果我们需要修改值,需要通过对象的形式去定义,并提供对应的get
和 set
方法,在vue3中同样的,下列有两个示例,分别是只读
的的计算属性 和 可修改
的计算属性的创建:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
创建一个可写的计算属性 ref:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
示例:
const arrList = ref([1,2,3,4,5,6,7,8,9,10])
const filterList = computed(()=>{
return arrList.value.filter(item => item>2)
})
console.log(filterList.value) // [3,4,5,6,7,8,9,10]
组合式API - watch
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)
、一个响应式对象
、一个 getter 函数
、或多个数据源组成的数组
:
语法:
<script setup>
import { ref ,watch } from 'vue'
// 定义一个响应式对象count
const count = ref(0)
// 调用watch 侦听变化
watch(count,(newValue,oldValue)=>{
console.log(`count发生了变化,旧值为${oldValue} ,新值为:${newValue}`)
})
</script >
<template>
<button @click="count++">点我加1</button>
</template>
效果展示:
侦听 getter函数
和 数组
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
// 多个数据源监听
// 第一个参数是要监听的ref对象,第二个参数是变化后的新旧值
const stop = watch(
[x, () => y.value],
([newX, newY], [oldX, oldY]) => {
console.log(`x is ${newX} (was ${oldX}) and y is ${newY} (was ${oldY})`);
}
);
对于 多个来源组成的数组的解释如下:
x 是一个 ref 对象,所以 newX 会直接获取其值。
() => y.value 是一个 getter 函数,所以 newY 会通过调用这个函数来获取其值。
这样,当 x 或 y 的值发生变化时,回调函数就会执行,打印新的 x 和 y 的值。
这种形式的 watch 可以很方便地同时监听多个数据源的变化。
深层侦听器
直接给 watch() 传入一个响应式对象,此响应对象必须是reactive
会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:
const obj = ref({ count: 0 })
// deep 深度监视,默认watch 进行的是浅层前世
// const obj = ref(简单类型) 可以直接监视
// cosnt obj = ref(复杂类型) 监视不到复杂类型内部数据的变化
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
}, { deep: true })
obj.count++
例如我现在去修改这个obj的count值:
<button @click="obj.count++"></button>
在监听obj的监听函数上,不能监听到,底层监听的是,当这个obj整个对象发生变化时即地址变化才会监听,而不是其中的一个属性,所以我们要想产生效果监听,必须在监听时加上 {deep:true}
谨慎使用
深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。
immediate 立即执行
watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。
我们可以通过传入 immediate: true 选项来强制侦听器的回调立即执行:
watch(source, (newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
}, { immediate: true })
精确侦听对象的某个属性
需求,在不开启deep的前提下,侦听age的变化,只有age变化时才执行回调
例如:
const info = ref({
name:'cp',
age:18
})
// 要监听其中的一个值变化
watch(
()=>info.age,
(newValue)=>{console.log('age发生了变化'+newValue)}
)
<template>
<button @click="info.age++">点我info属性age+1</button>
</template>
组合式API - 生命周期函数
官网:https://cn.vuejs.org/api/composition-api-lifecycle.html
Vue3的生命周期API (选项式 VS 组合式)
选项式API | 组合式API | 说明 |
---|---|---|
beforeCreate/created | setup | 在组件实例被创建之前调用 |
beforeMount | onBeforeMount | 在挂载之前调用 |
mounted | onMounted | 在挂载之后调用 |
beforeUpdate | onBeforeUpdate | 在更新之前调用 |
updated | onUpdated | 在更新之后调用 |
beforeUnmount | onBeforeUnmount | 在卸载之前调用 |
unmounted | onUnmounted | 在卸载之后调用 |
<script setup>
// beforeCreate 和 created 的相关代码,一律放在setup中执行
// 如果要使用生命周期的钩子,需要引入
import { onMounted } from 'vue'
const getList = () =>{
setTimeout(()=>{
console.log('模拟发起请求获取数据渲染')
},2000)
}
// 调用请求数据的方法
getList()
// 如果有些代码需要在mounted生命周期中执行
onMounted(() => {
console.log('mounted生命周期函数逻辑1')
})
// 写成函数的调用方式,可以调用多次,并不会冲突,而是按照顺序依次执行
onMounted(() => {
console.log('mounted生命周期函数逻辑2')
})
</script>
组合式API - 父传子通信
基本思想
- 父组件中给子组件绑定属性
- 子组件内部通过props选项接收数据
父传子:
<script setup>
import { onMounted , ref } from 'vue'
import SonCom from './components/son-com.vue'
const num = ref(0)
const addBtn = () => {
num.value++
}
// 构建一个响应式对象,传递给子组件
const user = ref({name:'zgf',age:22})
</script>
<template>
<h1>父组件</h1>
<p>{{num}}</p>
<button @click="addBtn">点击+1</button>
<!-- 传递一条基本消息 和 对象消息 -->
<SonCom :message="`向子组件传递一条消息${num}`" :data="user"></SonCom>
</template>
子组件接收消息:
<template>
<div class="son">
我是子组件:我接收到父组件传递过来的数据是:
{{ message }}
{{ data }}
</div>
</template>
<script setup>
// 注意:由于写了 setup,所有无法直接配置props选项
// 所以:此处需要借助于”编译器宏“函数接收子组件传递过来的数据
defineProps({
message:String,
data:Object
})
</script>
组合式API 子传父
基本思想:
- 父组件中给子组件标签通过@绑定事件
- 子组件内部通过 emit 方法触发事件
组合式API - 模板引用
通过 ref
标识,获取真实的dom对象或者组件实例对象
案例引用:
例如,在模板渲染时,我们需要对输入框自动聚焦,此时我们就需要获取到dom对象实例,然后操作dom对象使其聚焦
以下是代码演示:
- 调用ref函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
<template>
<div>
输入框:<input ref="input"/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)
onMounted(()=>{
input.value.focus()
})
</script>
v-for 中的模板引用
当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素:
应该注意的是,ref 数组并不保证与源数组相同的顺序。
一般场景:拿到引用,是组件就可以调用该组件的方法,是元素就可以修改样式,class,绑定事件等等
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
/* ... */
])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
组件上的模板引用 ref
官方文档:https://cn.vuejs.org/guide/essentials/template-refs.html#ref-on-component
使用了 <script setup>
的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup>
的子组件中的任何东西,除非子组件在其中通过 defineExpose
宏显式暴露:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
a,
b
})
</script>
当父组件通过模板引用获取到了该组件的实例时,得到的实例类型为 { a: number, b: number } (ref 都会自动解包,和一般的实例一样)。
具体使用:
总结:
获取模板引用的时机是什么?
- 组件挂载完毕
defineExpose编译宏的作用是什么?
- 显示暴露子组件对应的属性或方法
组合式API - provide 和 inject
作用和场景:顶层组件向任意的底层组件传递数据和方法,实现跨层级组件通信
跨层传递普通数据 和 对象数据
1.顶层组件通过 provide
函数提供数据
2.底层组件通过 inject
函数获取数据
跨层级传递函数,实现底层数据对顶层数据的修改
Vue3.3新特性-defineOptions
背景说明:
有 <script setup>
之前,如果要定义 props, emits 可以轻而易举地添加一个与 setup 平级的属性。
但是用了<script setup>
后,就没法这么干了 setup 属性已经没有了,自然无法添加与其平级的属性。
为了解决这一问题,引入了 defineProps 与 defineEmits 这两个宏。但这只解决了 props 与 emits 这两个属性。
如果我们要定义组件的 name 或其他自定义的属性,还是得回到最原始的用法——再添加一个普通的<script>
标签。
这样就会存在两个<script>
标签。让人无法接受。
所以在 Vue 3.3 中新引入了 defineOptions 宏。顾名思义,主要是用来定义 Options API 的选项。可以用 defineOptions 定义任意的选项, props, emits, expose, slots 除外(因为这些可以使用 defineXXX 来做到)
<script setup>
// 对于es来说,单个组件页面取名字需要两个单词,此时我们可以
// 通过 defaultOptions 去配置名称
import { defaultOptions } from 'vue'
defaultOptions({
// 除了配置 props,emits,expose,slots之外的所有选项
name: 'LoginIndex',
// 更多自定义属性
})
</script>
Vue3.3新特性 - defineModel
在Vue3中,自定义组件上使用v-model
相当于传递一个modelValue
属性,同时触发update:modelValue
事件
<Child v-model='isVisible'>
//相当于
<Child :modelValue='isVisible' @update:modelValue='isVisible=$event'>
我们需要先定义 props,在定义emits。其中有许多重复代码,如果需要修改此值,还需要手动调用emit函数
这样太麻烦了,于是出了 defineModel
代码引入:
从上图中的代码看起来实在是太麻烦了:下面通过 defineModel()
去实现
生效需要配置 vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true
}
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)