Vue3教程

221 阅读6分钟

参考自: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>

注意点

  • 它比beforeCreatecreated这两个生命周期还要,就是说,setup在beforeCreate,created前,它里面的this打印出来是undefined
  • setup可以接受两个参数,第一个参数是props,也就是组件传值,第二个参数是context,上下文对象,context里面还有三个很重要的东西attrsslots,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()getset来实现响应式(数据劫持)。
  • 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改名为beforeUnmountdestroyed改名为unmounted

然后在vue3中,beforeCreatecreated并没有组合式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

聪明一点,toRefstoRef有什么不同,加了个s,toRef是单个转化为响应式,那toRefs就是多个转化为响应式咯,这样的话就减少代码,不然要是有成千上万个,那你不是要当憨憨闷写吗?(...是结构哈,看不懂就麻溜的alt+←),当然它只会结构一层,深层里的代码还是要老实的写

<h1>当前姓名:{{name}}</h1>
<h1>当前薪水:{{job.salary}}K</h1>
return { ...toRefs(names) }

Fragment

在vue2里面,我们是有根标签这个概念的,但是到来vue3,它是自动给你创建个虚拟根标签VNodeFragment),所以可以不要根标签。好处就是 减少标签层级, 减小内存占用

Teleport

teleport 提供了一种有趣的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件。

其实就是可以不考虑你写在什么位置,你可以定义teleport在任意标签里进行定位等

to 允许接收值: 期望接收一个 CSS 选择器字符串或者一个真实的 DOM 节点。 提示: <Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。 如果目标元素也是由 Vue 渲染的,你需要确保在挂载 <Teleport> 之前先挂载该元素

<Teleport to="body">
</Teleport>

Suspense

大家都知道在渲染组件之前进行一些异步请求是很常见的事,suspense 组件提供了一个方案,允许将等待过程提升到组件树中处理,而不是在单个组件中。