创建vue3项目
1. 使用vue-cli创建项目
安装/升级:npm install -g @vue/cli
查看vue-cli版本(保证版本高于4.5.0以上):vue --version
创建项目:vue create 项目名
2. 使用vite创建项目
创建项目:npm init vite-app 项目名
初见 setup
1. setup
setup函数是所有组合API的入口,只在初始时执行一次(在beforeCreate生命周期回调之前)。
函数返回的是对象,对象中的属性和方法均可在模板中直接使用。
// App.vue
<template>
<div class="">啦啦啦~</div>
<h1>{
{ num }}</h1>
</template>
<script lang="ts">
// defineComponent函数:用于定义组件,内部可以传入一个配置对象
import { defineComponent } from 'vue';
// 暴露出去一个定义好的组件
export default defineComponent({
// 当前组件名为App
name: 'App',
setup() {
const num = 66
return {
num
}
}
});
</script>
2. ref
- 作用:定义一个数据的响应式
- 语法: const 参数名 = ref(参数初始值)
- 创建一个包含响应式数据的引用(reference)对象
- js中操作数据: 参数名.value
- html模板中操作数据: 参数名
- 一般用来定义一个基本类型的响应式数据
<template>
<h2>setup和ref的基本使用</h2>
<br />
<h4>{
{ count }}</h4>
<button @click="updataCount">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'App',
setup() {
// 变量
// ref是一个函数,作用:定义一个响应式的数据 ,返回的是一个ref对象
const count = ref(0) //此时count是一个对象
// 方法
function updataCount() {
console.log('~~')
count.value++;
}
// 返回的是一个对象
return {
count,
updataCount
}
}
});
</script>
ref — 获取元素
<template>
<h2>ref的另一个用法:获取元素</h2>
<br />
<input type="text" ref="inputRef">
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
export default defineComponent({
name: 'App',
// 需求:当页面加载完毕后,页面中的文本框可以直接获取焦点(自动获取焦点)
setup() {
// 默认是空的,当页面加载完毕后(组件已存在),获取文本框
const inputRef = ref<HTMLElement | null>(null)
onMounted(() => {
inputRef.value && inputRef.value.focus() //自动获取焦点
})
return {
inputRef,
}
}
});
</script>
3. reactive
- 作用:定义多个数据的响应式
- 语法: const 参数名 = reactive(obj)
- 接收一个普通对象然后返回该普通对象的响应式代理对象
- 响应式转换是“深沉的”:会影响对象内部所有嵌套的属性
- 内部基于es6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
总结:
操作代理对象,目标对象中的数据也会相应变化;
操作代理对象,界面也会重新渲染。
<template>
<h2>reactive的基本使用</h2>
<br />
<h4>{
{ obj.name }}</h4>
<h4>{
{ obj.age }}</h4>
<h4>{
{ obj.cars }}</h4>
<button @click="updateUser">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
name: 'App',
setup() {
// 返回的是一个Proxy的代理对象,被代理的对象就是reactive中传入的对象
const obj = reactive({
name: 'eiton',
age: 12,
cars: ['奔驰', '宝马']
})
const updateUser = () => {
obj.age++
}
return {
obj, updateUser
}
}
});
</script>
比较vue2和vue3的响应式(重要)
响应式:在改变数据的时候,视图也会跟着更新。当修改 Vue 实例中的数据时,视图就会重新渲染,出现新的内容。
vue2的响应式
vue2的响应式是利用Object.defineProperty结合 getter 与 setter 方法实现的监听和代理,进而来实现数据响应式。
//源数据
let person = {
name: '噸噸',
age: 21
}
//vue2中响应式
let p = {};
给p加了个name属性,值是undefined
Object.defineProperty(p, 'name', {})
Object.defineProperty(p, 'name', {
configurable: true,
//只要读取name属性就会触发get方法
get() {
console.log('我得到了');
return person.name
},
//只要修改name属性就能触发set方法
set(value) {
console.log('有人修改了name属性');
person.name = value
}
})
//这种用Object.defineProperty添加新的属性和删除已有的属性就是不响应式的了
vue3的响应式
vue3的响应式是通过es6的 Proxy结合Reflect 来实现的。
//vue3响应式
// window.Proxy window内置的构造函数,不用下载不用安装
// let p=new Proxy(代理的源对象,{})
// let p = new Proxy(person, {}) //把p映射到person对象上面,做到响应。console.log(p)可以看到效果
let p = new Proxy(person, {
get(target, propName) {
console.log('读取了p的属性');
return target[propName]
},
set(target, propName, value) {
console.log('修改或者添加了p的属性');
return target[propName] = value;
},
deleteProperty(target, propName) {
console.log('执行了删除操作');
return delete target[propName];
}
})
setup细节
setup细节:
- 执行时机:
- setup执行时,组件尚未创建,即组件实例对象this不能使用。
- 所有的composition Api相关回调函数亦不能使用。
- 返回值:
- 一般返回一个对象:为模板提供数据,即模板可直接使用对象中的属性/方法
- 返回对象中的属性与data函数返回的对象的属性合并成为组件对象的属性
- 返回对象中的方法与methods中的方法合并成为组件对象的方法
- 若有重名,setup优先 (在vue3中尽量不要混用setup和data / setup和methods)
- 参数:
- props参数,是一个对象,里面有父级向子级组件传递的数据,并在子级组件中使用props接收到的所有属性
- 包含props配置声明且传入的所有属性的对象
- context参数,是一个对象,对象里包含attre对象(获取当前组件标签上的所有的属性)、emit方法(分发事件的),slots对象(插槽)
- 包含没有在props配置中声明的属性对象,相当于this.$attrs
- props参数,是一个对象,里面有父级向子级组件传递的数据,并在子级组件中使用props接收到的所有属性
ref 和 reactive 细节
- ref 和 reactive 是vue3中Composition API中最重要的响应式API
- ref是用来处理ts基本类型的数据,reactive是用来处理对象的数据
- ref 内部原理:通过对value属性进行setter和getter操作,实现数据劫持
- reactive 内部原理:利用Proxy实现数据劫持,通过reflect操作内部数据
计算属性 和 监视
<template>
<h2>计算属性和监视</h2>
<br />
<fieldset>
<legend>姓名操作</legend>
姓氏: <input type="text" placeholder="请输入姓氏" v-model="user.firstName" />
<br /> <br />
名字: <input type="text" placeholder="请输入名字" v-model="user.lastName" />
</fieldset>
<fieldset>
<legend>计算属性和监视 演示:</legend>
姓名:{
{ fullName }}<br />
姓名:<input type="text" placeholder="显示姓名" v-model="fullName2" /><br />
姓名:<input type="text" placeholder="显示姓名" v-model="fullName3" /><br />
</fieldset>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, watch, watchEffect } from 'vue';
export default defineComponent({
name: 'App',
setup() {
const user = reactive({
firstName: '西方',
lastName: '常败',
})
// 计算属性
// 返回的是一个类型的对象
const fullName = computed(() => {
return user.firstName + '_' + user.lastName
})
const fullName2 = computed({
get() {
return user.firstName + '_' + user.lastName
},
set(val: string) {
console.log('----', val)
user.firstName = val.split('_')[0]
user.lastName = val.split('_')[1]
}
})
// 监视 --- 监视指定数据
const fullName3 = ref('')
// watch(user, (val) => {
// console.log('监视', val)
// fullName3.value = val.firstName + '_' + val.lastName
// }, { immediate: true })
// immediate: true 默认开始执行一次 ,deep 深度监视
// 监视,不需要配置immediate,本身默认就会进行监视,(默认执行一次)
// watchEffect(() => {
// fullName3.value = user.firstName + '_' + user.lastName
// })
// 监视fullName3的数据,改变firstName和lastName
watchEffect(() => {
const names = fullName3.value.split('_')
user.firstName = names[0] || ''
user.lastName = names[1] || ''
})
return {
user,
fullName,
fullName2,
fullName3
}
}
});
</script>
// watch可以监视多个数据
// watch([user.firstName,user.lastName,fullName3],()=>{
// // 这里的代码没有被执行 ---原因--- fullName3是响应式的数据 但user.firstName和user.lastName不是响应式数据
// })
// 使用watch监视非响应式的数据,需要回调
watch([()=>user.firstName,()=>user.lastName,fullName3],()=>{
console.log('~~~')
})
watchEffect和watch的区别
- 属性监听
watch:手动添加
watchEffect:自动监听 - 初始化执行
watchEffect:页面初始时会执行一次
生命周期
<script lang="ts">
import { defineComponent, onBeforeMount, onBeforeUpdate, onUpdated, onMounted, onBeforeUnmount, onUnmounted, ref } from 'vue';
export default defineComponent({
name: 'child',
// vue 2.x 的生命周期
beforeCreate() {
console.log('vue 2.x ---beforeCreate ')
},
created() {
console.log('vue 2.x ---created ')
},
beforeMount() {
console.log('vue 2.x ---beforeMount ')
},
mounted() {
console.log('vue 2.x ---mounted ')
},
beforeUpdate() {
console.log('vue 2.x ---beforeUpdate ')
},
updated() {
console.log('vue 2.x ---updated ')
},
// vue 2.x 中的beforeDestroy和destroyed 在 vue 3.x中已更名为beforeUnmount和unmounted,故不能再使用
beforeUnmount() {
console.log('vue 2.x ---beforeDestroy / beforeUnmount ')
},
unmounted() {
console.log('vue 2.x ---destroyed / unmounted')
},
// vue 3.x 组合api
setup() {
console.log('vue 3.x ---setup')
const msg = ref('vue 3.x ---setup')
const update = (() => {
msg.value += '~'
})
onBeforeMount(() => {
console.log('vue 3.x ---onBeforeMount ')
})
onMounted(() => {
console.log('vue 3.x ---onMounted')
})
onBeforeUpdate(() => {
console.log('vue 3.x ---onBeforeUpdate')
})
onUpdated(() => {
console.log('vue 3.x ---onUpdated')
})
onBeforeUnmount(() => {
console.log('vue 3.x ---onBeforeUnmount')
})
onUnmounted(() => {
console.log('vue 3.x ---onUnmounted')
})
return {
msg,
update,
}
},
});
</script>
自定义hook函数
Vue3 的 hook函数 相当于 vue2 的 mixin;
hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数;
// src -> hooks -> useMousePosition.ts
import { onBeforeUnmount, onMounted, ref } from 'vue';
export default function () {
const x = ref(-1)
const y = ref(-1)
// 点击事件的回调函数
const clickHandler = (event: MouseEvent) => {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {
window.addEventListener('click', clickHandler)
})
onBeforeUnmount(() => {
window.removeEventListener('click', clickHandler)
})
return {
x, y
}
}
// src ->App.vue
<template>
<h2>~~自定义hook函数~~</h2>
<h4>x:{
{x}},y:{
{y}}</h4>
<h4 v-if="loading">loading...</h4>
<h4 v-else-if="errorMsg">错误信息:{
{errorMsg}}</h4>
<!-- 对象数据 -->
<!-- <ul v-else>
<li>id:{
{ data.id}}</li>
<li>address:{
{data.address}}</li>
<li>distance:{
{data.distance}}</li>
</ul> -->
<!-- 数组数据 -->
<div v-else>
<ul v-for="(item,ind) in data" :key="ind">
<li>id:{
{ item.id}}</li>
<li>title:{
{item.title}}</li>
<li>price:{
{item.price}}</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue';
import useMousePosition from './hooks/useMousePosition';
import uesRequest from './hooks/uesRequest';
// 定义接口,约束对象的类型
interface AddressData {
id: number;
address: string;
distance: string;
}
interface ProjectData {
id: string;
title: string;
price: number;
}
export default defineComponent({
name: 'App',
// 注册组件
setup() {
const { x, y } = useMousePosition()
// const { loading, data, errorMsg } = uesRequest<AddressData>('/data/address.json') //获取对象数据
const { loading, data, errorMsg } = uesRequest<ProjectData>('/data/products.json') //获取数组对象数据
watch(data, () => {
if (data.value) {
console.log(data.value.length)
}
})
return {
x, y, loading, data, errorMsg
}
},
});
</script>
toRefs的使用
<template>
<h2>~~toRefs的使用~~</h2>
<!-- <h4>name:{
{state.name}}</h4>
<h4>age:{
{state.age}}</h4> -->
<h4>name:{
{name}}</h4>
<h4>age:{
{age}}</h4>
<br />
<h4>name2:{
{name2}}</h4>
<h4>age2:{
{age2}}</h4>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
function useFeatureX() {
const state = reactive({
name2: '制度化',
age2: 16
})
return {
...toRefs(state)
}
}
export default defineComponent({
name: 'App',
setup() {
const state = reactive({
name: '傲娇地上',
age: 68
})
// toRefs可以把 一个响应式对象 变成 普通对象,该普通对象的每一个property都是一个ref
const state2 = toRefs(state)
// 定时器
setInterval(() => {
// state.name += '~'
state2.name.value += "?"
}, 5000)
const { name2, age2 } = useFeatureX()
return {
// state , //响应式
// ...state, //非响应式
...state2, name2, age2
}
},
});
</script>
shallowReactive和shallowRef
<template>
<h2>shallowReactive和shallowRef</h2>
<br />
<h4>{
{m1}}</h4>
<h4>{
{m2}}</h4>
<h4>{
{m3}}</h4>
<h4>{
{m4}}</h4>
<button @click="update">更改数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, shallowReactive, ref, shallowRef } from 'vue';
export default defineComponent({
name: 'App',
setup() {
// 深度劫持(深监视)--- 深度响应式
const m1 = reactive({
name: '多返回',
age: 16,
car: {
brand: '奔驰',
color: 'red'
}
})
// 浅劫持(浅监视)--- 浅响应式
const m2 = shallowReactive({
name: '多返回',
age: 16,
car: {
brand: '奔驰',
color: 'red'
}
})
// 深度劫持(深监视)--- 深度响应式 --- 做了reactive的处理
const m3 = ref({
name: '多返回',
age: 16,
car: {
brand: '奔驰',
color: 'red'
}
})
// 浅劫持(浅监视)--- 浅响应式 --- 只处理value的响应式,不进行对象的reactive处理
const m4 = shallowRef({
name: '多返回',
age: 16,
car: {
brand: '奔驰',
color: 'red'
}
})
const update = () => {
// 更改数据
// m1.name += '*'
// m1.car.brand += '*'
// m2.name += '*'
// m2.car.brand += '*' // 单独这个,无响应
// m3.value.name += '*'
// m3.value.car.brand += '*'
//m4.value.name += '*' // 无响应
// m4.value.car.brand += '*' // 无响应
}
return {
m1, m2, m3, m4, update
}
}
});
</script>
readonly和shallowReadonly
<template>
<h2>readonly和shallowReadonly</h2>
<br />
<h4>state:{
{state2}}</h4>
<button v-on:click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, readonly, shallowReadonly } from 'vue';
export default defineComponent({
name: 'App',
setup() {
const state = reactive({
name: '多返回',
age: 16,
car: {
brand: '奔驰',
color: 'red'
}
})
// const state2 = readonly(state) // 只读,深度只读
const state2 = shallowReadonly(state) // 只读,浅只读
const update = () => {
// state2.name += '*' // 报错 只读不能更改 readonly处理后
// state2.car.brand += '*' // 报错 只读不能更改 readonly处理后
// state2.name += '*' // 报错 只读不能更改 shallowReadonly处理后
state2.car.brand += '*' // 不报错,可修改
}
return {
state2,
update
}
}
});
</script>
toRaw和markRaw
<template>
<h2>toRaw和markRaw</h2>
<br />
<h4>state:{
{state}}</h4>
<hr />
<button v-on:click="testToRaw">测试toRaw</button>
<button v-on:click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
import { defineComponent, markRaw, reactive, toRaw } from 'vue';
interface UserInfo {
name: string;
age: number;
car: object;
like?: string[];
}
export default defineComponent({
name: 'App',
setup() {
const state = reactive<UserInfo>({
name: '多返回',
age: 16,
car: {
brand: '奔驰',
color: 'red'
}
})
const testToRaw = () => {
// 把 代理对象 变为 普通对象 (数据变化,页面不变化)
const user = toRaw(state)
user.name += '*'
console.log(user)
}
const testMarkRaw = () => {
// state.like = ['吃', '喝']
// state.like[0] += '-'
// markRaw标记后的对象数据,从此以后不能再成为代理对象
const likes = ['吃', '喝']
state.like = markRaw(likes)
setInterval(() => {
if (state.like) {
state.like[0] += '-'
console.log('setInterval')
}
}, 2000)
}
return {
state, testToRaw, testMarkRaw
}
}
});
</script>
<style>
button {
margin: 0 10px;
}
</style>
toRef的使用及特点
<template>
<h2>toRef的使用及特点:</h2>
<br />
<h4>state:{
{state}}</h4>
<h4>money:{
{money}}</h4>
<h4>age:{
{age}}</h4>
<hr />
<button v-on:click="update">更新数据</button>
<hr />
<Child :age="age"></Child>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRef } from 'vue';
import Child from './components/child.vue'
export default defineComponent({
name: 'App',
components: {
Child
},
setup() {
const state = reactive({
name: '大家',
age: 12,
money: 100
})
// 把响应式数据state对象中的‘age’属性数据变成ref对象
const age = toRef(state, 'age') // 数据变化会影响state.age
// 吧响应式数据state对象中的‘money’属性用ref进行包装,变成一个ref对象
const money = ref(state.money) // 数据变化不会影响statemoney
const update = () => {
state.age += 1
age.value += 2
money.value += 100
}
return {
state,
money,
age,
update,
}
}
});
</script>
// 子组件 /components/child.vue
<template>
<h2>child子组件~</h2>
<h4>age:{
{age}}</h4>
<h4>length:{
{length}}</h4>
</template>
<script lang="ts">
import { computed, defineComponent, Ref, toRef, } from 'vue';
function useGetLength(age: Ref) {
return computed(() => {
return age.value.toString().length
})
}
export default defineComponent({
name: 'Child',
props: {
age: {
type: Number,
required: true
}
},
setup(props) {
const length = useGetLength(toRef(props, 'age'))
return {
length
}
}
});
</script>
customRef(不常用)
customRef — 自定义ref
<template>
<h2>CustomRef的使用</h2>
<input type="text" v-model="inputValue">
<h4>inputValue: {
{inputValue}}</h4>
</template>
<script lang="ts">
import { track, trigger } from '@vue/reactivity';
import { customRef, defineComponent } from 'vue';
// 自定义hook 防抖动的函数
function useDebouncedRef<T>(value: T, delay = 200) {
// 准备一个存储定时器的id变量
let timer
return customRef((track, trigger) => {
return {
// 返回数据
get() {
// 告诉vue追踪数据
track()
return value;
},
// 设置数据
set(val: T) {
// 清理定时器
clearTimeout(timer)
// 启动定时器
timer = setTimeout(() => {
value = val
// 通知vue 更新数据
trigger()
}, delay)
},
}
})
}
export default defineComponent({
name: 'App',
setup() {
const inputValue = useDebouncedRef('abc', 500)
return {
inputValue
}
}
});
</script>
provide 和 inject
provide 和 inject 主要用于跨层级组件间的通信
// 爷爷组件
<template>
<h2>provide 和 inject : 提供依赖注入,实现跨层级组件(祖孙)间通信</h2>
<h4>当前颜色: {
{color}}</h4>
<br>
<button @click="color='red'">红色</button><button @click="color='yellow'">黄色</button><button
@click="color='blue'">蓝色</button>
<hr>
<child></child>
</template>
<script lang="ts">
import { defineComponent, provide, ref } from 'vue';
import child from './components/child.vue';
export default defineComponent({
name: 'App',
components: {
child
},
setup() {
const color = ref('red')
// 给孙子组件传数据
provide('color', color)
return {
color
}
}
});
</script>
<style>
button {
margin: 0 10px;
}
</style>
// 子组件
<template>
<h2>child子组件~</h2>
<hr>
<grandson />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import grandson from './grandSon.vue';
export default defineComponent({
name: 'Child',
components: {
grandson
},
setup() {
return {
}
}
});
</script>
// 孙子组件
<template>
<h2>grandson孙子组件~</h2>
<h4>接收爷爷组件传过来的值---{
{color}}</h4>
</template>
<script lang="ts">
import { defineComponent, inject, } from 'vue';
export default defineComponent({
name: 'GrandSon',
setup() {
// 接收爷爷组件传过来的值
const color = inject('color')
return {
color
}
}
});
</script>
响应式数据的判断
- isRef,是否是由ref定义的响应式数据
- isReactive,是否是由reactive定义的响应式数据
- isReadonly,是否是由readonly定义的数据
- isProxy,是否是由reactive或readonly定义的数据
VUE3新内置组件
Fragment(片段)组件
- vue3时,组件的模板结构中出现多个标签时,可以不用根标签。这是因为vue3会自动将多个标签用fragment包裹。
- 好处:减少标签层级,减少内存占用.
Teleport(瞬移)组件
vue新增的内置组件Teleport,Teleport可以将特定内容转移至指定位置;
Teleport的两个prop参数:
- to - string。需要 prop,必须是有效的查询选择器或HTMLElement (如果在浏览器环境中使用)。指定将在其中移动 teleport标签 内容的目标元素。
- disabled - boolean。此可选属性可用于禁用 teleport标签的功能,这意味着其插槽内容将不会移动到任何位置,而是在您在周围父组件中指定了 teleport标签 的位置渲染。
<h2>Teleprot(瞬移)组件</h2>
<p>vue新增的内置组件Teleport,Teleport可以将特定内容转移至指定位置;</p>
<p>Teleport的两个prop参数</p>
<p>·to - string。需要 prop,必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)。指定将在其中移动 teleport标签 内容的目标元素</p>
<p>·disabled - boolean。此可选属性可用于禁用 teleport标签 的功能,这意味着其插槽内容将不会移动到任何位置,而是在您在周围父组件中指定了 teleport标签 的位置渲染。</p>
<div id="teleprot_id"></div>
<div class="teleprot_class"></div>
<Teleport to="body">
<h4>1.我是Teleport里面的东西~</h4>
</Teleport>
<Teleport to="#teleprot_id">
<h4>2.我是Teleport里面的东西~ id</h4>
</Teleport>
<Teleport to=".teleprot_class">
<h4>3.我是Teleport里面的东西~ class</h4>
</Teleport>
Suspense(不确定)组件
等待异步组件时渲染一些额外内容,让应用有更好的用户体验。
<template>
<h2>Suspense(不确定)组件</h2>
// 3. 使用Suspense包裹组件,并配置好default 与 fallback
<Suspense>
<template #default>
<AsyncComponent></AsyncComponent>
</template>
<template #fallback>
<!-- loading内容 -->
<h4>loading~~~</h4>
</template>
</Suspense>
</template>
<script lang="ts">
//1.引入组件
import { defineAsyncComponent, defineComponent } from 'vue';
// import AsyncComponent from './components/AsyncComponent.vue' //静态引入
// vue3中的动态引入
const AsyncComponent = defineAsyncComponent(() => import('./components/AsyncComponent.vue'))
export default defineComponent({
name: 'App',
// 2.实例化组件
components: {
AsyncComponent
},
setup() {
return {
}
}
});
</script>