cli创建要求vue版本高于4.5.0,我们可以在cmd中通过vue -V
查看
然后按照脚手架的方式创建:vue create -name
进入之后选择vue3脚手架即可
vite是新一代的前端构建工具,由vue团队开发,为了挑战webpack的地位
vite的速度更快更轻量级,使用vite构建工程的方法如下:
启动不再是npm run serve
而是npm run dev
我们会发现启动快了很多,但是vite是等你进入网址再进行动态加载页面
vue3中的main.js
中的代码和vue2
不同,它使用一个轻量级的app管理组件,提供了挂载和卸载功能
//引入调用vue的工厂函数 createApp
import { createApp } from 'vue'
import App from './App.vue'
// 创建一个app对象,类似于vue2中的vm,但是app更轻
const app = createApp(App)
// 挂载
app.mount('#app')
// 卸载
app.unmount('#app')
在组件中:
tips:我们可以使用#region和#endRegion括住注释的两端,实现折叠功能
set up
是一个配置项,它的值是一个函数,里面存放了数据,方法等(感觉有点像模块化的vuex)
setup() {
let name = "tony";
let age = 18
function sayHello() {
alert(`I am ${name},I am ${age} years old`)
}
// 在setup中数据需要return出去
return{
name,
age,
sayHello
}
}
return出来的数据可以在外部调用,也可以return
一个渲染函数render
而且在模板中引用时需要使用this.xxx而不是xxx.value
我是App组件
person:{{this.name}} {{this.age}}
注意set up 不要和vue2中的data,method混用,会有优先级的问题
set up也不能是一个async函数,被async修饰后我们得到的就是一个promise包裹的对象了,所以被禁止了。
但是后期我们学习了异步组件之后,异步组件中的setup可以是一个async函数
ref在vue2中是一个属性,而在vue3中是一个函数,用于实现数据响应式
先引入ref
import { ref } from "vue";
为了实现ref,我们在定义数据时需要
let name = ref("tony")
let age = ref(18)
这样ref将我们的数据转换成了一个对象
RefImpl {__v_isShallow: false, dep: Set(1), __v_isRef: true, _rawValue: 'tony', _value: 'tony'}
dep: Set(1) {ReactiveEffect}
__v_isRef: true
__v_isShallow: false
_rawValue:" 李四"
_value: "李四"
value: "李四"
我们使用value值就可以调用setter,实现响应式
// 设置响应式数据
function changeHello(){
console.log(name);
name.value = '李四',
age.value = 48
}
我们还可以使用ref操作对象
let job = ref({
type:'前端',
salary:'30k'
})
但是修改对象中的数据使用的是 job.value.salary='60k'
,
因为对象的封装不再是refimpl,而是proxy
Proxy
[[Handler]]: Object
[[Target]]: Object
salary: "60k"
type: "前端"
[[Prototype]: Object
[[IsRevoked]]: false
reactive适用于管理对象和数组类型的数据,但不能用于管理基本数据类型
也是将对象封装成一个proxy
let job = reactive({
type:'前端',
salary:'30k'
})
更改数据的时候只需要
job.salary='60k'
console.log(job)
我们的ref管理的基本类型数据也可以通过封装成对象中的属性让reactive进行管理
在vue2中我们的新增和删除要体现在页面上需要使用
Vue.delete()和Vue.set()或者this. s e t ( ) 和 t h i s . set()和this. set()和this.delete()
vue3中的响应式,只要是reactive中管理的数据,我们就可以直接使用delete删除
vue3是通过proxy调用set和get进行数据的更改,deleteProperty实现删除
在vue2中我们使用props实现父向子传值,子组件接收需要使用props
但是如果不使用props,我们也能在vc身上的$attr
中看到
在vue3中setup的执行时间比beforeCreate早
而且setup中的this是undefined
setup中的参数第一个是 props第二个是context
vue3中的自定义事件与vue2有区别
首先向子组件传值
然后在子组件接收
注意与vue2不同vue3中需要使用emits属性获取传入的自定义事件,然后使用context参数(必须是把前面的props写上,因为context是第二个参数)触发传参
emits: ['hello'],
setup(prop,context) {
function hello(){
context.emit('hello',666)
}
return{
hello
}
}
}
vue3中也可以vue2的计算属性,但是不推荐
vue3中的计算属性需要引入然后写入在setup中
我们写一个案例
import { reactive, computed } from 'vue';
export default {
name: 'setUp',
setup() {
let person = reactive({
firstName : 'tony',
lastName : 'stake',
})
// //书写计算属性使用computed-简写形式
// person.fullName = computed(()=>{
// return person.firstName+'-'+person.lastName
// })
// getter 和setter形式
person.fullName = computed({
get(){
return person.firstName+'-'+person.lastName
},
set(newVal){
const nameArr = newVal.split('-')
person.firstName=nameArr[0]
person.lastName=nameArr[1]
}
})
return{
person
}
}
}
和vue2一样如果需要修改计算得到的值要把计算属性写成扩写形式,写出set方法
对ref使用监视属性监视简单数据
import { ref, watch } from 'vue';
export default {
name: 'setUp',
setup() {
let sum = ref(0)
let msg = ref('hello')
//watch-情况一
// watch(sum, (newVal,oldVal)=>{
// console.log("sum改变了"+oldVal+newVal);
// })
//watch-情况二-监视多个
watch([sum, msg], (newVal,oldVal)=>{
console.log(oldVal,newVal);
})
return{
sum,
msg
}
}
}
注意情况二的数据,得到的oldVal和newVal是数组
[1, 'hello'] => [0, 'hello']
当需要打开深度监视和立即监视的时候在后面写配置项
watch(sum, (oldVal,newVal)=>{
console.log("sum改变了"+oldVal+newVal);
},{immediate:true, deep:true})
reactive操作对象数据
个人信息:
我们使用reactive包裹一个对象
let person = reactive({
name:'on',
age:'18'
})
//watch-情况三-监视对象
watch(person,(oldVal,newVal)=>{
console.log(oldVal,newVal);
})
但是我们得到的数据是:
Proxy {name: 'jkl', age: '18'} => Proxy {name: 'jkl', age: '18'}
我们发现没有oldValue了,只有newValue,而且自动开启了深度监视
如果我们需要使用oldValue就只有使用ref
还有几种情况:
注意一个特殊情况:这里的job是
job:{
j1:{
salary:"1K"
}
}
如果要监视一种属性,要监听里面的数据,要用到深度监视配置deep
我们使用ref包裹基本数据的时候,很多人会习惯了.value
,但是在watch中,我们不能使用.value
,因为value取到的是值,但是我们需要监视的是一个RefImpl
的结构
但是如果我们包裹的是对象数据,情况就不一样了,我们就需要使用.value
,因为对象中的.value
是一个proxy,它才是真正的监听数据,一般的person只是一个内存地址
watch(person.value,(oldVal,newVal)=>{
console.log(oldVal,newVal);
})
还有一种不使用.value
的方法,开启深度监视
watch(person,(oldVal,newVal)=>{
console.log(oldVal,newVal);
},{deep: true})
vue3中的新属性
官方文档的定义:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
watchEffect(()=>{
const x = person.name;
console.log("name改变了");
})
我们发现我们更改person.name的时候,就会触发回调
只要我们在回调中用到的数据改变,就会触发回调
其实watchEffect有点类似computed,回调中依赖的数据变化,就会执行回调
生命周期大体上和vue2差不多,更新了两个钩子
取消了beforedestroy和destroy
更新了:
beforeUnmount
在一个组件实例被卸载之前调用。
类型
interface ComponentOptions {
beforeUnmount?(this: ComponentPublicInstance): void
}
详细信息
当这个钩子被调用时,组件实例依然还保有全部的功能。
这个钩子在服务端渲染时不会被调用。
和
unmounted
在一个组件实例被卸载之后调用。
类型
interface ComponentOptions {
unmounted?(this: ComponentPublicInstance): void
}
详细信息
一个组件在以下情况下被视为已卸载:
setup()
时创建的计算属性和侦听器) 都已经停止。可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
这个钩子在服务端渲染时不会被调用。
一般形式和我们vue2中很像,所以我们这里介绍
导入之后就可以在setUp中写
导入:import { reactive, ref, onBeforeMount } from 'vue';
使用:(在setup中)
setup() {
let sum = ref(0)
let msg = ref('hello')
let person = reactive({
name:'on',
age:'18',
job:{
j1:{
salary:"1K"
}
}
})
onBeforeMount(()=>{
console.log("===onBeforeMount===");
})
return{
sum,
msg,
person,
}
}
但是不提倡组合式api和函数式混用,导致项目结构不清晰
hook是一个函数,在setup中对组合式api进行封装
比如我们需要一个获取鼠标坐标的函数,这种函数如果经常用到,我们就需要将它写成模块
这个模块就是hook
新建一个src/hook文件夹:写入文件usePoint.js
引入,暴露,函数,返回值都要有
import { reactive, onMounted, onBeforeUnmount } from 'vue';
export default function () {
let points = reactive({
x: 0,
y: 0,
})
//自定义函数
function savePoint(event) {
points.x = event.pageX;
points.y = event.pageY;
console.log(points.x+" "+points.y);
}
//生命周期钩子
onMounted(()=>{
window.addEventListener('click', savePoint)
})
//销毁钩子
onBeforeUnmount(()=>{
window.removeEventListener('click', savePoint)
})
//因为要作为函数调用,最后要返回出去
return points
}
然后这个模块可以在
组件中被引入,然后通过函数调用
当前求和为:{{sum}}
获取当前鼠标坐标:X:{{points.x}},y:{{points.y}}
当我们需要单独将一个reactive对象中的一个属性比如:name拿出来使用的时候
如果使用
return {
name: person.name
}
就会出现丢失了响应式的问题,所以我们需要使用到toRef
有需要和之前的person产生联系使用
相当于一个浅拷贝,有引用关系和指针指向问题
或者可以使用toRefs
对一个对象的数据进行toRef操作,如果是有深度的数据就需要使用的时候加上层级关系
但是return的时候需要使用...toRefs(xxx)
,展开来进行返回
import {toRefs} from 'vue';
export default {
name:'App'
setup(){
let obj = {name : 'alice', age : 12};
let newObj= toRefs(obj); //相当于将obj中所有的元素都执行了一遍toRef
function change(){
newObj.name.value = 'Tom';
newObj.age.value = 18;
console.log(obj,newObj)
}
return {newObj,change}
}
}
shallow也就是浅层的意思,顾名思义,shallowReactive只处理对象浅层的数据的响应式,shallowRef只处理基本类型的数据响应式,不处理对象类型的数据的响应式
此时x的值为:{{x.y}}
此时p1的值为:{{sr.p1}}
此时p2.p3的值为:{{sr.p2.p3}}
import {reactive, shallowReactive, shallowRef, toRefs} from 'vue';
export default {
name: 'setUp',
setup() {
let x = shallowRef({
y : 1
})
let sr = shallowReactive({
p1:1,
p2:{
p3: 1
}
})
return{
x,
sr
}
}
}
我们得到的结果是x.y 和p2.p3的值都不会改变 ,但是 p1的值会改变,说明了shallow的浅层响应式的作用
使用readOnly包裹一个数据,使其变成只读的数据,我们将person改为只读数据
let person = reactive({
name:'on',
age:'18',
job:{
j1:{
salary:"1K"
}
}
})
person = readonly(person)
此时再更改person中的数据的时候,就会失败而且控制台出现警告
Set operation on key "name" failed: target is readonly.
shallowReadOnly
带有shallow的都是浅层的意思,那么shallowReadOnly就代表只把浅层的数据改为readOnly的形式
let person = reactive({
name:'on',
age:'18',
job:{
j1:{
salary:"1K"
}
}
})
person = shallowReadonly(person)
这样person内层的数据还是可以改变,但是外层的数据已经不能改变了。
raw类方法实现的是将响应式数据变为普通数据,也就是取消响应式效果(proxy代理)
toRaw: 简单的取消响应式
person = toRaw(person)
markRaw: 将一个数据永远不设置为响应式
//给person添加car属性,但car属性不会更改
function addCar(){
let car = {brand: '奔驰', price: '40w'}
//markRaw使得数据永远不会变为响应式
person.car = markRaw(car)
}
customRef实现自定义Ref,什么是自定义ref呢?如下代码
其中的myRef就是我们自定义的ref,需要由我们来维护
而维护这个ref就需要使用customRef自己来写响应式的get和set
我们在set中设置了延迟显示,晚一秒再触发trigger模型解析
//自定义ref
function myRef(value){
let timer
// 使用customRef实现自定义ref
return customRef((track, trigger)=>{
//需要return一个对象
return{
get(){
//将myRef中的value返回出去实现get
//track实现get的多次调用
track()
return value
},
set(newValue){
//set可以获取一个newValue参数
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
//trigger实现模板的重新解析
trigger()
},1000)
}
}
})
}
let hello = myRef("hello");
return{
hello
}
可是问题是:如果更改速度过快或者过多,就会触发很多定时器,产生抖动
所以我们需要在创建一个定时器前清除上一个定时器,保证只有一个定时器
或者我们使用setTimeInterval,循环触发trigger
这两个api用于祖孙组件间通信
祖组件:使用provide将需要传输的数据传递出去
setup(){
let car = reactive({brand:"BMW", price:"40w"})
provide("car",car)
return{
car
}
},
孙组件:使用inject获取传出的数据
setup(){
let x = inject("car")
console.log(x)
}
使用isRef,isReactive,isReadOnly,isProxy
分别代表是否是ref等,和是否是Proxy代理的对象
一个vue3中的标签,不参与渲染,比如vue2中vue标签需要一个div包裹整个template,但是vue3中不需要而是一个隐藏的fragment
也是新标签,用于将组件中的dom节点传送到指定的地方
在我们的文件结构中,父元素是App,子元素是child,孙元素是sun,孙元素拥有一个组件dialog-实现弹窗对话的效果
dialog组件:
对话1
对话2
但是问题就在于,这时对话框默认是在sun组件里的,如果使用定位调节,也会有很多的repaint的问题
所以我们使用teleport实现传送:
对话1
对话2
这样我们再写样式,这个dom节点就是依靠body来进行定位
也是一个内置的标签组件,用于解决异步组件中由于加载快慢引起的抖动问题
首先引入异步组件:同步组件都是一起显示,等到所有的子组件都加在完毕之后再进行加载
而异步组件不同,异步组件是根据层级顺序进行加载,优先加载父组件,加载一个显示一个
而且异步组件中可以使用异步promise函数作为setup的返回对象,setup也可以是async函数
//静态引入-异步组件生成函数
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/child'))
export default {
name: "App",
setup(){
return{}
},
components: { Child }
}
为解决异步组件的抖动问题:我们需要Suspence,Suspence其实类似于插槽,需要使用v-slot
我是App组件
组件加载中》》》
在vue2中使用全局挂载是直接调用Vue的原型(prototype)
但是vue3中,我们不再创建vue实例,所以无法在原型上做更改
一些全局的配置都做了替换:
我们引进一个全新的函数 config.globalProperties
const app = createApp(App)
app.config.globalProperties.$axios = axios
app.mount('#app')
这样也可以实现全局挂载的效果
vue3的跨域问题配置也是同vue2一样在vue.config.js中(没有就创建一个)
其中target中相当于baseurl
是跳转的一个默认地址
changOrigin: true,
实现我们允许跨域的效果
//vue.config.js
module.exports = {
devServer: {
open: true, //是否自动弹出浏览器页面
// host: "localhost",
// port: '8080',
proxy: { //配置跨域,可以配置多个跨域
'/api': {
target: 'http://127.0.0.1/', //这里后台的地址模拟的;应该填写你们真实的后台接口
changOrigin: true, //允许跨域
pathRewrite: {
/* 重写路径,当我们在浏览器中看到请求的地址为:http://localhost:8080/api/core/getData/userInfo 时
实际上访问的地址是:http://121.121.67.254:8185/core/getData/userInfo,因为重写了 /api
*/
'^/api': ''
}
},
}
},
}
因为vue3中setup不能使用this
所以为了获取我们全局挂载上的方法,我们需要使用getCurrentInstance
const {proxy} = getCurrentInstance()
const $axios = proxy.$axios
究竟怎样的结局配得上这一路颠沛流离