认识Vue3
1. Vue2 选项式 API vs Vue3 组合式API
<script>
export default {
data(){
return {
count:0
}
},
methods:{
addCount(){
this.count++
}
}
}
</script>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const addCount = ()=> count.value++
</script>
特点:
- 代码量变少
- 分散式维护变成集中式维护
2. Vue3的优势
使用create-vue搭建Vue3项目
1. 认识create-vue
create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应
2. 使用create-vue创建项目
前置条件 - 已安装16.0或更高版本的Node.js
执行如下命令,这一指令将会安装并执行 create-vue
npm init vue@latest
使用npm初始化项目之后,再cd到项目目录
cd 项目名
cd到项目目录之后,再使用npm安装一些依赖
npm install
最后让项目跑起来,如果不遵循上面的规矩的化可能会出现很多的问题
npm run dev
熟悉项目和关键文件
在vue3中对创建对象进行了一定的封装,使用creat来进行创建对象。比如下面例子。
main.js
import './assets/main.css'
// new Vue()创建一个应用实例 => createApp()
// createRouter() createStore()
//将创建实例进行了封装,保证每个实例的独立封闭性,但是需要引入对应的函数才行。
import { createApp } from 'vue'
import App from './App.vue'
// mount设置挂载点#app (id为app的盒子),可以理解为前段为创建App这个对象,后半段为将这个App对象直接挂载到id为app的盒子上去
createApp(App).mount('#app')
组合式API - setup选项
<!-- 加上setup允许在script中直接编写组合式API !-->
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>
<template>
<!-- 在vue3中可以不用注册组件直接使用导入的组件 --!>
<--不再要求唯一根元素-->
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>
<main>
<TheWelcome />
</main>
</template>
<style scoped>
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
}
</style>
1. setup选项的写法和执行时机
写法
<script setup>
export default {
setup(){
},
beforeCreate(){
}
}
</script>
执行时机
在beforeCreate钩子之前执行
<script>
// setup
// 1.执行时机,比beforeCreate还要早
// 2. setup函数中,获取不到this (this是undefined),因为setup的执行时间比beforeCreate生命周期函数还要早
// this是基于当时的环境来看到底是指向谁的。在vue3中可以不用纠结this的指向了,因为vue3中很少再使用this了
export default {
setup ({
console.1og(' setup函数',this)
},
beforeCreate ( {
console.log( 'beforeCreate函数')
}
}
</script>
<template>
<div>学习Vue3</div>
</template>
2. setup中写代码的特点
在setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用
<script>
export default {
setup(){
// setup中可以提供数据,也可以提供方法,但是只有return对应的数据和方法之后才可以在模板中使用。
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
// 必须return才可以再模板中使用这个数据
return {
message,
logMessage
}
}
}
</script>
<template>
<div>{{ message }}</div>
</template>
// 这样就出现了一个问题,如果需要return多个数据或者函数,就需要一个一个return,这样做非常的麻烦。于是就出现了setup的语法糖
3.
script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句
<script setup>
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
</script>
组合式API - reactive和ref函数
在vue中,数据并不是响应式的如果需要将数据变成响应式的需要使用reactive函数和ref函数。
1. reactive
接受对象类型数据的参数传入并返回一个响应式的对象
<script setup>
// reactive: 接受对象类型数据的参数传入并返回一个响应式的对象
// 如果是简单类型的数据该怎么办呢?可以使用ref函数。
// 导入
import { reactive } from 'vue'
// 执行函数 传入参数 变量接收
const state = reactive({
msg:'this is msg'
})
const setSate = ()=>{
// 修改数据更新视图
state.msg = 'this is new msg'
}
</script>
<template>
{{ state.msg }}
<button @click="setState">change msg</button>
</template>
2. ref
接收简单类型或者对象类型的数据传入并返回一个响应式的对象
<script setup>
// 导入
import { ref } from 'vue'
// 执行函数 传入参数 变量接收
// ref的本质:是在原有传入数据的基础上,外层包了一层对象,包成了复杂类型
// ref的底层:包成复杂类型之后,再借助 reactive 实现的响应式
// 注意点:
// 1.脚本 script 中访问数据,需要通过.value
// 2.在template中, .value不需要加(帮我们扒了一层)
// ref 的底层也是使用 reactive 函数
// 由于同时使用 reactive 函数和 ref 函数会造成混乱,于是就有一个统一的规定,那就是统一使用 ref 函数
// 因为 ref 函数可以同时将普通类型和复杂类型的数据变成响应式的。
const count = ref(0)
const setCount = ()=>{
// 修改数据更新视图必须加上.value
count.value++
}
</script>
<template>
<button @click="setCount">{{count}}</button>
</template>
3. reactive 对比 ref
- 都是用来生成响应式数据
- 不同点
- reactive不能处理简单类型的数据
- ref参数类型支持更好,但是必须通过.value做访问修改
- ref函数内部的实现依赖于reactive函数
- 在实际工作中的推荐
- 推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref
组合式API - computed
计算属性基本思想和Vue2保持一致,组合式API下的计算属性只是修改了API写法
语法:
const 计算属性 = computed(()=>{
return 计算返回的结果;
})
const count = computed(()=>{
return count*2;
});
<script setup>
// 导入
import {ref, computed } from 'vue'
// 原始数据
const count = ref(0)
// 计算属性
const doubleCount = computed(()=>count.value * 2)
// 原始数据
const list = ref([1,2,3,4,5,6,7,8])
// 基于list派生一个计算属性,从list中过滤出 > 2 的数据
const newFilterList = computed(()=>{
return list.value.filter((elem)=> elem > 2 );
// 注意这里的list的属性值之一value才是真正的目标数组
})
const filterList = computed(item=>item > 2)
const addFn() = ()=>{
list.value.push(111);
}
</script>
<template>
<div>
<div>原始数据: {{ list }</div>
<div>计算后的数据: {{ newFilterList }}</div>
<button type="button" @click="addFn">修改</ button>
</div>
</template>
vue3中的computed函数的计算属性也是和vue2中的计算属性是差不多的,也是有get和set函数的,不可以随便的修改计算属性的值,否则会直接报错的。详细的内容可以直接去官网上查看。通过ref创建的计算属性原值,得到的是一个只读的属性。
计算属性中不可以有副作用,比如说:异步请求和修改dom的操作。
组合式API - watch
侦听一个或者多个数据的变化,数据变化时执行回调函数,俩个额外参数 immediate控制立刻执行,deep开启深度侦听
1. 侦听单个数据
语法:
watch(ref对象, (newValue, oldValue)=>{...});
// 注意第一个参数一定是一个ref对象,不可以是ref对象的value值比如说:ref对象.value
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
})
</script>
2. 侦听多个数据
侦听多个数据,第一个参数可以改写成数组的写法
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('cp')
// 2. 调用watch 侦听变化
watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName])
})
</script>
3. immediate
在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
},{
immediate: true
})
</script>
4. deep
通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep
语法:
// 4. deep深度监视,默认watch进行的是浅层监视
// const ref1 = ref(简单类型)可以直接监视
// const ref2 = ref(复杂类型)监视不到复杂类型内部数据的变化
watch(ref对象, (newValue, oldValue)=>{...},{deep:true});
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const state = ref({ count: 0 })
// 2. 监听对象state
watch(state, ()=>{
console.log('数据变化了')
})
const changeStateByCount = ()=>{
// 直接修改不会引发回调执行
state.value.count++
}
// 这种情况只有修改了state的整个地址,watch函数才可以监视到,
// 比如说:
// state = ref({name: 'tom'})
// state.value = {name: 'tom'}
// 这样的情况watch函数才可以直接监视到
</script>
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const state = ref({ count: 0 })
// 2. 监听对象state 并开启deep
watch(state, ()=>{
console.log('数据变化了')
},{deep:true})
const changeStateByCount = ()=>{
// 此时修改可以触发回调
state.value.count++
}
</script>
5.精确监视
可以直接监视一个对象的具体的某个值
const userInfo = ref({
name: 'tom',
age: 10
});
watch(
() => userInfo.value.age,
(newValue,oldvalue) => {
console.log(newValue,oldValue);
});
组合式API - 生命周期函数
1. 选项式对比组合式
这里的意思就是:以前的写在选项式中的API生命周期对应到组合式API中是相应的生命周期函数,比如说:选项式的beforeCreate和created生命周期函数对应到组合式API中的生命周期函数是setup函数,只要将对应的选项式API中的生命周期函数写道组合式API对应的生命周期函数中就行了。
2. 生命周期函数基本使用
- 导入生命周期函数
- 执行生命周期函数,传入回调
<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
// 自定义逻辑
})
</script>
3. 执行多次
生命周期函数执行多次的时候,会按照顺序依次执行
<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
// 自定义逻辑
})
onMounted(()=>{
// 自定义逻辑
})
</script>
组合式API - 父子通信
1. 父传子
由于写了steup,所以无法直接配置 props 选项,所以就使用vue3提供的 编译器宏来完成。
基本思想
- 父组件中给子组件绑定属性
- 子组件内部通过props选项接收数据
注意:
vue3父传子属性的方式和vue2是相同的,vue3对于直接赋值的属性可以不用在属性的前面添加 :
但是传入的值如果是父组件的某一个数据,就需要在传入的属性前面添加 :
比如说:,name属性传入的是固定值,不需要添加 :
但是age传入的是父组件的某一个属性就需要在前面添加 :
。
对于props传过来的数据,在模板template中可以直接通过名称使用,但是在script脚本中只能通过 对象.属性名 的方式获取对应的值。
2. 子传父
基本思想
- 父组件中给子组件标签通过@绑定事件
- 子组件内部通过 emit 方法触发事件
父组件给子组件添加事件的方式还是一样的,但是子组件触发父组件的方式还是和vue2有一点区别的。比如说:子组件必须通过defineEmit编译器宏的方式来生成emit,注意,在vue3中不在emit前面添加this和$了,直接使用就行了,但是,在使用之前必须在编译器宏defineEmit里面注册。
组合式API - 模版引用
概念:通过 ref标识 获取真实的 dom对象或者组件实例对象
1. 基本使用
实现步骤:
- 调用ref函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
- 对象.value就可以在脚本script中使用这个绑定的元素了。
注意:
必须要在dom元素渲染完成之后才可以进行绑定,否则就是undefined。
比较全面的写法如下:
<script>
import { ref } from 'vue';
const inp = ref(null);
onMounted(()=>{
inp.value.focus(); // 一进入页面就聚焦
});
console.log(inp.value); // 这样打印是不行的,因为dom在这个时候还没有渲染完成
const clickFn = () => {
inp.value.focus(); // 点击之后就聚焦
}
// 下面是通过ref获取子组件,尝试使用子组件数据和函数
const testCom = ref(null);
onMounted(()=>{
console.log(testCom); // 这样做虽然会打印出testCom子组件对象,但是无法在子组件对象中找到子组件的方法和数据。
})
</script>
<template>
<div>
<input ref="inp" type="text">
<button @click="clickFn">点击让输入框聚焦</button>
</div>
<Testcom ref="testCom"></TestCom>
<button>获取组件</button>
</template>
注意:
默认情况下在
可以通过defineExpose编译宏指定子组件中的哪些属性和方法允许访问
2. defineExpose
默认情况下在
使用defineExpose指定子组件传递出的数据,在父组件中也可以修改,但是这样好像不太符合编程的规范。比如说,谁的数据就由谁来维护,哪个组件的数据就由哪个组件来维护。
组合式API - provide和inject
1. 作用和场景
顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
2. 跨层传递普通数据
实现步骤
- 顶层组件通过
provide
函数提供数据- 底层组件通过
inject
函数提供数据
3. 跨层传递响应式数据
在调用provide函数时,第二个参数设置为ref对象
4. 跨层传递方法
顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据
有了vue3之后,非父子之间的传递数据和传递方法就方便多了,不用再像vue2一样,使用 provide 和 inject 来传递数据,使用 event 和 bus 事件总线来传递修改数据的方法。vue3的 provide 就同时拥有了这两个特性,既可以传递数据也可以传递修改数据的方法。
Vue3.3 新特性-defineOptions
背景说明:
有
但是用了
为了解决这一问题,引入了 defineProps 与 defineEmits 这两个宏。但这只解决了 props 与 emits 这两个属性。
如果我们要定义组件的 name 或其他自定义的属性,还是得回到最原始的用法——再添加一个普通的
这样就会存在两个
比如说:
// 这是在没有引入新特性defineOptions之前的写法,如果vue文件的名字太短,就需要在script脚本中自己添加name属性
// 但是在setup的script脚本中无法添加name属性,于是就出现了另外添加一个script脚本,用来添加其他的和setup同级的属性
// 比如说name属性,和其他的没有宏函数的钩子函数。
// 这样做会显得非常的奇怪,一个vue文件中出现了两个script脚本。于是就出现了vue3的新特性来解决这个问题。
<script>
export default {
name: 'RegisterIndex'
}
</script>
<script setup>
</script>
<template>
<div>
我是注册页
</div>
</template>
所以在 Vue 3.3 中新引入了 defineOptions 宏。顾名思义,主要是用来定义 Options API 的选项。可以用 defineOptions 定义任意的选项, props, emits, expose, slots 除外(因为这些可以使用 defineXXX 来做到)
Vue3.3新特性-defineModel
在Vue3中,自定义组件上使用v-model, 相当于传递一个modelValue属性,同时触发 update:modelValue 事件
我们需要先定义 props,再定义 emits 。其中有许多重复的代码。如果需要修改此值,还需要手动调用 emit 函数。
于是乎 defineModel 诞生了。
在defineModel出现之前父组件对子组件使用 v-model 简化代码的写法:
父组件:
<script setup>
import MyInput from '@/components/my-input.vue'
import { ref } from 'vue'
const txt = ref('123456')
</script>
<template>
<div>
<MyInput v-model="txt"></MyInput>
{{ txt }}
</div>
</template>
子组件:
<script setup>
defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelvalue'])
</script>
<template>
<div>
<input
type="text"
:value="modelValue"
@input="e => emit('update:modelValue', e.target.value)"
</div>
</template>
使用了defineModel宏定义的子组件写法:
<script setup>
import { defineModel } from 'vue'
const modelValue = defineMode1()
</script>
<template>
<div>
<input
type="text"
:value="modelValue"
input="e => modelValue = e.target.value"
</div>
</template>
生效需要配置 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))
}
}
})