一、为什么学习 Vue3?
-
Vue3 运行性能大幅提升,速度是 Vue2 的 1.5 倍左右
-
Vue3 支持 tree shaking,可以进行按需编译,编译后的文件体积比 Vue2 更小
-
Vue3 组合式 API 使应用中的功能代码更聚合,使组件间公共逻辑的抽取更容易
-
Vue3 中提供了更加先进的功能,比如 teleport,suspense 等
-
Vue3 对 TypeScript 的支持更加友好,对大型前端应用的支持更加游刃有余
-
Vue 是目前国内前端使用者最多的框架,Vue 官方已经将 Vue3 作为默认版本使用
二、创建 Vue 项目
必须升级vue cli版本为5
npm install @vue/cli@5.0.4 -g
三、创建 Vue 项目
1. [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar): Vue3 语法支持
- TypeScript Vue Plugin (Volar)
- Prettier-Code formatter: 代码格式化
- ESLint: 代码质量检查
四、组合式 API 入口
1.setup 函数是一个新的组件选项,它被作为组合式 API 的入口
export default {
setup () {}
}
2.setup 函数在任何生命周期函数之前执行,且函数内部 `this` 为 `undefined`,它不绑定组件实例对象
export default {
setup() {
console.log(this) // 1. undefined
},
beforeCreate() {
console.log("before create") // 2. before create
},
}
3.setup 函数的返回值必须是对象,对象中的属性会被添加到组件实例对象中,所以它们可以在其他选项和模板中使用
export default {
setup() {
let name = "张三"
let age = 20
return { name, age }
},
beforeCreate() {
console.log(this.name);
},
}
<template>{{ name }} | {{ age }}</template>
4.在 setup 方法中声明的变量虽然可以在模板中显示,但它不是响应式数据,就是说当数据发生变化后视图不会更新。
export default {
setup() {
let name = "张三"
let age = 20
const onClickHandler = () => {
name = "李四"
age = 30
}
return { name, age, onClickHandler }
}
}
<template>
{{ name }} | {{ age }}
<button @click="onClickHandler">button</button>
</template>
五、创建响应式数据 ref
1.ref 方法用于创建响应式数据,即数据的变化可以引起视图的更新。
//响应式数据ref 从vue中导入
import { ref } from "vue"
export default {
setup() {
const name = ref("张三")
const age = ref(20)
return { name, age }
},
}
<template>{{ name }} | {{ age }}</template>
在 JavaScript 中通过 value 属性修改数据。(基本数据类型)
export default {
setup() {
const name = ref("张三")
const age = ref(20)
const onClickHandler = () => {
console.log(ref('娄哥'),111)
name.value = "李四"
age.value = 30
}
return { name, age, onClickHandler }
},
}
<template>
{{ name }} | {{ age }}
<button @click="onClickHandler">button</button>
</template>
2.使用 ref 方法创建引用数据类型的响应式数据。
export default {
setup() {
const name = ref({ name: '张三', age: '18' });
const person = () => {
console.log(ref(name), 'name');
name.value.name = '李四';
name.value.age = 40;
};
return { name, person, a };
},
};
<div>
<span>{{ name }}{{ age }}</span>
<button @click="person">account</button>
</div>
六、创建响应式数据 reactive
reactive用来创建响基于引用类型的响样式数据;对于基本数据类型不起作用
import { reactive } from 'vue';
export default {
setup() {
let name = reactive({ name: '张三', age: 18 });
const onClickHandler = () => {
console.log(name);
name.name = '李四';
name.age = 50;
};
return { name, onClickHandler };
},
};
<template>
<div>
<span>{{ name }}</span>
<button @click="onClickHandler">account</button>
</div>
</template>
案例:在点击按钮后将 `newPerson` 中的值赋值给 `person`
这里的newPerson因为不是响样式数据,所以赋值需要for in一下
import { reactive } from 'vue';
export default {
setup() {
let person = reactive({ name: '张三', age: 30 });
const newPerson = { name: '李四', age: 50 };
const onClickHandler = () => {
for (const key in newPerson) {
person[key] = newPerson[key];
}
};
return { person, onClickHandler };
},
};
<template>
<div>
<span>{{ person }}</span>
<button @click="onClickHandler">account</button>
</div>
</template>
七、计算属性 computed
计算属性是指基于现有状态派生(演变)出新的状态,现有状态发生变化,派生状态重新计算,在 Vue3 中通过 computed 方法创建计算属性。(简单点就是根据依赖关系进行缓存的计算,当计算属性引用的响应式属性发生改变时才会重新计算,如果引用的属性没有改变,则调用上一次缓存值。)
export default {
setup() {
let num1 = ref(10);
let sum = computed(() => {
return num1.value * 2;
});
return { sum };
},
};
<template>
<div>{{ sum }}</div>
</template>
案例:在搜索框中输入名字,在现有名字中查找,找到后列出名称列表。
export default {
setup() {
const names = ref([
'林俊杰',
'孙燕姿',
'周杰伦',
'张惠妹',
'刘若英',
'林宥嘉',
'刘德华',
'张韶涵',
'周笔畅',
'孙楠',
]);
const search = ref('');
const filterNames = computed(() =>
names.value.filter((name) => name.includes(search.value))
);
return { search, filterNames };
},
};
<template>
<input type="text" v-model="search" />
<ul>
<li v-for="name in filterNames">{{ name }}</li>
</ul>
</template>
八、监听状态 watch
watch 方法用于监听响应式数据的变化。
1.使用 watch 方法监听基于 ref 创建的响应式数据 (基本数据类型)。
export default {
setup() {
const text = ref('');
watch(text, (newVal, oldVal) => {
console.log(newVal, 'newVal');
console.log(oldVal, 'oldVal');
});
return { text };
},
};
<input type="text" v-model="text" />
2.使用 watch 监听响应式数据内部的具体属性 (基本数据类型)
export default {
setup() {
const person = ref({ name: '张三' });
watch(
() => person.value.name,
(newVal, oldVal) => {
console.log(newVal);
console.log(oldVal);
}
);
return { person };
},
};
<template>
<div>{{ person.name }}</div> //输出结果 张三
</template>
3.使用 watch 监听响应式数据内部的具体属性 (引用数据类型)
import { ref, watch } from 'vue';
export default {
setup() {
const person = ref({ name: { title: '啊娄' } });
const onClickHandler = () => {
person.value.name.title = '娄哥哥';
};
watch(
() => person.value.name.title,
(newVal, oldVal) => {
console.log(newVal, 'newVal');
console.log(oldVal, 'oldVal');
}
);
return { person, onClickHandler };
},
};
<template>
<p>{{ person.name.title }}</p>
<button @click="onClickHandler">title</button>
</template>
4.使用 watch 监听基于 reactive 创建的响应式数据。
import { reactive, watch } from 'vue';
export default {
setup() {
const person = reactive({ name: '张三' });
const onClickHandler = () => {
person.name = '李四';
};
watch(
() => person.name,
(newVal, oldVal) => {
console.log(newVal, 'newVal');
console.log(oldVal);
}
);
return { person, onClickHandler };
},
};
<template>
<p>{{ person.name }}</p>
<button @click="onClickHandler">title</button>
</template>
5.使用 watch 监听多个值的变化
import { ref, watch } from 'vue';
export default {
setup() {
const firstName = ref('');
const lastName = ref('');
watch([firstName, lastName], (newVal, oldVal) => {
console.log(newVal, oldVal);
});
return { firstName, lastName };
},
};
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
6.使 watch 监听数据在初始时执行一次
import { ref, watch } from 'vue';
export default {
setup() {
const firstName = ref('');
const lastName = ref('');
watch(
[firstName, lastName],
(newVal, oldVal) => {
console.log(newVal, oldVal);
},
{
immediate: true,
deep: true, //深度监听只有引用数据类型才需要开启,vue3内部会强制开启
}
);
return { firstName, lastName };
},
};
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
九、 监听状态 watchEffect
watchEffect 和 watch 一样,都是用于监听响应式数据的变化。
1.watchEffect 只关心数据的最新值,不关心旧值是什么,而且 watchEffect 默认会在初始时执行一次。
import { ref, watchEffect } from 'vue';
export default {
setup() {
const firstName = ref('');
const lastName = ref('');
watchEffect(() => {
console.log(firstName.value);
console.log(lastName.value);
});
return { firstName, lastName };
},
};
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
十、toRef 方法
在讲解toRef方法前,我们先来看一个js基础输出语句
let person = { name: "张三" };
let name = person.name;
person.name = "李四";
console.log(name); // 张三,这里涉及到的是堆栈问题;
toRef 方法用于将响应式数据内部的普通数据转换为响应式数据,并且转换后的数据和原始数据存在引用关系,存在引用关系意味着当原始数据发生变化后,toRef 转换后的数据也会跟着变化。
import { toRef, ref } from 'vue';
export default {
setup() {
let person = ref({ name: '张三' });
let onClickHandler = () => {
person.value.name = '李四';
};//person.name 引用的是简单数据类型,但是使用ref创建了响样式数据
return {
name: person.value.name,//name这里获取到的只是普通数据张三
person,
onClickHandler,
};
},
};
<template>
<p>{{ name }}</p>
<p>{{ person.name }}</p>
<button @click="onClickHandler">account</button>
</template>
解决办法:使用toRef
import { toRef, ref } from 'vue';
export default {
setup() {
let person = ref({ name: '张三' });
let onClickHandler = () => {
person.value.name = '李四';
};
return {
name: toRef(person.value,"name"),//观察结果点击后输出的都是李四
person,
onClickHandler,
};
},
};
需求: 将模板中的 `person.brand.name` 简化成 `brandName`
import { toRef, ref } from 'vue';
export default {
setup() {
const person = ref({ brand: { name: '奥迪' } });
const onClickHandler = () => {
person.value.brand.name = '娄哥';
};
return {
person,
onClickHandler,
brandName: toRef(person.value.brand, 'name'),//输出结果 点击之后都变为娄哥
};
},
};
<template>
<p>{{ person.brand.name }}</p>
<p>{{ brandName }}</p>
<button @click="onClickHandler">account</button>
</template>
十一、toRefs 函数
通过 toRef
方法一次只能转换一个数据,通过 toRefs
方法可以实现批量数据转换。
toRefs 方法接收引用数据类型的响应式数据,它可以将数据中的第一层属性全部转换为响应式数据, 返回值是一个对象, 对象中存储了所有转换之后的响应式数据。
import { toRefs, reactive } from 'vue';
export default {
setup() {
const person = reactive({
name: '张三',
age: 20,
brand: { title: '奥迪', year: 2 },
});
return { ...toRefs(person) };
},
};
<template>
<p>{{ name }}</p>
<p>{{ age }}</p>
<p>{{ brand.title }}{{ brand.year }}</p>
</template>
对引用数据类型内部的数据进行转换
import { toRefs, reactive } from 'vue';
export default {
setup() {
const person = reactive({
name: '张三',
age: 20,
brand: { title: '奥迪', year: 2 },
});
return { ...toRefs(person), ...toRefs(person.brand) };
},
};
<template>
<p>{{ name }}</p>
<p>{{ age }}</p>
<p>{{ title }}{{ year }}</p>
</template>
十二、组件通讯props
1.父组件通过 props 向子组件传递数据
父组件
import { toRefs, ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
setup() {
const msg = ref('我是你的小可爱噢!');
return { msg };
},
components: { HelloWorld },
};
<template>
<div>
<HelloWorld :msg="msg"></HelloWorld>
</div>
</template>
子组件
import { computed } from 'vue';
export default {
props: ['msg'],
setup(props) {
// 当父组件更新 props 时 setup 函数是不会重新执行的
// 所以在 setup 函数中使用 props 时需要用到 computed 或者 watch 来响应 props 的变化
// 注意: 直接在模板中使用 props 数据是没有这个问题的
const helloMsg = computed(() => {
return props.msg;
});
return { helloMsg };
},
};
<template>
<div>{{ msg }}</div>
<p>{{ helloMsg }}</p>
</template>
2.子组件通过自定义事件向父组件传递数据
父组件
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
setup() {
const msg = ref('我是你的小可爱噢!');
const onClickHandler = (data) => {
// console.log(data, 'data'); data就是setup第二个参数context中的emit的...args传递过来的
msg.value = data;
};
return { msg, onClickHandler };
},
components: { HelloWorld },
};
<template>
<div>
<HelloWorld :msg="msg" @onClickHandler="onClickHandler"></HelloWorld>
</div>//onClickHandler($event)这里是自定义事件
</template>
子组件
import { computed } from 'vue';
export default {
props: ['msg'],
emits: ['onClickHandler'],
setup(props, { emit }) {
// console.log(context, '333333');
// console.log(context.emit, '22222');
const helloMsg = computed(() => props.msg);
const onClickHandler = () => {
emit('onClickHandler', '艾瑞利亚');//输出结果 点击后都变为艾瑞利亚
};
return { helloMsg, onClickHandler };
},
};
<template>
<div>{{ msg }}</div>
<p>{{ helloMsg }}</p>
<button @click="onClickHandler">account</button>
</template>
十三、组件生命周期
onMounted、onUpdated、onUnmounted 组件生命周期函数的执行时机
`setup`: 组件初次挂载前、重新挂载前都会执行。
`onMounted` 组件挂载完成后执行
`onUpdated` 组件数据更新后执行
`onUnmounted` 组件卸载后执行
父组件
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
components: { HelloWorld },
name: 'App',
setup() {
const show = ref(true);
return { show };
},
};
<template>
<button @click="show = !show">toggle</button>
<HelloWorld v-if="show"></HelloWorld>
</template>
子组件
import { ref, onMounted, onUpdated, onUnmounted } from 'vue';
export default {
name: 'ChildComponent',
setup() {
console.log('setup');
let timer = null;
// 组件挂载完成之后开启定时器
onMounted(() => {
timer = setInterval(() => {
console.log('timer...');
}, 1000);
});
// 组件卸载完成之后清除定时器
onUnmounted(() => {
clearInterval(timer);
console.log(timer, '卸载完成');
});
const count = ref(0);
const onClickHandler = () => {
count.value = count.value + 1;
};
// 组件更新之后在控制台中输出 onUpdated
onUpdated(() => {
console.log('onUpdated');
});
return { count, onClickHandler };
},
};
<template>{{ count }} <button @click="onClickHandler">button</button></template>
十四、与服务端通信
需求:向服务器端发送请求获取列表数据渲染列表数据, 没有数据要显示暂无数据, 如果请求报错展示错误信息, 加载过程显示loading.
import axios from 'axios';
import { ref } from 'vue';
export default {
name: 'App',
setup() {
let data = ref(null);
let error = ref(null);
let loading = ref(false);
async function getSever() {
loading.value = true;
try {
await axios({
url: 'https://jsonplaceholder.typicode.com/posts',
}).then((res) => {
data.value = res.data;
});
} catch (err) {
console.log(err, '12');
error.value = err;
}
loading.value = false;
}
getSever();
return { data, error, loading };
},
};
<template>
<div v-if="loading">loading...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else-if="data && data.length > 0">
<ul>
<li v-for="item in data" :key="item">{{ item.title }}</li>
</ul>
</div>
<div v-else>暂无数据</div>
</template>
十五、获取 DOM 对象
1.使用 ref 获取单个 DOM 对象
import { ref, onMounted } from 'vue';
export default {
name: 'App',
setup() {
const myRef = ref(null);
onMounted(() => {
console.log(myRef.value);
});
return { myRef };
},
};
<template>
<div ref="myRef">艾欧尼亚昂扬不灭</div>
</template>
2.使用 ref 获取一组 DOM 对象
import { ref, onMounted, onUpdated, nextTick } from 'vue';
export default {
setup() {
const list = ref(['a', 'b', 'c']);
const elms = ref([]);
console.log(elms, 'elms');
const onClickHandler = () => list.value.push('d');
onMounted(() => console.log(elms.value));
onUpdated(() => console.log(elms.value));
nextTick(() => {
console.log(elms.value, 'elms.value');
});
return { list, elms, onClickHandler };
},
};
<template>
<ul>
<li
v-for="(item, index) in list"
:key="index"
:ref="(el) => (elms[index] = el)"
>
{{ item }}
</li>
</ul>
<button @click="onClickHandler">button</button>
</template>
十六、provide、inject 函数
在父子组件传递数据时,通常使用的是 props 和 emit,父传子时,使用的是 props,如果是父组件传孙组件时,就需要先传给子组件,子组件再传给孙组件,如果多个子组件或多个孙组件使用时,就需要传很多次,会很麻烦。
解决:通过 provide、inject 函数的配合使用,可以实现跨组件传递数据(组件与组件存在嵌套关系)
父组件
import { ref, provide, readonly, onMounted } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
components: {
HelloWorld,
},
setup() {
let person = ref({ name: '张三' });
const onClickHandler = () => {
person.value.name = '李四';
};
provide('person', readonly(person));
provide('onClickHandler', onClickHandler);
},
};
<template>
<HelloWorld></HelloWorld>
</template>
子组件
import Home from '@/components/Home.vue';
export default {
name: 'HelloWorld',
setup() {},
components: {
Home,
},
};
<template>
<Home></Home>
</template>
孙组件
import { inject } from 'vue';
export default {
name: 'Home',
setup() {
const person = inject('person');
const onClickHandler = inject('onClickHandler');
return { person, onClickHandler };
},
};
<template>
<div>{{ person.name }}</div>
<button @click="onClickHandler">account</button>
</template> //输出结果 点击按钮后张三变李四
十七、teleport 组件(传送门Teleport)
1.teleport 组件可以将指定组件渲染到应用外部的其他位置。
2.使用Teleport 组件,可以让我们在组件的逻辑位置写模板代码,可以使用组件的data或者props状态,然后在组件的范围之外渲染它
3.比如弹框组件,它可能在任意组件中使用,但它不属于任意组件,所以不能在使用它的组件中渲染它,我们需要将它渲染到指定位置。
1.需求:包含全屏模式的组件(单个组件)
<template>
<div>
<button @click="modelOpen = true">点击打开弹窗</button>
<teleport to="body">
<div v-if="modelOpen" class="model">
<div class="model-body">
这是一个模态框
<button @click="modelOpen = false">关闭弹窗</button>
</div>
</div>
</teleport>
</div>
</template>
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "ModelButton",
setup() {
const modelOpen = ref(false);
return {
modelOpen,
};
},
});
.model {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
.model-body {
width: 300px;
height: 250px;
background: #fff;
}
2.需求:包含全屏模式的组件(多个组件)
//app.vue
<template>
<teleport to="#modal">
<HelloWorld></HelloWorld>
</teleport>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
components: {
HelloWorld,
},
};
//HelloWorld
<template>
<div class="wrapper">
<div class="content">
<a class="close" href="javascript:">关闭</a>
</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
};
</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
<div id="modal"></div>
十八、Suspense 组件
Suspense 用于确保组件中的 setup 函数调用和模板渲染之间的执行顺序。先执行 setup 后渲染模板。当组件中的 setup 被写成异步函数的形式, 代码执行的顺序就变成了先渲染模板后执行 setup 函数了。
//父组件
import { defineAsyncComponent } from "vue";
export default {
components: {
HelloWorld: defineAsyncComponent(() =>
import("./components/HelloWorld.vue")
),
},
};
<template>
<Suspense>
<template #default>
<HelloWorld></HelloWorld>
</template>
<template #fallback> loading... </template>//添加等待提示效果
</Suspense>
</template>
//子组件
import axios from "axios";
export default {
name: "HelloWorld",
async setup() {
const data = await axios({
url: "https://jsonplaceholder.typicode.com/posts",
});
return { data: data.data };
},
};
<template>
<div>
<ul>
<li v-for="item in data" :key="item">{{ item.title }}</li>
</ul>
</div>
</template>
十九、过渡动画transition
Vue 提供了 transition 组件供我们执行过渡动画, 我们只需要使用 transition 组件包裹你要执行动画的元素即可。执行过渡动画的前提条件是元素具有创建与销毁的操作。
import { ref } from "vue";
export default {
setup() {
const show = ref(false);
return { show };
},
};
<template>
<div>
<transition name="slide-fade">
<h2 v-if="show">我是无敌的无敌的小可爱</h2>
</transition>
<button @click="show = !show">account</button>
</div>
</template>
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
具体的各种效果查看官方文档即可,https://vuejs.org/guide/built-ins/transition.html#css-based-transitions
总结
这里对文章进行总结:以上就是今天要讲的内容