vue3.0 Composition API(转载整理)

vue3.0 Composition API

  • 一、setup()
    • 1.接收props数据
    • 2.context
  • 二、reactive
    • 1、reactive和ref区别
  • 三、ref
  • 四、shallowReactive
  • 五、shallowRef
  • 六、triggerRef
  • 七、toRaw
  • 八、markRaw
  • 九、customRef
  • 十、isRef
  • 十一、toRefs
  • 十二、toRef
  • 十三、computed
  • 十四、watch
    • 1、监听指定的数据源
      • 1.1、监听reactive的数据变化
      • 1.2、监听ref类型的数据变化
      • 1.3、监听多个指定数据变化
      • 1.4、监听ref类型数据变化
      • 1.5、侦听复杂的嵌套对象
      • 1.6、清除监视
      • 1.7、清除无效的异步任务
  • 十五、watchEffect
    • 1、基础用法
    • 2、停止监听
    • 3、使 side effect 无效
  • 十六、shallowReactive
  • 十七、shallowRef
  • 十八、customRef
  • 十九、自定义Hook函数
  • 二十、readonly与shallowReadonly
  • 二十一、Template refs
  • 二十二、defineComponent
  • 二十三、getCurrentInstance
  • 二十四 、部分总结
  • 二十五、生命周期钩子
  • 26、简单对比vue2.0与vue3.0响应性
  • 27、新功能 Teleport
    • 1、Teleport 概念
    • 2、Teleport 的使用
  • 28、新特性 Suspense
  • 29、 片段(Fragment)
  • 30、更好的Tree-shaking
  • 31、受影响的 API
  • 32、变更
    • 1、slot 具名插槽语法
    • 2、自定义指令
    • 3、v-model 升级
    • 4、异步组件

一、setup()

setup()函数是vue3中专门新增的方法,可以理解为Composition Api的入口
setup 执行时机

export default defineComponent({
  beforeCreate() {
    console.log("----beforeCreate----");
  },
  created() {
    console.log("----created----");
  },
  setup() {
    console.log("----setup----");
  },
});

vue3.0 Composition API(转载整理)_第1张图片

1.接收props数据

export default {
  props: {
    msg: {
      type: String,
      default: () => {}
    }
  },
  setup(props) {
    console.log(props);
  }
}

setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。错误代码示例, 这段代码会让 props 不再支持响应式:

// demo.vue
export default defineComponent ({
  setup(props, context) {
    const { name } = props
    console.log(name)
  },
})

2.context

setup()的第二个参数是一个上下文对象,这个上下文对象大致包含了这些属性,注意:在setup()函数中无法访问this
setup中不能访问 Vue2 中最常用的this对象,所以context中就提供了this中最常用的三个属性:attrs、slot 和emit,分别对应 Vue2.x 中的 $ attr属性、slot插槽 和$ emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。

const MyComponent = {
  setup(props, context) {
    context.attrs
    context.slots
    context.parent
    context.root
    context.emit
    context.refs
  }
}

二、reactive

reactive是用来创建一个响应式对象,等价于2.x的Vue.observable
在 vue2.x 中, 定义数据都是在data中, 但是 Vue3.x 可以使用reactive和ref来进行数据定义。那么ref和reactive他们有什么区别呢?分别什么时候使用呢?说到这里,我又不得不提一下,看到很多网上不少文章说 (reactive用于处理对象的双向绑定,ref则处理 js 基础类型的双向绑定)。我其实不太赞同这样的说法,这样很容易初学者认为ref就能处理 js 基本类型, 比如ref只能是可以定义对象的双向绑定的,

setup() {
    const obj = ref({count:1, name:"张三"})
    setTimeout(() =>{
      obj.value.count = obj.value.count + 1
      obj.value.name = "李四"
    }, 1000)
    return{
      obj
    }
  }

reactive的使用:
vue3.0 Composition API(转载整理)_第2张图片
vue3.0 Composition API(转载整理)_第3张图片
上面的代码中,我们绑定到页面是通过user.name,user.age;这样写感觉很繁琐,我们能不能直接将user中的属性解构出来使用呢? 答案是不能直接对user进行结构, 这样会消除它的响应式, 这里就和上面我们说props不能使用 ES6 直接解构就呼应上了。那我们就想使用解构后的数据怎么办,解决办法就是使用toRefs。**toRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象。**具体使用方式如下:
没有用torefs

<template>
  <div>
    <p @click="count()">
      click
    </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>

使用torefs

<template>
  <div class="homePage">
    <p>{{ year }}</p>
    <p>姓名:{{ nickname }}</p>
    <p>年龄:{{ age }}</p>
  </div>
</template>

<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
  setup() {
    const year = ref(0);
    const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
    setInterval(() => {
      year.value++;
      user.age++;
    }, 1000);
    return {
      year,
      // 使用reRefs
      ...toRefs(user),
    };
  },
});
</script>

1、reactive和ref区别

vue3.0 Composition API(转载整理)_第4张图片

三、ref

基本语法
ref:ref也是vue3提供的实现数据响应式的方法,但是针对基本数据类型。其本质原理是reactive,将传过来的简单数据包装成一个对象,ref(xx)-> reactive({value:xx}),将值赋值在对象的value属性上,所以在js中使用ref中的值要加上.value,例如上述例子中的count2.value可以拿到值,但在template中可以直接{{count}},不需要.value,因为3.0帮我们做了处理。
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
    };
  }
});

vue3.0 Composition API(转载整理)_第5张图片
在template中访问ref创建的响应式数据

import { ref } from 'vue';
export default {
  setup () {
    const val = ref(1);
    console.log(val.value) // 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
    };
  }
};

vue3.0 Composition API(转载整理)_第6张图片
我们发现,这次的count值却没有发生改变,还是原始值1,是因为新创建的newCount替换并覆盖了之前的count值,取代了它的位置.
ref和reactive区别:
在模板中的ref数据默认添加.value,而reactive不会
原理:在解析中会去查找是否是ref类型,通过当前数据的__v_isRef的值来判断 true/false

四、shallowReactive

相比于reactive,shallowReactive是非递归监听,只监听数据第一层就是proxy包装的第一层的数据,第一层数据不变后面改变没用,但如果第一层改变整体都会重新渲染。


const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2,
  },
})

// 变更 state 的自有属性是响应式的
state.foo++

// ...但不会深层代理
isReactive(state.nested) // false
state.nested.bar++ // 非响应式

五、shallowRef

相比于ref,shallowRef监听的是第一层.value的变化。

六、triggerRef

state.value.a = "1"; triggerRef(state);  //这里只有state.value.a做出更新

七、toRaw

let b= toRaw(a) b===a //true 这里改变数据不会引起UI更新

八、markRaw

a = markRaw(a);

九、customRef

自定义ref,可以用来处理异步


function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    //这里处理接口请求
    return {
      get() {
        // get里面不能处理请求,不然会造成死循环
        track() //告诉VUE这个数据是要追踪的
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger() //告诉VUE触发UI界面更新
        }, delay)
      }
    }
  })
}

十、isRef

用来判断某个值是否为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

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>

十二、toRef

概念:为源响应式对象上的某个属性创建一个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

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

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。看下面这个案例,会实时监听count值的变化,

watch(source, callback, [options])

参数说明:

  • source: 可以支持 string,Object,Function,Array; 用于指定要侦听的响应式变量
  • callback: 执行的回调函数
  • options:支持 deep、immediate 和 flush 选项。

import { ref, watch } from 'vue';
export default {
  setup () {
    const count = ref(1);
    watch(()=>{
      console.log(count.value, 'value');
    })
    setInterval(()=>{
      count.value++;
    },1000);

    return {
      count,
    };
  }
};

1、监听指定的数据源

1.1、监听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
    };
  }
};

1.2、监听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
    };
  }
};

1.3、监听多个指定数据变化

监听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
    };
  }
};

1.4、监听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';
    }, );

    return {
      count,
      msg
    };
  }
};

1.5、侦听复杂的嵌套对象


const state = reactive({
  room: {
    id: 100,
    attrs: {
      size: "140平方米",
      type: "三室两厅",
    },
  },
});
watch(
  () => state.room,
  (newType, oldType) => {
    console.log("新值:", newType, "老值:", oldType);
  },
  { deep: true }
);

如果不使用第三个参数deep:true, 是无法监听到数据变化的。前面我们提到,默认情况下,watch 是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?其实使用也很简单, 给第三个参数中设置immediate: true即可。关于flush配置,还在学习,后期会补充

1.6、清除监视

我们在组件中创建的watch监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值,操作如下:



const stopWatchRoom = watch(() => state.room, (newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
}, {deep:true});

setTimeout(()=>{
  // 停止监听
  stopWatchRoom()
}, 3000)

1.7、清除无效的异步任务

有时候watch()监视的值发生了变化,我们期望清除无效的异步任务,此时watch回调函数中提供了cleanup registrator function来执行清除工作
场景:
1.watch被重复执行了
2.watch被强制stop()

十五、watchEffect

vue3中新增的api,用于属性监听.
与watch有什么不同?

  • watchEffect不需要指定监听属性,可以自动收集依赖,只要我们回调中引用了响应式的属性,那么这些属性变更的时候,这个回调都会执行,而watch只能监听指定的属性而做出变更(v3中可以同时监听多个)
  • watch可以获取到新值和旧值,而watchEffect获取不到
  • watchEffect会在组件初始化的时候就会执行一次与computed同理,而收集到的依赖变化后,这个回调才会执行,而watch不需要,除非设置了指定参数。

1、基础用法

// 例子1 基础
import { watchEffect, ref } from 'vue'
setup () {
  const userID = ref(0)
  watchEffect(() => console.log(userID))
  setTimeout(() => {
    userID.value = 1
  }, 1000)

/*
      * LOG
      * 0 
      * 1
    */

  return {
    userID
  }
}

// 例子2
import { defineComponent, ref, reactive, toRefs, watchEffect } from "vue";
export default defineComponent({
  setup() {
    const state = reactive({ nickname: "xiaofan", age: 20 });
    let year = ref(0)
    setInterval(() =>{
      state.age++
      year.value++
    },1000)
    watchEffect(() => {
      console.log(state);
      console.log(year);
    });
    return {
      ...toRefs(state)
    }
  },
});

执行例子2结果首先打印一次state和year值;然后每隔一秒,打印state和year值。从上面的代码可以看出, 并没有像watch一样需要先传入依赖,watchEffect会自动收集依赖, 只要指定一个回调函数。在组件初始化时, 会先执行一次来收集依赖, 然后当收集到的依赖中数据发生变化时, 就会再次执行回调函数。所以总结对比如下:

  1. watchEffect 不需要手动传入依赖
  2. watchEffect 会先执行一次用来自动收集依赖
  3. watchEffect 无法获取到变化前的值, 只能获取变化后的值

2、停止监听

如果watchEffect是在setup或者生命周期里面注册的话,在取消挂在的时候会自动停止。


//停止监听

const stop = watchEffect(() => {
/* ... */
})

// later
stop()

3、使 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()
  })
})

十六、shallowReactive

概念:只处理对象最外层属性的响应式(也就是浅响应式),所以最外层属性发生改变,更新视图,其他层属性改变,视图不会更新.
场景:如果一个对象的数据结构比较深,但变化只是最外层属性.


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 }
  }
}

十七、shallowRef

概念:只处理了value的响应式,不进行对象的reactive处理.

场景:如果有一个对象数据,后面会产生新的对象替换.

mport { shallowRef } from 'vue'

export default {
  setup () {
    const m1 = shallowRef({a: 1, b: {c: 2}})
    const update = () => {
      m1.value.a += 1
    }

    return {
      m1,
      update
    }
  }
}

十八、customRef

创建一个自定义的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函数

自定义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>

自定义Hook例子2 加减

// useCount.ts
import { ref, Ref, computed } from "vue";

type CountResultProps = {
  count: Ref<number>;
  multiple: Ref<number>;
  increase: (delta?: number) => void;
  decrease: (delta?: number) => void;
};

export default function useCount(initValue = 1): CountResultProps {
  const count = ref(initValue);
  
  const increase = (delta?: number): void => {
    if (typeof delta !== "undefined") {
      count.value += delta;
    } else {
      count.value += 1;
    }
  };
  const multiple = computed(() => count.value * 2);
  const decrease = (delta?: number): void => {
    if (typeof delta !== "undefined") {
      count.value -= delta;
    } else {
      count.value -= 1;
    }
  };

  return {
    count,
    multiple,
    increase,
    decrease,
  };
}

使用


<template>
  <p>count: {{ count }}</p>
  <p>倍数:{{ multiple }}</p>
<div>
  <button @click="increase()">1</button>
  <button @click="decrease()">减一</button>
</div>
</template>

<script lang="ts">
  import useCount from "../hooks/useCount";
  setup() {
    const { count, multiple, increase, decrease } = useCount(10);
    return {
      count,
      multiple,
      increase,
      decrease,
    };
  },
</script>

二十、readonly与shallowReadonly

readonly:

  • 深度只读数据
  • 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
  • 只读代理是深层的:访问的任何嵌套 property 也是只读的。
  • shallowReadonly
  • 浅只读数据
  • 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
  • 应用场景: 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除

二十一、Template refs

通过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>

二十二、defineComponent

这个函数不是必须的,除非你想完美结合TypeScript提供的类型推断来进行项目开发
场景:这个函数仅仅提供了类型推断,能为setup()函数中的props提供完整的类型推断.

import { defineComponent} from 'vue'

export default defineComponent({
  props: {
    foo: String
  },
  setup(props) {
    props.foo // <- type: string
  }
})

二十三、getCurrentInstance

描述:可以获取当前组件的实例,然后通过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>

二十四 、部分总结

在逻辑组织和逻辑复用方面,Composition API是优于Options API,因为Composition API几乎是函数,会有更好的类型推断,Composition API对 tree-shaking 友好,代码也更容易压缩,Composition API中见不到this的使用,减少了this指向不明的情况,说到这里我们对Vue3.0的Composition-Api有了一个大致的了解,可以看出composition api他更灵活,有利于写出高内聚、低耦合代码。

二十五、生命周期钩子

vue3.0 Composition API(转载整理)_第7张图片
从图中我们可以看到 Vue3.0 新增了setup,这个在前面我们也详细说了, 然后是将 Vue2.x 中的beforeDestroy名称变更成beforeUnmount; destroyed 表更为 unmounted,作者说这么变更纯粹是为了更加语义化,因为一个组件是一个mount和unmount的过程。其他 Vue2 中的生命周期仍然保留。上边生命周期图中并没包含全部的生命周期钩子, 还有其他的几个, 全部生命周期钩子如图所示:
vue3.0 Composition API(转载整理)_第8张图片

我们可以看到beforeCreate和created被setup替换了(但是 Vue3 中你仍然可以使用, 因为 Vue3 是向下兼容的, 也就是你实际使用的是 vue2 的)。其次,钩子命名都增加了on; Vue3.x 还新增用于调试的钩子函数onRenderTriggered和onRenderTricked下面我们简单使用几个钩子, 方便大家学习如何使用,Vue3.x 中的钩子是需要从 vue 中导入的:

import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated,
onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked,
onRenderTriggered } from "vue"; 
export default defineComponent({
  // beforeCreate和created是vue2的 
  beforeCreate() {
    console.log("------beforeCreate-----"); }, 
  created() {
    console.log("------created-----"); 
  }, 
  setup() {
    console.log("------setup-----");
    // vue3.x生命周期写在setup中 
    onBeforeMount(() => {
      console.log("------onBeforeMount-----"); 
    }); 
    onMounted(() => {
      console.log("------onMounted-----"); 
    });
    // 调试哪些数据发生了变化
    onRenderTriggered((event) =>{
      console.log("------onRenderTriggered-----",event);
    })
  },
 });

26、简单对比vue2.0与vue3.0响应性

其实在 Vue3.x 还没有发布 bate 的时候, 很火的一个话题就是Vue3.x 将使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty。没有无缘无故的爱,也没有无缘无故的恨。为何要将Object.defineProperty换掉呢,咋们可以简单聊一下。我刚上手 Vue2.x 的时候就经常遇到一个问题,数据更新了啊,为何页面不更新呢?什么时候用 s e t 更 新 , 什 么 时 候 用 set更新,什么时候用 setforceUpdate强制更新,你是否也一度陷入困境。后来的学习过程中开始接触源码,才知道一切的根源都是 Object.defineProperty。对这块想要深入了解的小伙伴可以看这篇文章 为什么 Vue3.0 不再使用 defineProperty 实现数据监听?要详细解释又是一篇文章,这里就简单对比一下Object.defineProperty 与 Proxy

  1. Object.defineProperty只能劫持对象的属性, 而 Proxy 是直接代理对象由于Object.defineProperty只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是 Proxy 直接代理对象, 不需要遍历操作
  2. Object.defineProperty对新增属性需要手动进行Observe,因为Object.defineProperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象, 对其新增属性再次使用Object.defineProperty进行劫持。也就是 Vue2.x 中给数组和对象新增属性时,需要使用$set才能保证新增的属性也是响应式的, $set内部也是通过调用Object.defineProperty去处理的。

27、新功能 Teleport

Teleport 是 Vue3.x 新推出的功能, 没听过这个词的小伙伴可能会感到陌生;翻译过来是传送的意思,可能还是觉得不知所以,没事下边我就给大家形象的描述一下

1、Teleport 概念

Teleport 是什么呢?
Teleport 就像是哆啦 A 梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。有了这个认识,我们再来看一下为什么需要用到 Teleport 的特性呢,看一个小例子:在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。此时就需要 Teleport 上场,我们可以用包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。接下来就举个小例子,看看 Teleport 的使用方式

2、Teleport 的使用

我们希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:


<body>
<div id="app"></div>
<div id="dialog"></div>
</body>

定义一个Dialog组件Dialog.vue, 留意 to 属性, 与上面的id选择器一致:

<template>
<teleport to="#dialog">
<div class="dialog">
<div class="dialog_wrapper">
<div class="dialog_header" v-if="title">
<slot name="header">
<span>{{ title }}</span>
</slot>
</div>
</div>
<div class="dialog_content">
<slot></slot>
</div>
<div class="dialog_footer">
<slot name="footer"></slot>
</div>
</div>
</teleport>
</template>

最后在一个子组件Header.vue中使用Dialog组件, 这里主要演示 Teleport 的使用,不相关的代码就省略了。

// header
<div class="header">
    ...
<navbar />
<Dialog v-if="dialogVisible"></Dialog>
</div>

渲染如下:
vue3.0 Composition API(转载整理)_第9张图片

图片. png 可以看到,我们使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与

同级,也就是在 body 下,但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制.

28、新特性 Suspense

Suspense是 Vue3.x 中新增的特性, 那它有什么用呢?别急,我们通过 Vue2.x 中的一些场景来认识它的作用。Vue2.x 中应该经常遇到这样的场景:


<template>
<div>
<div v-if="!loading">
        ...
</div>
<div v-if="loading">
        加载中...
</div>
</div>
</template>

在前后端交互获取数据时, 是一个异步过程,一般我们都会提供一个加载中的动画,当数据返回时配合v-if来控制数据显示。如果你使用过vue-async-manager这个插件来完成上面的需求, 你对Suspense可能不会陌生,Vue3.x 感觉就是参考了vue-async-manager. Vue3.x 新出的内置组件Suspense, 它提供两个template slot, 刚开始会渲染一个 fallback 状态下的内容, 直到到达某个条件后才会渲染 default 状态的正式内容, 通过使用Suspense组件进行展示异步渲染就更加的简单。如果使用 Suspense, 要返回一个 promise。

  <Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
// asyncComponent.vue
<template>
  <div>
    <h4>这个是一个异步加载数据</h4>
    <p>用户名:{{user.nickname}}</p>
    <p>年龄:{{user.age}}</p>
  </div>
</template>

<script>
import { defineComponent } from "vue"
import axios from "axios"
export default defineComponent({
  setup(){
    const rawData = await axios.get("http://xxx.xinp.cn/user")
    return {
      user: rawData.data
    }
  }
})
</script>

从上面代码来看,Suspense 只是一个带插槽的组件,只是它的插槽指定了defaultfallback 两种状态。

29、 片段(Fragment)

在 Vue2.x 中, template中只允许有一个根节点:


<template>
<div>
<span></span>
<span></span>
</div>
</template>

但是在 Vue3.x 中,你可以直接写多个根节点:


<template>
<span></span>
<span></span>
</template>

30、更好的Tree-shaking

Vue3.x 在考虑到 tree-shaking的基础上重构了全局和内部 API, 表现结果就是现在的全局 API 需要通过 ES Module的引用方式进行具名引用, 比如在 Vue2.x 中,我们要使用 nextTick:


// vue2.x
import Vue from "vue"

Vue.nextTick(()=>{
    ...
})

Vue.nextTick() 是一个从 Vue 对象直接暴露出来的全局 API,其实 $nextTick() 只是 Vue.nextTick() 的一个简易包装,只是为了方便而把后者的回调函数的 this 绑定到了当前的实例。虽然我们借助webpack的tree-shaking, 但是不管我们实际上是否使用Vue.nextTick(), 最终都会进入我们的生产代码, 因为 Vue 实例是作为单个对象导出的, 打包器无法坚持出代码总使用了对象的哪些属性。在 Vue3.x 中改写成这样:

import { nextTick } from "vue"

nextTick(() =>{
    ...
})

31、受影响的 API

这是一个比较大的变化, 因为以前的全局 API 现在只能通过具名导入,这一更改会对以下 API 有影响:

  • Vue.nextTick
  • Vue.observable(用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile(仅限完整版本时可用)
  • Vue.set(仅在 2.x 兼容版本中可用)
  • Vue.delete(与上同)

32、变更

1、slot 具名插槽语法

在 Vue2.x 中, 具名插槽的写法:

<!--  子组件中:-->
<slot name="title"></slot>
<!-- 父组件中: -->
<template slot="title">
<h1>歌曲:成都</h1>
<template>

如果我们要在 **slot 上面绑定数据,可以使用作用域插槽,**实现如下:


// 子组件
<slot name="content" :data="data"></slot>
export default {
data(){
  return{
    data:["走过来人来人往","不喜欢也得欣赏","陪伴是最长情的告白"]
    }
  }
<!-- 父组件中使用 -->
<template slot="content" slot-scope="scoped">
<div v-for="item in scoped.data">{{item}}</div>
<template>

在 Vue2.x 中具名插槽和作用域插槽分别使用slotslot-scope来实现, 在 Vue3.0 中将slotslot-scope进行了合并同意使用。Vue3.0 中v-slot:

<!-- 父组件中使用 -->
<template v-slot:content="scoped">
<div v-for="item in scoped.data">{{item}}</div>
</template>

<!-- 也可以简写成:-->
<template #content="{data}">
<div v-for="item in data">{{item}}</div>
</template>

2、自定义指令

首先回顾一下 Vue 2 中实现一个自定义指令:


// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
// 聚焦元素
    el.focus()
  }
})

在 Vue 2 中, 自定义指令通过以下几个可选钩子创建:

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。
    Vue 3 中对自定义指令的 API 进行了更加语义化的修改, 就如组件生命周期变更一样, 都是为了更好的语义化, 变更如下:
    vue3.0 Composition API(转载整理)_第10张图片
    所以在 Vue3 中, 可以这样来自定义指令:
const { createApp } from "vue"

const app = createApp({})
app.directive('focus', {
    mounted(el) {
        el.focus()
    }
})

然后可以在模板中任何元素上使用新的 v-focus指令, 如下:

<input v-focus />

3、v-model 升级

在使用 Vue 3 之前就了解到 v-model 发生了很大的变化, 使用过了之后才真正的 get 到这些变化, 我们先纵观一下发生了哪些变化, 然后再针对的说一下如何使用:

  • 变更:在自定义组件上使用v-model时, 属性以及事件的默认名称变了
  • 变更:v-bind的.sync修饰符在 Vue 3 中又被去掉了, 合并到了v-model里
  • 新增:同一组件可以同时设置多个 v-model
  • 新增:开发者可以自定义 v-model修饰符
    在 Vue2 中, 在组件上使用 v-model其实就相当于传递了value属性, 并触发了input事件
<!-- Vue 2 -->
<search-input v-model="searchValue"><search-input>

<!-- 相当于 -->
<search-input :value="searchValue" @input="searchValue=$event"><search-input>

这时v-model只能绑定在组件的value属性上,那我们就不开心了, 我们就想给自己的组件用一个别的属性,并且我们不想通过触发input来更新值,在.sync出来之前,Vue 2 中这样实现:

// 子组件:searchInput.vue
export default {
model:{
prop: 'search',
event:'change'
    }
}

修改后, searchInput 组件使用v-model就相当于这样:

<search-input v-model="searchValue"><search-input>
<!-- 相当于 -->
<search-input :search="searchValue" @change="searchValue=$event"><search-input>

但是在实际开发中,有些场景我们可能需要对一个 prop 进行 “双向绑定”, 这里以最常见的 modal 为例子:modal 挺合适属性双向绑定的,外部可以控制组件的visible显示或者隐藏,组件内部关闭可以控制 visible属性隐藏,同时 visible 属性同步传输到外部。组件内部, 当我们关闭modal时, 在子组件中以 update:PropName 模式触发事件:

this.$emit('update:visible', false)

然后在父组件中可以监听这个事件进行数据更新:

<modal :visible="isVisible" @update:visible="isVisible = $event"></modal>

此时我们也可以使用v-bind.sync来简化实现:

<modal :visible.sync="isVisible"></modal>

上面回顾了 Vue2 中v-model实现以及组件属性的双向绑定,那么在 Vue 3 中应该怎样实现的呢?在 Vue3 中, 在自定义组件上使用v-model, 相当于传递一个modelValue 属性, 同时触发一个update:modelValue事件:

<modal v-model="isVisible"></modal>
<!-- 相当于 -->
<modal :modelValue="isVisible" @update:modelValue="isVisible = $event"></modal>

如果要绑定属性名, 只需要给v-model传递一个参数就行, 同时可以绑定多个v-model:

<modal v-model:visible="isVisible" v-model:content="content"></modal>

<!-- 相当于 -->
<modal
:visible="isVisible"
:content="content"
    @update:visible="isVisible"
    @update:content="content"
/>

不知道你有没有发现,这个写法完全没有.async什么事儿了, 所以啊,Vue 3 中又抛弃了.async写法, 统一使用v-model

4、异步组件

Vue3 中 使用 defineAsyncComponent 定义异步组件,配置选项 component 替换为 loader ,Loader 函数本身不再接收 resolve 和 reject 参数,且必须返回一个 Promise,用法如下:


<template>
  <!-- 异步组件的使用 -->
  <AsyncPage />
</tempate>

<script>
import { defineAsyncComponent } from "vue";

export default {
  components: {
    // 无配置项异步组件
    AsyncPage: defineAsyncComponent(() => import("./NextPage.vue")),
    
    // 有配置项异步组件
    AsyncPageWithOptions: defineAsyncComponent({
      loader: () => import(".NextPage.vue"),
      delay: 200,
      timeout: 3000,
      errorComponent: () => import("./ErrorComponent.vue"),
      loadingComponent: () => import("./LoadingComponent.vue"),
    })
  },
}
</script>

你可能感兴趣的:(vue)