vue3
准备工作
创建项目
# npm 6.x
npm init @vitejs/app vite-vue3-starter --template vue-ts
# npm 7+(需要额外的双横线)
npm init @vitejs/app vite-vue3-starter -- --template vue-ts
# yarn
yarn create @vitejs/app vite-vue3-starter --template vue-ts
注:选择项目类型为vue+ts(小tips:git bash中直接按上下键切换选择自己想要的选项,虽然界面中不会动态展示切换的用户交互过程,但是别担心,在回车后可以看到是切换到了对应选项的)
vite
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。
支持了动态模块热重载(HMR):允许一个模块 “热替换” 它自己,而不会影响页面其余部分。
虚拟DOM
虚拟DOM就是通过JS来生成一个AST节点树(抽象语法树(AST)
- 有key
- 前序对比算法
- 尾序对比算法
- 新节点如果多出来就是挂载通过patch函数第一个参数为null
- 旧节点如果多出来就是卸载
- 情况特殊乱序
- 无key patch的时候会替换
- 乱序
- 进行前序和后序对比算法
- 然后进行相同的标记
- 移除和新增
一、组件核心 API 的使用
1.组件自动注册
在 script setup 中,引入的组件可以直接使用,无需再通过components
进行注册,并且无法指定当前组件的名字,它会自动以文件名为主,也就是不用再写name
属性了。示例:
<template>
<Child />
</template>
<script setup>
import Child from './Child.vue'
</script>
如果需要定义类似 name 的属性,可以再加个平级的 script 标签,在里面实现即可。
2.setup
script setup 是个啥?
它是 Vue3 的一个新语法糖,在 setup
函数中。所有 ES 模块导出都被认为是暴露给上下文的值,并包含在 setup() 返回对象中。相对于之前的写法,使用后,语法也变得更简单。
使用方式极其简单,仅需要在 script
标签加上 setup
关键字即可。示例:
<script setup></script>
setup是组合Api的入口,返回值是一个对象
setup执行时机
- setup是在beforeCreate生命周期之前执行
- setup在执行的时候,当前组件的实例还没有被创建出来,也就意味着:组件实例对象this根本就不能用,此时this是undefinde,不能来访问date/computed/methods/props
参数props,context
- props参数,是一个对象,里面有父级组件向子级组件传递的数据,
- context参数,
- slots 对象(插槽)
- attrs对象(获取当前标签上的所有的属性对象,但是该属性是在props中没有声明接受的所有的对象
- emits方法(分发事件)
2.1.使用 props
通过defineProps
指定当前 props 类型,获得上下文的props对象。示例:
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
title: String,
})
</script>
复制代码
2.2.使用 emits
vue3.1
子组件:
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props:{
count:Number
},
setup(props,{attrs,emit,slots}) {
const changeData = ()=>{
emit('plus',1)
}
return{
changeData
}
},
})
</script>
<template>
父组件传入的参数:{{count}}
<button v-on:click="changeData">子组件改变父组件参数</button>
</template>
父组件:
<template>
<h1>setup和ref</h1>
<div>count:{{count}}</div>
<Child :canshu="count" @plus="plus"></Child>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Child from './setup-child.vue'
export default defineComponent({
components: {
Child
},
setup() {
// return 一个对象
const count = ref('0');
const plus = (num:Number) => {
count.value += num
}
return {
count,
plus
}
},
})
</script>
vue3.2
使用defineEmit
定义当前组件含有的事件,并通过返回的上下文去执行 emit。示例:
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['change', 'delete'])
</script>
复制代码
2.3.slots (插槽)attrs
可以通过useContext
从上下文中获取 slots 和 attrs。不过提案在正式通过后,废除了这个语法,被拆分成了useAttrs
和useSlots
。示例:
// 新3.2
<script setup>
import { useAttrs, useSlots } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
父组件
<template>
<Child msg="非porps传值子组件用attrs接收" >
<!-- 匿名插槽 -->
<span >默认插槽</span>
<!-- 具名插槽 -->
<template #title>
<h1>具名插槽</h1>
</template>
<!-- 作用域插槽 -->
<template #footer="{ scope }">
<footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
</template>
</Child>
</template>
<script setup>
// 引入子组件
import Child from './child.vue'
</script>
子组件:
<template>
<!-- 匿名插槽 -->
<slot />
<!-- 具名插槽 -->
<slot name="title" />
<!-- 作用域插槽 -->
<slot name="footer" :scope="state" />
<!-- $attrs 用来获取父组件中非props的传递到子组件的参数 -->
<p>{{ attrs.msg == $attrs.msg }}</p>
<!--true 没想到有啥作用... -->
<p>{{ slots == $slots }}</p>
</template>
<script setup>
import { useSlots, useAttrs, reactive, toRef } from 'vue'
const state = reactive({
name: '张三',
age: '18'
})
const slots = useSlots()
console.log(slots.default()); //获取到默认插槽的虚拟dom对象
console.log(slots.title()); //获取到具名title插槽的虚拟dom对象
// console.log(slots.footer()); //报错 不知道为啥有插槽作用域的无法获取
//useAttrs() 用来获取父组件传递的过来的属性数据的(也就是非 props 的属性值)。
const attrs = useAttrs()
</script>
4.对外暴露 setup 中的数据和方法defineExpose
传统的写法,我们可以在父组件中,通过 ref 实例的方式去访问子组件的内容,但在 script setup 中,该方法就不能用了,setup 相当于是一个闭包,除了内部的 template
模板,谁都不能访问内部的数据和方法。
如果需要对外暴露 setup 中的数据和方法,需要使用 defineExpose API。示例:
<script setup>
import { defineExpose } from 'vue'
const a = 1
const b = 2
defineExpose({
a
})
</script>
5.在 vue3.2 script setup 中属性和方法无需返回,直接使用!
这可能是带来的较大便利之一,在以往的写法中,定义数据和方法,都需要在结尾 return 出去,才能在模板中使用。在 script setup 中,定义的属性和方法无需返回,可以直接使用!示例:
<template>
<div>
<p>My name is {{name}}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const name = ref('Sam')
</script>
二、ref全家桶
2.1 ref
2.1.1.把一个基本数据类型变成响应式
<template>
<div>count:{{ count }}</div>
</template>
<script lang="ts">
import { defineComponent, ref} from 'vue'
export default defineComponent({
setup() {
// return 一个对象
const count = ref('0');
return {
count,
}
},
})
</script>
2.1.2.另一个作用:可以获取页面中的元素
栗子:让输入框自动获取焦点
<template>
<h2>ref:可以获取页面中的元素</h2>
<input type="text" ref="inputRef">
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onBeforeUnmount, } from 'vue'
export default defineComponent({
setup(){
// 默认是空的,它加载完毕,说明组件已经存在,获取文本框元素
const inputRef = ref<HTMLElement | null>(null)
onMounted(()=>{
inputRef.value && inputRef.value.focus() // 自动获取焦点
})
return{
inputRef
}
}
})
</script>
2.2.reactive
作用:定义多个响应式数据
- 接受一个普通对象然后返回该普通对象的响应式代理器对象,响应式转换为深层次的:会影响对象内部所有的嵌套属性
- 返回的是一个Proxy代理对象,被代理者目标对象就是reactive中的传入对象obj
- 内部给予ES6 的Proxy 实现。通过代理对象操作对象内部数据都是响应式的
<script lang="ts">
import { defineComponent, ref, reactive, toRefs } from 'vue';
interface Datatprops{
rw:string[];
selectRw:string;
selectRwFun:(index:number)=> void
}
export default defineComponent({
name:"app",
setup(){
const data: Datatprops = reactive({
rw : ref(["萧炎","萧薰儿"]),
selectRw : ref(""),
selectRwFun : (index:number)=>{
data.selectRw = data.rw[index];
}
})
const refDatae = toRefs(data) // 把data作为可扩展形的
return{
...refDatae,
}
}
})
</script>
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<h1>选择初始人物</h1>
<button v-for="(item,index) in rw" :key="index" @click="selectRwFun(index)">{{item}}</button>
<div>
你选择了【{{selectRw}}】作为初始化人物
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
2.3.toRefs
可以把一个响应式对象转换为普通对象,该普通对象的每一个 property 都是一个ref
就是可以使用展开运算符,而且调用时候可以直接使用属性名
<script lang="ts">
import { defineComponent, ref, reactive, toRefs, watch,} from 'vue';
interface Datatprops{
rw:string[];
selectRw:string;
selectRwFun:(index:number)=> void
}
export default defineComponent({
name:"app",
setup(){
const data: Datatprops = reactive({
rw : ref(["萧炎","萧薰儿"]),
selectRw : ref(""),
selectRwFun : (index:number)=>{
data.selectRw = data.rw[index];
}
})
return{
...refDatae,
}
}
})
</script>
<template>
<h1>选择初始人物</h1>
<button v-for="(item,index) in rw" :key="index" @click="selectRwFun(index)">{{item}}</button>
<div>
你选择了【{{selectRw}}】作为初始化人物
</div>
</template>
<style>
</style>
2.4.toRef
-
为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是统一数据值,更新时二者是同步的
-
区别ref:拷贝了一份新的数据值单独操作,更新时相互不影响
-
应用:当要将某个prop的ref传递给复合函数时,toRef很有用
子组件内使用useGetLength时候需要的是ref类型数据,但是传入的是number数据使用toRef(props,‘age’)就可以把number转换为ref
三、vue3生命周期
- setup() :开始创建组件之前,在
beforeCreate
和created
之前执行。创建的是data
和method
- onBeforeMount() : 组件挂载到节点上之前执行的函数。
- onMounted() : 组件挂载完成后执行的函数。
- onBeforeUpdate(): 组件更新之前执行的函数。
- onUpdated(): 组件更新完成之后执行的函数。
- onBeforeUnmount(): 组件卸载之前执行的函数。
- onUnmounted(): 组件卸载完成后执行的函数
- onActivated(): 被包含在
<keep-alive>
中的组件,会多出两个生命周期钩子函数。被激活时执行。 - onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
- onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展现)。
执行顺序
- 初次渲染:
- 父组件 – onBeforeMount
- 子组件 – onBeforeMount
- 子组件 – onMounted
- 父组件 – onMounted
- 数据更新:
- 父组件 – onBeforeUpdate
- 子组件 – onBeforeUpdate
- 子组件 – onUpdated
- 子组件 – onUpdated
- 子组件卸载:
- 父组件 – onBeforeUpdate
- 子组件 – onBeforeUnmount
- 子组件 – onUnmounted
- 父组件 – onUpdated
四、watch和watchEffect监视
4.1watch
const overText = ref('哈哈哈哈')
const overHandle = () =>{
overText.value = "点餐完成|" + overText.value;
// document.title = overText.value
};
// watch 两个参数
// 1.监听的内容 可以是单个值也可以是数组形式
// 2.第二个是方法,第一个参数接受第一个函数的返回新值,第二个参数返回改变之前的值
// 3.immediate默认会执行一次watch,deep深度监视
watch([overText, () => data.selectRw],(newValue, oldValue)=>{
console.log("newValue========》",newValue);
console.log("oldValue========》",oldValue);
document.title = newValue[0];
},{immediate:true,deep:true})
return{
...refDatae,
overText,
overHandle,
}
// watch监听非响应式数据时候不会执行第二个参数回调 () => data.selectRw
4.2watchEffect
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue'
export default defineComponent({
setup() {
const msg1 = ref<String>('第一')
const msg2 = ref<String>('第二')
const stopWatchEffect = ()=> stop() // 停止监听
const stop = watchEffect((oninvalidate)=>{
const input1 = document.querySelector('#input1') as HTMLInputElement
console.log("input1",input1)
console.log("msg1",msg1.value)
console.log("msg2",msg2.value)
// 清除副作用
oninvalidate(()=>{
console.log("before")
})
},{
flush:"post", // 注意:这也将推迟副作用的初始运行,直到组件的首次渲染完成。
onTrigger(e){
debugger
}
})
return{
msg1,
msg2,
stopWatchEffect
}
},
})
</script>
<template>
<h1>watchEffect</h1>
<input id="input1" v-model="msg1" />
<input v-model="msg2" />
<button @click="stopWatchEffect">停止监听</button>
</template>
4.3watch和watchEffect的区别
1.watch可以访问新值和旧值,watchEffect不能访问。
2.watchEffect有副作用,DOM挂载或者更新之前就会触发,需要我们自己去清除副作用。
3.watch是惰性执行,也就是只有监听的值发生变化的时候才会执行,但是watchEffect不同,每次代码加载watchEffect都会执行。
4.watch需要指明监听的对象,也需要指明监听的回调。watchEffect不用指明监视哪一个属性,监视的回调函数中用到哪个属性,就监视哪个属性。
作者:赵小磊Ben
链接:https://juejin.cn/post/7032658568272691231
五、computend计算属性
import { defineComponent, ref, computed } from 'vue'
export default defineComponent({
setup(props, ctx){
const sentence = ref("欢迎来到王者荣耀")
// 只传入一个回调函数,表示get
const superSentence = computed(()=>{
return '小明' + sentence.value
})
// 返回的是一个ref对象
console.log(superSentence.value)
return{
}
}
})
<script lang="ts">
import { computed, defineComponent, reactive, ref } from 'vue'
export default defineComponent({
setup() {
const user = reactive({
name:'',
endName:''
})
const username = computed({
get(){
return user.name + '-' + user.endName
},
set(val){
console.log("e====",val)
const names = (val as String).split('-')
user.name = names[0]
user.endName = names[1]
}
})
return{
user,
username
}
},
})
</script>
<template>
名:<input v-model="user.name"/>
名:<input v-model="user.endName"/>
姓名:<input v-model="username"/>
</template>
六、hooks
将一些公共的方法抽离出来
- useMousePositions.ts
// 分装hooks函数
import {ref, onMounted, onBeforeUnmount, } from 'vue'
export default function(){
const x= ref(-1)
const y = ref(-1)
// 点击事件的回调函数
const clickHandler = (e:MouseEvent) =>{
x.value = e.pageX
y.value = e.pageY
}
onMounted(()=>{
window.addEventListener('click',clickHandler)
})
// 组件卸载前 的生命周期
onBeforeUnmount(()=>{
window.removeEventListener('click',clickHandler)
})
return{
x,
y,
}
}
- 引用页面
<script lang="ts">
import { defineComponent, ref, onMounted, onBeforeUnmount, } from 'vue'
import useMousePositions from '../hooks/useMousePositions'
import useRequest from '../hooks/require'
export default defineComponent({
setup(){
const {x,y} = useMousePositions()
// 发送请求
const {loading, data, errorMsg} = useRequest('地址1')
return{
x,
y,
loading,
data,
errorMsg
}
}
})
</script>
<template>
<h2>坐标</h2>
<div>
x:{{x}}
y:{{y}}
</div>
<div>
<h2 v-if="loading"> 数据加载中</h2>
<h2 v-else-if="errorMsg">错误信息:{{errorMsg}}</h2>
<ul v-else>
<li>name:{{data.name}}</li>
<li>id:{{data.id}}</li>
</ul>
</div>
</template>
<style scoped>
</style>
七,shallowReactive(浅响应式) 和 shallowRef(不进行对象的响应式处理)
-
shallowReactive:只处理了对象最外成层响应式,也就是浅响应式(浅监视,浅劫持)
-
shallowRef:只处理了value的响应式,不进行对象的响应式处理
-
是什么时候用浅响应式呢?
-
一般情况下使用ref和reactive即可
-
如果有一个对象数据,结构比较浅,但变化时候只是外层属性变化 ====》 shallowReactive
-
如果有一个对象数据,后面会产生新的对象来替换 ===》 shallowRef
-
八、readonly(深度只读) 和shallowReadonly(浅只读)
- readonly
- 深度只读
- 获取一个对象(响应式或者纯对象)或ref并返回原始代理的只读代理
- 只读代理是深层的:访问的任何嵌套property也是只读的
- shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的property为只读,但是不执行嵌套对象的深度只读转换(深层次对象还是可以修改的)
- 应用场景:
- 在某些特定情况下,我们可能不许忘对数据进行更新的操作,那就可以包装成一个只读代理对象来读取数据,而不能修改或者是删除
<script lang="ts">
import { defineComponent, ref, onMounted, reactive, onBeforeUnmount, readonly, shallowReadonly, } from 'vue'
export default defineComponent({
setup(){
// 默认是空的,它加载完毕,说明组件已经存在,获取文本框元素
const state = reactive({
name:'佐伊',
age:'200',
car:{
name:'奔驰',
color:'yellow'
}
})
// 只读数据 -- 深层只读
// const state2 = readonly(state)
// 只读数据 -- 浅只读(只有第一层只读)
const state2 = shallowReadonly(state)
const update = () =>{
console.log('ceshi')
state2.car.name += 2
}
return{
state,
update
}
}
})
</script>
<template>
<hr/>
{{state}}
<hr/>
<button @click="update"> 更新数据</button>
</template>
<style scoped>
</style>
九、toRow 与 markRaw
- toRaw(转换成普通的对象)
- 返回由
reactive
或readonly
方法转换为响应式代理的普通对象. - 这是一个还原方法,可用于临时读取,访问不会被dialing/跟踪,写入时也不会触发界面更新。
- 返回由
- markRaw
- 标记一个对象,时期用原不会转换为代理,返回对象本身
- 应用场景:
- 有些值不应被设置为响应式,例如负责的第三类实力或Vue组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能
十.customRef
customRed
用于自定义一个ref
,可以显式的控制一来最终和触发响应,接受一个工厂函数,俩格参数分别用于最终的track喻用于触发响应的trigger,并返回一个带有get
和set
属性的对象。
- 使用自定义ref实现带防抖功能的v-model;
<template>
<h2>CustomRef的使用</h2>
<input type="text" v-model="keyword"><br />
{{keyword}}
</template>
<script lang="ts">
import { customRef, defineComponent, ref } from 'vue'
// 自定义hook防抖函数
function useDebouncedRef<T>(value: T, delay = 200) {
let timeOutId: number
// 自定义ref对象
return customRef((track, trigger) => {
return {
// 返回数据
get() {
// 追踪数据
track()
return value
},
// 设置数据
set(newValue: T) {
clearTimeout(timeOutId)
timeOutId = setTimeout(() => {
value = newValue
trigger()
}, delay)
},
}
})
}
export default defineComponent({
name: 'App',
setup() {
const keyword = useDebouncedRef('abc', 500)
return {
keyword,
}
},
})
</script>
十一.Provide 和 inject多级通信
父子组件之间的通信或者父级直接与孙子组件通信
父级:
setup(){
// 响应式数据
const color = ref('red')
// 提供数据
provide('color',color)
return{
color
}
}
孙子:
setup(){
// 注入
const color = inject('color')
return{
color
}
}