vue3 新特性

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>

2d0d434f692ced3f2e97fddfee263e13.png

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

通过 按需导入的 createStore 方法来来构建 store 实例

通过 按需导入的 createRouter 方法来构建 router 实例

  • emits属性

        子组件需要声明自定义事件;

  • 多事件处理

  • 移除.sync 

  • 异步组件

  •   移除过滤器

        可以用computed实现

  •  移除keyCode修饰符;修改过渡类名。

7 vue3速度更快 

  1. 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

templateJSX
插值{{ 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>
        }
    }
})

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的主要优势在于静态类型检查和环境声明

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值