创建vue3项目
1. 使用vue-cli创建
npm i @vue-cli -g
## 需要4.5.0以上
vue -V
## 创建项目
vue create vue3-project
选择第三个自定义安装
2. 使用vite创建
速度快
npm install -g create-vite-app
npm init vite-app vite_study
cd vite_study
npm i
npm run dev
启动成功测试
部分源码分析
main.ts
//程序主入口文件
//引入createApp函数,创建对应的应用给,产生应用的实例对象
import { createApp } from 'vue'
//引入App组件(所有组件的父级组件)
import App from './App.vue'
//创建App应用返回对应的实例对象,调用mount方法进行挂载
createApp(App).mount('#app')//public/index.js
app.vue
项目做eslint检查,需要在设置列关闭vetur
<template>
<!-- vue2中的html模板中必须要有一对根标签,vue3组件的html模板中可以没有根标签 -->
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</template>
<script lang="ts">
//可以使用ts代码
//defineComponent函数,目的是定义一个组件,内部可以传入一个配置对象
import { defineComponent } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
export default defineComponent({
name: "App",
components: {
HelloWorld
}
});
</script>
CompositionAPI
(1)setup
- 新的option,所有的组合API都在此函数中使用,只在初始化时执行一次。
- 函数如果返回对象,对象中的属性或方法,模板中可以直接使用。
(2)ref
- 作用:是一个函数,定义一个响应式数据。
- 返回的是一个Ref对象,调用.value属性操作数据。html模板中不需要使用.value
<template>
<div>{{count}}</div>
<div @click="add">add</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "App",
setup() {
const count = ref(0);
function add() {
count.value++;
}
return {
count,
add
};
}
});
</script>
(3)reactive
- 作用:把复杂的数据变为响应式。
- 接受一个普通对象返回一个代理对象。
- 基于ES6 proxy实现,只能通过代理对象操作源对象内部数据都是响应式。
- 如果操作代理对象,目标对象中的数据也会随之变化。
- 操作代理对象实现响应式。
<template>
<div>{{user}}</div>
<div @click="add">add</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "App",
setup() {
//目标对象
const obj = {
name: "aaa",
age: 15
};
//代理对象proxy
const user = reactive(obj);
function add() {
//只能使用代理对象的方式来更新数据
user.name = "bbb";
}
return {
user,
add
};
}
});
</script>
(4)比较vue2与vue3响应式
vue2响应式
- 核心:
- 对象:通过defineProperty对对象的已有属性的读取和修改进行劫持
- 数组:通过重写数组更新
- 问题:
- 对象直接添加的属性或删除已有属性,界面不会自动更新。
- 直接通过下标替换元素或更新length,界面不会自动更新。
vue3响应式
- 核心:
- 通过proxy(代理):拦截对data任意属性的任意操作。
- 通过reflect(反射):动态对被代理对象的相应属性进行特定的操作。
<script type="text/javascript">
//目标对象
const user = {
name: "aaa",
age: 23,
wife: {
name: "bbb",
age: 19
}
};
//目标对象转成代理对象
// 参数1:user->target目标对象
// 参数2:handler->处理器对象,用来监视数据,及数据操作
const proxyUser = new Proxy(user, {
get(target, prop) {
//需要反射出该对象
return Reflect.get(target, prop);
},
//修改,添加目标对象的属性值
set(target, prop, val) {
//目标对象,属性,值
return Reflect.set(target, prop, val);
},
//删除目标对象上的某个属性
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop);
}
});
//通过代理对象更新目标对象上的某个属性值
proxyUser.name = "ccc";
//通过代理对象向目标对象上添加的某个属性值
proxyUser.gender = "男";
delete proxyUser.name;
//深度监听
proxyUser.wife.name = "ddd";
console.log(proxyUser);
</script>
(5)setup细节
- setup执行的时机
- 在beforeCreated之前调用一次,此时组件对象没有创建。
- this是undefined,不能通过this来访问data/computed/methods/ props中的相关内容。
- setup的返回值
- setup中的返回值是一个对象,内部的属性和方法是给html模板使用的。
- setup中的对象会与data函数中的对象中的属性会合并为组件对象的属性。
- setup中的方法会与data函数中的对象中的方法会合并为组件对象的方法。
- 如有重名setup优先。
- 尽量不要混合使用:method中可以访问setup提供的属性和方法(可以使用this访问), 在setup中不能访问data和method定义的属性和方法(不可以使用this)
- setup返回值是promise对象,模板看不到return对象中的属性数据。
- setup的参数
- props参数:父向子传递的数据
- context参数:是一个对象,有attrs,emit,slots
(6)reactive ref的细节
- ref可以传入对象或数组,经过reactive的处理,形成了一个proxy对象。
- ref内部:通过给value属性调价getter/setter来实现对数据的劫持。
- reactive内部:通过使用proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据。
- ref的数据操作:在js中需要.value调用,模板中直接使用。
(7)计算属性和监视
- 计算属性:computed
- 监视:watch可以监视多个数据 watchEffect,不需要配置immediate,本身默认就会执行监视。
(8)生命周期钩子函数
- 与2.X版本生命周期对应的组合式API
- beforeCreate -> setup()
- created -> setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdated -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeDestroy
- destoryed -> onUnmounted
(9)自定义hook
类似于mixins
useMousePosition.ts
import { ref, onMounted, onBeforeUnmount } from "vue";
//自定义hook
export default function () {
const x = ref(-1);
const y = ref(-1);
const clickHandler = (event: MouseEvent) => {
x.value = event.pageX;
y.value = event.pageY;
}
//页面加载完毕的生命周期组合API
onMounted(() => {
window.addEventListener("click", clickHandler);
})
//页面卸载之前的生命周期组合API
onBeforeUnmount(() => {
window.removeEventListener("click", clickHandler);
})
return {
x,
y
}
}
app.vue
<template>
<h2>x:{{x}},y:{{y}}</h2>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import useMousePosition from "./hooks/useMousePosition";
//自定义hook
export default defineComponent({
name: "App",
setup() {
const { x, y } = useMousePosition();
return {
x,
y
};
}
});
</script>
(10) toRefs的使用
<template>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
//自定义hook
export default defineComponent({
name: "App",
setup() {
const state = reactive({
name: "test",
age: 34
});
//toRefs可以把响应式对象转化成一个普通对象,该普通对象的每个property都是一个ref
const state2 = toRefs(state);
setInterval(() => {
// state.name += "123";
state2.name.value += "123";
}, 2000);
return {
...state2
};
}
});
</script>
(11)可以利用ref获取元素
利用ref函数获取组件中的标签元素
<template>
<input type="text" ref="inputRef" />
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from "vue";
export default defineComponent({
name: "App",
setup() {
//默认是空的,页面加载完毕说明组件已经存在了,获取文本框元素
const inputRef = ref<HTMLElement | null>(null);
onMounted(() => {
inputRef.value && inputRef.value.focus();
});
return {
inputRef
};
}
});
</script>
CompositionAPI(其他部分)
(1)shallowReactive 与 shallowRef
- shallowReactive:只处理了对象内最外层地 响应式(浅响应式)
- shallowRef:只处理了vaule的响应式,不进行对象的reactive处理
- 什么时候使用浅响应式
- 一般情况下不使用
- 如果有一个对象数据,只改变对象外层数据使用----->shallowReactive
- 如果有一个对象数据,后面会产生一个新的对象进行替换----->shallowRef
(2)readOnly 与 shallowReadOnly
- shallowReadOnly 浅只读 可修改外层
- readOnly 只读
(3)toRaw 与 markRaw
- toRaw: 将代理对象变成普通对象,数据变化不会响应式。
- markRaw:标记的对象数据,再也不会变为代理对象。
(4) toRef的特点及使用
<template>
<h2>param:{{param}}</h2>
<h2>nameRef:{{nameRef}}</h2>
<h2>nameToRef:{{nameToRef}}</h2>
<button @click="update">更新</button>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, toRef } from "vue";
export default defineComponent({
name: "App",
setup() {
const param = reactive({
name: "eee",
age: 1
});
//把响应式数据对象param的name属性使用ref包装成ref对象
const nameRef = ref(param.name);
// 把响应式数据对象param的name属性包装成ref对象
const nameToRef = toRef(param, "name");
const update = () => {
param.name += "1";
// nameToRef.value += "1";//param中的name同时会发生变化
// nameRef.value += "1"; //param中的name不会发生变化
};
return {
param,
nameRef,
nameToRef,
update
};
}
});
</script>
(5) customRef
用于自定义一个ref,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的track,与用于触发响应的trigger,并返回一个带有get和set属性的对象。
- 使用自定义ref实现带防抖功能的v-model
<template>
<input v-model="name" />
<div>{{name}}</div>
</template>
<script lang="ts">
import { defineComponent, ref, customRef } from "vue";
//自定义防抖hook函数
function useCustomRef(delay = 200, value: any) {
//返回一个工厂函数参数track,trigger
return customRef((track, trigger) => {
return {
//返回数据
get() {
//告诉vue追踪数据
track();
return value;
},
//设置数据
set(newValue) {
setTimeout(() => {
value = newValue;
//告诉vue更新画面
trigger();
}, delay);
}
};
});
}
export default defineComponent({
name: "App",
setup() {
// const name = ref("aaa");
const name = useCustomRef(500, "aaa");
return {
useCustomRef,
name,
ref
};
}
});
</script>
(6) provide与inject
- provide和inject提供依赖注入,功能类似vue2
- 实现跨层级组件(祖孙)间通信
(7) 响应数据的判断
- isRef :检查一个值是否为一个
ref对象 - isReactive:检查一个对象是否由
reactive创建的响应式代理 - isReadonly:检查一个对象是否由
readonly创建的只读代理 - isProxy:检查一个对象是否由
reactive或者ref创建的响应式代理
简单举个栗子
setup() {
const isRefObj = ref("aaa");
console.log(isRef(isRefObj));//true
}
新组件
(1)Fragment(片段)
- 在vue2中:组件必须有一个根标签
- 在vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
- 好处:减少标签层级,减少内存占用。
(2)Teleport(瞬移)
- Teleport提供了一种干净的方法,让组件的html在父组件界面外的特定标签(很可能是body)下插入显示
child.vue
<template>
<botton @click="showDialog = true">点击显示弹窗</botton>
<div v-if="showDialog">
<div>弹窗内容。。。。</div>
<botton @click="showDialog = false">关闭弹窗</botton>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "childDialog",
setup() {
const showDialog = ref(false);
return {
showDialog
};
}
});
</script>
app.vue
<template>
<h2>这是父组件</h2>
<child-dialog></child-dialog>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import childDialog from "@/components/child.vue";
export default defineComponent({
name: "App",
components: {
childDialog
},
setup() {
return {};
}
});
</script>
展示效果 注意dom
使用Teleport后效果
child.vue
<template>
<botton @click="showDialog = true">点击显示弹窗</botton>
<Teleport to="body">
<div v-if="showDialog">
<div>弹窗内容。。。。</div>
<botton @click="showDialog = false">关闭弹窗</botton>
</div>
</Teleport>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "childDialog",
setup() {
const showDialog = ref(false);
return {
showDialog
};
}
});
</script>
展示效果 注意dom
(3)Suspense(不确定的)
- 允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
child.vue
<template>
<h3>AsyncComponent子组件</h3>
<h3>{{msg}}</h3>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "AsyncComponent",
setup() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
msg: "异步显示msg"
});
}, 2000);
});
}
});
</script>
app.vue
<template>
<h2>这是父组件:Suspense组件使用</h2>
<Suspense>
<!-- v-slot="default 简写 #default" -->
<template #default>
<!-- 异步组件 -->
<AsyncComponent />
</template>
<template #fallback>
<!-- loading内容 -->
<h2>Loading内容</h2>
</template>
</Suspense>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
//静态引入组件
// import AsyncComponent from "@/components/child.vue";
//vue2动态引入(vue3中这种写法不可以)
// const AsyncComponent = () => import("./components/child.vue");
//vue3动态引入
const AsyncComponent = defineAsyncComponent(() =>
import("./components/child.vue")
);
export default defineComponent({
name: "App",
components: {
AsyncComponent
},
setup() {
return {};
}
});
</script>
显示效果
感谢
尚硅谷 24kcs.github.io/vue3_study