赞
踩
在新版的生命周期函数,可以按需导入到组件中,且只能在setup()函数中使用.
- import { onMounted, onUnmounted } from 'vue';
- export default {
- setup () {
- onMounted(()=>{
- //
- });
-
- onUnmounted(()=> {
- //
- });
- }
- };
理解
setup()函数是vue3中专门新增的方法,可以理解为Composition Api的入口.
执行时机
在beforecreate之后,create之前执行.
接收props数据
- export default {
- props: {
- msg: {
- type: String,
- default: () => {}
- }
- },
- setup(props) {
- console.log(props);
- }
- }
context:
setup()的第二个参数是一个上下文对象,这个上下文对象大致包含了这些属性,注意:在setup()函数中无法访问this
- const MyComponent = {
- setup(props, context) {
- context.attrs
- context.slots
- context.parent
- context.root
- context.emit
- context.refs
- }
- }
reactive是用来创建一个响应式对象,等价于2.x的Vue.observable,具体可以参考下面demo。
- <template>
- <div>
- <p @click="incment()">
- click Me!
- </p>
- <p>
- 一:{{ state.count }} 二: {{ state.addCount }}
- </p>
- </div>
- </template>
-
- <script>
- import { reactive } from 'vue';
- export default {
- setup () {
- const state = reactive({//创建响应式数据
- count: 0,
- addCount: 0
- });
-
- function incment () {
- state.count++;
- state.addCount = state.count * 2;
- }
-
- return {
- state,
- incment
- };
- }
- };
- </script>
基本语法
ref()函数用来给定的值创建一个响应式的数据对象,ref()的返回值是一个对象,这个对象上只包含一个.value属性.下面是基本数据类型创建步骤.
- import { ref, defineComponent } from 'vue';
- export default defineComponent ({
- setup () {
- const valueNumber = ref(0);
- const valueString = ref('hello world!');
- const valueBoolean = ref(true);
- const valueNull = ref(null);
- const valueUndefined = ref(undefined);
-
- return {
- valueNumber,
- valueString,
- valueBoolean,
- valueNull,
- valueUndefined
- };
- }
- });
在template中访问ref创建的响应式数据
- import { ref } from 'vue';
- export default {
- setup () {
- const value = ref(1);
-
- return {
- value,
- msg: 'hello world!'
- };
- }
- };
- <template>
- <p>
- {{ value }} {{ msg }}
- </p>
- </template>
将ref响应式数据挂载到reactive中
当把ref()创建出来值直接挂载到reactive()中时,会自动把响应式数据对象的展开为原始的值,不需要通过.value就可以直接访问到.
- import { ref, reactive } from 'vue';
- export default {
- setup () {
- const count = ref(1);
- const state = reactive({
- count
- });
-
- console.log(state.count);//1 可以直接访问到,不需要通过.value就可以直接访问到
- state.count++;
- console.log(count.value);//2 我们发现,最初count值也发生了改变
-
- return {
- count
- };
- }
- };
新的ref会覆盖旧的ref,实例如下:
- import { ref, reactive } from 'vue';
- export default {
- setup () {
- const count = ref(1);
- const state = reactive({
- count
- });
- const newCount = ref(9);
-
- state.count = newCount;
- state.count++;
-
- console.log(state.count, newCount, count);// 10 10 1
-
- return {
- count
- };
- }
- };
我们发现,这次的count值却没有发生改变,还是原始值1,是因为新创建的newCount替换并覆盖了之前的count值,取代了它的位置.
用来判断某个值是否为ref创建出来的对象,场景:当需要展开某个值可能是ref()创建出来的对象时.
- import { ref, isRef } from 'vue';
- export default {
- setup () {
- const count = ref(1);
- const unwrappend = isRef(count) ? count.value : count;
-
- return {
- count,
- unwrappend
- };
- }
- };
torefs()函数可以将reactive()创建出来的响应式对象转换为普通的对象,只不过这个对象上的每个属性节点都是ref()类型的响应式数据
- <template>
- <p>
- <!-- 可以不通过state.value去获取每个属性 -->
- {{ count }} {{ value }}
- </p>
- </template>
-
- <script>
- import { ref, reactive, toRefs } from 'vue';
- export default {
- setup () {
- const state = reactive({
- count: 0,
- value: 'hello',
- })
-
- return {
- ...toRefs(state)
- };
- }
- };
- </script>
概念:为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步的。相当于浅拷贝一个属性.
区别ref: 拷贝的是一份新的数据单独操作,更新时相互不影响,相当于深拷贝。
场景:当要将某个prop的ref传递给某个复合函数时,toRef很有用.
- import { reactive, ref, toRef } from 'vue'
-
- export default {
- setup () {
- const m1 = reactive({
- a: 1,
- b: 2
- })
-
- const m2 = toRef(m1, 'a');
- const m3 = ref(m1.a);
-
- const update = () => {
- // m1.a++;//m1改变时,m2也会改变
- // m2.value++; //m2改变时m1同时改变
- m3.value++; //m3改变的同时,m1不会改变
- }
-
- return {
- m1,
- m2,
- m3,
- update
- }
- }
- }
computed()用来创建计算属性,返回值是一个ref的实例。
创建只读的计算属性
- import { ref, computed } from 'vue';
- export default {
- setup () {
- const count = ref(0);
- const double = computed(()=> count.value + 1);//1
-
- double++;//Error: "double" is read-only
-
- return {
- count,
- double
- };
- }
- };
创建可读可写的计算属性
在使用computed函数期间,传入一个包含get和set函数的对象,可以额得到一个可读可写的计算属性
- // 创建一个 ref 响应式数据
- const count = ref(1)
-
- // 创建一个 computed 计算属性
- const plusOne = computed({
- // 取值函数
- get: () => count.value + 1,
- // 赋值函数
- set: val => {
- count.value = val - 1
- }
- })
-
- // 为计算属性赋值的操作,会触发 set 函数
- plusOne.value = 9
- // 触发 set 函数后,count 的值会被更新
- console.log(count.value) // 输出 8
watch()函数用来监视某些数据项的变化,从而触发某些特定的操作,看下面这个案例,会实时监听count值的变化. 查看官方文档API
- import { ref, watch } from 'vue';
- export default {
- setup () {
- const count = ref(1);
-
- watch(()=>{
- console.log(count.value, 'value');
- })
-
- setInterval(()=>{
- count.value++;
- },1000);
- return {
- count,
- };
- }
- };
监听指定的数据源
监听reactive的数据变化
- import { watch, reactive } from 'vue';
- export default {
- setup () {
- const state = reactive({
- count: 0
- })
-
- watch(()=>state.count,(count, prevCount)=>{
- console.log(count, prevCount);//变化后的值 变化前的值
- })
-
- setInterval(()=>{
- state.count++;
- },1000);
-
- return {
- state
- };
- }
- };
监听ref类型的数据变化
- import { ref, watch } from 'vue';
- export default {
- setup () {
- const count = ref(0);
-
- watch(count,(count, prevCount)=>{
- console.log(count, prevCount);//变化后的值 变化前的值
- })
-
- setInterval(()=>{
- count.value++;
- },1000);
-
- return {
- count
- };
- }
- };
监听多个指定数据变化
监听reactive类型数据变化
- import { watch, reactive } from 'vue';
- export default {
- setup () {
- const state = reactive({
- count: 0,
- msg: 'hello'
- })
-
- watch([()=> state.count, ()=> state.msg],([count, msg], [prevCount, prevMsg])=>{
- console.log(count, msg);
- console.log('---------------------');
- console.log(prevCount, prevMsg);
- })
-
- setTimeout(()=>{
- state.count++;
- state.msg = 'hello world';
- },1000);
-
- return {
- state
- };
- }
- };
监听ref类型数据变化
- import { ref, watch } from 'vue';
- export default {
- setup () {
- const count = ref(0);
- const msg = ref('hello');
-
- watch([count, msg],([count, name], [prevCount, prevname])=>{
- console.log(count, name);
- console.log('---------------------');
- console.log(prevCount, prevname);
- })
-
- setTimeout(()=>{
- count.value++;
- msg.value = 'hello world';
- },1000);
-
- return {
- count,
- msg
- };
- }
- };
清除监视
在setup()函数内创建的watch监视,会在当前组件被销毁的时候自动停止。如果想要明确的停止某个监视,可以调用watch()函数的返回值即可
- // 创建监视,并得到 停止函数
- const stop = watch(() => {
- /* ... */
- })
-
- // 调用停止函数,清除对应的监视
- stop()
清除无效的异步任务
有时候watch()监视的值发生了变化,我们期望清除无效的异步任务,此时watch回调函数中提供了cleanup registrator function来执行清除工作
- <template>
- <p>
- <input type="text" v-model="keyword">
- </p>
- </template>
-
- <script>
- import { watch, ref } from 'vue';
- export default {
- setup () {
- const keyword = ref('');
-
- const asyncPrint = val => {
- return setTimeout(()=>{
- console.log(val);
- },1000);
- }
-
- watch(keyword, (keyword, prevKeyword, onCleanup) => {
- const timeId = asyncPrint(keyword);
-
- onCleanup(()=> clearTimeout(timeId));
- }, {lazy: true})
-
- return {
- keyword
- };
- }
- };
- </script>
vue3中新增的api,用于属性监听.
与watch有什么不同?
基础用法
- import { watchEffect, ref } from 'vue'
- setup () {
- const userID = ref(0)
- watchEffect(() => console.log(userID))
- setTimeout(() => {
- userID.value = 1
- }, 1000)
-
- /*
- * LOG
- * 0
- * 1
- */
-
- return {
- userID
- }
- }
停止监听
如果watchEffect是在setup或者生命周期里面注册的话,在取消挂在的时候会自动停止。
- //停止监听
-
- const stop = watchEffect(() => {
- /* ... */
- })
-
- // later
- stop()
使 side effect 无效
什么是 side effect ,不可预知的接口请求就是一个 side effect,假设我们现在用一个用户ID去查询用户的详情信息,然后我们监听了这个用户ID, 当用户ID 改变的时候我们就会去发起一次请求,这很简单,用watch 就可以做到。 但是如果在请求数据的过程中,我们的用户ID发生了多次变化,那么我们就会发起多次请求,而最后一次返回的数据将会覆盖掉我们之前返回的所有用户详情。这不仅会导致资源浪费,还无法保证 watch 回调执行的顺序。而使用watchEffect我们就可以做到.
onInvalidate(fn)传入的回调会在watchEffect重新运行或者watchEffect停止的时候执行。
- watchEffect(() => {
- // 异步api调用,返回一个操作对象
- const apiCall = someAsyncMethod(props.userID)
-
- onInvalidate(() => {
- // 取消异步api的调用。
- apiCall.cancel()
- })
- })
概念:只处理对象最外层属性的响应式(也就是浅响应式),所以最外层属性发生改变,更新视图,其他层属性改变,视图不会更新.
场景:如果一个对象的数据结构比较深,但变化只是最外层属性.
- import { shallowReactive } from 'vue'
-
- export default {
- setup() {
- const obj = {
- a: 1,
- first: {
- b: 2,
- second: {
- c: 3
- }
- }
- }
-
- const state = shallowReactive(obj)
-
- function change1() {
- state.a = 7
- }
-
- function change2() {
- state.first.b = 8
- state.first.second.c = 9
- console.log(state);
- }
-
- return { state }
- }
- }
概念:只处理了value的响应式,不进行对象的reactive处理.
场景:如果有一个对象数据,后面会产生新的对象替换.
- import { shallowRef } from 'vue'
-
- export default {
- setup () {
- const m1 = shallowRef({a: 1, b: {c: 2}})
-
- const update = () => {
- m1.value.a += 1
- }
-
- return {
- m1,
- update
- }
- }
- }
创建一个自定义的ref,并对其依赖跟踪和更新触发进行显式控制.
场景:使用customRef实现输入框防抖
- <template>
- <div>
- <input v-model="keyword" placeholder="搜索关键字"/>
- <p>{{keyword}}</p>
- </div>
- </template>
-
- <script>
- import { customRef } from 'vue'
-
- export default {
- setup () {
- const keyword = useDebouncedRef('', 500)
- console.log(keyword)
-
- return {
- keyword
- }
- }
-
- }
- function useDebouncedRef(value, delay = 200) {
- let timeout;
- return customRef((track, trigger) => {
- return {
- get() {
- // 告诉Vue追踪数据
- track()
- return value
- },
- set(newValue) {
- clearTimeout(timeout)
- timeout = setTimeout(() => {
- value = newValue
- // 告诉Vue去触发界面更新
- trigger()
- }, delay)
- }
- }
- })
- }
- </script>
自定义hook的作用类型于vue2中的mixin技术。
优势:清楚知道代码来源,方便复用
案例:收集用户点击的页面坐标
hook/useMousePosition.js
- import { ref, onMounted, onUnmounted } from "vue";
-
- export default function useMousePosition() {
- // 初始化坐标数据
- const x = ref(-1);
- const y = ref(-1);
-
- // 用于收集点击事件坐标的函数
- const updatePosition = e => {
- x.value = e.pageX;
- y.value = e.pageY;
- };
-
- // 挂载后绑定点击监听
- onMounted(() => {
- document.addEventListener("click", updatePosition);
- });
-
- // 卸载前解绑点击监听
- onUnmounted(() => {
- document.removeEventListener("click", updatePosition);
- });
-
- return { x, y };
- }
模版中使用hook函数
- <template>
- <div>
- <p>{{ x }}</p>
- <p>{{ y }}</p>
- </div>
- </template>
-
- <script>
- import useMousePosition from '@/hook/useMousePosition'
- export default {
- setup () {
- const {x, y} = useMousePosition();
- return {
- x,
- y
- }
- }
- }
- </script>
- <template>
- <div @click="update()">
- <p>{{ a }}</p>
- <p>{{ b }}</p>
- </div>
- </template>
-
- <script>
- import { reactive, readonly, shallowReadonly } from 'vue'
- export default {
- setup () {
- const state = reactive({
- a: 1,
- b: {
- c: 2
- }
- })
-
- const m = readonly(state);
- const m2 = shallowReadonly(state);
-
- const update = () => {
- m.a++ //无法修改 只读
- m2.a++ //无法修改
- m2.b.c++ //可以修改 视图层数据改变
- }
-
- return {
- ...toRefs(state),
- update
- }
- }
-
- }
- </script>
通过ref()还可以引用页面上的元素或者组件.
元素引用
使用ref()函数创建DOM引用,需在onMounted中获取.
- <template>
- <div>
- <p ref="dom">hello</p>
- </div>
- </template>
-
- <script>
- import { ref, onMounted } from 'vue';
- export default {
- setup () {
- const dom = ref(null);
-
- onMounted(()=> {
- console.log(dom.value)//当前dom元素
- });
-
- return {
- dom
- }
- }
- };
- </script>
组件引用
- <template>
- <div>
- <Test ref="comRef"/>
- </div>
- </template>
-
- <script>
- import { ref, onMounted } from 'vue';
- import Test from "./test2";
- export default {
- components: { Test },
- setup () {
- const comRef = ref(null);
-
- onMounted(()=> {
- comRef.value.coun;//获取子组件值
- comRef.value.Handle();//调用子组件函数
- })
-
- return {
- comRef
- }
- }
- };
- </script>
- <template>
- <p>
- {{ count }}
- </p>
- </template>
-
- <script>
- import { ref } from 'vue';
- export default {
- setup () {
- const count = ref('count');
-
- const Handle = (()=> {
- console.log('hello');
- })
-
- return {
- count,
- Handle
- }
- }
- };
- </script>
这个函数不是必须的,除非你想完美结合TypeScript提供的类型推断来进行项目开发
场景:这个函数仅仅提供了类型推断,能为setup()函数中的props提供完整的类型推断.
- import { createComponent } from 'vue'
-
- export default createComponent({
- props: {
- foo: String
- },
- setup(props) {
- props.foo // <- type: string
- }
- })
描述:可以获取当前组件的实例,然后通过ctx属性获取当前上下文,这样我们就可以在steup中使用router和vuex了.
- <script>
- import { getCurrentInstance } from 'vue'
- export default {
- setup () {
- const { ctx } = getCurrentInstance()
- console.log(ctx.$router.currentRoute.value) //当前路径
- //与以前this获取原型上东西一样
- //ctx.$parent 父组件
- // ctx.$nextTick 组件更新完毕
- // ctx.$store VueX
- }
- }
- </script>
描述:传送门组件提供一种简洁的方式可以指定它里面内容的父元素,允许我们控制`Teleport`的嵌套的内容在DOM中哪个父节点下呈现HTML,而不必求助于全局状态或者拆分为两个组件.
- <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>
-
-
- <script>
- import { defineComponent, ref } from "vue";
-
- export default defineComponent({
- name: 'ModelButton',
- setup() {
- const modelOpen = ref(false);
- return {
- modelOpen
- }
- }
- })
- </script>
-
- <style scoped>
- .model {
- position: absolute;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, .3);
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .model-body {
- width: 300px;
- height: 250px;
- background: #fff;
- }
- </style>
使用`Teleport`组件,通过props `to`属性指定该组件的渲染位置在body下,但该组件的状态`modelOpen`则是由vue内部组件控制.
Fragments
描述:Fragments作为vue3的新特性之一,允许一个组件可以有多个根节点。
- <template>
- <header>...</header>
- <main v-bind="$attrs">...</main>
- <footer>...</footer>
- </template>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。