10分钟带你上手 Vue3

1,140 阅读7分钟

前言

Vue3 自2020年9月份发布之后,截至目前也有半年的时间了,一直以来,刷过文档,看过文章,窥过视频,就是没有撸过代码 ~ 那么 ~ 今天 ~ 现在 ~ 在这里 ~ 撸一把

针对 Vue3 和 Vue2 的差异,官网也给到了一些说明,最直接能体现的就是编码的风格上,把Vue2中的零散逻辑改成新提供的CompositionAPI组合在一起来维护,并且还可以将单独的逻辑的功能拆分成单独的文件,完美的解决了Vue2中mixin混入的缺点(命名冲突,逻辑重用等问题)

进入正题 : Composition API

Composition Api

setup

setup 是 Vue3 新增的一个选项,是Composition API 的入口函数,它的执行时机是在 beforCreate 之前,实践是检验真理的唯一标准

export default {
	 beforeCreate () {
	    console.log(`----beforeCreate----`);
	 },
	 created () {
	    console.log(`----created----`);
	 },
	setup () {
	    console.log(`----setup----`);
	    // 这些跟 vue2 的生命周期差不多,就是写法上加了前缀 on 
	    onBeforeMount(() => console.log(`----onBeforeMount----`));
	    onMounted(() => console.log(`----onMounted----`));
	    onBeforeUpdate(() => console.log(`----onBeforeUpdate----`));
	    onUpdated(() => console.log(`----onUpdated----`));
	    onBeforeUnmount(() => console.log(`----onBeforeMount----`));
	    onUnmounted(() => console.log(`----onUnmounted----`));
	}
}

在这里插入图片描述 由于在执行setup 时尚未创建组件实例,因此在 setup 选项中没有 this 。

注,Vue3 的声明周期与Vue2 的使用上有点小小的差异与改变,在 Vue3 里需放在 setup 函数里,并且有个前缀 on , 然后组件实例销毁前后生命周期变的更语义化,onBeforeUnmount , onUnmounted

setup 参数

setup 接收两个参数,分别是 props,context , 组件传入的属性和上下文,这里的props是响应式的,由于是响应式的,所以不可以使用 ES6 解构,解构会影响它的响应式

那么context有什么作用呢 ?

在 setup 执行的时候并没有 this 对象,所以 context 就提供了 Vue 常用的三个属性,attrs , slot 和 emit , 分别对应的 Vue2 中的 attr属性slot插槽和attr 属性 slot 插槽和 emit 发布事件,并且这几个属性都是自动同步最新的值

默认的 props 和 context

在这里插入图片描述

ref , toRefs 和 reactive

在Vue2 中初始化的响应式数据都放在 data 中 , 但是 Vue3 做了一个小小的变动,响应式数据通过 ref 和 reactive 来声明

有人说: ref 一般用于声明基本数据类型,reactive 一般用于声明引用数据类型,其实 ref 也是可以声明对象的响应式绑定的,不信小伙伴可以试试,反正我是试过了,好了,我们来看看它们具体怎么使用的

<template>
  <div class="about">
    <h3>我是 About</h3>
    <p>
      {{ refValue }}
      <br />
      原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
      <br />
      toRefs形式(解构):{{ name }} : {{ age }}
    </p>
</template>

<script>
import { ref, reactive } from "vue";
export default {
  setup () {
    // 声明响应式变量
    const refValue = ref("---");
    const reactiveValue = reactive({ name: "小娜", age: 17 });
    // 返回
    return {
      refValue,
      reactiveValue,
      ...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
    };
  },
};
</script>

在这里插入图片描述

相信小伙伴们也看到了 toRefs , 其实它就是将对象解构,然后将结构后的 key return 出去,给到组件实例使用 ;

通过上边的一个实例代码,相信童鞋们对Composition API 有个大概的认识,脱离开 Vue2 的 options API 来讲,Composition API 就是按需引入需要使用的 vue 属性,通过入口函数 setup 声明,然后进行一系列操作(生命周期,监听计算,逻辑处理等)最后 return 出去,给到组件渲染。

这里需要注意一点,凡是响应式的数据,组件需要使用,必须 return 出去

好,我们按照上边的逻辑,分别实践一下生命周期,监听计算,逻辑处理等操作

生命周期

<template>
  <div class="about">
    <h3>我是 About</h3>
    <p>
      {{ refValue }}
      <br />
      原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
      <br />
      toRefs形式(解构):{{ name }} : {{ age }}
    </p>
  </div>
</template>

<script>
import {
  ref, reactive, toRefs, onMounted, onBeforeUpdate, onUpdated, onRenderTriggered,
} from "vue";

export default {
  setup (props, context) {
    onBeforeUpdate(() => console.log(`----onBeforeUpdate----`));
    onUpdated(() => console.log(`----onUpdated----`));
    // 新增生命周期:监听哪些数据发生变化
    onRenderTriggered((event) =>
      console.log(`----onRenderTriggered----`, event)
    );

    const refValue = ref("---");
    const reactiveValue = reactive({ name: "小娜", age: 17 });

    // 赋值声明变量
    const D = new Date();
    onMounted(() => {
      console.log("执行mounted")
      refValue.value = `${D.getFullYear()}${D.getMonth()}${D.getDay()}日`;
      reactiveValue.name = "Hisen";
      reactiveValue.age++;
    });

    return {
      refValue,
      reactiveValue,
      ...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
    };
  },
};
</script>

在这里插入图片描述 在这里插入图片描述

从代码的角度,可以知道,首先进入 setup 函数,然后执行生命周期,到 mounted 时对变量声明进行赋值,会触发 Vue3 新增的生命周期 onRenderTriggered , 这个生命周期主要用来监听哪些数据发生了变化,最后触发 update 更新前后生命周期,此时,如若我们离开当前组件,则会触发组件销毁前后声明周期,跟 Vue2 几乎可以说是一样的顺序;具体的生命周期可参考下图

在这里插入图片描述

监听与计算

这两个属性,可以说写过 vue 的童鞋都不陌生吧,反而还会用的很多,我们来看看Vue3 中式怎么使用它们的

<template>
  <div class="about">
    <h3>我是 About</h3>
    <p>
      {{ refValue }}
      <br />
      原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
      <br />
      toRefs形式(解构):{{ name }} : {{ age }}
    </p>

    计算属性值:{{computedValue}}

  </div>
</template>

<script>
import {
  ref, reactive, watch, toRefs, onMounted, computed
} from "vue";

export default {
  setup () {
    const refValue = ref("---");
    const reactiveValue = reactive({ name: "小娜", age: 17 });

    // 复制声明变量
    const D = new Date();
    onMounted(() => {
      refValue.value = `${D.getFullYear()}${D.getMonth()}${D.getDay()}日`;
      reactiveValue.name = "Hisen";
      reactiveValue.age++;
    });

    // 多变量监听,注意基本类型与引用类型的不同写法
    // 第三个参数是 options 支持 deep、immediate 和 flush 
    watch(
      [refValue, () => reactiveValue.age],
      ([n_ref, n_rt], [o_ref, o_rt]) => {
        console.log("refValue:new", n_ref, "old", o_ref);
        console.log("reactiveValue.key: new", n_rt, "old", o_rt);
      }, {}
    );

    // 计算属性
    let computedValue = computed({
      get: () => reactiveValue.age + 10,
      set: val => {
        // 创建一个可写的 ref 对象
        console.log(val)
      }
    })

    return {
      refValue,
      reactiveValue,
      ...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
      computedValue
    };
  },
};
</script>

在这里插入图片描述 在这里插入图片描述

同样,我们从代码的逻辑上来看,效果是实现了,细心的童鞋可能会发现 watch 写法上略有一丝复杂,其实并不是,我这里直接写了一个多变量同时监听,然后多个 watch 执行,当然也可以写成单个的监听,具体的用法是这样的

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

还有一个小小的点,需要大家注意一下,针对 ref 声明和 reactive 声明,watch 传递的第一个参数是有要求的,写法上需要注意 ref 声明的变量,可以直接写变量名,而 reactive 声明的变量则需要使用箭头函数,类似与这样

const a = ref(); // watch(a,,() => {},{})
const b = reactive() // watch(() => b,() => {},{})

多变量同时监听如下这样

// 根据不同类型的声明,第一个参数,使用不同的方式
watch([one,two],([new_one,new_two],[old_one,old_two]) => { ... },{})

计算属性是直接创建了一个可读写的新变量(依赖已声明的变量)

逻辑处理这块儿,不没有太多要分享的,其实会 Vue2 的童鞋看到这里就可以大概的明白了,基本就是在 setup 函数里做所有操作,最终返回出去响应式变量 。

组件通信

这个也许是众多童鞋比较在意的事情,Vue3 中如何进行组件通信,参数传递,当然了,Vuex 依然是通用的,那么在小型项目中,简单的通信如何做 ? 通过一个小实例了解一下

父组件

<template>
  <div class="about">
    <h3>我是 About</h3>
    <p>
      {{ refValue }}
      <br />
      原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
      <br />
      toRefs形式(解构):{{ name }} : {{ age }}
    </p>
    <hr />
    <button @click="addList">Push list.lengrth++</button>
    <br />
    <span v-for="i in reactiveValueArray"
          :key="i">{{ i }}, </span>
  </div>
  <hr />
  <h3>About 子组件</h3>
  <Detail :attr="reactiveValue"
          @addList="addAgeFun" />
</template>

<script>
import {
  ref, reactive, toRefs, provide, onMounted
} from "vue";
// 导入子组件
import Detail from "./Detail.vue";
export default {
  // 注册子组件
  components: { Detail },
  setup () {
    const refValue = ref("---");
    const reactiveValue = reactive({ name: "小娜", age: 17 });
    const reactiveValueArray = reactive([1, 2, 3, 4, 5]);
    // 复制声明变量
    const D = new Date();
    onMounted(() => {
      refValue.value = `${D.getFullYear()}${D.getMonth()}${D.getDay()}日`;
      reactiveValue.name = "Hisen";
      reactiveValue.age++;
    });
    // 按钮点击事件  
    const addList = () => {
      reactiveValueArray.push(reactiveValueArray.length + 1);
    };
    // 父组件监听子组件 emit 
    const addAgeFun = (num) => {
      reactiveValueArray.push(num._value);
    }
    // provide / inject 提供注入的方式传参  
    provide("provideValue", {
      title: 'from about comp',
      value: refValue
    })
    return {
      refValue,
      reactiveValue,
      reactiveValueArray,
      ...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
      addList,
      addAgeFun,
    };
  },
};
</script>

子组件

<template>
  <div>
    我是 Detail , About 的子组件
    <p>props 接收参数: {{name || "From App link-router"}} {{age || "no attrs"}}</p>
    <p>
      <!-- inject_value 可以用 toRefs 解构 -->
      provie/inject 接收参数:
      静态:{{inject_value.title}}
      动态:{{inject_value.value._value}}
    </p>
    <input v-model="txt"
           type="text">
    <button @click="add">添加到父组件 num 数组</button>
    <hr>
    <p>计算属性值 {{computedValue}}</p>
  </div>
</template>
 
<script> 
import { ref, reactive, toRefs, nextTick, inject, computed } from 'vue';
export default {
  // props 接收
  props: {
    attr: Object
  },
  setup (props, context) {
    const txt = ref();
    // 获取父组件传递过来的 attr
    const attrs = reactive(props.attr);
    // 注入的方式接收参数  
    const inject_value = inject("provideValue");
    // 上下文包含 emit , slot , props 
    // 利用 emit 发布向父组件传参 
    const add = () => {
      nextTick(() => {
        txt.value && context.emit("addList", txt);
        txt.value = ""
      })
    }
    // 计算属性
    let computedValue = computed({
      get: () => txt.value,
      set: val => {
        // 创建一个可写的 ref 对象
        console.log(val)
      }
    })
    return {
      ...toRefs(attrs),
      add,
      txt,
      inject_value,
      computedValue
    }
  }
};
</script>

在这里插入图片描述 props : 父组件传递来的参数,跟 Vue2 中一样,需要在子组件用 props 去接收,但是Vue3 的所有操作,均在setup 函数里,所以就用到了我们上边说的,setup 函数接收两个参数,props/context , 在这里就起到了质的作用

其实简单点儿,就是在 setup 函数声明变量接收props传递过来的参数,再 return 出去。

细心的小伙伴可能也看到了另外一个东西,provide / inject 在 Vue2 中相信有的童鞋已经用过了,跨级传递参数,注入的方式,用法上也大同小异,只是都写在了 setup 函数里,子组件接收也是同理,声明接收 inject 函数的返回值,即父组件传递过来的参数,再 return 出去,即可。

通过上边的两种形式,可以看出来,父子组件之间的传递,那么子组件如何向父组件传递数据呢,跟Vue2 基本相同,只是写法上略有差异,同样的发布订阅,在Vue3中,emit 在 setup 函数的第二个参数context 里,所以当子组件需要向父组件传递参数的时候,只用触发 context.emit , 用法参考 Vue2 中的$emit , 父组件同样的监听方式,只是方法定义在了 steup 函数中 , 记着需要 return 出去。

注:再次提醒,凡是组件需要使用到的变量和方法,均需要在 setup 函数的最后 return 出去, 否则找不到 !

mixin

纵观 Vue2 的 mixin 混入,说的简单点就是代码合并,存在优先级,变量/方法命名冲突,不语义化等问题,在 Vue3 中,有人说是重用逻辑抽取,纯函数编程,也有人说是自定义 hook , 其实都是对的, 自定义 hook 本身就是重复逻辑的封装,方便处处使用,那我们来实践一下

<template>
  <div class="about">
    <h3>我是 About</h3>
    <h3>Mixin 复用代码</h3>
    <input type="number"
           v-model="number">
    mixin 通过复用方法将输入的数字千分位
    <br>
    <h4>{{mixinValue}}</h4>
    <button @click="mixinFunc(number)">变化</button>
  </div>
</template>
<script>
import { ref } from "vue"
// 导入mixin 
import Mixin from "./useMixin.js"
export default {
  setup () {
    const number = ref();
    // 获取重用属性与方法
    const { mixinValue, mixinFunc } = Mixin();
    return {
      number,
      mixinValue,
      mixinFunc,
    };
  },
};
</script> 

useMixin.js 提供一个将普通数字转为为千分位

import { ref } from "vue"
export default function () {
  const mixinValue = ref();
  const mixinFunc = (n) => {
    mixinValue.value = n.replace(/\d{1,3}(?=(\d{3})+$)/g, function (s) {
      return s + ','
    })
  }
  return { mixinValue, mixinFunc }
}

在这里插入图片描述

Vue3 中的 mixin 基本上就是单独定义通用逻辑与方法,甚至说是自定义的一个复用hook,可以拿到其它任何组件使用,与此同时也解决了Vue2中的变量命名冲突,不语义化 等劣势,相比之下,我可能更喜欢 Vue3 的这种写法,更清晰明了,更好维护

Vue Router

路由是我们开发过程中必不可少的神器,那Vue官方本身提供的router也是很友好的,在这里 Vue3 跟Vue2 的使用上稍微有点不一样,其实说到底就是 setup 里没有 this , 在 Vue3 中该怎么用而已,我们看一个实例就明白了

<template>
  <div class="about">
    <h3>我是 About</h3>
    <h4>Vue Router</h4>
    <router-link to="/copy_about"> router-link 跳转方式 About 2 号</router-link>
    <button @click="routerClick">routerClick</button>
  </div>
</template>

<script>
// 导入 router 
import { useRoute, useRouter } from 'vue-router'
export default {
  setup () { 
    const router = useRouter();
    const route = useRoute();
    const routerClick = () => {
      // router 的两种方法使用 
      // router.push({ path: "/", query: { params: '123456789' } });
      router.push({ name: "Home", params: { params: '123456789' } });
    }

    return {
      routerClick
    };
  },
};
</script> 

在这里插入图片描述

看到代码,也许很多童鞋就已经明白了,就是按需引入,哪个组件里边需要使用到路由,就单独引入,主要有两个模块 router , route 路由实例和路由对象,这个熟悉Vue2 路由的话,应该是不陌生的,那使用上也是一样的,直接调用路由实例的方法,进行跳转,传参等,截张图来看一下 router 和 route 都有些啥吧

在这里插入图片描述

Vuex

状态管理,Vue的标配,Vue3 中除了使用上的不同,其它可以说是一毛一样,话不多说,直接上代码

<template>
  <div class="about">
    <h3>我是 About</h3>
    <h4>Vuex</h4>
    vuex store state count 值 {{storeNum}}
    <button @click="vuexClick">commit / dispatch count++</button>
  </div>
</template>

<script>
import { computed } from "vue"
import { useStore } from "vuex"
export default {
  setup () {
    // vuex 
    const store = useStore();
    const storeNum = computed(() => {
      return store.state.count;
    })
    const vuexClick = () => {
      store.commit("addCount", { attr: "传参,mutations" });
      store.dispatch("asyncAddCount", { attr: "传参,actions" });
    }

    return {
      storeNum,
      vuexClick
    };
  },
};
</script> 

store 代码 也可以模块分发

import { createStore } from "vuex";

export default createStore({
  state: {
    count: 1
  },
  mutations: {
    addCount (state, obj) {
      console.log(state, obj)
      state.count++;
    }
  },
  actions: {
    asyncAddCount (context, obj) {
      console.log(context, obj)
      context.commit("addCount", obj)
    }
  },
  modules: {},
});

在这里插入图片描述

看上去跟 vue2 的 Vuex 非常类似,直接一个 useStore hook ,在需要使用的组件里引入 store , store 里的状态,需要使用 computed 计算属性获取,这样才可以触发响应式更新,那唯一修改 state 的 mutation ,还是老样子,需要使用 commit 触发, 所以在组件里就是 store.commit () , actions 也是同理

结语

回顾下来,整个 Vue3 常用的知识点,我们都默默的过了个遍,当然也不排除还一些知识小细节,剩下的就需要大家自行解决了。

Github 实例源码

ok 觉得有帮助的可以关注,点赞,评论,相互吹捧,共同进步

相关链接