参考自:vue3保姆级教程 - 掘金 (juejin.cn)
与Vue2相比的变化
- template没有根标签
vite是什么?
新一代的前端构建工具
优点
- 无需打包,快速的冷服务器启动
- 即时热模块更换(HMR,热更新)
- 真正的按需编译。 webpack是一开始是入口文件,然后分析路由,然后模块,最后进行打包,然后告诉你,服务器准备好了(默认8080)。然而vite是什么,它一开始是先告诉你服务器准备完成,然后等你发送HTTP请求,然后是入口文件,
Dynamic import(动态导入)code split point(代码分割)
常用组合式API
setup
-
setup函数是 Composition API(组合API)的入口
-
在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用
-
因为作用域的问题,然而这次我们都在
setup里面,所以不会用到this -
vue3和vue2不要混用,如果有重名那么优先
setup -
也可以用vue3新语法糖
<script setup>
<script>
export default {
name: "App",
setup() {
let name = "流星";
let age = 18;
//方法
function say() {
console.log(`我叫${name},今年${age}岁`);
}
//返回一个对象
return {
name,
age,
say,
};
},
};
</script>
script setup
-
组件直接引入即可使用,无需注册
-
引入的组件可以直接用作自定义组件标签名,类似于 JSX 中的工作方式
-
属性和方法无需挂载到对象上后再次返回
<template>
<div>
<Foo />
<h2 @click="increment">{{ count }}</h2>
</div>
</template>
<script setup>
import { ref } from "vue";
import Foo from "./components/Foo.vue";
const count = ref(0);
const increment = () => count.value++;
</script>
props 和 emits
子组件
<template>
<div>
<h2>{{ count }}</h2>
<button @click="$emit('increment')">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script setup>
// 接收props,并返回一个对象,可以在js中使用props来获取传入的数据
const props = defineProps({
count: {
type: Number,
default: 0,
},
});
// 声明需要触发的事件,返回一个emit函数,作用和this.$emit函数的作用是一致的
const emit = defineEmits(["increment", "decrement"]);
const decrement = () => emit("decrement");
</script>
父组件
<template>
<div>
<Foo :count="count" @increment="increment" @decrement="decrement" />
</div>
</template>
<script setup>
import { ref } from "vue";
import Foo from "./components/Foo.vue";
const count = ref(0);
const increment = () => count.value++;
const decrement = () => count.value--;
</script>
-
defineProps 和 defineEmits 是编译器宏(compiler macros ),只能在
<script setup>中使用。它们不需要被导入 -
传递给 defineProps 和 defineEmits 的选项将被从 setup 中提升到模块范围
export default {
props: {
foo: String,
},
emits: ["change", "delete"],
// setup中定义的props和emits会被抽取到模块作用域中
setup(props, { emit }) {
//setup code
},
};
slots 和 attrs
<script setup>
import { useSlots, useAttrs } from "vue";
const slots = useSlots();
const attrs = useAttrs();
</script>
与普通 script 一起使用
script setup语法提供了表达大多数现有 Options API 选项同等功能的能力,只有少数选项除外
- name
- inheritAttrs
- 模块导出
如果你需要声明这些选项,请使用单独的普通 <script> 块,并使用导出默认值。
<script>
export default {
name: "CustomName",
inheritAttrs: false,
};
</script>
<script setup>
//script setup logic
</script>
注意点
-
它比
beforeCreate和created这两个生命周期还要快,就是说,setup在beforeCreate,created前,它里面的this打印出来是undefined -
setup可以接受两个参数,第一个参数是
props,也就是组件传值,第二个参数是context,上下文对象,context里面还有三个很重要的东西attrs,slots,emit,它们就相当于vue2里面的this.$attrs,this.$slots,this.$emit
ref与reactive
ref
ref能将数据变成响应式数据
<template>
<div class="home">
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<h2>职业:{{ job.occupation }}</h2>
<h2>薪资:{{ job.salary }}</h2>
<button @click="say">修改</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "Home",
setup() {
let name = ref("燕儿");
let age = ref(18);
let job = ref({
occupation: "程序员",
salary: "10k",
});
console.log(name);
console.log(age);
//方法
function say() {
job.value.salary = "12k";
}
return {
name,
age,
job,
say,
};
},
};
</script>
reactive
reactive只能定义对象类型的响应式数据
<template>
<div class="home">
<h1>姓名:{{ data.name }}</h1>
<h1>年龄:{{ data.age }}</h1>
<h2>职业:{{ data.job.occupation }}<br />薪资:{{ data.job.salary }}</h2>
<h3>爱好:{{ data.hobby[0] }},{{ data.hobby[1] }},{{ data.hobby[2] }}</h3>
<button @click="say">修改</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: "Home",
setup() {
let data = reactive({
name: "燕儿",
age: 18,
job: {
occupation: "程序员",
salary: "10k",
},
hobby: ["刷剧", "吃鸡", "睡觉"],
});
//方法
function say() {
data.job.salary = "12k";
data.hobby[0] = "学习";
}
return {
data,
say,
};
},
};
</script>
ref与reactive的区别
-
ref用来定义:基本类型数据。
-
ref通过
Object.defineProperty()的get与set来实现响应式(数据劫持)。 -
ref定义的数据:操作数据需要
.value,读取数据时模板中直接读取不需要.value。 -
reactive用来定义:对象或数组类型数据。
-
reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源代码内部的数据。
-
reactive定义的数据:操作数据与读取数据:均不需要
.value。 当然,我之前就说过,ref可以定义对象或数组的,它只是内部自动调用了reactive来转换
computed,watch与watchEffect
computed
<template>
<div class="home">
姓:<input type="text" v-model="names.familyName" /><br />
名:<input type="text" v-model="names.lastName" /><br />
姓名:{{ fullName }}<br />
</div>
</template>
<script>
import { reactive, computed } from "vue";
export default {
name: "Home",
setup() {
let names = reactive({
familyName: "阿",
lastName: "斌",
});
fullName = computed(() => {
return names.familyName + "." + names.lastName;
});
return {
names,
fullName,
};
},
};
</script>
要是我们想要修改怎么办呢,那么就要用到computed的终结写法了
<template>
<div class="home">
姓:<input type="text" v-model="names.familyName" /><br />
名:<input type="text" v-model="names.lastName" /><br />
姓名:<input type="text" v-model="names.fullName" /><br />
</div>
</template>
<script>
import { reactive, computed } from "vue";
export default {
name: "Home",
setup() {
let names = reactive({
familyName: "阿",
lastName: "斌",
});
names.fullName = computed({
get() {
return names.familyName + "." + names.lastName;
},
set(value) {
let nameList = value.split(".");
names.familyName = nameList[0];
names.lastName = nameList[1];
},
});
return {
names,
};
},
};
</script>
watch
<template>
<div class="home">
<h1>当前数字为:{{ num }}</h1>
<button @click="num++">点击数字加一</button>
</div>
</template>
<script>
import { ref, watch } from "vue";
export default {
name: "Home",
setup() {
let num = ref("0");
watch(num, (newValue, oldValue) => {
console.log(`当前数字增加了,${newValue},${oldValue}`);
});
return {
num,
};
},
};
</script>
监听多个数据
watch([num, msg], (newValue, oldValue) => {
console.log("当前改变了", newValue, oldValue);
}, { immediate: true, deep: true });
监听reactive的数据
<template>
<div class="home">
<h1>当前姓名:{{ names.familyName }}</h1>
<h1>当前年龄:{{ names.age }}</h1>
<h1>当前薪水:{{ names.job.salary }}K</h1>
<button @click="names.familyName += '!'">点击加!</button>
<button @click="names.age++">点击加一</button>
<button @click="names.job.salary++">点击薪水加一</button>
</div>
</template>
<script>
import { reactive, watch } from "vue";
export default {
name: "Home",
setup() {
let names = reactive({
familyName: "鳌",
age: 23,
job: {
salary: 10,
},
});
watch(
names,
(newValue, oldValue) => {
console.log(`names改变了`, newValue, oldValue);
},
{ deep: false }
);
return {
names,
};
},
};
</script>
监听单个属性
watch(() => names.age, (newValue, oldValue) => {
console.log("names改变了", newValue, oldValue);
});
监听多个属性
watch(() => names.age, () => names.familyName, (newValue, oldValue) => {
console.log("names改变了", newValue, oldValue);
});
监听深度属性
//第一种
watch(() => names.job.salary, (newValue, oldValue) => {
console.log("names改变了", newValue, oldValue);
});
//第二种
watch(() => names.job, (newValue, oldValue) => {
console.log("names改变了", newValue, oldValue);
}, { deep: true });
watchEffect
watchEffect是vue3的新函数,它是来和watch来抢饭碗的,它和watch是一样的功能,那它有什么优势呢?
- 自动默认开启了
immediate:true - 用到了谁就监视谁
watchEffect(() => {
const one = num.value;
const tow = person.age;
console.log("watchEffect执行了");
});
生命周期
其实在vue3中生命周期没有多大的改变,只是改变了改变了销毁前,和销毁,让它更加语义化了 beforeDestroy改名为beforeUnmount,destroyed改名为unmounted
然后在vue3中,beforeCreate与created并没有组合式API中,setup就相当于这两个生命周期函数
在setup里面应该这样写
beforeCreate`===>`Not needed*
created`=======>`Not needed*
beforeMount` ===>`onBeforeMount
mounted`=======>`onMounted
beforeUpdate`===>`onBeforeUpdate
updated` =======>`onUpdated
beforeUnmount` ==>`onBeforeUnmount
unmounted` =====>`onUnmounted
hooks函数
-
Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数
-
Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
其实就是代码的复用,可以用到外部的数据,生命钩子函数...,具体怎么用直接看代码
//一般都是建一个hooks文件夹,都写在里面
import { reactive, onMounted, onBeforeUnmount } from "vue";
export default function () {
//鼠标点击坐标
let point = reactive({
x: 0,
y: 0,
});
//实现鼠标点击获取坐标的方法
function savePoint(event) {
point.x = event.pageX;
point.y = event.pageY;
console.log(event.pageX, event.pageY);
}
//实现鼠标点击获取坐标的方法的生命周期钩子
onMounted(() => {
window.addEventListener("click", savePoint);
});
onBeforeUnmount(() => {
window.removeEventListener("click", savePoint);
});
return point;
}
//在其他地方调用
import useMousePosition from "./hooks/useMousePosition";
let point = useMousePosition();
toRef与toRefs
toRef
<template>
<div class="home">
<h1>当前姓名:{{ names.name }}</h1>
<h1>当前年龄:{{ names.age }}</h1>
<h1>当前薪水:{{ names.job.salary }}K</h1>
<button @click="names.name += '!'">点击加!</button>
<button @click="names.age++">点击加一</button>
<button @click="names.job.salary++">点击薪水加一</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: "Home",
setup() {
let names = reactive({
name: "老谭",
age: 23,
job: {
salary: 10,
},
});
return {
names,
};
},
};
</script>
是不是一直都是用到代码name.xx,可能你会说,那我就return的时候不这样写,改成这样
return {
name: names.name,
age: names.age,
salary: names.job.salary,
};
但是你要是在页面进行操作时就不是响应式了,为什么呢?那是因为你现在暴露出去的是简简单单的字符串,字符串会有响应式吗?肯定没有呀,但是你要是用到了toRef,那就是把name.xx变为响应式,然后操作它时会自动的去修改name里面的数据
return {
name: toRef(names, "name"),
age: toRef(names, "age"),
salary: toRef(names.job, "salary"),
};
toRefs
聪明一点,toRefs与toRef有什么不同,加了个s,toRef是单个转化为响应式,那toRefs就是多个转化为响应式咯,这样的话就减少代码,不然要是有成千上万个,那你不是要当憨憨闷写吗?(...是结构哈,看不懂就麻溜的alt+←),当然它只会结构一层,深层里的代码还是要老实的写
<h1>当前姓名:{{name}}</h1>
<h1>当前薪水:{{job.salary}}K</h1>
return { ...toRefs(names) }
Fragment
在vue2里面,我们是有根标签这个概念的,但是到来vue3,它是自动给你创建个虚拟根标签VNode(Fragment),所以可以不要根标签。好处就是 减少标签层级, 减小内存占用
Teleport
teleport 提供了一种有趣的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件。
其实就是可以不考虑你写在什么位置,你可以定义teleport在任意标签里进行定位等
to 允许接收值: 期望接收一个 CSS 选择器字符串或者一个真实的 DOM 节点。 提示:
<Teleport>挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。 如果目标元素也是由 Vue 渲染的,你需要确保在挂载<Teleport>之前先挂载该元素
<Teleport to="body">
</Teleport>
Suspense
大家都知道在渲染组件之前进行一些异步请求是很常见的事,suspense 组件提供了一个方案,允许将等待过程提升到组件树中处理,而不是在单个组件中。