1 创建工程
vue-cli v4.5/:webpack
vite:Vite | 下一代的前端工具链
vite为何启动快?
开发环境使用ES6 Module,可以无需预先打包,而是采用实时编译的方式;
但是生产环境使用rollop,并不会快很多。
webpack需要转es5。
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
vue-router:Vue Router | Vue.js 的官方路由
vuex:Vuex 是什么? | Vuex
2 setup
this 为 undefined,可通过 getCurrentInstance() 获取组件实例。
export default {
name:'App',
components:{Demo},
props:['msg','school'],//props如果不声明接收,值会在$attrs(vue2)
emits:['hello'],//子组件向父组件传值要声明emit(vue2)
//在beforeCreate之前执行一次,this是undefined
setup(props,context){//context为上下文对象,包含attrs/slots/emit(同vue2)
//数据
let name = '张三'
let age = 18
//方法
function sayHello(){
alert(`我叫${name},我${age}岁了,你好啊!`)
}
//返回一个对象(常用)
return {
name,
age,
sayHello,
}
}
}
3 常用组合式API
3.0 模拟vue3实现响应式
实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
//proxy增删改属性都能获取到;Object移植到Reflect:
//#region
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`)
//return target[propName]
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
//target[propName] = value
Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
//return delete target[propName]
return Reflect.deleteProperty(target,propName)
}
})
//#endregion
3.1 ref和reactive
ref函数
ref函数用来定义一个响应式的数据。(基本数据)通过.value修改值
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。
对象类型的数据:内部求助了Vue3.0中的一个新函数—— reactive函数。
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h3>工作种类:{{job.type}}</h3>
<h3>工作薪水:{{job.salary}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup(){
//数据
let name = ref('张三')//基本类型
let age = ref(18)
let job = ref({//对象类型
type:'前端工程师',
salary:'30K'
})
//方法
function changeInfo(){
name.value = '李四'
age.value = 48
//ref函数返回一个RefImpl对象(引用实现的实例),实现响应式:__proto__上有setter和getter
console.log(name,age)
job.value.type = 'UI设计师'
job.value.salary = '60K'
//job.value是一个Proxy对象,封装在reactive函数中
console.log(job.value)
}
//返回一个对象(常用)
return {
name,
age,
job,
changeInfo
}
}
}
</script>
why .value
vue3用了proxy实现响应式,那如果用reactive定义引用类型的话是正常的,如果用ref是一个基本类型的值,就没办法实现proxy的拦截,所以vue3中对ref定义的值进行了包装,变成一个对象实现proxy进行拦截达到响应式。
reactive函数
reactive函数定义一个对象类型的响应式数据(深层次的)。
接收一个对象或数组,返回一个proxy对象。
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h3>工作种类:{{person.job.type}}</h3>
<h3>工作薪水:{{person.job.salary}}</h3>
<h3>爱好:{{person.hobby}}</h3>
<h3>测试的数据c:{{person.job.a.b.c}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
type:'前端工程师',
salary:'30K',
a:{
b:{
c:666
}
}
},
hobby:['抽烟','喝酒','烫头']
})
//方法
function changeInfo(){
person.name = '李四'
person.age = 48
person.job.type = 'UI设计师'//job直接就是proxy对象
person.job.salary = '60K'
person.job.a.b.c = 999
person.hobby[0] = '学习'
}
//返回一个对象(常用)
return {
person,
changeInfo
}
}
}
</script>
3.2 toRef和toRefs
toRef:创建一个 ref 对象,返回ObjectRefImpl对象,用于将响应式对象中的某个属性单独提供给外部使用。两者保持引用关系
toRefs:批量创建多个 ref 对象。
<template>
<h4>{{person}}</h4>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪资:{{job.j1.salary}}K</h2>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
// const name1 = person.name//无响应式
// console.log('%%%',name1)
// const name2 = toRef(person,'name')//有响应式
// console.log('####',name2)
const x = toRefs(person)//只交出第一层
console.log('******',x)
const ageRef = toRef(person, 'age')
setTimeout(() => {
person.age = 25
}, 1500)
setTimeout(() => {
ageRef.value = 30 // .value 修改值
}, 3000)
//返回一个对象(常用)
return {
person,
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary'),
...toRefs(person)
}
}
}
</script>
why toRef
对象数据分解
3.3 computed
<template>
<h1>一个人的信息</h1>
姓:<input type="text" v-model="person.firstName">
<br>
名:<input type="text" v-model="person.lastName">
<br>
<span>全名:{{person.fullName}}</span>
<br>
全名:<input type="text" v-model="person.fullName">
</template>
<script>
import {reactive,computed} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
firstName:'张',
lastName:'三'
})
//计算属性——简写(没有考虑计算属性被修改的情况)
/* person.fullName = computed(()=>{
return person.firstName + '-' + person.lastName
}) */
//计算属性——完整写法(考虑读和写)
person.fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
//返回一个对象(常用)
return {
person
}
}
}
</script>
age1具有响应式
3.4 watch和watchEffect
watch
监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
监视reactive定义的响应式数据中某个属性时:deep配置有效。
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>当前的信息为:{{msg}}</h2>
<button @click="msg+='!'">修改信息</button>
<hr>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>薪资:{{person.job.j1.salary}}K</h2>
<button @click="person.name+='~'">修改姓名</button>
<button @click="person.age++">增长年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
</template>
<script>
import {ref,reactive,watch} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
let person2 = ref({//ref定义
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//情况一:监视ref所定义的一个响应式数据(一个)
watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true})
//情况二:监视ref所定义的多个响应式数据(多个)
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变了',newValue,oldValue)
},{immediate:true})
/*
情况三:监视reactive所定义的一个响应式数据的全部属性(全部属性)
1.注意:此处无法正确的获取oldValue
2.注意:强制开启了深度监视(deep配置无效)
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:false}) //此处的deep配置无效
watch(person2,(newValue,oldValue)=>{
console.log('person2的值变化了',newValue,oldValue)
},{deep:true})
//情况四:监视reactive所定义的一个响应式数据中的某个属性(某个属性)
watch(()=>person.name,(newValue,oldValue)=>{
console.log('person的name变化了',newValue,oldValue)
})
//情况五:监视reactive所定义的一个响应式数据中的某些属性(某些属性)
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log('person的name或age变化了',newValue,oldValue)
})
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
//返回一个对象(常用)
return {
sum,
msg,
person
}
}
}
</script>
watchEffect
watch:既要指明监视的属性,也要指明监视的回调。
watchEffect:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。初始化时执行一次,用于收集要监听的数据。
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>薪资:{{person.job.j1.salary}}K</h2>
<button @click="person.name+='~'">修改姓名</button>
<button @click="person.age++">增长年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
</template>
<script>
import {ref,reactive,watch,watchEffect} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//监视
/* watch(sum,(newValue,oldValue)=>{
console.log('sum的值变化了',newValue,oldValue)
},{immediate:true}) */
watchEffect(()=>{
const x1 = sum.value
const x2 = person.job.j1.salary
console.log('watchEffect所指定的回调执行了')
})
//返回一个对象(常用)
return {
sum,
person
}
}
}
</script>
3.5 生命周期
销毁阶段的两个名字改了,创建阶段的2个钩子在setup中调用。
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
</template>
<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
name: 'Demo',
setup(){
// 等于 beforeCreate 和 created
console.log('---setup---')
//数据
let sum = ref(0)
//通过组合式API的形式去使用生命周期钩子
onBeforeMount(()=>{
console.log('---onBeforeMount---')
})
onMounted(()=>{
console.log('---onMounted---')
})
onBeforeUpdate(()=>{
console.log('---onBeforeUpdate---')
})
onUpdated(()=>{
console.log('---onUpdated---')
})
onBeforeUnmount(()=>{
console.log('---onBeforeUnmount---')
})
onUnmounted(()=>{
console.log('---onUnmounted---')
})
//返回一个对象(常用)
return {sum}
},
//通过配置项的形式使用生命周期钩子
//#region
beforeCreate() {
console.log('---beforeCreate---')
},
created() {
console.log('---created---')
},
beforeMount() {
console.log('---beforeMount---')
},
mounted() {
console.log('---mounted---')
},
beforeUpdate(){
console.log('---beforeUpdate---')
},
updated() {
console.log('---updated---')
},
beforeUnmount() {
console.log('---beforeUnmount---')
},
unmounted() {
console.log('---unmounted---')
},
//#endregion
}
</script>
3.6 provide/inject
//祖组件
setup(){
......
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
......
}
//后代组件
setup(props,context){
......
const car = inject('car')
return {car}
......
}
3.7 自定义hook函数
本质是一个函数,把setup函数中使用的Composition API进行了封装,类似于vue2.x中的mixin。
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function (){
//实现鼠标“打点”相关的数据
let point = reactive({
x:0,
y:0
})
//实现鼠标“打点”相关的方法
function savePoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY)
}
//实现鼠标“打点”相关的生命周期钩子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>当前点击时鼠标的坐标为:x:{{point.x}},y:{{point.y}}</h2>
</template>
<script>
import {ref} from 'vue'
import usePoint from '../hooks/usePoint'
export default {
name: 'Demo',
setup(){
//数据
let sum = ref(0)
let point = usePoint()
//返回一个对象(常用)
return {sum,point}
}
}
</script>
3.8 优点
vue3定义数据和使用数据放在一起进行处理,更加易读和方便处理。
4 其他组合式API
shallowReactive 与 shallowRef
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
readonly 与 shallowReadonly
readonly: 让一个响应式数据变为只读的(深只读)。
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
toRaw 与 markRaw
toRaw:将一个由reactive生成的响应式对象转为普通对象
markRaw:标记一个对象,使其永远不会再成为响应式对象。
customRef
<template>
<input type="text" v-model="keyWord">
<h3>{{keyWord}}</h3>
</template>
<script>
import {ref,customRef} from 'vue'
export default {
name: 'App',
setup() {
//自定义一个ref——名为:myRef
function myRef(value,delay){
let timer
return customRef((track,trigger)=>{
return {
get(){
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
return value
},
set(newValue){
console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger() //通知Vue去重新解析模板
},delay)
},
}
})
}
// let keyWord = ref('hello') //使用Vue提供的ref
let keyWord = myRef('hello',500) //使用程序员自定义的ref
return {keyWord}
}
}
</script>
响应式数据的判断
isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 `reactive` 创建的响应式代理
isReadonly: 检查一个对象是否是由 `readonly` 创建的只读代理
isProxy: 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理
5 新组件
Fragment
在Vue2中: 组件必须有一个根标签
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
好处: 减少标签层级, 减小内存占用
Teleport
`Teleport` 是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to="body">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
Suspense
异步渲染子组件:通过插槽控制显示
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>稍等,加载中...</h3>
</template>
</Suspense>
</div>
</template>
<script>
// import Child from './components/Child'//静态引入
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child')) //异步引入
export default {
name:'App',
components:{Child},
}
</script>
<style>
.app{
background-color: gray;
padding: 10px;
}
</style>
6 其他改变
- createAPP
通过 按需导入的
createApp
方法来来构建vue
实例,通过vue实例.use
方法来挂载插件(router
、vuex
)通过 按需导入的
createStore
方法来来构建store
实例通过 按需导入的
createRouter
方法来构建router
实例
- emits属性
子组件需要声明自定义事件;
- 多事件处理
- 移除.sync
- 异步组件
- 移除过滤器
可以用computed实现
- 移除keyCode修饰符;修改过渡类名。
7 vue3速度更快
- Proxy响应式
按需实现深度监听
2. PatchFlag & hoistStatic & cacheHandler
https://vue-next-template-explorer.netlify.app
PatchFlag:编译模板时,给动态节点做标记,如TEXT/ CLASS/ PROPS。diff算法根据标记做对比。
hoistStatic:将静态节点的定义,提升到父作用域,缓存起来;多个相邻的静态节点合并。
cacheHandler:事件缓存。
3.SSR优化
静态节点直接输出,绕过了vdom
4. tree shaking
编译时,根据不同的情况,引入不同的API
8 vue中使用JSX
8.1 基本使用
<script>
import { ref } from 'vue'
import Child from './Child'
export default {
components: { Child },
setup() {
const countRef = ref(200)
//函数中使用jsx
const render = () => {
return <>
<p>demo1 {countRef.value}</p> // jsx
<Child a={1}/>
</>
}
return render
}
}
</script>
//.jsx文件
import { defineComponent } from 'vue'
export default defineComponent({//传递setup函数或组件配置
props: ['a'],
setup(props) {
const render = () => {
return <p>Child {props.a}</p>
}
return render
}
})
8.2 对比template&JSX
JSX本质是js代码,可以使用js的任何能力
template只能嵌入简单的js表达式,其他需要指令,如v-if
template | JSX | |
插值 | {{ msg }} | { msg } |
组件定义 | 大小写 | 大写 |
属性 | :name="name" | name={name} |
事件 | @click="demo(123)" | onClick={()=>demo(123)} |
条件 | v-if | {flagRef.value && <Child></Child>} |
循环 | v-for | {state.list.map(item => <li>{item}</li>)} |
插槽 | v-slot | 通过函数传参实现 |
import { defineComponent } from 'vue'
import Child from './Child'
export default defineComponent(() => {
function render(msg) {
return <p>msg: {msg} 123123</p>
}
return () => {
return <>
<p>Demo - JSX</p>
<Child render={render}></Child>
</>
}
})
import { defineComponent, ref } from 'vue'
export default defineComponent({
props: ['render'],
setup(props) {
const msgRef = ref('作用域插槽 Child - JSX')
return () => {
return <p>{props.render(msgRef.value)}</p>
}
}
})
9 script setup(v3.2)
定义属性defineProps;定义事件defineEmits;defineExpose暴露数据给父组件
<script>
function add(a, b) { return a + b }
</script>
<script setup>
//无setup函数,无需return
import { ref, reactive, toRefs, onMounted } from 'vue'
//组件无需注册
import Child1 from './Child1'
import Child2 from './Child2'
import Child3 from './Child3'
//ref
const countRef = ref(100)
function addCount() {
countRef.value++
}
//reactive
const state = reactive({
name: '双越'
})
const { name } = toRefs(state)
//和其他script同时使用
console.log( add(10, 20) )
//触发自定义事件
function onChange(info) {
console.log('on change', info)
}
function onDelete(info) {
console.log('on delete', info)
}
//获取子组件数据
const child3Ref = ref(null)
onMounted(() => {
// 拿到 Child3 组件的一些数据
console.log(child3Ref.value)
console.log(child3Ref.value.a)
console.log(child3Ref.value.b)
})
</script>
<template>
<p @click="addCount">{{countRef}}</p>
<p>{{name}}</p>
<child-1></child-1>
<hr>
<child-2 :name="name" :age="countRef" @change="onChange" @delete="onDelete"></child-2>
<hr>
<child-3 ref="child3Ref"></child-3>
</template>
Child2.vue
<script setup>
import { defineProps, defineEmits } from 'vue'
// 定义属性:defineProps
const props = defineProps({
name: String,
age: Number
})
// 定义事件:defineEmits
const emit = defineEmits(['change', 'delete'])
function deleteHandler() {
emit('delete', 'aaa')
}
</script>
<template>
<p>Child2 - name: {{props.name}}, age: {{props.age}}</p>
<button @click="$emit('change', 'bbb')">change</button>
<button @click="deleteHandler">delete</button>
</template>
Child3.vue
<script setup>
import { ref, defineExpose } from 'vue'
const a = ref(101)
const b = 201
//子向父组件传值:defineExpose
defineExpose({
a,
b
})
</script>
<template>
<p>Child3</p>
</template>
10 TS支持
ts的主要优势在于静态类型检查和环境声明