Vue3
Vite
- 它是一个更加轻量(热更新速度快,打包构建速度快)的
vue项目脚手架工具。 - 相对于
vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置。 - 所以: 在单纯学习
vue3语法会使用它,后面做项目的时候我们还是使用vue-cli
创建项目
创建项目
npm init vite 项目名称或yarn create vite 项目名称安装依赖
npm i或者yarn启动项目
npm run dev或者yarn dev我们可以使用
vite创建项目学习vue3语法,使用vue-cli创建项目正式开发。
组合式 API
setup
<script>
export default {
setup(){
console.log(this,'setup 执行了'); // setup自身没有this
},
beforeCreate(){
console.log('beforeCreate 执行了');
}
//undefined setup 执行了
//beforeCreate 执行了
}
</script>setup自动执行, 并且在所有生命周期执行之前- 没有
this指向, 不能绑定组件实例
渲染数据
<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
setup() {
const msg = "Hello World!"
// 这里msg必须由return返回出来,页面中才能使用
return { msg }
}
}
</script>绑定方法
由于数据不是响应式的。所以点击按钮修改 msg 的数据,在页面中其实并没有发生变化。但 msg 的值确实已经被修改为了:"Hello Vue3!"
<template>
<!-- Hello World! -->
<!-- 页面中的值不是响应式的,所以点击按钮修改值并不会发生变化 -->
<div @click="btn">{{ msg }}</div>
</template>
<script>
export default {
setup() {
let msg = "Hello World!"
const btn = () => {
msg = "Hello Vue3!"
console.log(msg);
// "Hello Vue3!"
}
// 这里msg必须由return返回出来,页面中才能使用
return { msg, btn }
}
}
</script>生命周期
Vue2的生命周期
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyedVue3的生命周期
setup创建实例前onBeforeMount挂载DOM前onMounted挂载DOM后onBeforeUpdate更新组件前onUpdated更新组件后onBeforeUnmount卸载销毁前onUnmounted卸载销毁后
示例
<template>
<div class="content">Hello World!</div>
</template>
<script>
// 导入生命周期函数
import { onBeforeMount, onMounted } from 'vue'
export default {
setup() {
// DOM渲染前
onBeforeMount(() => {
// 此时还获取不到DOM元素
console.log('DOM渲染前:', document.querySelector('.content'));
})
// DOM渲染后
onMounted(() => {
console.log('DOM渲染后1:', document.querySelector('.content'));
})
// 相同的声明周期函数可以写多个,执行顺序由上到下
onMounted(() => {
console.log('DOM渲染后2:', document.querySelector('.content'));
})
}
}
</script>响应式
ref
默认 Vue3 不支持响应式,所以修改数据页面中不会发生变化。
我们可以通过 ref 函数将简单或复杂类型的数据转换响应式
定义简单的数据类型为响应式
<template>
<div @click="btn">{{ msg }}</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// 通过ref包裹的数据就会变成响应式数据
const msg = ref("Hello World!")
function btn() {
// 只能通过.value属性改变响应式的值
msg.value = "Hello Vue3!"
}
return { msg, btn }
}
}
</script>定义复杂的数据类型为响应式
<template>
<div @click="btn">{{ obj.name }} {{ obj.age }}</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const obj = ref({
name: 'zs',
age: 20
})
function btn() {
// 注意:想要修改响应式数据,必须通过.value
obj.value.name = "ls"
obj.value.age = 30
// 或者也可以使用这种写法:obj.value = { name: "ls", age: 30 }
}
return { obj, btn }
}
}
</script>reactive
文章推荐: vue3.0中reactive的正确使用姿势 - 南风晚来晚相识 - 博客园 (cnblogs.com)
reactive 它跟 ref 函数相同,不同的是 reactive 只能定义复杂类型为响应式数据,比如对象、数组等等。不能定义简单数据类型为响应式数据,比如:数字、字符串等等。而 ref 函数无论简单还是复杂类型都可以定义
所以一般 reactive 用于定义复杂数据类型,而 ref 用于定义简单数据类型为响应式数据
使用 reactive 将复杂类型数据定义为响应式,使数据与视图绑定为一起,这样只要数据发生变化既视图就会发生变化
<template>
<div @click="btn">{{ obj.name }} {{ obj.age }}</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
// 用reactive(包裹起来的数据就会转换为响应式数据)
const obj = reactive({
name: "yy",
age: 20
})
function btn() {
obj.age = 30
}
return { obj, btn }
}
}
</script>注意: rective 修改数据无需 value,而 ref 需要
使用场景:
- 当你明确知道需要的是一个响应式数据 对象 那么就使用
reactive即可 - 其他情况使用
ref
toRef
默认情况下我们想使用某响应式对象的某个属性,我们可以解构出来,但是解构出来的值就不是响应式了。
所以我们可以通过 toRef 函数可以将某个响应式对象的某个属性单独抽离出来,并且是响应式的
<template>
<div @click="btn">{{ obj.name }} {{ obj.age }}</div>
<div>{{ age }}</div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
setup() {
const obj = reactive({
name: "yy",
age: 20
})
// 如果想要使用obj响应式对象中的某个数据不能直接解构出来, 因为解构出来后他的值就不是响应式的了
// const { age } = obj
// 此时我们可以通过toRef方法使obj响应式对象中单独的属性age变成响应式的
const age = toRef(obj, 'age')
function btn() {
// 此时修改obj.age的值时,toRef的age也会发生变化,因为他们是响应式的
// obj.age = 30
// 如果我们想修改toRef的age可以这么写,不能直接age = 40
age.value = 40
}
return { obj, age, btn }
}
}
</script><template>
<div>{{ name }}</div>
<div>{{ msg }}</div>
<button @click="btn">按钮</button>
</template>
<script>
import { ref, toRef, watch } from 'vue'
export default {
setup() {
const msg = ref({
name: "鱿鱼须",
age: 20,
hobby: "敲代码"
})
const btn = () => {
msg.value.name = "尤雨溪"
}
// 默认情况下响应式对象被解构出来后就失去了响应式特性,可以通过toRef重新使数据响应式
// return { msg, name: mas.name, btn }
return { msg, name: toRef(msg.value, "name"), btn }
}
}
</script> setup() {
const msg = ref({
name: "鱿鱼须",
age: 20,
hobby: "敲代码",
obj: {
abc: 100
}
})
const btn = () => {
// msg.value.name = "尤雨溪"
msg.value.obj = { abc: 200 }
}
// msg对象中的obj改为响应式数据
return { msg, name: toRef(msg.value, "obj"), btn }
}toRefs
toRefs 函数可以把某个响应式对象中所有的属性变成响应式的,即使解构也不影响响应式
<template>
<div @click="btn">{{ name }} {{ age }}</div>
<div>{{ age }}</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
const obj = reactive({
name: "yy",
age: 20
})
// 将obj响应式数据中的所有属性都转换为响应式的,而且解构出来也不影响响应式
const newObj = toRefs(obj)
const { name, age } = newObj
function btn() {
// obj.name = 'ls'
// obj.age = 30
// 解构出来的值也是响应式的,并且需要加value
name.value = 'ww'
newObj.age.value = 50
}
return { ...newObj, btn }
}
}
</script>知识运用案例
<template>
<div>
坐标轴:{{ x }} {{ y }}
</div>
</template>
<script>
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'
export default {
setup() {
const obj = reactive({ x: 0, y: 0 })
const move = (e) => {
obj.x = e.pageX
obj.y = e.pageY
}
// 绑定事件
onMounted(() => {
document.addEventListener('mousemove', move)
})
// 销毁事件
onUnmounted(() => {
document.removeEventListener('mousemove', move)
})
return { ...toRefs(obj) }
}
}
</script>将代码单独抽离出来
<template>
<div>
坐标轴:{{ x }} {{ y }}
</div>
</template>
<script>
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'
// 将代码单独抽离出来
const useMouse = () => {
const obj = reactive({ x: 0, y: 0 })
const move = (e) => {
obj.x = e.pageX
obj.y = e.pageY
}
// 绑定事件
onMounted(() => {
document.addEventListener('mousemove', move)
})
// 销毁事件
onUnmounted(() => {
document.removeEventListener('mousemove', move)
})
return obj
}
export default {
setup() {
const move = useMouse()
return { ...toRefs(move) }
}
}
</script>computed
Vue2写法
// 在这里定义计算属性
computed: {
// 定义一个计算属性
sum() {
// 当x或y其中一个发生变化就会自动触发这个sum()函数
return this.x + this.y
}
}Vue3写法
const sum = computed(() => {
return this.x + this.y
})如果参数为一个对象 可以在这里写 get、set
const sum = computed({
// get 方法
get() {
return
},
// set 方法
set() {
},
})示例
两数相加
<template>
<div>{{ sum }}</div>
<button @click="addX"> x + 1 </button>
<button @click="addY"> y + 1 </button>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const x = ref(0)
const y = ref(0)
const addX = () => {
x.value++
}
const addY = () => {
y.value++
}
// 当依赖的值x或y发生变化时就会自动触发计算属性
const sum = computed(() => "计算后的结果:" + (x.value + y.value))
return { addX, addY, sum }
}
}
</script>搜索案例
<template>
<input type="text" v-model="search" />
<ul>
<li v-for="item, index in list" :key="index">{{ item }}</li>
</ul>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const names = ref([
"林俊杰",
"孙燕姿",
"周杰伦",
"张惠妹",
"刘若英",
"林宥嘉",
"刘德华",
"张韶涵",
"周笔畅",
"孙楠",
])
const search = ref("")
// const list = computed(() => names.value.filter(item => item.includes(search.value)))
const list = computed(() => {
return names.value.filter(item => item.includes(search.value))
})
return { search, list, names }
}
}
</script>缓存
计算属性是基于它们依赖项的值结果进行缓存的,只要结果不变, 就直接从缓存中取值。如果值变了,就重新赋值到缓存中
watch
文章推荐: https://zhuanlan.zhihu.com/p/465651353
Vue2写法
export default {
data() {
return {
str: ""
}
},
// 在这里定义侦听器
watch: {
// 侦听str的变化
str(newData, oldData) {
console.log('新值:', newData);
console.log('旧值:', oldData);
}
}
}Vue3写法
export default {
setup() {
const str = ref("")
watch(str, (newData, oldData) => {
console.log("新值:", newData);
console.log("旧值:", oldData);
})
return { str }
}
}监听基本响应式数据时,参数一需要加 value , 监听复杂响应式数据时,参数一不需要加 value
监听响应式对象中的单个属性数据,如果使用的 ref 则响应式对象需要加上 value ,使用 reactive 不需要加 value
<script setup lang="ts">
import { watch, ref } from 'vue';
const info = ref<string>("Hello")
const update = () => {
info.value = "Hello Vue3"
}
// 如果需要监听的值为基本数据类型,则需要通过箭头函数返回并且加上.value
// watch(()=>info.value, data => {
// 如果需要监听的值为引用类型,则不需要
watch(info, data => {
console.log(data);
})
</script>
<template>
<div>{{ info }}</div>
<button @click="update">更新数据</button>
</template>
<style scoped lang="scss"></style>注意:
想要监听响应式对象中的某个基本类型的属性必须这样写:
() => obj.hobby而 引用类型可以直接写对象.属性名重新赋值不会触发
watch,只有值被修改时才会触发
deep / immediate
watch(obj.hobby, (newData, oldData) => {
console.log("新值:", newData);
console.log("旧值:", oldData);
}, { deep: true, immediate: true })组合监听
<template>
<div>
<input type="text" v-model="msg.str1">
<input type="text" v-model="msg.str2">
</div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
setup() {
const msg = ref({
str1: "",
str2: ""
})
watch([() => msg.value.str1, () => msg.value.str2], (newStr, oldStr) => {
// console.log("str1:", [newStr, oldStr]);
// console.log("str2:", [newStr, oldStr]);
console.log(newStr, oldStr);
// 新值:newStr[0]
// 旧值:oldStr[1]
})
return { msg }
}
}
</script>watchEffect
watchEffect 监听所有的数据,并且只关心数据的最新值,不关心旧值是什么。默认会执行一次
<template>
<div>
<input type="text" v-model="msg.str">
</div>
</template>
<script>
import { watchEffect, ref, watch } from 'vue'
export default {
setup() {
const msg = ref({
str: "",
})
watchEffect(() => {
console.log(msg.value.str);
})
return { msg }
}
}
</script>watchEffect 只能监听基本数据类型,无法监听引用类型
const str = ref("")
watchEffect(() => {
// 只能监听基本数据类型
console.log(str.value);
// console.log(obj.str.value);
})停止监听
const stop = watchEffect(() => {
console.log(msg.value.str);
})
// 调用一下stop即可停止watchEffect监听
stop()组件通讯
父向子通讯
App.vue
<template>
<HelloWorld msg="Hello World" />
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
components: { HelloWorld },
}
</script>components/HelloWorld.vue
<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
// 不加props也可以,不过在js中获取不到子组件传递的数据,但页面中可以正常使用
props: ['msg'],
setup(props, emit) {
console.log(props); // Proxy {msg: 'Hello World'}
return { msg: props.msg }
}
}
</script>值的更新
App.vue
<template>
<Hello :n="n" />
<button @click="btn">按钮</button>
</template>
<script>
import { ref } from 'vue'
import Hello from './components/HelloWorld.vue'
export default {
components: { Hello },
setup() {
let n = ref(0)
const btn = () => {
n.value++
}
return { n, btn }
}
}
</script>components/HelloWorld.vue
<template>
<div>{{ n }}</div>
</template>
<script>
import { watch } from 'vue'
export default {
props: ["n"],
// 当父组件更新 props 时 setup 函数是不会重新执行的
// 所以在 setup 函数中使用 props 时需要用到 computed 或者 watch 来响应 props 的变化
// 注意: 直接在模板中使用 props 数据是没有这个问题的 可以直接在模板中使用 n 的值并且是响应式的
setup(props, { emit }) {
watch(() => props.n, (newData) => {
console.log(newData);
})
}
}
</script>子向父通讯
App.vue
<template>
<Hello @abc="func" />
</template>
<script>
import Hello from './components/HelloWorld.vue'
export default {
components: { Hello },
setup() {
const func = (data) => {
console.log("触发了父组件的func方法");
console.log("子组件传递的数据:", data);
}
return { func }
}
}
</script>
<style></style>components/HelloWorld.vue
<template>
<div @click="btn">Hello World</div>
</template>
<script>
export default {
setup(props, context) {
const btn = () => {
context.emit("abc", "Hello World")
}
return { btn }
}
}
</script>
<style></style>代码片段
在 Vue2 模板中不支持写多个标签,所有标签需要写在根标签中,在 Vue3 中支持多跟标签写法
<template>
<div>{{ childMsg }}</div>
<button @click="onClickHandler">change msg</button>
</template>如果在组件模板中, 自定义事件需要在 emits 选项中声明.
emits: ["onMsgChanged"] // 事件名:onMsgChanged生命周期
App.vue
<template>
<Hello v-if="show" />
<button @click="btn">按钮</button>
</template>
<script>
import { ref } from 'vue'
import Hello from './components/HelloWorld.vue'
export default {
components: { Hello },
setup() {
const show = ref(true)
const btn = () => {
show.value = !show.value
}
return { show, btn }
}
}
</script>components/HelloWorld.vue
<template>
<div>Hello World</div>
</template>
<script>
import { onMounted, onUnmounted, onUpdated } from 'vue';
export default {
setup(props, context) {
console.log("组件中的setup执行");
// 组件DOM渲染完毕后执行
onMounted(() => {
console.log("组件中的onMounted执行");
})
// 组件数据更新后执行
onUpdated(() => {
console.log("组件中的onUpdated执行");
})
// 组件卸载后执行
onUnmounted(() => {
console.log("组件中的onUnmounted执行");
})
}
}
</script>components/HelloWorld.vue
<template>
<div>Hello World</div>
</template>
<script>
import { onMounted, onUnmounted, onUpdated } from 'vue';
export default {
setup(props, context) {
console.log("组件中的setup执行");
let time = null
// 组件DOM渲染完毕后执行
onMounted(() => {
console.log("定时器开始");
let i = 0
time = setInterval(() => {
console.log(++i);
}, 1000)
})
// 组件数据更新后执行
onUpdated(() => {
console.log("组件中的onUpdated执行");
})
// 组件卸载后执行
onUnmounted(() => {
// 组件卸载后一般做一些清除相关的代码,例如清除定时器
clearInterval(time)
console.log("定时器已卸载");
})
}
}
</script>与服务端通信
<template>
<div v-if="data.loading">loading...</div>
<div v-else-if="data.error">{{ data.error }}</div>
<div v-else-if="data.list && data.list.length > 0">
<ul>
<li v-for="item in data.list" :key="item.id">{{ item.title }}</li>
</ul>
</div>
<div v-else>暂无数据</div>
</template>
<script>
import axios from 'axios'
import { reactive } from 'vue'
export default {
setup() {
const data = reactive({
loading: false,
list: [],
error: ""
})
const getPosts = async () => {
try {
data.loading = true
const res = await axios.get("https://jsonplaceholder.typicode.com/posts1")
data.list = res.data
data.loading = false
} catch (error) {
data.loading = false
data.error = error.message
}
}
getPosts()
return { data }
}
}
</script>抽离组合式API
将获取 Posts 数据的逻辑抽取单独文件中,使其可以在多个组件中被重复使用。并且提高程序的可读性
src/App.vue
<template>
<div v-if="data.loading">loading...</div>
<div v-else-if="data.error">{{ data.error }}</div>
<div v-else-if="data.list && data.list.length > 0">
<ul>
<li v-for="item in data.list" :key="item.id">{{ item.title }}</li>
</ul>
</div>
<div v-else>暂无数据</div>
</template>
<script>
import { userPosts } from './hooks/getPost'
export default {
setup() {
const data = userPosts()
return { data }
}
}
</script>src/hooks/getPost.js
import axios from "axios";
import { reactive } from "vue";
export function userPosts() {
const data = reactive({
loading: false,
list: [],
error: "",
});
const getPosts = async () => {
try {
data.loading = true;
const res = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
data.list = res.data;
data.loading = false;
} catch (error) {
data.loading = false;
data.error = error.message;
}
};
getPosts();
return data
}抽离组合式API子向父传值
src/App.vue
<template>
<Hello @abc="fn" />
</template>
<script>
import Home from './hooks/Home'
import Hello from './components/HelloWorld.vue'
export default {
components: { Hello },
setup() {
// const fn = () => {
// console.log("Hello World!");
// }
const fn = Home().fn
return { fn }
}
}
</script>src/hooks/Home.js
export default function Home() {
const fn = () => {
console.log("Hello World!");
};
return { fn };
}components/HelloWorld.vue
<template>
<button @click="btn">按钮</button>
</template>
<script>
export default {
setup(props, { emit }) {
const btn = () => {
emit('abc', "Hello World")
}
return { btn }
}
}
</script>获取DOM元素
获取DOM元素
<template>
<div ref="divRef">Hello World!</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const divRef = ref(null)
onMounted(() => {
console.log(divRef.value);
// divRef.value.style.color = "red"
// <div>Hello World!</div>
})
return { divRef }
}
}
</script>获取组件的实例
<template>
<Hello ref="H" />
</template>
<script>
import Hello from './components/HelloWorld.vue'
import { ref, onMounted } from 'vue'
export default {
components: { Hello },
setup() {
const H = ref(null)
onMounted(() => {
console.log(H);
console.log(H.value); //获取Hello组件的实例,可以使用组件的属性和方法
})
return { H }
}
}
</script>批量获取真实DOM
<template>
<ul>
<!-- 真实DOM:el -->
<!-- 给每一项赋值真实DOM:elms[index] = el -->
<li v-for="(item, index) in list" :key="index" :ref="(el) => (elms[index] = el)">
{{ item }}
</li>
</ul>
</template>
<script>
import { ref, onMounted } from "vue"
export default {
setup() {
const list = ref(["a", "b", "c"])
const elms = ref([])
onMounted(() => console.log("真实dom列表:", elms.value))
return { list, elms }
},
}
</script>provide、inject
通过 provide、inject 函数的配合使用,可以实现跨组件传递数据
注意: 该方式传递数据只能父传子,不支持子传父
基本使用
src/App.vue
<template>
<A />
</template>
<script>
import { provide, ref } from 'vue'
import A from './components/A.vue'
export default {
components: { A },
setup() {
const msg = ref("Hello World")
provide("data", msg)
}
}
</script>components/A.vue
<template>
<B />
</template>
<script>
import B from './B.vue'
export default {
components: { B }
}
</script>components/B.vue
<template>
<div>{{ data }}</div>
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
const data = inject("data")
return { data }
}
}
</script>进阶用法
src/App.vue
<template>
<A />
</template>
<script>
import { provide, reactive } from 'vue'
import A from './components/A.vue'
export default {
components: { A },
setup() {
const data = reactive({
name: "刘宇阳",
hobby: "敲代码"
})
const update = (val) => {
// data.hobby = "写代码"
data.hobby = val
}
provide("data", data)
provide("update", update)
}
}
</script>components/A.vue
<template>
<B />
</template>
<script>
import B from './B.vue'
export default {
components: { B }
}
</script>components/B.vue
<template>
<div>{{ data }}</div>
<button @click="updateComponents('写代码')">按钮</button>
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
const data = inject("data")
const updateComponents = inject("update")
return { data, updateComponents }
}
}
</script>Teleport
teleport 组件可以将指定的组件渲染到项目外部的其他位置。
比如弹框、返回顶部组件,它可能在任意组件中使用,我们可以把他渲染到指定的位置,这样就不用来回引入了
src/App.vue
<template>
<!-- to是标签的ID或类名 -->
<teleport to='#modal'>
<Modal />
</teleport>
</template>
<script>
import Modal from './components/Modal.vue'
export default {
components: { Modal }
}
</script>components/Modal.vue
<!-- Modal.vue -->
<template>
<div class="wrapper">
<div class="content">
<a class="close" href="javascript:">关闭</a>
</div>
</div>
</template>
<script>
export default {
name: "Modal",
};
</script>
<style scoped>
.wrapper {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
}
.content {
width: 660px;
height: 400px;
background: white;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.close {
position: absolute;
right: 10px;
top: 10px;
color: #999;
text-decoration: none;
}
</style>index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<!-- 写在这里,所有组件都可以看到了 -->
<div id="modal"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>Suspense
通过 Suspense 组件可以为异步操作添加等待提示效果
src/App.vue
<template>
<Suspense>
<template v-slot:default>
<Posts />
</template>
<template v-slot:fallback> 加载中,请稍后... </template>
</Suspense>
</template>
<script>
import Posts from './components/Posts.vue'
import { ref } from 'vue'
export default {
components: { Posts },
setup() {
let show = ref(true)
return { show }
}
}
</script>从上面的代码片段中,使用组件 suspense 可以很简单就实现了 loading 的效果
带有 #default 为初始化模板组件,用于显示异步请求完成之后的 UI
带有 #fallback 为异步请求中的处理 UI ,即常见的 loading 效果。
components/Posts.vue
<template>
<pre>{{ data }}</pre>
</template>
<script>
import axios from "axios";
export default {
name: "Posts",
async setup() {
let response = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
return { data: response.data };
},
};
</script>Transition
Vue 提供了 transition 内置组件供我们执行过渡动画,,我们只需要使用 transition 组件包裹你要执行动画的元素即可。
transition 的触发方式如下:
- 由
v-if所触发的切换 - 由
v-show所触发的切换 - 由特殊元素
<component>切换的动态组件 - 改变特殊的
key属性
执行过渡动画的前提条件是元素具有创建与销毁的操作。
<template>
<Transition>
<h1 v-if="show">hello world</h1>
</Transition>
<button @click="show = !show">显示隐藏</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let show = ref(true)
return { show }
}
}
</script>
<style>
/* 从无到有 */
/* 开始显示 */
.v-enter-from {
opacity: 0
}
/* 结束显示 */
.v-enter-to {
opacity: 1
}
/* 显示动画 */
.v-enter-active {
transition: opacity 0.5s ease-in
}
/* 从有到无 */
/* 开始隐藏 */
.v-leave-from {
opacity: 1
}
/* 结束隐藏 */
.v-leave-to {
opacity: 0
}
/* 隐藏动画 */
.v-leave-active {
transition: opacity 0.5s ease-out
}
</style>为过渡效果命名
我们可以给 <Transition> 组件传一个 name prop 来声明一个过渡效果名:
<Transition name="fade">
...
</Transition>对于一个有名字的过渡效果,对它起作用的过渡 class 会以其名字而不是 v 作为前缀。比如,上方例子中被应用的 class 将会是 fade-enter-active 而不是 v-enter-active。这个 “fade” 过渡的 class 应该是这样:
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>数据双向绑定
表单双向数据绑定
<template>
<div>
<input type="text" v-model="msg">
<button @click="change">Button</button>
</div>
</template>
<script setup>
import { ref } from "vue"
const msg = ref("")
const change = () => {
msg.value += "!"
}
</script>组件双向数据绑定
普通版
<template>
<div @click="update">父组件:{{ msg }}</div>
<Hello :msg="msg" @change="change" />
<button @click="update">Button</button>
</template>
<script setup>
import Hello from './components/Hello.vue'
import { ref } from "vue"
const msg = ref("Hello World!")
const change = () => {
msg.value = "子组件触发了!"
}
const update = () => {
msg.value = "父组件触发了!"
}
</script>
<style></style><template>
<div @click="update">子组件:{{ msg }}</div>
</template>
<script setup>
import { defineProps } from 'vue'
import { defineEmits } from 'vue'
// 使用父组件传递的数据
const props = defineProps(["msg"])
// 使用emit
const emit = defineEmits(['change'])
const update = () => {
emit("change")
}
</script>
<style></style>升级版
父组件向子组件通过 v-model 传递的数据
<template>
<div @click="update">父组件:{{ msg }}</div>
<!-- v-model 没有指定参数名时,子组件默认参数名是 modelValue -->
<Hello v-model="msg" />
</template>
<script setup>
import Hello from './components/Hello.vue'
import { ref } from "vue"
const msg = ref("Hello World!")
const update = () => {
msg.value = "父组件触发了!"
}
</script>
<style></style>接收父组件 v-model 传递的数据
如果父组件的 v-model 没有指定参数名时,子组件默认参数名是 modelValue
<template>
<div @click="update">子组件:{{ modelValue }}</div>
</template>
<script setup>
import { defineProps } from 'vue'
import { defineEmits } from 'vue'
// 使用父组件传递的数据
const props = defineProps(["modelValue"])
// 使用emit
const emit = defineEmits(['change'])
// 通过update修改数据
const update = () => {
emit("update:modelValue", "子组件触发了!")
}
</script>
<style></style>终极版
父组件通过 v-model 传递多个数据
<template>
<div @click="update">父组件:{{ msg1 }} {{ msg2 }}</div>
<Hello v-model:msg1="msg1" v-model:msg2="msg2" />
</template>
<script setup>
import Hello from './components/Hello.vue'
import { ref } from "vue"
const msg1 = ref("Hello")
const msg2 = ref("World!")
const update = () => {
msg1.value = "Hello"
msg2.value = "World"
}
</script>
<style></style>接收多个数据
<template>
<div @click="update">子组件:{{ msg1 }} {{ msg2 }}</div>
</template>
<script setup>
import { defineProps } from 'vue'
import { defineEmits } from 'vue'
// 使用父组件传递的数据
const props = defineProps(["msg1", "msg2"])
// 使用emit
const emit = defineEmits(['change'])
// 通过update修改数据
const update = () => {
emit("update:msg1", "World")
emit("update:msg2", "Hello")
}
</script>
<style></style>ref
某些特殊情况下我们需要调用 dom 的 api,则需要使用 ref 方法来获取真实 dom 元素
<script setup lang='ts'>
const target = ref()
// 获取 & 操作DOM必须在DOM渲染完毕之后,所以必须写在onMounted中或使用nextTick
onMounted(() => {
// 获取真实DOM
console.log(target);
// 操作真实DOM
target.value.style.color = "red"
})
</script>
<template>
<div ref="target">Hello</div>
</template>nextTick
等 DOM 渲染完毕后,在下一次更新循环结束时执行 nextTick 中的回调函数
<script setup lang='ts'>
const target = ref("")
// 此时DOM还没有开始渲染,所以获取不到元素
console.log(target.value);
// 等DOM渲染完毕后,在下一次更新循环结束时执行nextTick中的回调函数
nextTick(() => {
console.log(target.value);
// <div>Hello World!</div>
})
</script>
<template>
<div ref="target">Hello World!</div>
</template>应用场景
如果想要在修改数据后立刻得到更新后的
DOM结构,可以使用Vue.nextTick()在
created生命周期中进行DOM操作
示例
<template>
<div>
<h1 v-if="show" ref="target">这是标题</h1>
<button @click="update">点击按钮</button>
</div>
</template>
<script setup lang="ts">
const show = ref(false)
const target = ref()
function update() {
show.value = true
// 此时show的值其实还没有改成true,相当于v-if="false" 所以页面上还没有h1这个标签
console.log(target.value); // undefined
// // 使用nextTick等Dom渲染完毕后 在下一次更新循环结束时执行里面的回调函数
nextTick(() => {
console.log('DOM更新完成')
// 此时DOM已经渲染完毕,所以可以获取DOM了
console.log(target.value); // <h1 style="color: red;">这是标题</h1>
// 也可以直接操作DOM
target.value.style.color = "red"
})
}
</script>总结
在 $nextTick 当中的代码不会立即执行,而是等 DOM 更新完成之后再执行,这样我们拿到的一定是最新的数据
customRef
**什么是防抖?**防抖就好比如王者荣耀中的回城,被打断就要重新来
使用传统方式实现防抖
<template>
<div>{{ content }}</div>
<input type="text" v-model="text">
</template>
<script setup>
import { watch, ref } from "vue";
const content = ref("")
const text = ref("")
let time = null
// 核心代码:每次输入先清除之前的定时器,等输入完毕后执行定时器中的代码实现防抖功能
watch(() => text.value, (data) => {
clearTimeout(time)
time = setTimeout(() => {
content.value = data
}, 500)
})
</script>customRef基本语法
<template>
<div>{{ msg }}</div>
<input type="text" v-model="msg">
</template>
<script setup lang="ts">
import { customRef, ref } from "vue";
let initValue = ""
let msg = customRef((track, trigger) => {
return {
// 默认get方法会执行一次,以后只要在set中调用了trigger方法就会触发get方法
get() {
// 追踪数据
track()
// 返回最新数据:initValue
return initValue
},
set(value) {
// 最新值:value
// 拿到最新的值赋值给:initValue
initValue = value
// 视图更新
trigger()
}
}
})
</script>
<style></style>注意: 默认 get 方法会执行一次,以后只要在 set 中调用了 trigger 方法就会自动触发 get 方法
使用 customRef 实现防抖
<script setup lang="ts">
import { customRef, ref } from "vue";
let initValue = ""
let msg = customRef((track, trigger) => {
let time: any = null
return {
// 默认get方法会执行一次,以后只要在set中调用了trigger方法就会触发get方法
get() {
// 追踪数据
track()
// 返回最新数据:initValue
return initValue
},
set(value) {
// 最新值:value
// 每次值发生改变,先清除之前的定时器
clearTimeout(time)
// 执行现在的定时器
time = setTimeout(() => {
// 拿到最新的值赋值给:initValue
initValue = value
// 视图更新
trigger()
}, 500)
}
}
})
</script>使用 customRef 封装一个防抖的方法
<template>
<div>{{ msg }}</div>
<input type="text" v-model="msg">
</template>
<script setup lang="ts">
import { customRef, ref } from "vue";
// 使用 customRef 封装一个防抖的方法
const custom = (initValue: string, delay: number) => {
return customRef((track, trigger) => {
let time: any = null
return {
// 默认get方法会执行一次,以后只要在set中调用了trigger方法就会触发get方法
get() {
// 追踪数据
track()
// 返回最新数据:initValue
return initValue
},
set(value) {
// 最新值:value
// 每次值发生改变,先清除之前的定时器
clearTimeout(time)
// 执行现在的定时器
time = setTimeout(() => {
// 拿到最新的值赋值给:initValue
initValue = value
// 视图更新
trigger()
}, 500)
}
}
})
}
let msg = custom("", 500)
</script>代理对象
数据驱动视图, 即数据和视图进行绑定, 当数据发生变化后, 视图自动更新.
实现数据响应式的核心在于监听数据的变化, 当数据发生变化后, 执行视图更新操作.
Vue3 使用代理对象监听数据变化.
创建对象的代理对象, 从而实现对对象操作的拦截和自定义.
Proxy
基本使用
const person = { name: "zs", age: 20 }
const p = new Proxy(person, {
get(target, pro) {
console.log("GET");
return target[pro]
},
set(target, pro, val) {
console.log("SET");
target[pro] = val
return true
},
deleteProperty(target, pro) {
console.log("DEL");
delete target[pro]
return true
}
})用法
const person = { name: "zs", hobby: "敲代码", obj: { age: 20, sex: "男" } }
const p = new Proxy(person, {
// 当被读取属性时会触发
get(target, pro) {
// target:{name: '宇阳', hobby: '敲代码'}
// pro:name
console.log("拦截到了读取操作");
return target[pro]
},
// 当设置或新增属性时触发
set(target, pro, value) {
console.log("拦截到了设置/新增操作");
target[pro] = value
return true
},
// 拦截删除操作
deleteProperty(target, pro) {
console.log("拦截到了删除操作");
return true
// return delete target[pro]
}
})
p.obj.age = 23 // 拦截到了设置/新增操作
console.log(p.obj.age); // 拦截到了读取操作
delete p.obj.sex // 拦截到了删除操作
console.log(p.obj); // {age: 23}被代理的对象 person 也会被改变
Object.defineProperty
const person = { name: "zs", age: 20 }
let value = ''
// 拦截person中的name属性
Object.defineProperty(person, 'name', {
get() {
console.log('GET');
return value
},
set(val) {
console.log('SET');
value = val
},
})
person.name = 'ls'
console.log(person);const person = { name: "zs", age: 20 }
let value = ''
// 拦截person中的name属性
Object.defineProperty(person, 'name', {
get() {
console.log('GET');
return value
},
set(val) {
console.log('SET');
value = val
},
})
// 拦截person中的age属性
Object.defineProperty(person, 'age', {
get() {
console.log('GET');
return value
},
set(val) {
console.log('SET');
value = val
},
})可以看出 Vue2 响应式原理需要把每个属性都要写一遍Object.defineProperty,十分麻烦,而 Vue3 只需要给一个对象设置 Proxy 即可
setup语法糖
setup 是 Vue3 的一个语法糖,它可以简化语法,使代码更加简单
使用setup
使用它的方法很简单,只需要在 script 标签中加上 setup 关键字即可
<script setup></script>话不多说,看以下示例你会爱上 setup
组件注册
使用前: 需要手动注册组件
<template>
<Hello />
</template>
<script>
import Hello from './components/Hello.vue'
export default {
components: { Hello }
}使用后: 自动注册组件
<template>
<Hello />
</template>
<script setup>
import Hello from './components/Hello.vue'
</script>组件通讯
使用前
<script>
export default {
props: ["msg"],
setup(props, { emit }) {
const update = () => {
emit("change")
}
return { update }
}
}
</script>使用后
<script setup>
import { defineProps } from 'vue'
import { defineEmits } from 'vue'
// 约束对父组件传递的数据类型
// const props = defineProps({
// msg: String,
// })
// 使用父组件传递的数据
const props = defineProps(["msg"])
// 核心代码:在defineEmits里面写父组件的方法
const emit = defineEmits(['change'])
const update = () => {
// 然后通过emit进行使用
emit("change")
// 传参 emit("change","这里写参数", "可以写多个参数哦")
}
</script>onBeforeRouteUpdate
onBeforeRouteUpdate 是 Vue3 中的路由守卫之一,用于在路由更新前执行一些操作。它的作用是在当前组件复用时调用,即当路由参数发生变化时,同一个组件会被复用,此时 onBeforeRouteUpdate 会被调用,可以在这个钩子函数中执行一些操作,比如更新组件内的数据。
onBeforeRouteUpdate 接收三个参数,分别是 to、from 和 next。其中 to 表示即将要进入的路由对象,from 表示即将离开的路由对象,next 是一个函数,用于执行跳转或取消跳转。
render
渲染函数允许开发者通过 JavaScript 的方式创建组件模板。
// 选项式 API - JavaScript
export default {
// render 函数返回什么, 页面中就显示什么
render () {}
}// 选项式 API - TypeScript
defineComponent({
// render 函数返回什么, 页面中就显示什么
render() {},
});<!-- 组合式 API 语法糖 -->
<script lang="ts" setup>
// render 函数返回什么, 页面中就显示什么
const render = () => {};
</script>
<template>
<!-- 以组件的方式调用 render 方法 -->
<render />
</template>示例
<script setup lang='ts'>
import { h } from 'vue'
// 写一个渲染函数
const render = () => {
// 渲染一个h1标签
return h('h1')
}
</script>
<template>
<!-- 通过组件的方式调用render函数 -->
<render />
</template>
<style scoped>
.col {
color: red;
}
</style>给一个标签赋值
const render = () => {
return h('h1', "Hello World")
}给一个标签赋值并绑定样式
const render = () => {
return h('h1', { class: "col" }, "Hello World")
}给一个标签赋值并绑定样式与事件
const render = () => {
return h('h1', { class: "col", onclick: () => alert(123) }, "Hello World")
}在 h1 标签中嵌套一个 span 标签
const render = () => {
return h('h1', { class: "col" }, h("span", "嵌套一个span标签"))
}useSlots
Vue 中的 useSlots 的作用
useSlots 是 Vue 的一个内部函数,它用于在函数式组件中访问插槽。它接受一个参数 context,该参数包含组件的 props、slots、parent、children 等信息。useSlots 函数返回的是一个 Proxy 对象,里面包含着父组件插槽中的所有虚拟 DOM 对象
在实际开发中,useSlots 函数通常用于函数式组件中访问插槽内容。由于函数式组件没有实例,无法直接访问this.$slots 属性,因此需要使用 useSlots 函数来获取插槽内容。
通过 useSlots 获取的插槽内容能做什么
通过 useSlots 获取的插槽内容可以用于在子组件内部访问父组件的内容。这对于在父组件中定义一些通用的模板或布局,并在子组件中根据需要使用它们非常有用。通过 useSlots,子组件可以访问父组件中定义的插槽内容,并将其渲染到自己的模板中。这样,子组件就可以利用父组件中的模板和布局,同时保持自己的独立性和可重用性。
如下: 子组件通过调用 default() 获取父组件插槽中的内容
<XtxBrand>
<div>100</div>
<span>200</span>
</XtxBrand>import { h, useSlots } from 'vue'
// 返回的是一个Proxy对象,里面包含着父组件插槽中的所有虚拟DOM对象
let defaults = useSlots().default?.()
console.log(defaults, 666);
const render = () => {
return h("div", { class: "xtx-bread" }, [defaults])
}配置组件自动导入
目标
配置组件不需要手动引入,直接使用
<script setup lang='ts'>
// 不需要引入组件,可以直接使用
// import XtxTopNav from './XtxTopNav.vue';
// import XtxHeader from './XtxHeader.vue';
// import XtxFooter from './XtxFooter.vue';
</script>
<template>
<!-- 不需要引入组件,可以直接使用 -->
<XtxTopNav />
<XtxHeader />
<RouterView />
<XtxFooter />
</template>安装
npm install unplugin-vue-components@0.22.7 -D示例
vite.config.ts
import vueJsx from '@vitejs/plugin-vue-jsx'
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
vue(), vueJsx(),
// 配置组件自动导入
Components({
// 使这两个目录下的文件不需要引入组件就能直接使用
dirs: ["src/components", "src/views"],
// 是否包括所有子目录都可以直接使用
deep: true,
// 组件的有效文件扩展名
extensions: ["vue"],
// 配置自动导入组件的文件生成的位置
dts: "components.d.ts"
})
],
})tsconfig.json
{
// 规定只有src目录包含模块才会进行编译
"include": ["components.d.ts"],
}配置库自动导入
目标
配置库不导入,直接使用
// 不需要导入 直接使用
// import { defineStore } from 'pinia'
// 不需要导入 直接使用
export const useCounterStore = defineStore('counter', () => {
return {}
})安装
npm i -D unplugin-auto-import@0.11.2
npm i @vueuse/core@9.2.0示例
vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// 核心代码 - 导入 unplugin-auto-import/vite
import AutoImport from "unplugin-auto-import/vite"
import { join } from 'path'
export default defineConfig({
plugins: [
vue(), vueJsx(),
// 配置指定自动导入的库
AutoImport({
// 这些库在使用时候不需要再导入了,可以直接使用
imports: ["vue", "vue-router", "pinia", "@vueuse/core"],
eslintrc: {
enabled: true, // Default `false`
filepath: "./.eslintrc-auto-import.json",
globalsPropValue: true,
},
// 配置文件
dts: "auto-import.d.ts"
})
],
})解决在不引入 hooks 就使用 hooks 时 TS 编译器报错的问题
// tsconfig.json
{
"include": ["auto-import.d.ts"],
}解决在不引入 hooks 就使用 hooks 时 ESLint 报错问题
// .eslintrc.cjs
module.exports = {
'extends': [
"./.eslintrc-auto-import.json"
],
}技术文章推荐
**reactive:**https://www.cnblogs.com/IwishIcould/p/15096750.html