Vue+TypeScripts 尚硅谷vue3教程学习笔记

1.Vue3简介

Vue 3 是 Vue.js 框架的一次重大升级,于 2020 年 9 月 18 日正式发布,代号为"One Piece(海贼王)"。作为新一代的前端构建框架,它在性能、源码架构、开发体验以及生 态支持上进行了全面的革新,旨在为开发者提供更高效、更灵活的开发体验。

核心特性与优势

1. 性能的大幅提升

Vue 3 在底层进行了大量优化,使得应用运行更加流畅。其打包体积减少了 41%,初次渲染速度提升了 55%,更新渲染速度提升了 133%,同时内存占用减少了 54%。这得益于其重写的虚拟 DOM 实现和编译时优化(如静态标记和按需更新),使得 Diff 算法更加高效。

2. 响应式系统的升级

Vue 2 使用 Object.defineProperty 实现响应式,存在无法监听数组索引变化和对象新增属性等局限。Vue 3 则采用了 ES6 的 Proxy 来替代,能够直接拦截对象和数组的变化,不仅解决了上述问题,还实现了"懒拦截",使得初始化性能更高。

3. 引入组合式 API (Composition API)

这是 Vue 3 最重要的新特性之一。相比于 Vue 2 将逻辑分散在 datamethodscomputed 等选项中的"选项式 API",组合式 API 允许开发者按照功能模块来组织代码。通过 setup 配置项以及 refreactivewatch 等函数,相关的逻辑可以被聚合在一起,极大地提升了代码的可读性和复用性,尤其适合大型复杂项目。

4. 更好的 TypeScript 支持

Vue 3 的源码完全使用 TypeScript 重写,使其能够原生、更好地支持 TypeScript。组合式 API 的设计也天然适配 TS,类型推导更加精准,为大型项目的类型安全和开发体验提供了有力保障。

5. 全新的内置组件

Vue 3 引入了几个强大的内置组件:

  • Fragment(片段): 允许组件模板拥有多个根节点,不再强制要求单根节点,减少了不必要的 DOM 嵌套。

  • Teleport(传送门): 可以将组件的 DOM 结构渲染到 DOM 树中的任意位置,非常适合实现模态框、全局提示等场景。

  • Suspense(异步组件): 能够优雅地处理异步组件的加载状态,在组件加载完成前展示占位内容,提升用户体验。

6. Tree-Shaking 支持

Vue 3 的 API 设计使得构建工具可以更好地进行 Tree-Shaking(摇树优化),将未使用的代码在打包时剔除,从而进一步减小最终产物的体积。

2.创建Vue3工程

2.1 基于vite创建

vite 是新一代前端构建工具,官网地址:https://vitejs.cn,vite的优势如下:

  • 轻量快速的热重载(HMR),能实现极速的服务启动
  • 对TypeScripts、JSX、CSS等支持开箱即用
  • 真正的按需编译,不再等待整个应用编译完成
  • webpack构建与vite构建对比图如下:

创建项目如图

  • vite 项目中,index.html是项目的入口文件,在项目的最外层。
  • 加载index.html后,Vite解析<script type="module" src="xxx"></script>指向的JavaScript。
  • Vue3中是通过createApp函数创建一个应用实例。

2.2 一个简单的效果

html 复制代码
<template>
 <div class="app">
  <h1>你好呀</h1>
 </div>
</template>

<script lang="ts">
export default {
  name:'APP'//组件名
}

</script>
<style scoped>
.app{
  background-color: #ddd;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
</style>

sre目录创建一个Person.vue文件

html 复制代码
<template>
 <div class="person">
  <h2>姓名:{{name}}</h2>
  <h2>年龄:{{age}}</h2>
  <button @click="showTel">查看联系方式</button>
 </div>
</template>

<script lang="ts">
export default {
  name:'Person',
  data(){
    return {
        name:'张三',
        age:18,
        tel:1388888888888
    }
  },
  methods:{
    showTel(){
        alert(this.tel);
    }
  }
}

</script>
<style scoped>
.person{
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
</style>

App.vue中注册person.vue

效果如图

3.Vue3核心语法

3.1 OptionsApiCompositionApi

  • vue2的API设计是Options(配置) 风格的。
  • Vue3的API设计是Composition(组合) 风格的。

Options API 的弊端

Options类型的API,数据、方法、计算属性等,是分散在:data、methods、computed中的,若想新增或者修改一个需求,就需要分别修改data、methods、computed,不便于维护和复用。

Composition Api的优势

可以使用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

3.2 setup

setup概述

setup是Vue3中一个新的配置项,值是一个函数,它是CompositionApi表演的舞台,组件中所用到的:数组、方法、计算属性、监视.....等等,均配置在setup中。

特点如下:

  • setup函数返回的对象中的内容,可直接在模板中使用。
  • setup中访问this是undefined。
  • setup函数会在beforeCreate之前调用,它是领先所有钩子函数执行的

setup 测试

html 复制代码
<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">查看联系方式</button>
  </div>
</template>

<script lang="ts">
import func from "vue-editor-bridge";
export default {
  name: "Person",
  setup() {
    // console.log(this) //setup函数中的this是undefined,Vue3中已经弱化this了
    // 数据,下面定义数据不是响应式的
    let name = "张三";
    let age = 18;
    let tel = "13888888888";

    // 方法
    function changeName() {
        // 这样修改name,页面是没有变化的
      name = "zhang-san";//name确实改了,但name不是响应式的
    }
    function changeAge() {
      age += 1;
    }
    function showTel() {
      alert(tel);
    }
     //将数据、方法交出去,模板中才可以使用
    return { name, age, changeName, changeAge, showTel };
  },
};
</script>
<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
button {
  margin: 0 5px;
}
</style>

setup语法糖

script 标签中加入setup,组件命名script标签中+ name="Person123" ,name="Person123"生效需要终端先运行npm i vite-plugin-vue-setup-extend -D,然后vite.config.ts中配置如下图。

组件名默认和文件名一致

html 复制代码
<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">查看联系方式</button>
  </div>
</template>

<script lang="ts" setup name="Person123">
// console.log(this) //setup函数中的this是undefined,Vue3中已经弱化this了
    // 数据,下面定义数据不是响应式的
    let name = "张三";
    let age = 18;
    let tel = "13888888888";

    // 方法
    function changeName() {
        // 这样修改name,页面是没有变化的
      name = "zhang-san";//name确实改了,但name不是响应式的
    }
    function changeAge() {
      age += 1;
    }
    function showTel() {
      alert(tel);
    }
</script>
<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
button {
  margin: 0 5px;
}
</style>

3.3 ref创建-基本类型的响应式数据

script标签中导入

html 复制代码
import { ref } from "vue";

将变量用ref包裹,一个RefImpl的实例对象,简称ref对象或ref,ref对象的value属性是响应式的。

html 复制代码
let name = ref("张三");
let age = ref(18);

script中要用xxx.value改变值,template中默认有.value,可以省略。

完整代码如下

html 复制代码
<template>
  <div class="person">
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">查看联系方式</button>
  </div>
</template>

<script lang="ts" setup name="Person123">
// console.log(this) //setup函数中的this是undefined,Vue3中已经弱化this了
import { ref } from "vue";
let name = ref("张三");
let age = ref(18);
let tel = "13888888888";

console.log(name);
console.log(tel);

// 方法
function changeName() {
  // 这样修改name,页面是没有变化的
  name.value = "zhang-san"; //name确实改了,但name不是响应式的
}
function changeAge() {
  age.value += 1;
}
function showTel() {
  alert(tel);
}
</script>
<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
button {
  margin: 0 5px;
}
</style>

3.4 reactive 创建响应式对象

scripts标签中应用

html 复制代码
import { reactive } from "vue";

定义响应式对象;

html 复制代码
let car = reactive({ brand: "奔驰", price: 8000 });

测试代码:

html 复制代码
<template>
  <div class="person">
    <h2>汽车信息:一辆{{ car.brand }}车,价值{{ car.price }}万</h2>
    <button @click="changePrice">修改汽车的价格</button>
  </div>
  <br>
  <h2>游戏列表:</h2>
  <ul>
    <li v-for="item in games" :key="item.id">{{item.name}}</li>
  </ul>
  <button @click="changeFirstGame">修改第一个游戏的名字</button>
</template>

<script lang="ts" setup name="Person123">
import { ref } from "vue";
import { reactive } from "vue";
// 数据
let car = reactive({ brand: "奔驰", price: 8000 });
console.log(car);
function changePrice() {
  car.price += 10;
}
let games =reactive([
    {id:'aaaaa1',name:'王者荣耀'},
    {id:'aaaaa2',name:'原神'},
    {id:'aaaaa3',name:'三国志'}
])

function changeFirstGame(){
games[0].name='三国杀';
}
</script>
<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
button {
  margin: 0 5px;
}

li{
    font-size: 20px;
}
</style>

reactive 只能定义对象类型的响应式数据

3.5 ref 创建响应式对象数据类型

scripts标签中应用

html 复制代码
import { ref } from "vue";

定义数据

html 复制代码
/ 数据
let car = ref({ brand: "奔驰", price: 8000 });
let games =ref([
    {id:'aaaaa1',name:'王者荣耀'},
    {id:'aaaaa2',name:'原神'},
    {id:'aaaaa3',name:'三国志'}
])

测试代码

html 复制代码
<template>
  <div class="person">
    <h2>汽车信息:一辆{{ car.brand }}车,价值{{ car.price }}万</h2>
    <button @click="changePrice">修改汽车的价格</button>
  </div>
  <br>
  <h2>游戏列表:</h2>
  <ul>
    <li v-for="item in games" :key="item.id">{{item.name}}</li>
  </ul>
  <button @click="changeFirstGame">修改第一个游戏的名字</button>
</template>

<script lang="ts" setup name="Person123">
import { ref } from "vue";
// 数据
let car = ref({ brand: "奔驰", price: 8000 });
let games =ref([
    {id:'aaaaa1',name:'王者荣耀'},
    {id:'aaaaa2',name:'原神'},
    {id:'aaaaa3',name:'三国志'}
])

function changePrice() {
  car.value.price += 10;
}

function changeFirstGame(){
games.value[0].name='三国杀';
}



</script>
<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
button {
  margin: 0 5px;
}

li{
    font-size: 20px;
}
</style>

3.6 ref 对比reactive

宏观角度看

  • 1.ref用来定义:基本类型数据、对象类型数据。
  • 2.reactive用来定义:对象类型的数据。

区别:

  • 1.ref 创建的变量必须用.value(可以使用volar插件自动添加.value)。
  • 2.reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。

Object.assign使用:

html 复制代码
function changeCar(){
  Object.assign(car,{brand:"奥拓",price:1})
}

使用原则:

  • 1.若需要一个基本的响应式数据类型,必须有ref。
  • 2.如需要一个响应式对象,层级不深,ref、reactive都可以
  • 3.若需要一个响应式对象,且层级较深,推荐使用reactive。

3.7 toRefs和toRef

html 复制代码
import { reactive, toRefs, toRef } from "vue";
html 复制代码
let { name, age } = toRefs(person);
let nl = toRef(person,'age');

toRefs 全部转换,toRef 单个属性转换

测试

html 复制代码
<template>
  <div class="person">
    <h2>{{ person.name }}</h2>
    <h2>{{ person.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>

<script lang="ts" setup name="Person123">
import { reactive, toRefs, toRef } from "vue";
let person = reactive({
  name: "张三",
  age: 18,
});

let { name, age } = toRefs(person);
let nl = toRef(person,'age');

console.log("test"+nl.value);

function changeName() {
  name.value += "~";
}

function changeAge() {
  age.value += 1;
}
</script>
<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
button {
  margin: 0 5px;
}

li {
  font-size: 20px;
}
</style>

3.8 computed计算属性

  • computed具有依赖缓存机制。只有当它依赖的数据发生变化时才会重新计算;否则直接返回缓存结果,性能优于普通方法。
  • 读写特性 :默认只读,但可以通过配置 getset 实现双向读写。

使用例子;

html 复制代码
import { ref, computed } from "vue";
html 复制代码
<template>
  <div class="person">
    姓:<input type="text" v-model="firstName" /><br />
    名:<input type="text" v-model="lastNmae" /><br />
    全名:<span>{{ fullName }}</span
    ><br />
    <button @click="changeFullName">将全名改为li-si</button>
  </div>
</template>

<script lang="ts" setup name="Person123">
import { ref, computed } from "vue";
let firstName = ref("zhang");
let lastNmae = ref("san");
// 这么定义的fullName是一个计算属性,且是只读的
// let fullName = computed(() => {
//   return (
//     firstName.value.slice(0, 1).toUpperCase() +
//     firstName.value.slice(1) +
//     "-" +
//     lastNmae.value
//   );
// });
// 这么定义的fullName是一个可读可写的
let fullName = computed({
  get() {
    return (
      firstName.value.slice(0, 1).toUpperCase() +
      firstName.value.slice(1) +
      "-" +
      lastNmae.value
    );
  },
  set(val) {
    const [str1, str2] = val.split("-");
    firstName.value = str1;
    lastNmae.value = str2;
  }
});

console.log(fullName);

function changeFullName() {
  fullName.value = "li-si";
}
</script>
<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
button {
  margin: 0 5px;
}

li {
  font-size: 20px;
}
</style>