Vue3+TypeScript初体验

283 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

image.png 目前大部分的项目还在使用Vue2.x,感觉落伍了。技术更新得太快,太卷了,不学不行呀!本文是在学习技术胖的课程后写下的学习笔记,方便以后查阅。好记性不如烂笔头,写过笔记印象深刻很多。这是基础入门篇,不喜轻喷。

一. 脚手架搭建

查看vue版本

image.png

npm install -g @vue/cli 
yarn global add @vue/cli //vue -version 版本必须大于4.5.6 
vue create vue3-demo

image.png

image.png

也可以通过vue ui可视化创建

image.png

二. 项目初始结构和文件分析

-node_modules -- 依赖包 
|-public -- 公共文件夹 
---|favicon.ico -- 网站的显示图标 
---|index.html -- 入口的html文件 
|-src -- 源文件目录 
---|assets -- 放置静态文件的目录 
---|components -- Vue的组件文件 
---|App.vue -- 根组件 
---|main.ts -- 入口文件
---|shims-vue.d.ts -- 类文件(也叫定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件 
|-.eslintrc.js -- Eslint的配置文件,不用作过多介绍 
|-.gitignore -- 用来配置那些文件不归git管理 
|-package.json -- 命令配置和包管理文件
-README.md -- 项目的说明文件,使用markdown语法进行编写 
|-tsconfig.json -- 关于TypoScript的配置文件 
|-yarn.lock -- 使用yarn后自动生成的文件,由Yarn管理,安装yarn包时的重要信息存储到yarn.lock文件中

三. setup()和ref()函数

ref一般用来声明基础类型,比如string,number,boolean。也可以声明引用类型数据,array,object,但其本质内部依然采用了raective声明。如果在声明数据的时候,不使用ref 或者 reactive 则数据不会响应。

<template>
  <div>{{ name }}</div>
  <button @click="changeName('开始学习Vue3')">修改name值</button>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "App",
  setup() { //setup函数可以代替Vue2的data和methods属性
    const name = ref("初识Vue3"); 
    const changeName = (newName: string) => {
      name.value = newName; //注意使用ref重新赋值要在.value上操作
    };
    return { //return出去的数据和方法可以在模板中使用
      name,
      changeName
    };
  },
});
</script>

四. reactive和toRefs的使用方法

reactive针对于引用类型数据的声明,array,object。

<template>
  <ul>
    <li
      v-for="(item, index) in course"
      :key="index"
      @click="selectCourseFun(index)"
    >
      {{ item }}
    </li>
  </ul>
  <div class="currentSelected">{{ selectCourse }}</div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";

export default defineComponent({
  name: "App",
  setup() {
    interface DataProps {
      //给data添加类型注解
      course: string[];
      selectCourse: string;
      selectCourseFun: (index: number) => void;
    }
    const data: DataProps = reactive({
      course: ["vue3进阶", "typeScript基础", "ES6高级"],
      selectCourse: "",
      selectCourseFun: (index: number) => {
        data.selectCourse = data.course[index]; //不需要加.value
      },
    });
    return {
      ...toRefs(data),  ////使用toRefs转换,然后扩展,不然在模板上需要data.course这样使用
    };
  },
});
</script>

五. setup语法糖代码更精简

如果不喜欢每个变量或方法都要return才能在模板上使用,可以用setup语法糖。

<template>同上</template>
<script setup  lang="ts">
import { ref, reactive, toRefs } from "vue";
const name = ref("初识Vue3");  //不需要retutn,可以直接在模板上使用
const changeName = (newName: string) => {
  name.value = newName;
};
interface DataProps {
  //给data添加类型注解
  course: string[];
  selectCourse: string;
  selectCourseFun: (index: number) => void;
}
const data: DataProps = reactive({
  course: ["vue3进阶", "typeScript基础", "ES6高级"],
  selectCourse: "",
  selectCourseFun: (index: number) => {
    console.log(index);
    console.log(data);
    data.selectCourse = data.course[index];
  },
});
const { course, selectCourse, selectCourseFun } = toRefs(data); //使解构后的数据重新获得响应式
</script>

六. Vue3.x的生命周期和钩子函数

setup() //开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method 
onBeforeMount() // 组件挂载到节点上之前执行的函数。 
onMounted() //组件挂载完成后执行的函数。 
onBeforeUpdate() //组件更新之前执行的函数。 
onUpdated() //组件更新完成之后执行的函数。 
onBeforeUnmount() //组件卸载之前执行的函数。 
onUnmounted() //组件卸载完成后执行的函数 
onActivated() //被包含在<keep-alive>中的组件,会多出两个生命周期钩子函数。被激活时执行。 
onDeactivated() //比如从 A 组件,切换到 B 组件,A 组件消失时执行。 
onErrorCaptured() //当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展现)。
onRenderTracked()  //状态跟踪,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用`return`返回去的值
onRenderTriggered() //状态触发。跟踪变化的值

vue2.x和vue3.x生命周期对比

Vue2--------------vue3 
beforeCreate -> setup() 
created -> setup() 
beforeMount -> onBeforeMount 
mounted -> onMounted 
beforeUpdate -> onBeforeUpdate 
updated -> onUpdated 
beforeDestroy -> onBeforeUnmount 
destroyed -> onUnmounted 
activated -> onActivated 
deactivated -> onDeactivated 
errorCaptured -> onErrorCaptured

使用方式:

<script lang="ts">
import { defineComponent, onMounted, onBeforeMount } from "vue";

export default defineComponent({
  name: "App",
  setup() {
    onBeforeMount(() => {
      console.log("onBeforeMount");
    });
    onMounted(() => {
      console.log("onMounted");
    });
  },
});
</script>

七. onRenderTracked()和 onRenderTriggered()钩子函数的使用

Vue3.x新增了onRenderTracked和onRenderTriggered钩子函数。

onRenderTracked直译过来就是状态跟踪,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用return返回去的值,他都会跟踪。函数只会在页面渲染的时候执行一次,局部更新不执行。

<script lang="ts">
import { .... ,onRenderTracked,} from "vue";
  setup() {
   onRenderTracked((event) => {
      console.log("状态跟踪组件----------->");
      console.log(event);
    });
  }
 </script>

onRenderTriggered直译过来是状态触发,它不会跟踪每一个值,而是给你变化值的信息,并且新值和旧值都会给你明确的展示出来。

<script lang="ts">
import { .... ,onRenderTriggered,} from "vue";
  setup() {
   onRenderTriggered((event) => {
      console.log("状态跟踪组件----------->");
      console.log(event);
      //event 对象属性的详细介绍
      //key 那边变量发生了变化 
      //newValue 更新后变量的值 
      //oldValue 更新前变量的值 
      //target 目前页面中的响应变量和函数
    });
  }
 </script>

八. Vue3中watch的使用和注意事项

监听单个值,引入后编写watch函数,它接受两个参数,第一个是要监听的值,这里是course,然后是一个回调函数。在函数中你可以获得到新值和旧值。

<template>
  <div>当前课程:{{ course }}</div>
  <button @click="changeCourseState">修改课程状态</button>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from "vue";

export default defineComponent({
  name: "HomeView",
  setup() {
    const course = ref("Javascript");
    const changeCourseState = () => {
      course.value = course.value + "学习完成|";
    };
    watch(course, (newValue, oldValue) => {
      //使用watch监听course变化,在回调函数中获取新旧值
      console.log(`new--->${newValue}`);
      console.log(`old--->${oldValue}`);
    });
    return {
      course,
      changeCourseState,
    };
  },
});
</script>

GIF.gif

监听多个值,当你要监听多个值的时候,不是写多个watch函数,而是要传入数组的形式。

<script lang="ts">
import { defineComponent, ref, reactive, toRefs, watch } from "vue";

export default defineComponent({
  name: "HomeView",
  setup() {
    interface DataProps {
      //给data增加类型注解
      courseList: string[];
      selectCourse: string;
      selectCourseFun: (index: number) => void;
    }
    const data: DataProps = reactive({
      courseList: ["vue3初识", "Node.js进阶", "css世界"],
      selectCourse: "vue3初识",
      selectCourseFun: (index: number) => {
        data.selectCourse = data.courseList[index];
      },
    });
    const course = ref("Javascript");
    const changeCourseState = () => {
      course.value = course.value + "学习完成|";
    };
    ////不是直接data.selectCourse,因为watch不能监听没有getter/effect方法。可以使用箭头函数直接返回。
    watch([course, () => data.selectCourse], (newValue, oldValue) => {
      console.log(`new--->${newValue}`);
      console.log(`old--->${oldValue}`);
    });
    return {
      ...toRefs(data),
      course,
      changeCourseState,
    };
  },
});
</script>

可以看到监听多个值时候,新旧值会以逗号隔开。

watch3.gif

九. Vue3中模块化介绍

//模块 useNowTime.ts
import { ref } from 'vue'
const nowTime = ref("00:00:00");
const getNowTime = () => {
    const now = new Date();
    const hour = now.getHours() < 10 ? String(now.getHours()).padStart(2, "0") : now.getHours();
    const minu = now.getMinutes() < 10 ? "0" + now.getMinutes() : now.getMinutes();
    const sec = now.getSeconds() < 10 ? "0" + now.getSeconds() : now.getSeconds();
    nowTime.value = hour + ":" + minu + ":" + sec;
    setTimeout(getNowTime, 1000); //每一秒执行一次这个方法
}
export { nowTime, getNowTime } //导出
<template>
  <div>{{ nowTime }}</div>
  <div><button @click="getNowTime">显示时间</button></div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { nowTime, getNowTime } from "../hooks/useNowTime"; //引入模块
export default defineComponent({
  name: "HomeView",
  setup() {
    return {
      nowTime,
      getNowTime,
    };
  },
});
</script>

time.gif

使用axios获取图片

// 模块useUrlAxios.js
import { ref } from 'vue'
import axios from 'axios' //需要先安装依赖 yarn add axios 或 cnpm i axios -S
function useUrlAxios(url: string) {
    const result = ref(null)
    const loading = ref(true)
    const loaded = ref(false)
    const error = ref(null)
    axios.get(url).then((res) => {
        loading.value = false;
        loaded.value = true;
        result.value = res.data;
    }).catch(e => {
        error.value = e;
        loading.value = false;
    })
    return {
        result,
        loading,
        loaded,
        error
    }
}
export default useUrlAxios
<template>
  <div>
    <div v-if="loading">Loading.....</div>
    <img v-if="loaded" :src="result.message" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import useUrlAxios from "../hooks/useUrlAxios"; //引入模块
export default defineComponent({
  name: "HomeView",
  setup() {
    const { result, loading, loaded } = useUrlAxios(
      "https://dog.ceo/api/breeds/image/random"
    );
    return { result, loading, loaded };
  },
});
</script>

dog.gif

十. Teleport瞬间移动函数的使用(独立函数)

Teleport在国内大部分都翻译成了瞬间移动组件,个人觉得不太好理解。我把这个函数叫独立组件,它可以把你写的组件挂载到任何你想挂载的DOM上,所以说是很自由很独立的。 在使用Vue2的时候是作不到的。

 // 新建组件 newDialog.vue
 <template>
  <teleport to="#modal">
    //组件挂载在modal节点上
    <h2>自定义Dialog组件</h2>
  </teleport>
</template>
// 在public/index.html增加一个Modal节点
<div id="app"></div> <div id="modal"></div>
<template>
  <div>
    //组件挂载在app节点上
    <h2>原节点</h2>
  </div>
  <new-dialog></new-dialog>
</template>
<script lang="ts">
import newDialog from "@/components/newDialog.vue"; //引入组件
const app = {
  name: "HomeView",
  components: {
    newDialog,
  },
};
export default app;
</script>

Teleport方法,可以把Dialog组件渲染到你任意想渲染的外部Dom上,不必嵌套再#app里了,这样就不会互相干扰了。你可以把Teleport看成一个传送门,把你的组件传送到你需要的地方。 teleport组件和其它组件没有任何其它的差异,用起来都是一样的。

image.png

十一. Suspense 初识异步请求组件

在Vue2.x时代,判断异步请求的状态是一件必须的事情,但是这些状态都要自己处理,根据请求是否完毕展示不同的界面。尤大神深知民间饥苦,在Vue3.x中给我们提供了Suspense组件。

<suspense> 组件有两个插槽。它们都只接收一个直接子节点。default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点。

Demo1

 //新建组件 AsyncShow.vue
 <template>
  <h1>{{ result }}</h1>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  setup() {
    return new Promise((resolve, reject) => {
      //模拟异步请求数据
      setTimeout(() => {
        return resolve({ result: "已经延迟两秒了,我要出来了" });
      }, 2000);
    });
  },
});
</script>
<template>
  <div>
    <Suspense>
      <template #default>  
        <AsyncShow></AsyncShow>
      </template>
      <template #fallback>
        <h1>loading....</h1>
      </template>
    </Suspense>
  </div>
</template>
<script lang="ts">
import AsyncShow from "@/components/AsyncShow.vue"; //引入组件
export default {
  name: "HomeView",
  components: { AsyncShow },
  setup() {
    return {};
  },
};
</script>

async.gif

Demo2

//新建组件 DogShow.vue
<template>
  <img :src="result" alt="" />
</template>
<script lang="ts">
import axios from "axios";
import { defineComponent } from "vue";
export default defineComponent({
  async setup() {
    //使用async await代替promise
    const rowData = await axios.get("https://dog.ceo/api/breeds/image/random");
    return { result: rowData.data.message };
  },
});
</script>
<template>
  <div>
    <Suspense>
      <template #default>
        <DogShow></DogShow>
      </template>
      <template #fallback>
        <h1>loading....</h1>
      </template>
    </Suspense>
  </div>
</template>
<script lang="ts">
import DogShow from "@/components/DogShow.vue"; //引入组件
export default {
  name: "HomeView",
  components: { DogShow },
  setup() {
    return {};
  },
};
</script>

dog.gif

onErrorCaptured钩子函数

在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。【传送门】 Demo2 组件DogShow.vue稍微修改一下,故意把接口写错"dog.ceo/api/breeds/…" 。

<template>
  <div>
    <Suspense>
      <template #default>
        <DogShow></DogShow>
      </template>
      <template #fallback>
        <h1>{{ currentState }}</h1>
      </template>
    </Suspense>
  </div>
</template>
<script lang="ts">
import { ref, onErrorCaptured } from "vue";
import DogShow from "@/components/DogShow.vue";
export default {
  name: "HomeView",
  components: { DogShow },
  setup() {
    const currentState = ref("loading....");
    //捕获后代组件报错信息
    onErrorCaptured((error, instance, info) => {
      console.log(`error====>`, error);
      console.log(`instance====>`, instance);
      console.log(`info====>`, info);
      currentState.value = "异步数据请求失败!";
      return true;
    });
    return {
      currentState,
    };
  },
};
</script>

image.png

errror.gif