Vue3+TypeScript基础应用

Vue-cli构建vue3

安装vue-cli

npm install -g @vue/cli
yarn global add @vue/cli
查看安装版本:vue --version
如果低于4.5.6可以更新一下,老版本没有创建vue3的选项

使用命令行构建

输入vue create vuedemo
会跳出三个菜单

? Please pick a preset: (Use arrow keys)            //请选择预选项
> Default ([Vue 2] babel, eslint)                   //使用Vue2默认模板进行创建
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)   //使用Vue3默认模板进行创建
  Manually select features                          //手动选择(自定义)的意思

因为要使用TypeScript进行开发,所以选择第三项

? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 ( ) Router
 ( ) Vuex
 ( ) CSS Pre-processors             //CSS预处理器
 (*) Linter / Formatter             //格式化工具
 ( ) Unit Testing                   //单元测试
 ( ) E2E Testing                    //E2E测试

这里需要多选一个TypeScript的选项(按空格),然后再按回车进入下一层

? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
> 2.x
  3.x (Preview)

这里要选择 3.x 的版本

Use class-style component syntax?

不使用class-style可以选n

Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n)

使用TypeScript和Babel的形式编译 JSX的选n

? Pick a linter / formatter config: (Use arrow keys)
> ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier
  TSLint (deprecated)

练习一般默认就好,其他情况根据需要选择

? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Lint on save         //保存的时候进行Lint
 ( ) Lint and fix on commit   //需要帮你进行fix(修理),这项我们不进行选择

选一

Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

选择配置文件是单独存放还是直接存放在package.json,这里选择单独存放

Save thisas a preset for future projects? (y/N)

是否保存刚才的配置,下次继续使用。根据需要选择

? Pick the package manager to use when installing dependencies:
> Use Yarn
  Use NPM

如果你同时安装了npm和yarn,它还会最后询问选哪个,根据习惯选择

最后运行项目即可
Vue3+TypeScript基础应用_第1张图片

使用图形界面构建

命令:vue ui

访问http://localhost:80

Vue3+TypeScript基础应用_第2张图片

创建 → 在此处创建新项目 → 输入项目名 → 选择yarn或npm→ 下一步

Vue3+TypeScript基础应用_第3张图片

选择手动

Vue3+TypeScript基础应用_第4张图片

根据自己的项目来进行配置

Vue3+TypeScript基础应用_第5张图片

根据需要选择是否保存这个配置

Vue3+TypeScript基础应用_第6张图片

这时候终端就会开始构建项目了。出现该界面说明构建完成

Vue3+TypeScript基础应用_第7张图片

项目初始结构和重要文件

|-node_modules       -- 所有的项目依赖包都放在这个目录下
|-public             -- 公共文件夹
---|favicon.ico      -- 网站的显示图标
---|index.html       -- 入口的html文件
|-src                -- 源文件目录,编写的代码基本都在这个目录下
---|assets           -- 放置静态文件的目录,比如logo.pn就放在这里
---|components       -- Vue的组件文件,自定义的组件都会放到这
---|App.vue          -- 根组件,这个在Vue2中也有
---|main.ts          -- 入口文件,因为采用了TypeScript所以是ts结尾
---|shims-vue.d.ts   -- 类文件(也叫定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件
|-.browserslistrc    -- 在不同前端工具之间公用目标浏览器和node版本的配置文件,作用是设置兼容性
|-.eslintrc.js       -- Eslint的配置文件,不用作过多介绍
|-.gitignore         -- 用来配置那些文件不归git管理
|-package.json       -- 命令配置和包管理文件
|-README.md          -- 项目的说明文件,使用markdown语法进行编写
|-tsconfig.json      -- 关于TypoScript的配置文件
|-yarn.lock          -- 使用yarn后自动生成的文件,由Yarn管理,安装yarn包时的重要信息存储到yarn.lock文件中

package.json 中的三条命令

{
  //----
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  //----
}
  • serve : 在开发时用于查看效果的命令,视频中演示看一下效果
  • build : 打包打码,一般用于生产环境中使用
  • lint : 检查代码中的编写规范

dependencies和devDependencies

  • 开发环境:编写代码、测试代码、修改 Bug的环境,也就说这些代码没有上线
  • 生产环境:就是代码已经上线,放到了正式的服务器上的代码

入口文件main.ts

import { createApp } from "vue"; // 引入vue文件,并导出`createApp`
import App from "./App.vue"; // 引入自定义组件,你在页面上看的东西基本都在这个组件里

createApp(App).mount("#app"); // 把App挂载到#app节点上,在public目录下的index.html找节点

Vue3

setup()和 ref()函数

App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div>
    <h2>hello vue3</h2>
    <div>请选择</div>
  </div>
  <div>
    <button
      v-for="(item, index) in temps"
      v-bind:key="index"
      @click="selectTempFun(index)"
    >
      {{ index }} : {{ item }}
    </button>
  </div>
  <div>你选择了【{{ selectTemp }}</div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "App",
  setup() {
    const temps = ref(["vue2", "vue3"]);
    const selectTemp = ref("");
    const selectTempFunc = (index: number) => {
      selectTemp.value = temps.value[index];
    };
    //因为在模板中这些变量和方法都需要条用,所以需要return出去。
    return {
      temps,
      selectTemp,
      selectTempFunc,
    };
  },
});
</script>
  • setup 函数的用法,可以代替 Vue2 中的 date 和 methods 属性,直接把逻辑写在 setup 里就可以
  • ref 是一个神奇的函数,注意要使用template中的变量,必须用ref包装
  • return出去的数据和方法,在模板中才可以使用,这样可以精准的控制暴露的变量和方法

reactive()函数

上面的代码所有的变量和方法都混淆在一起,且在使用setup中要改变和读取一个值还要加上value

Reactive:是一个函数(方法),里边接受的参数是一个 Object

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div>
    <h2> hello vue3</h2>
    <div>请选择</div>
  </div>
  <div>
    <button
      v-for="(item, index) in data.temps"
      v-bind:key="index"
      @click="data.selectTempFun(index)"
    >
      {{ index }} : {{ item }}
    </button>
  </div>
  <div>你选择了【{{ data.selectTemp }}</div>
</template>

<script lang="ts">
import { ref, reactive } from "vue";
export default {
  name: "App",
  setup() {
    const data = reactive({
      temps: ["vue2", "vue3"],
      selectTemp: "",
      selectTempFun: (index: number) => {
        data.selectTemp = data.temps[index];
      },
    });

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

给data添加类型注解

上面的代码定义data时没有做类型注解,而是使用了TypeScrip的类型推断,实际开发工作一般不允许出现类型推断,不严谨。

定义接口作为类型注解

interface DataProps {
  temps: string[];
  selectTemp: string;
  selectTempFun: (index: number) => void;
}

使用

cosnt data: DataProps = ...

toRef()函数

上面的代码每次输出变量前面都要加一个data,但是不能通过…解构对象返回

return {
  …data
}

这样返回的值会失去响应式能力,使用toRef函数可以解决这个问题

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div>
    <h2> hello vue3</h2>
    <div>请选择</div>
  </div>
  <div>
    <button
      v-for="(item, index) in temps"
      v-bind:key="index"
      @click="selectTempFun(index)"
    >
      {{ index }} : {{ item }}
    </button>
  </div>
  <div>你选择了【{{selectTemp }}</div>
</template>

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

interface DataProps {
  temps: string[];
  selectTemp: string;
  selectTempFun: (index: number) => void;
}

export default {
  name: "App",
  setup() {
    const data: DataProps = reactive({
      temps: ["vue2", "vue3"],
      selectTemp: "",
      selectTempFun: (index: number) => {
        data.selectTemp = data.temps[index];
      },
    }); 
    const refData = toRefs(data);
    return { 
      ...refData
    };
  },
};
</script>

Vue3的生命周期和钩子函数

生命周期

钩子函数

  • setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
  • onBeforeMount() : 组件挂载到节点上之前执行的函数
  • onMounted() : 组件挂载完成后执行的函数
  • onBeforeUpdate(): 组件更新之前执行的函数
  • onUpdated(): 组件更新完成之后执行的函数
  • onBeforeUnmount(): 组件卸载之前执行的函数
  • onUnmounted(): 组件卸载完成后执行的函数
  • onActivated(): 被包含在< keep-alive >中的组件,会多出两个生命周期钩子函数,被激活时执行
  • onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行
  • onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数

< keep-alive >组件会将数据保留在内存中

钩子函数的使用

使用Vue3钩子函数需要引入

import { reactive, toRefs, onMounted, onBeforeMount, onBeforeUpdate, onUpdated} from "vue"

setup()

在beforeCreate和created之前运行的,可以用它来代替这两个钩子函数

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

interface DataProps {
  temps: string[];
  selectTemp: string;
  selectTempFun: (index: number) => void;
}

export default {
  name: "App",
  setup() {
    console.log("1-开始创建组件-----setup()");
    const data: DataProps = reactive({
      temps: ["vue2", "vue3"],
      selectTemp: "",
      selectTempFun: (index: number) => {
        data.selectTemp = data.temps[index];
      },
    });
    onBeforeMount(() => {
      console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
    });
    onMounted(() => {
      console.log("3-组件挂载到页面之后执行-----onMounted()");
    });
    onBeforeUpdate(() => {
      console.log("4-组件更新之前-----onBeforeUpdate()");
    });
    onUpdated(() => {
      console.log("5-组件更新之后-----onUpdated()");
    });
    const refData = toRefs(data);
    return { 
      ...refData
    };
  },
};
</script>

运行,浏览器效果如下

1 - 开始创建组件---- - setup();
2 - 组件挂载到页面之前执行---- - onBeforeMount();
3 - 组件挂载到页面之后执行---- - onMounted();
4 - 组件更新之前---- - onBeforeUpdate();
5 - 组件更新之后---- - onUpdated();

Vue2的钩子函数

  • Vue3的项目依然可以使用Vue2的钩子函数,与setup()同级别
  • 不要混用,Vue 官方的文档里,明确指出如果使用 Vue3,尽量使用新的生命周期钩子函数
beforeCreate() {
  console.log("1-组件创建之前-----beforeCreate()");
},
beforeMount() {
  console.log("2-组件挂载到页面之前执行-----BeforeMount()");
},
mounted() {
  console.log("3-组件挂载到页面之后执行-----Mounted()");
},
beforeUpdate() {
  console.log("4-组件更新之前-----BeforeUpdate()");
},
updated() {
  console.log("5-组件更新之后-----Updated()");
}

Vue2和Vue3的钩子函数对比

Vue2--------------vue3
beforeCreate  -> setup()
created       -> setup()
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
activated     -> onActivated
deactivated   -> onDeactivated
errorCaptured -> onErrorCaptured
  • vue3 的钩子基本是再 vue2 的基础上加了一个on
  • BeforeDestroy变成了onBeforeUnmount,destroyed变成了onUnmounted,主要因为mount比Destroy更形象,也和beforeMount相对应

onRenderTracked

状态跟踪,会跟踪页面上所有响应式变量和方法的状态,也就是只要页面有update的情况就会跟踪,生成一个event对象,可以通过event对象进行调试

import { .... ,onRenderTracked,} from "vue";

在setup()函数中进行引用

onRenderTracked((event) => {
  console.log("状态跟踪组件----------->");
  console.log(event);
});
  • 组件没有更新的时候,onRenderTracked不会执行
  • 组件更新时,onRenderTracked会跟踪 每个值 和方法的变化

onRenderTriggered

状态触发,不会跟踪每一个值,只跟踪变化的值

import { .... ,onRenderTriggered,} from "vue";

在setup()函数中进行引用

onRenderTriggered((event) => {
  console.log("状态触发组件--------------->");
  console.log(event);
});

onRenderTriggered的event属性有

  • key:那边变量发生了变化

  • newValue:更新后变量的值

  • oldValue:更新前变量的值

  • target:目前页面中的响应变量和函数

效果和watch很像

watch的使用

基本用法

先去掉上面代码的钩子函数

watch与钩子函数、return同级

{
  //.....
  const finalText = ref("element");
  const finalAction = () => {
    finalText.value = finalText.value + "done | ";
  };
  return { ...refData,  finalText,   finalAction};
}

template模板中,调用这些变量和方法

<div><button @click="finalAction">语言选择完毕</button></div>
<div>{{ finalText }}</div>

引入watch

import {... , watch} from "vue"

watch函数有两个参数,第一个是要监听的值,第二个是个回调函数

watch(finalText, (newValue, oldValue) => {
    console.log(`new--->${newValue}`);
    console.log(`old--->${oldValue}`);
    document.title = newValue;
});

watch多个值

watch([finalText, () => data.selectTemp], (newValue, oldValue) => {
  console.log(`new--->${newValue}`);
  console.log(`old--->${oldValue}`);
  document.title = newValue[0];
});

注意,因为selectTemp是在reactive函数中的值,不能直接监听,需要用一个函数去解决(类似getter函数的效果)

这是为了保持和Vue2的一致性,Vue2中的解决方案是要么是ref或者get/set方法的形式,或者开启设置watchOptions的deep为true,也就是深度监听模式

模块化

基本用法

vue2版本一般会使用mixins进行代码重用

vue3则更加简单

先清空代码

<template>
  <div>

  </div>
</template>
<script lang="ts">
import { ref } from "vue";
const app = {
  name: "App",
  setup() {

  },
};
export default app;
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

实现时间功能

//....
const app = {
  name: "App",
  setup() {
    //.....
    const nowTime = ref("00:00:00");
    const getNowTime = () => {
      const now = new Date();
      const hour = now.getHours() < 10 ? "0" + now.getHours() : 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);   //每一秒执行一次这个方法
    };
    //...
    return {
      //....
      nowTime,
      getNowTime,
    };
  },
};
export default app;

在template中使用

<div>{{ nowTime }}</div>
<div><button @click="getNowTime">显示时间</button></div>

将时间功能的逻辑抽出进行模块化

在src新建一个文件夹hooks(所有抽离出来的功能模块都放到这个文件夹里),然后新建NowTime.ts

import { ref } from "vue";

const nowTime = ref("00:00:00");
const getNowTime = () => {
    const now = new Date();
    const hour = now.getHours() < 10 ? "0" + now.getHours() : 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 }

引入并使用模块

import { nowTime, getNowTime } from "./hooks/useNowTime";
//....
return {nowTime,getNowTime};

深入理解

写一个随机选择器模块

<template>
  <div
    <div>随机选择一只宠物</div>
  </div>
</template>
<script lang="ts">
import { reactive, toRefs, ref, watch } from "vue";
const app = {
  name: "App",
  setup() {
    return {};
  },
};
export default app;
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

axios

安装:npm install axios --save

新建文件URLAxios.ts

import { ref } from 'vue'
import axios from 'axios'

function urlAxios(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 urlAxios

App.vue调用模块

<template>
  <div>
    <div>随机选择一只宠物</div>
    <div v-if="loading">Loading.....</div>
    <img v-if="loaded" :src="result.message" />
    <div></div>
  </div>
</template>
<script lang="ts">
import { ref } from "vue";
import urlAxios from "./hooks/urlAxios";
const app = {
  name: "App",
  setup() {
    const { result, loading, loaded } = urlAxios(
      "https://dog.ceo/api/breeds/image/random"
    );

    return { result, loading, loaded };
  },
};
export default app;
</script>

Teleport瞬间移动函数

可以把写好的vue组件放到指定的html节点下,而不是跟随vue固定在id=app的结点下

基本用法

弹出组件Modal.vue

<template>
  <div id="center">
    <h2>gogogo</h2>
  </div>
</template>
<script lang="ts">
export default {};
</script>
<style>
#center {
  width: 200px;
  height: 200px;
  border: 2px solid black;
  background: white;
  position: fixed;
  left: 50%;
  top: 50%;
  margin-left: -100px;
  margin-top: -100px;
}
</style>

在vue组件中引入

//...
import modal from "./components/Modal.vue";
const app = {
  name: "App",
  components: {
    modal,
  },
  //...
}
//...

运行查看页面元素,发现所有的dom都是在app节点下,如果想改变节点,在Vue2非常困难,vue3只需要使用teleport函数

在index.html增加一个节点

<div id="app"></div>
<div id="modal"></div>

修改Modal.vue,用<teleport>标签进行包裹,用to属性指向需要渲染的DOM节点

<template>
  <teleport to="#modal">
    <div id="center">
      <h2>JSPang11</h2>
    </div>
  </teleport>
</template>

运行,modal组件相关的元素都是id=modal的节点下

Suspense异步请求组件

初识

Vue2判断异步请求的状态是一件必须的事情,但是这都要自己处理,根据请求是否完毕展示不同的界面(比如loading),在Vue3中给我们提供了Suspense组件可以更方便完成这些事。

新建Async.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: "done" });
      }, 2000);
    });
  },
});
</script>
  • 使用 Suspense 要返回一个promise对象,而不是 JSON
  • defineComponent是用来解决TypeScript情况下,传统的Vue.extends无法对组件给出正确的参数类型推断的。也就是说在TypeScript环境中如果参数类型推断不正常时,用defineComponent()组件来进行包装函数

清空App.vue的其他代码

<template>
  <div>
  </div>
</template>
<script lang="ts">
const app = {
  name: "App",
  components: { },
  setup() {
    return {};
  },
};
export default app;
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

引入写好的组件,并在components中声明

import Async from "./components/Async.vue";
const app = {
  name: "App",
  components: { AsyncShow },
  setup() {
    return {};
  },
};

使用Suspense

<template>
  <div>
    <Suspense>
      <template #default>
        <Async />
      </template>
      <template #fallback>
        <h1>Loading...</h1>
      </template>
    </Suspense>
  </div>
</template>
  • 插槽default代表异步请求完成后要显示的内容
  • 插槽fallback代表异步请求还没返回结果过程中要显示的内容

深入

把上面用setTimeout模拟异步请求改为使用真实的请求

新建AsyncImg.vue组件

<template>
    <img :src="result && result.imgUrl"  />
</template>
<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
    async setup() {  //promise 语法糖  返回之后也是promise对象
        const rawData = await  axios.get('https://apiblog.jspang.com/default/getGirl')
        return {result:rawData.data}
    }
})
</script>
  • async…await是promise的语法糖

清空App.vue的其他代码并引入AsyncImg.vue

import AsyncImg from "./components/AsyncImg.vue";
const app = {
  name: "App",
  components: { AsyncImg },
  setup() {
    return {};
  },
};

模板部分

<template>
  <div>
    <Suspense>
      <template #default>
         <async-img />
      </template>
      <template #fallback>
        <h1>Loading...</h1>
      </template>
    </Suspense>
  </div>
</template>

拓展:处理异步请求错误

在vue3.x的版本中,可以使用onErrorCaptured钩子函数来捕获异常

import { onErrorCaptured} from "vue";

用法

const app = {
  name: "App",
  components: { AsyncImg },
  setup() {
    onErrorCaptured((error) => {
      console.log(`error====>`,error)
      return true  
    })
    return {};
  },
};

这样,所有的请求异常都会被捕获并打印到控制台

你可能感兴趣的:(前端,vue.js,typescript)