1.认识及创建Vue3
https://www.bilibili.com/video/BV18K4y1T7Nc?spm_id_from=333.337.search-card.all.click&vd_source=e499c93499db59599a71c5a3d947fade
了解相关信息
- Vue.js 3.0 "One Piece"正式版在2020年9月份发布
- 2年多开发,100+位贡献者,2600+次提交,600+次PR
- Vue3支持Vue2的大多数特性s
- 更好的支持Typescript
性能提升
- 打包大小减少了41%
- 初次渲染快55%,更新渲染快133%
- 内存减少54%
- 使用Proxy代替defineProperty实现数据响应式
- 重写虚拟DOM的实现和Tree-shaking
新增特性
- Composition(组合)Api
- setup
- ref和reactive
- computed和watch
- 新的生命周期函数
- provide和inject
- …
- 新组件
- Fragment-文档碎片
- Teleport-瞬移组件的位置
- Suspense-异步加载组件的loading界面
- 其他API更新
- 全局API的修改
- 将原来的全局API转移到应用对象
- 模板语法变化
使用vue-cli创建
文档:https://cli.vuejs.org/zh/guide/creating-a-project.html
//安装或升级
npm install -g @vue/cli
//保证 vue-cli版本在4.5.0以上
vue --version
//创建项目
vue create my-project
使用create-vue创建项目
- 前提环境条件
- 已安装16.0或更高版本的Node.js
- 创建一个Vue应 (这一指令将会安装并执行create-vue)
npm init vue @latest
2.setup及接收参数
setup
我们可以跟以前一样定义data和methods,但是在vue3中我们更推荐使用setup函数
- setup是一个函数,只在初始化时执行一次
- 以后大部分代码都是在setup函数中写
- 返回一个对象,对象中的属性或方法,模板中可以直接使用
- setup返回的数据会和data和methods进行合并,setup优先级更高
- setup函数中没有this
- 以后开发都不使用this了
- setup不要写成async函数
因为setup函数必须返回一个json对象供模板使用,如果setup是一个async函数,返回的将是一个promise对象
如果setup是一个async函数,那该组件就成了一个异步组件,需要配合Suspense组件才能使用
原始写法(需要return)及语法糖写法(不需要return)
//原始
<script>
export default {
setup() {
const message = "this is message";
const logMessage = () => {
console.log(message);
};
return {
message,
logMessage,
};
}
};
//语法糖
<script setup>
const message = "this is message";
const logMessage = () => {
console.log(message);
};
</script>
setup的参数
- props-接收父组件传入的通过props声明过的属性
- context-是一个对象,解构出来包含
- attrs-接收父组件传入的没有通过props声明过的属性,相当于this.$attrs
- slots-接收父组件传入的插槽内容的对象,相当于this.$slots
- emit-用来分发自定义事件的函数,相当于this.$emit
父组件:
<template>
<Child msg="hello world" msg2="vue3" @custom-event="handle">
<template #aaa>我是插槽</template>
</Child>
</template>
<script lang="ts">
import Child from "./Child.vue";
export default {
components: {
Child,
},
setup() {
const handle = (val) => {
console.log("val", val);
};
return {
handle,
};
},
};
</script>
子组件:
<template>
<div>msg-{{ msg }}</div>
<div>msg2-{{ msg2 }}</div>
<slot name="aaa" />
<button @click="fn">按钮</button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props: ["msg"],
setup(props, { attrs, slots, emit }) {
const fn = () => {
emit("custom-event", 123);
};
return {
msg2: attrs.msg2,
fn,
};
},
});
</script>
3. 组合式API - reactive和ref函数
reactive()
作用:接收对象类型数据的参数传入并返回一个响应式的对象
核心步骤:
<script setup>
import { reactive } from "vue";
const state = reactive({
count: 0,
});
const setState = () => {
state.count++;
};
</script>
<template>
<div>
<button @click="setState">{{ state.count }}</button>
</div>
</template>
toRefs
- 将响应式对象中所有属性包装为ref对象,并返回包含这些ref对象的普通对象
- 应用:对reactive定义的对象进行toRefs包装,包装之后的对象中每个属性都是响应式的
<template>
<h1>首页</h1>
<p>a:{{ a }}</p>
<p>b:{{ b }}</p>
<p>str:{{ str }}</p>
<p>boo:{{ boo }}</p>
<p>boo:{{ obj }}</p>
<button @click="update">更新</button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
name: "HomeView",
setup() {
const state = reactive({
a: 1,
b: 2,
str: "abc",
boo: true,
obj: { a: 123 },
});
const update = () => {
console.log("update");
state.a++;
state.b++;
state.str += "--";
state.boo = !state.boo;
state.obj.a++;
};
return {
...toRefs(state),
update,
};
},
});
</script>
ref
作用:接收简单类型或对象类型的数据传入并返回一个响应式的对象
<script setup>
import { ref } from "vue";
const count = ref(0);
const setState = () => {
count.value++;
};
</script>
<template>
<div>
<button @click="setState">{{ count }}</button>
</div>
</template>
<template>
<h1>首页</h1>
<p>a:{{ a }}</p>
<p>b:{{ b }}</p>
<p>str:{{ str }}</p>
<p>boo:{{ boo }}</p>
<button @click="handle2">按钮</button>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "HomeView",
setup() {
let a = ref(1);
let b = ref(2);
let str = ref("abc");
let boo = ref(true);
const handle2 = () => {
a.value++;
b.value++;
str.value += "--";
boo.value = !boo.value;
console.log(a, b);
};
return {
a,
b,
str,
boo,
handle2,
};
},
});
</script>
- 作用:定义响应式数据
- 语法:const xxx = ref(initValue)
- 创建一个包含响应式数据的引用(reference)对象
- js中修改数据: xxxx.value = otherValue
- 模板中显示数据:不需要.value, 直接使用{{xxxx}}
- 一般用来定义一个原始类型的响应式数据
4. 计算属性computed
回顾vue2中的计算属性
computed: {
// 只有getter
fullName() {
return this.firstName + " " + this.lastName;
},
// 有getter和setter
fullName2: {
get() {
return this.firstName + " " + this.lastName;
},
set(val) {
const names = val.split(" ");
this.firstName = names[0];
this.lastName = names[1];
},
},
},
vue3的计算属性
- computed-用法跟vue2类似,不过需要先引入computed
<script setup>
import { ref, computed } from "vue";
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9]);
const computedList = computed(() => {
return list.value.filter((item) => item > 2);
});
</script>
<template>
<div>原始响应式数组-{{ list }}</div>
<div>计算属性数组-{{ computedList }}</div>
</template>
5. 侦听属性watch
侦听属性watch
回顾vue2的侦听属性
watch:{
obj(newVal,oldVal){
console.log(newVal,oldVal);
},
// 立即监听、深度监听
obj:{
handler(newVal,oldVal){
console.log(newVal,oldVal)
},
immediate:true, //初始化立即执行一次
deep:true, //深度监视
},
'obj.a'(newVal,oldVal){
console.log(newVal,oldVal)
}
}
vue3的侦听属性:
- watch-指定监听数据
- 监视指定的一个或多个响应式数据,一旦数据变化,就自动执行监视回调
- 如果是监听reactive对象中的属性,必须通过函数来指定
- 监听多个数据,使用数组来指定
- 默认初始时不执行回调,但可以通过配置immediate为true,来指定初始时立即执行第一次
- 通过配置deep为true,来指定深度监视
- 监视指定的一个或多个响应式数据,一旦数据变化,就自动执行监视回调
- watchEffect-不指定监听数据
- 不用直接指定要监视的数据,回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次
使用时需要先引入watch和watchEffect
//监听一个值
<script setup>
import { ref, watch } from "vue";
const count = ref(0);
const setCount = () => {
count.value++;
};
watch(count, (newCal, oldVal) => {
console.log("count变化了", newCal, oldVal);
});
</script>
<template>
<div>
<button @click="setCount">{{ count }}</button>
</div>
</template>
//监听多个值
<script setup>
import { ref, watch } from "vue";
const count = ref(0);
const setCount = () => {
count.value++;
};
const name = ref("cp");
const setName = () => {
name.value = "pc";
};
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log("count或者name变化了", [newCount, oldCount], [newName, oldName]);
});
</script>
<template>
<div>
<button @click="setCount">修改count-{{ count }}</button>
<button @click="setName">修改name-{{ name }}</button>
</div>
</template>
//立即执行
<script setup>
import { ref, watch } from "vue";
const count = ref(0);
const setCount = () => {
count.value++;
};
watch(
count,
(newCount, oldCount) => {
console.log("count变化了", newCount, oldCount);
},
{
immediate: true, //立即执行 (首次自动调用一次)
}
);
</script>
<template>
<div>
<button @click="setCount">{{ count }}</button>
</div>
</template>
deep
默认机制:通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要deep选项
//deep监听全部属性对象
<script setup>
import { ref, watch } from "vue";
const state = ref({ count: 0, age: 20 });
const setCount = () => {
// 对象直接修改属性 ->不会触发回调(必须加deep)
state.value.count++;
};
watch(
state,
() => {
console.log("变化了");
},
{
deep: true, //深度监视
}
);
</script>
<template>
<div>
<button @click="setCount">{{ state.count }}</button>
</div>
</template>
精确侦听对象的某个属性
需求:再不开启deep的前提下,侦听age的变化,只有age变化时才执行回调
<script setup>
import { ref, watch } from "vue";
const state = ref({ name: "chaichai", age: 20 });
const setName = () => {
state.value.name = "new-chaichai";
};
const setAge = () => {
state.value.age = 22;
};
//语法:两个回调函数的写法(只侦听其中一个属性)
watch(
() => state.value.age,
() => {
console.log("age变化了");
}
);
</script>
<template>
<div>
<div>当前name -- {{ state.name }}</div>
<div>当前age -- {{ state.age }}</div>
<button @click="setName">修改name</button>
<button @click="setAge">修改age</button>
</div>
</template>
6. 组合式API - 生命周期函数
声明周期
vue2中的生命周期钩子函数依旧可以使用,不过建议使用vue3的钩子函数
- vue2与vue3声明周期对比
注意:beforeDestroy和destroyed已经被废弃,如果想继续使用vue2的写法,对应的api时onBeforeUnmount(beforeDestroy)和onUnmounted(destroyed)
<template></template>
<script lang="ts">
import {
defineComponent,
ref,
onBeforeMount,
onMounted,
onActivated,
onDeactivated,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
export default defineComponent({
name: "HomeView",
setup() {
onBeforeMount(() => {
console.log("onBeforeMount");
});
onMounted(() => {
console.log("onMounted");
});
onActivated(() => {
console.log("onActivated");
});
onDeactivated(() => {
console.log("onDeactivated");
});
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
onUpdated(() => {
console.log("onUpdated");
});
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
});
onUnmounted(() => {
console.log("onUnmounted");
});
return {};
},
});
</script>
7. 组合式API - 父子通信
父传子
基本思想
1.父组件中给子组件绑定属性
2.子组件内部通过defineProps接收
//父组件
<script setup>
import { ref } from "vue";
import Son from "./components/son.vue";
const message = ref("father");
</script>
<template>
<div>
<h2>父组件</h2>
<Son :message="message" />
</div>
</template>
//子组件
<script setup>
const props = defineProps({
message: String,
});
</script>
<template>
<div>
<h2>子组件</h2>
<div>父组件传过来的数据-{{ props.message }}</div>
</div>
</template>
<style scoped>
</style>
子传父
基本思想
1.负组件中给子组件标签通过@绑定事件
2.子组件内部通过defineEmits方法触发事件
//父组件
<script setup>
import { ref } from "vue";
import Son from "./components/son.vue";
const getMessage = (msg) => {
console.log(msg);
};
</script>
<template>
<div>
<h2>父组件</h2>
<Son @get-message="getMessage" />
</div>
</template>
//子组件
<script setup>
const emit = defineEmits(["get-message"]);
const sendMsg = () => {
emit("get-message", "123");
};
</script>
<template>
<div>
<h2>子组件</h2>
<button @click="sendMsg">sendMsg</button>
</div>
</template>
<style scoped>
</style>
8. 组合式API - 模板引用(ref拿节点)
概念:通过ref标识获取真实的dom对象或者组件实例对象
获取拿dom对象
<script setup>
import { onMounted, ref } from "vue";
const h1Ref = ref(null);
onMounted(() => {
console.log("123", h1Ref.value);
});
</script>
<template>
<div>
<h1 ref="h1Ref">我是dom标签h1</h1>
</div>
</template>
获取组件实例对象
defineExpose()
默认情况下
//父组件
<script setup>
import { onMounted, ref } from "vue";
import Son from "./components/son.vue";
const comRef = ref(null);
onMounted(() => {
console.log("123", comRef.value);
});
</script>
<template>
<div>
<Son ref="comRef" />
</div>
</template>
//子组件
<script setup>
import { ref } from "vue";
const name = ref("test name");
const setName = () => {
name.value = "newtest";
};
defineExpose({
name,
setName,
});
</script>
<template>
<div>
<h2>我是子组件</h2>
</div>
</template>
<style scoped>
</style>
9. 组合式API - provide和inject
作用和场景:顶层组件想任意的底层组件传递数据和方法,实现跨层组件通信
跨层传递普通数据
1.顶层组件通过provide函数提供数据
provide('key', 顶层组件中的的数据)
2.底层组件通过inject函数获取数据
const message = inject('key')
//父组件
<script setup>
import {ref, provide } from "vue";
import RoomMsgItem from "./components/room-msg-item.vue";
const data = ref(0)
provide("data-key", data);
</script>
<template>
<div>
顶层组件
<RoomMsgItem />
</div>
</template>
//子组件
<script setup>
import { inject } from "vue";
const roomData = inject("data-key");
</script>
<template>
<div>
底层组件
<div>来自顶层组件中的数据为 -- {{ roomData }}</div>
</div>
</template>
跨层传递方法
顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件中的数据
//父组件
<script setup>
import { ref, provide } from "vue";
import RoomMsgItem from "./components/room-msg-item.vue";
const data = ref(0);
const setCount = () => {
data.value++;
};
provide("data-key", data);
provide("count-key", setCount);
</script>
<template>
<div>
顶层组件
<RoomMsgItem />
</div>
</template>
//子组件
<script setup>
import { inject } from "vue";
const roomData = inject("data-key");
const countKey = inject("count-key");
</script>
<template>
<div>
底层组件
<div>来自顶层组件中的数据为 -- {{ roomData }}</div>
<button @click="countKey">来自顶层的方法</button>
</div>
</template>
10. Pinia 状态管理库
Pinia是Vue的专属的最新状态管理库,是Vuex状态管理工具的替代品
优势:
- 提供更加简单的API(去掉了mutation)
- 提供符合组合式风格的API(和Vue3新语法统一)
- 去掉了modules的概念,每一个store都是一个独立的模块
- 搭配TypeScript一起使用提供可靠的类型推断
下载依赖
npm install pinia
main.js配置
import { createPinia } from 'pinia'
const pinia = createPinia()
createApp(App).use(pinia).mount('#app')
使用Pinia实现计数器案例
在src下新建 stores/counter.js
// counter.js文件
import { defineStore } from "pinia";
import { ref } from 'vue'
export const useCounterStore = defineStore('counter',()=>{
// 定义数据(state)
const count = ref(0)
// 定义修改数据的方法(action 同步+异步)
const increment = () =>{
count.value++
}
// 以对象的方式return供组件使用
return{
count,
increment
}
})
// 调用的文件
<script setup>
// 1.导入use打头的方法
import { useCounterStore } from "@/stores/counter.js";
// 2.执行方法得到store实例对象
const counterStore = useCounterStore();
console.log("counterStore", counterStore);
</script>
<template>
<button @click="counterStore.increment">{{ counterStore.count }}</button>
</template>
getters实现
Pinia中的getters直接使用computed函数进行模拟
// counter.js文件
// 定义数据(state)
const count = ref(0)
// getter定义
const doubleCount = computed(()=>count.value * 2)
// 调用的文件
<script setup>
// 1.导入use打头的方法
import { useCounterStore } from "@/stores/counter.js";
// 2.执行方法得到store实例对象
const counterStore = useCounterStore();
console.log("counterStore", counterStore);
</script>
<template>
<button @click="counterStore.increment">{{ counterStore.count }}</button>
{{ counterStore.doubleCount }}
</template>
action如何实现异步
action中实现异步和组件中定义数据和方法的风格完全一致
// counter.js文件
const API_URL = 'http://geek.itheima.net/v1_0/channels'
// 定义数据(state)
const list = ref([])
// 异步action
const getList = async() =>{
const res = await axios.get(API_URL)
list.value = res.data.data.channels
}
// 调用的文件
<script setup>
// 1.导入use打头的方法
import { useCounterStore } from "@/stores/counter.js";
import { onMounted } from "vue";
// 2.执行方法得到store实例对象
const counterStore = useCounterStore();
console.log("counterStore", counterStore);
onMounted(() => {
counterStore.getList();
});
</script>
<template>
<ul>
<li v-for="item in counterStore.list" :key="item.id">{{ item.name }}</li>
</ul>
</template>
storeToRefs
使用storeToRefs函数可以辅助保持数据(state+getter)的响应式解构
// 解构变量
<script setup>
// 1.导入use打头的方法
import { useCounterStore } from "@/stores/counter.js";
import { onMounted } from "vue";
import { storeToRefs } from "pinia";
// 2.执行方法得到store实例对象
const counterStore = useCounterStore();
const { count, doubleCount } = storeToRefs(counterStore);
onMounted(() => {
counterStore.getList();
});
</script>
<template>
<button @click="counterStore.increment">{{ count }}</button>
{{ doubleCount }}
<ul>
<li v-for="item in counterStore.list" :key="item.id">{{ item.name }}</li>
</ul>
</template>
// 解构方法
<script setup>
// 1.导入use打头的方法
import { useCounterStore } from "@/stores/counter.js";
import { onMounted } from "vue";
import { storeToRefs } from "pinia";
// 2.执行方法得到store实例对象
const counterStore = useCounterStore();
const { count, doubleCount, list } = storeToRefs(counterStore);
// 方法直接从原来的counterStore中解构赋值
const { increment } = counterStore;
onMounted(() => {
counterStore.getList();
});
</script>
<template>
<button @click="increment">{{ count }}</button>
{{ doubleCount }}
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</template>