vue3 + vite + ts + pinia + yarn

Vue3 + Vite + TypeScript + Pinia + Yarn

yarn

常用命令

# 查看yarn 版本
yarn -v
# 查看yarn配置
yarn config list
# 查看当前yarn源
yarn config get registry

# 修改yarn源(初始)
yarn config set registry https://registry.yarnpkg.com
 
# 修改yarn源(此处为淘宝的源)
yarn config set registry https://registry.npm.taobao.org
 
# yarn安装依赖
yarn add 包名          # 局部安装
yarn global add 包名   # 全局安装
 
# yarn 卸载依赖
yarn remove 包名         # 局部卸载
yarn global remove 包名  # 全局卸载(如果安装时安到了全局,那么卸载就要对应卸载全局的)
 
# yarn 查看全局安装过的包
yarn global list 
npm install -g yarn  # 安装yarn 
yarn --version       # 安装成功后,查看版本号
md yarn   # 创建文件夹 yarn  
cd yarn   # 进入yarn文件夹 
 
# 初始化项目 
yarn init # 同npm init,执行输入信息后,会生成package.json文件
 
# yarn的配置项: 
yarn config list # 显示所有配置项
yarn config get <key> # 显示某配置项
yarn config delete <key> # 删除某配置项
yarn config set <key> <value> [-g|--global] # 设置配置项
 
# 安装包: 
yarn install         # 安装package.json里所有包,并将包及它的所有依赖项保存进yarn.lock
yarn install --flat  # 安装一个包的单一版本
yarn install --force         # 强制重新下载所有包
yarn install --production    # 只安装dependencies里的包
yarn install --no-lockfile   # 不读取或生成yarn.lock
yarn install --pure-lockfile # 不生成yarn.lock
# 添加包(会更新package.json和yarn.lock):
 
yarn add [package] #  在当前的项目中添加一个依赖包,会自动更新到package.json和yarn.lock文件中
yarn add [package]@[version] #  安装指定版本,这里指的是主要版本,如果需要精确到小版本,使用-E参数
yarn add [package]@[tag] #  安装某个tag(比如beta,next或者latest)
 
# 不指定依赖类型默认安装到dependencies里,你也可以指定依赖类型:
yarn add --dev/-D # 加到 devDependencies
yarn add --peer/-P # 加到 peerDependencies
yarn add --optional/-O # 加到 optionalDependencies
 
# 默认安装包的主要版本里的最新版本,下面两个命令可以指定版本:
yarn add --exact/-E # 安装包的精确版本。例如yarn add foo@1.2.3会接受1.9.1版,但是yarn add foo@1.2.3 --exact只会接受1.2.3版
yarn add --tilde/-T # 安装包的次要版本里的最新版。例如yarn add foo@1.2.3 --tilde会接受1.2.9,但不接受1.3.0
 
yarn publish # 发布包
yarn remove <packageName>  # 移除一个包,会自动更新package.json和yarn.lock
yarn upgrade # 更新一个依赖: 用于更新包到基于规范范围的最新版本
yarn run   # 运行脚本: 用来执行在 package.json 中 scripts 属性下定义的脚本
yarn info <packageName> # 可以用来查看某个模块的最新版本信息
 
yarn cache # 缓存
yarn cache list # 列出已缓存的每个包 
yarn cache dir # 返回 全局缓存位置 
yarn cache clean # 清除缓存

vite 构建工具

对比 webpack:

  • 需要查找依赖,打包所有的模块,然后才能提供服务,更新速度会随着代码体积增加越来越慢

image-20220711150331172

vite 的原理:

  • 使用原生 ESModule 通过 script 标签动态导入,访问页面的时候加载到对应模块编译并响应

image-20220711151009063

注明:项目打包的时候最终还是需要打包成静态资源的,打包工具 Rollup

问题:

  • 基于 webpack 构建项目,基于 vite 构建项目,谁更快体验更好?vite
  • 基于 webpackvue-cli 可以创建 vue 项目吗?可以,慢一点而已

vite 创建项目

  1. 运行创建项目命令:
# 使用npm
npm create vite@latest
# 使用yarn
yarn create vite
# 使用pnpm
pnpm create vite
  1. 输入项目名称,默认是 vite-project

image-20220713110332145

  1. 选择前端框架

image-20220713110539914

  1. 选择项目类型

image-20220713110719136

  1. 创建完毕

image-20220713110801896

  1. 进入项目目录,安装依赖,启动项目即可。

Vue 3

  1. 需要切换插件

    vue3 组件代码和 vue2 有些不一样,使用的语法提示和高亮插件也不一样。

    • vetur 插件需要禁用,安装 volar插件。
  2. 总结 vue3 写法不同

    1. 组件一个根节点非必需
    2. 创建应用挂载到根容器
    3. 入口页面,ESM 加载资源
  • 平常组件

    <template>
      <div>节点1</div>
      <div>节点2</div>
    </template>
    
  • main.js

    import { createApp } from 'vue'
    import App from './App.vue'
    // 根据App组件创建一个应用实例
    const app = createApp(App)
    // app应用挂载(管理)index.html的 #app 容器
    app.mount('#app')
    
  • index.html

    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
    

vue 3 组件库

库名称简介
ant-design-vuePC 端组件库:Ant Design 的 Vue 实现,开发和服务于企业级后台产品
arco-design-vuePC 端组件库:字节跳动出品的企业级设计系统
element-plusPC 端组件库:基于 Vue 3,面向设计师和开发者的组件库
Naive UIPC 端组件库:一个 Vue 3 组件库,比较完整,主题可调,使用 TypeScript,快,有点意思
vant移动端组件库:一个轻量、可靠的移动端组件库,于 2017 年开源
VueUse基于 composition 组合式 api 的常用函数集合

vue 文档

中文文档:

  1. 相关文档
    1. Vue3 中文文档(新) https://cn.vuejs.org/
    2. Vue2 中文文档(旧) https://v2.cn.vuejs.org/
    3. Vue3 设计理念 https://vue3js.cn/vue-composition/
  2. 了解框架优点特点
    1. 首次渲染更快
    2. diff 算法更快
    3. 内存占用更少
    4. 打包体积更小
    5. 更好的 Typescript 支持
    6. Composition API 组合 API

组合式 API

1. setup 函数

setup函数是组合式API的入口函数

  • setup 函数作为组合式API的起点
  • 它在 beforeCreate 之前执行
  • 函数中 this 不是组件实例,是 undefined
  • 如果数据或者函数在模板中使用,需要在 setup 返回
<template>
  <div class="container">
    <h1 @click="say()">{{msg}}</h1>
  </div>
</template>

<script>
export default {
  setup () {
    console.log('setup执行了')
    console.log(this)
    // 定义数据和函数
    const msg = 'hi vue3'
    const say = () => {
      console.log(msg)
    }
    // 返回给模板使用
    return { msg , say}
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)
  }
}
</script>

2. reactive 函数

reactive 函数通常定义:复杂类型的响应式数据 且 知道明确字段

  • 不可以转换简单数据

使用步骤:

  • vue 中导出 reactive 函数
  • setup 函数中,使用 reactive 函数,传入一个普通对象,返回一个响应式数据对象
  • 最后 setup 函数返回一个对象,包含该响应式对象即可,模板中可使用
<template>
  <div>
    <p>姓名:{{state.name}}</p>
    <p>年龄:{{state.age}} <button @click="state.age++">一年又一年</button></p>
  </div>
</template>

<script>
// 1. 导入函数
import { reactive } from "vue"; 
export default {
  setup() {
    // 2. 创建响应式数据对象
    const state = reactive({ name: 'tom', age: 18 })
    // 3. 返回数据
    return { state }
  }
};
</script>

3. ref 函数

通常使用它定义响应式数据,不限类型

  • ref 可以把简单数据或者复杂数据转换成响应式数据,注意在JS使用时加上 .value,不过模板可省略。

使用步骤:

  • vue 中导出 ref 函数
  • setup 函数中,使用 ref 函数,传入普通数据(简单or复杂),返回一个响应式数据
  • 最后 setup 函数返回一个对象,包含该响应式数据即可
  • 注意:使用 ref 创建的数据,js 中需要 .valuetemplate 中可省略

代码示例:

<template>
  <div>
    <p>
      计数器:{{ count }}
      <button @click="count++">累加1</button>
      <!-- template中使用可省略.value -->
      <button @click="increment">累加10</button>
    </p>
  </div>
</template>

<script>
// 1. 导入函数
import { ref } from "vue";
export default {
  setup() {
    // 2. 创建响应式数据对象
    const count = ref(0);
    const increment = () => {
      // js中使用需要.value
      count.value += 10;
    };
    // 3. 返回数据
    return { count, increment };
  },
};
</script>

4. reactive 和 ref 的选择

在定义响应式数据的函数选择上,遵循:尽量使用 ref 函数支持所有场景,确定字段的对象使用 reactive 可以省去.value

  • reactive 可以转换对象成为响应式数据对象,但是不支持简单数据类型。
  • ref 可以转换简单数据类型为响应式数据对象,也支持复杂数据类型,但是操作的时候需要 .value

推荐用法:

  • 如果能确定数据是对象且字段名称也确定,可使用 reactive 转成响应式数据,其他一概使用 ref

参考代码:

// 1. 明确表单对象有两个字段
const form = reactive({
    username: '',
    password: ''
})

// 2. 后台返回的数据对象
const data = ref(null)
const res = await axios.get('/user/100')
data.value = res.data

5. setup 语法糖

script setup 中的顶层变量都可以在模板使用,数据,函数,组件。

发现

  • 使用 setup 有几件事必须做:默认导出配置选项,setup函数声明,返回模板需要数据与函数。
<script>
export default {
  setup() {
    const say = () => console.log('hi')
    
    return { say }
  }
}
</script>

解法

  • 使用 setup 语法糖
<script setup>
  const say = () => console.log('hi')
</script>

示例代码

<script setup>
  // 显示隐藏
  const show = ref(true)
  const toggle = () => {
    show.value = !show.value
  }
  // 计数器
  const count = ref(0)
  const increment = () => {
    count.value ++
  }
</script>

<template>
  <button @click="toggle">显示隐藏图片</button>
  <img v-show="show" alt="Vue logo" src="./assets/logo.png" />
  <hr />
  计数器:{{ count }} <button @click="increment">累加</button>
</template>

6. computed 函数

使用 computed 定义计算属性,场景:当需要依赖一个数据得到新的数据使用计算属性

使用步骤:

  • vue 中导出 computed 函数
  • setup 函数中,使用 computed 函数,传入一个函数,函数返回计算好的数据
  • 最后 setup 函数返回一个对象,包含该计算属性数据即可,然后模板内使用

代码示例:

<script setup>
  import { ref, computed } from "vue";

  const scoreList = ref([80, 100, 90, 70, 60]);
  // 计算属性
  const betterList = computed(() => scoreList.value.filter((item) => item >= 90));
  // 改变数据,计算属性改变
  setTimeout(() => {
    scoreList.value.push(92, 66);
  }, 3000);

</script>

<template>
  <div>
    <p>分数:{{ scoreList }}</p>
    <p>优秀:{{ betterList }}</p>
  </div>
</template>

7. watch 函数

  • watch(需要监听的数据,数据改变执行函数,配置对象) 来进行数据的侦听
  • 数据:单个数据,多个数据,函数返回对象属性,属性复杂需要开启深度监听
  • 配置对象:deep 深度监听 immediate 默认执行
  • watch('x', () => {}, {})
7.1 使用 watch 监听一个响应式数据
<script setup>
  import { ref, watch } from "vue";
  const count = ref(0);
  // 1. 监听一个响应式数据
  // watch(数据, 改变后回调函数)
  watch(count, () => {
    console.log("count改变了");
  });
  // 2s改变数据
  setTimeout(() => {
    count.value++;
  }, 2000);
</script>

<template>
  <p>计数器:{{ count }}</p>
</template>
7.2 使用 watch 监听多个响应式数据
<script setup>
  import { reactive, ref, watch } from "vue";
  const count = ref(0);
  const user = reactive({
    name: "tom",
    info: {
      gender: "男",
      age: 18,
    },
  });
  
  // 2. 监听多个响应式数据
  // watch([数据1, 数据2, ...], 改变后回调函数)
  watch([count, user], () => {
    console.log("数据改变了");
  });
  
  // 2s改变数据
  setTimeout(() => {
    count.value++;
  }, 2000);
  
  // 4s改变数据
  setTimeout(() => {
    user.info.age++;
  }, 4000);
</script>

<template>
  <p>计数器:{{ count }}</p>
  <p>
    姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
  </p>
</template>
7.3 使用 watch 监听响应式对象数据中的一个属性(简单)
<script setup>
  import { reactive, watch } from "vue";
  const user = reactive({
    name: "tom",
    info: {
      gender: "男",
      age: 18,
    },
  });
  // 3. 监听响应式对象数据的一个数据,简单类型
  // watch(()=>数据, 改变后回调函数)
  watch(()=>user.name, () => {
    console.log("数据改变了");
  });
  // 2s改变数据
  setTimeout(() => {
    user.name = 'jack';
  }, 2000);
  // 4s改变数据
  setTimeout(() => {
    user.info.age = 60;
  }, 4000);
</script>

<template>
  <p>
    姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
  </p>
</template>
7.4 使用 watch 监听响应式对象数据中的一个属性(复杂),配置深度监听
<script setup>
  import { reactive, watch } from "vue";
  const user = reactive({
    name: "tom",
    info: {
      gender: "男",
      age: 18,
    },
  });
  // 4. 监听响应式对象数据的一个数据,复杂类型
  // watch(()=>数据, 改变后回调函数, {deep: true})
  watch(
    () => user.info,
    () => {
      console.log("数据改变了");
    },
    {
      // 开启深度监听
      deep: true,
    }
  );
  // 2s改变数据
  setTimeout(() => {
    user.info.age = 60;
  }, 2000);
</script>

<template>
  <p>
    姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
  </p>
</template>

使用 watch 监听,配置默认执行

{
    // 开启深度监听
    deep: true,
    // 默认执行一次
    immediate: true
}

8. 声明周期函数

常用的 onMounted 组件渲染完毕:发请求,操作dom,初始化图表…

  • 生命周期钩子函数可以调用多次

具体内容:

  • Vue3和vue2的生命周期对比
选项式API下的生命周期函数使用组合式API下的生命周期函数使用
beforeCreate不需要(直接写到setup函数中)
created不需要(直接写到setup函数中)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyedonBeforeUnmount
destroyedonUnmounted
activatedonActivated
deactivatedonDeactivated

代码示例:

<script setup>
  import { onMounted } from "vue";
  // 生命周期函数:组件渲染完毕
  onMounted(()=>{
    console.log('onMounted触发了')
  })

  onMounted(()=>{
    console.log('onMounted也触发了')
  })
</script>

<template>
  <div>生命周期函数</div>
</template>

9. ref 获取 DOM 元素

元素上使用 ref属性关联响应式数据,获取DOM元素

  • 默认值是null,需要在渲染完毕后访问DOM属性

步骤:

  1. 创建 ref => const hRef = ref(null)
  2. 模板中建立关联 => <h1 ref="hRef">我是标题</h1>
  3. 使用 => hRef.value

代码:

<script setup>
import { ref } from 'vue'

const hRef = ref(null)  
const clickFn = () => {
  hRef.value.innerText = '我不是标题'
}
</script>

<template>
  <div>
    <h1 ref="hRef">我是标题</h1>
    <button @click="clickFn">操作DOM</button>
  </div>
</template>

10. ref操作组件 - defineExpose

  • 配合 defineExpose 暴露数据和方法,ref获取的组件实例才可以使用

步骤:

  • 使用 <script setup> 的组件是默认关闭的,组件实例使用不到顶层的数据和函数。
  • 需要配合 defineExpose 暴露给组件实例使用,暴露的响应式数据会自动解除响应式。

代码示例:

<script setup>
import { ref } from 'vue'

const count = ref(0)
const validate = () => {
  console.log('表单校验方法')
}

// 暴露属性给外部组件使用
defineExpose({count, validate})
</script>

<template>
  <h3>我是Form组件</h3>
</template>

ref操作组件

<script setup>
import { ref } from 'vue'
import Form from './components/Form.vue'

// 1. 提供一个ref
const formRef = ref(null)
// 2. 使用组件组件和方法
const fn = () => {
  console.log(formRef.value.count)
  formRef.value.validate()
}
</script>

<template>
  <Form ref="formRef"></Form>
</template>

11. 父传子 - defineProps 函数

  • 如果使用 defineProps 接收数据,这个数据只能在模板中渲染
  • 如果想要在 script 中也操作 props 属性,应该接收返回值

步骤:

  1. 父组件提供数据
  2. 父组件将数据传递给子组件
  3. 子组件通过 defineProps 进行接收
  4. 子组件渲染父组件传递的数据

代码:

ParentCom.vue

<script setup>
import { ref } from 'vue'
import ChildCom from './components/ChildCom.vue'

const money = ref(100)
const car = ref('玛莎拉蒂')
</script>

<template>
  <div>
    <h1>我是父组件</h1>
    <div>金钱:{{ money }}</div>
    <div>车辆:{{ car }}</div>
    <hr />
    <ChildCom :money="money" :car="car"></ChildCom>
  </div>
</template>

ChildCom.vue

<script setup>
import { computed } from 'vue'

// defineProps: 接收父组件传递的数据
const props = defineProps({
  money: Number,
  car: String,
})
// 使用props
console.log(props.money)
</script>

<template>
  <div>
    <h3>我是子组件</h3>
    <div>{{ money }} --- {{ car }}</div>
  </div>
</template>

12. 子传父 - defineEmits 函数

  • defineEmits 获取 emit 函数,且组件需要触发的事件需要显性声明出来

步骤:

  1. 子组件通过 defineEmits获取 emit 函数(因为没有this)
  2. 子组件通过 emit 触发事件,并且传递数据
  3. 父组件提供方法
  4. 父组件通过自定义事件的方式给子组件注册事件

代码:

ChildCom.vue

<script setup>
defineProps({
  money: Number,
  car: String,
})

// 得到emit函数,显性声明事件名称
const emit = defineEmits(['changeMoney'])
const change = () => {
  emit('changeMoney', 10)
}
</script>

PrarentCom.vue

<script setup>
import { ref } from 'vue'
import ChildCom from './components/ChildCom.vue'

const money = ref(100)
const car = ref('玛莎拉蒂')
const changeMoney = (num) => {
  money.value = money.value - num
}
</script>
<ChildCom :money="money" :car="car" @changeMoney="changeMoney"></ChildCom>

13. 跨级组件通讯provide与inject函数

  • provide 和 inject 是解决跨级组件通讯的方案
    • provide 提供后代组件需要依赖的数据或函数
    • inject 注入(获取)provide提供的数据或函数
  • 官方术语:依赖注入
    • App是后代组件依赖的数据和函数的提供者,Child是注入(获取)了App提供的依赖

落地代码:

  • 祖先组件:App.vue
<script setup>
import { provide, ref } from 'vue';
import ParentCom from './ParentCom.vue';

// 1. app组件数据传递给child
const count = ref(0);
provide('count', count);

// 2. app组件函数传递给child,调用的时候可以回传数据
const updateCount = (num) => {
  count.value += num;
};
provide('updateCount', updateCount);
</script>

<template>
  <div
    class="app-page"
    style="border: 10px solid #ccc; padding: 50px; width: 600px"
  >
    app 组件 {{ count }} updateCount
    <ParentCom />
  </div>
</template>
  • 父级组件:ParentCom.vue
<script setup>
import ChildCom from './ChildCom.vue';
</script>

<template>
  <div class="parent-page" style="padding: 50px">
    parent 组件
    <hr />
    <ChildCom />
  </div>
</template>
  • 子级组件:ChildCom.vue
<script setup>
const count = inject('count');
const updateCount = inject('updateCount');
</script>

<template>
  <div class="child-page" style="padding: 50px; border: 10px solid #ccc">
    child 组件 {{ count }} <button @click="updateCount(100)">修改count</button>
  </div>
</template>

14. 保持响应式 - toRefs 函数

当去解构和展开响应式数据对象使用 toRefs 保持响应式

  • 作用:把对象中的每一个属性做一次包装成为响应式数据
  • 响应式数据展开的时候使用,解构响应式数据的时候使用

落地代码:

  • 基础案例
<script setup>
import { reactive } from "vue";
const user = reactive({ name: "tom", age: 18 });
</script>

<template>
  <div>
    <p>姓名:{{ user.name }}</p>
    <p>年龄:{{ user.age }} <button @click="user.age++">一年又一年</button></p>
  </div>
</template>
  • 使用响应式数据,踩坑
<script setup>
import { reactive } from "vue";
const { name, age } = reactive({ name: "tom", age: 18 });
</script>

<template>
  <div>
    <p>姓名:{{ name }}</p>
    <!-- 响应式丢失 -->
    <p>年龄:{{ age }} <button @click="age++">一年又一年</button></p>
  </div>
</template>
  • 使用 toRefs 处理响应式数据,爬坑
import { reactive, toRefs } from "vue";
const user = reactive({ name: "tom", age: 18 });
const { name, age } = toRefs(user)

TypeScript

TypeScript 是一种带有 类型语法 的 JavaScript 语言,在任何使用 JavaScript 的开发场景中都可以使用。

  • TS 需要编译才能在浏览器运行。

TypeScript 文档

官方网站:https://www.typescriptlang.org/

中文官网: https://www.tslang.cn/

TypeScript 编译

全局安装:

# npm 安装
npm i -g typescript
# yarn 安装
yarn global add typescript
# 部分mac电脑安装需要sudo权限
# sudo npm i -g typescript
# sudo yarn global add typescript

查看版本:

tsc -v

编译 TS:

  • 新建 hello.ts 文件
  • 当前目录打开命令行窗口,执行 tsc hello.ts 命令,同级目录生成 hello.js 文件
  • 执行 node hello.js 验证一下

创建 vue-ts 项目

# npm 6.x
npm create vite@latest my-vue-ts-app --template vue-ts

# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-ts-app -- --template vue-ts

# yarn
yarn create vite my-vue-ts-app --template vue-ts

# pnpm
pnpm create vite my-vue-ts-app --template vue-ts

TypeScript 核心

1. 类型注解

变量后面约定类型的语法,就是类型注解

  • : number 就是类型注解,它为变量提供类型约束。
  • 约定了什么类型,就只能给该变量赋值什么类型的值,否则报错。

示例代码:

// 约定变量 age 的类型为 number 类型
let age: number = 18;
age = 19;

2. 原始类型

  • JS 已有类型
    • 简单类型,number string boolean null undefined
    • 复杂类型,对象 数组 函数
  • TS 新增类型
    • 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any、泛型 等

原始类型:

  • 使用简单,完全按照 JS 的类型来书写即可
let age: number = 18;
let myName: string = '黑马程序员';
let isLoading: boolean = false;
let nullValue: null = null;
let undefinedValue: undefined = undefined;

3. 数组类型

推荐使用:

  • number[] 写法
  • 写法 1
let numbers: number[] = [1, 3, 5];
  • 写法 2
let strings: Array<string> = ['a', 'b', 'c'];

4. 联合类型

  • 类型与类型之间使用 | 连接,代表类型可以是它们当中的其中一种,这种类型叫:联合类型

代码示例:

数组中有 numberstring 类型

let arr: (number | string)[] = [1, 'a', 3, 'b'];

定时器添加类型:

let timer: number | null = null;
timer = setInterval(() => {}, 1000);

5. 类型别名

  • type 类型别名 = 具体类型 基本语法
  • 定义类型别名,遵循大驼峰命名规范,类似于变量
  • 使用类型别名,与类型注解的写法一样即可

示例代码:

// let arr: ( number | string )[] = [ 1, 'a', 4]
// 类型别名: type 类型别名 = 具体类型
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];

使用场景:

  • 当同一类型(复杂)被多次使用时,可以通过类型别名,简化 该类型的使用
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];
let arr2: CustomArr = [2, 'b', 8];

6. 函数类型

  • 给函数指定类型,其实是给 参数返回值 指定类型。
  • 两种写法:
    • 在函数基础上 分别指定 参数和返回值类型
    • 使用类型别名 同时指定 参数和返回值类型
  • 通过类似箭头函数形式的语法来为函数添加类型,只适用于 函数表达式

示例代码 1:分别指定

// 函数声明
function add(num1: number, num2: number): number {
  return num1 + num2;
}

// 箭头函数
const add = (num1: number, num2: number): number => {
  return num1 + num2;
};

示例代码 2:同时指定

type AddFn = (num1: number, num2: number) => number;

const add: AddFn = (num1, num2) => {
  return num1 + num2;
};

7. void 类型

如果函数没有返回值,定义函数类型时返回值类型为 void

  • 如果函数没有返回值,且没有定义函数返回值类型的时候,默认是 void
  • JS 中如果没有返回值,默认返回的是 undefined,但是 voidundefinedTypeScript 中并不是一回事,如果指定返回值类型是 undefined 那返回值必须是 undefined

代码示例:

const say = (): void => {
  console.log('hi');
};
const say = () => {
  console.log('hi');
};

**注意事项:**在 JS 中如果没有返回值,默认返回的是 undefined,但是 voidundefinedTypeScript 中并不是一回事,如果指定返回值类型是 undefined 那返回值必须是 undefined

const add = (): undefined => {
  return undefined;
};

8. 可选参数

使用 ? 将参数标记为可选

如果函数的参数,可以传也可以不传,这种情况就可以使用 可选参数 语法,参数后加 ? 即可

  • 必选参数必须在可选参数的前面
    • (start?: number, end: number) 错误的写法

代码示例:

const fn = (n?: number) => {
  // ..
};
fn();
fn(10);
const mySlice = (start?: number, end?: number) => {
  console.log('起始Index:', start, '结束Index:', end);
};
mySlice();
mySlice(1);
mySlice(1, 2);

9. 对象类型

基本使用:

  • TS 的对象类型,其实就是描述对象中的 属性 方法 的类型,因为对象是由属性和方法组成的。
// 空对象
let person: {} = {};

// 有属性的对象
let person: { name: string } = {
  name: '同学',
};

// 有属性和方法,一行书写多个属性 ; 分隔
let person: { name: string; sayHi(): void } = {
  name: 'jack',
  sayHi() {},
};

// 换行写可以省略 ; 符号
let person: {
  name: string;
  sayHi(): void;
} = {
  name: 'jack',
  sayHi() {},
};

扩展用法:

  • 函数使用箭头函数类型
let person: {
  name: string
  sayHi: () => void
} = {
  name: 'jack',
  sayHi() {},
};
  • 对象属性可选
// 例如:axios({url,method}) 如果是 get 请求 method 可以省略
const axios = (config: { url: string; method?: string }) => {};
  • 使用类型别名
// {} 会降低代码可阅读性,建议对象使用类型别名
// const axios = (config: { url: string; method?: string }) => {};
type Config = {
  url: string;
  method?: string;
};
const axios = (config: Config) => {};
  • 使用声明描述对象结构?{}
  • 属性怎么写类型?属性名: 类型
  • 方法怎么写类型? 方法名(): 返回值类型
  • 对象的方法使用箭头函数类型怎么写?{sayHi:()=>void}
  • 对象的可选参数怎么设置?{name?: string}
  • 对象类型会使用 {} 如何提供可阅读性?类型别名

10. 接口 interface

接口声明是命名对象类型的另一种方式

定义:interface A {} 继承:interface A extends B {}

继承后 接口A 拥有 接口B 的所有属性和函数的类型声明

继承:

  • 相同的属性或展示可以抽离出来,然后使用 extends 实现继承复用
interface Point2D {
  x: number;
  y: number;
}
// 继承 Point2D
interface Point3D extends Point2D {
  z: number;
}
// 继承后 Point3D 的结构:{ x: number; y: number; z: number }

type 交叉类型:

使用 & 可以合并连接的对象类型,也叫:交叉类型

  • 实现 Point2D{z: number} 类型合并得到 Ponit3D 类型
// 使用 type 来定义 Point2D 和 Point3D
type Point2D = {
  x: number;
  y: number;
};

// 使用 交叉类型 来实现接口继承的功能:
// 使用 交叉类型 后,Point3D === { x: number; y: number; z: number }
type Point3D = Point2D & {
  z: number;
};

let o: Point3D = {
  x: 1,
  y: 2,
  z: 3,
};

11. 类型推断

在 TS 中存在类型推断机制,在没有指定类型的情况下,TS 也会给变量提供类型。

  • 能省略类型注解的地方就省略,充分利用TS推断 的能力,提高开发效率
  • 不知道类型怎么写,可以把鼠标放至变量上,可以通过 Vscode 提示看到类型

发生类型推断的几个场景场景:

  • 声明变量并初始化时
// 变量 age 的类型被自动推断为:number
let age = 18;
  • 决定函数返回值时
// 函数返回值的类型被自动推断为:number
const add = (num1: number, num2: number) => {
  return num1 + num2;
};

12. 字面量类型

使用 js字面量 作为变量类型,这种类型就是字面量类型

js 字面量如:18 'jack' ['a'] {age: 10}

代码示例:

// : 'jack' 是字面量类型
let name: 'jack' = 'jack';
// : 18 是字面量类型
let age: 18 = 18;

// 报错:不能将类型“19”分配给类型“18”
age = 19;

let str1 = 'Hello TS'; // 类型 string
const str2 = 'Hello TS'; // 类型 Hello TS
// 原因:str2 是 const 声明的,值只能是 Hello TS,所以类型只能是 Hello TS

类型应用:

例如:性别只能是 男 和 女,不会出现其他值。

// let gender = '男'
// gender = '女'
// ------------------------
type Gender = '男' | '女'
let gender: Gender = '男'
gender = '女'

// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
  console.log(direction)
}
// 调用函数时,会有类型提示:
changeDirection('up')

13. any 类型

any 类型的作用是逃避 TS 的类型检查

  • any 的使用越多,程序可能出现的漏洞越多,因此不推荐使用 any 类型,尽量避免使用。
  • 显式any情况:当变量的类型指定为 any 的时候,不会有任何错误,也不会有代码提示,TS会忽略类型检查
let obj: any = { age: 18 }
obj.bar = 100
obj()
const n: number = obj
// 以上的代码虽然没有报错提示,但是将来是可能出现错误的
  • 隐式any的情况:声明变量不给类型或初始值,函数参数不给类型或初始值
// 声明变量不给类型或初始值
let a;
// 函数参数不给类型或初始值
const fn = (n) => {}

14. 类型断言

你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型

使用 as 关键字实现类型断言

// aLink 的类型 HTMLElement,该类型只包含所有标签公共的属性或方法
// 这个类型太宽泛,没包含 a 元素特有的属性或方法,如 href
const aLink = document.getElementById('link')
  • 但是我们明确知道获取的是一个 A 元素,可以通过 类型断言 给它指定一个更具体的类型。
const aLink = document.getElementById('link') as HTMLAnchorElement
  • 解释:
    1. 使用 as 关键字实现类型断言
    2. 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)
    3. 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了

例如:

const img = document.getElementById('img') as HTMLImageElement
// 如果不知道标签的类型:document.querySelector('div') 鼠标摸上去就可以看见

15. 泛型

在TypeScript中,泛型是一种创建可复用代码组件的工具。这种组件不只能被一种类型使用,而是能被多种类型复用。类似于参数的作用,泛型是一种用以增强类型(types)、接口(interfaces)、函数类型等能力的非常可靠的手段。

15.1 泛型别名
  • 泛型:定义类型别名后加上<类型参数> 就是泛型语法, 使用的时候传入具体的类型即可
  • <T> 是一个变量,可以随意命名,建议遵循大驼峰即可。
  • 和类型别名配合,在类型别名后加上泛型语法,然后类型别名内就可以使用这个类型参数
  • 泛型可以提高类型的复用性灵活性

代码示例:

// 对后台返回的数据进行类型定义
type User = {
  name: string;
  age: number;
}

type Goods = {
  id: number;
  goodsName: string;
}

type Data<T> = {
  msg: string;
  code: number;
  data: T
}

// 使用类型
type UserData = Data<User>
type GoodsData = Data<Goods>
15.2 泛型接口

在接口名称的后面添加 <类型变量>,那么,这个接口就变成了泛型接口,接口中所有成员都可以使用类型变量

代码示例:

// 对象,获取单个ID函数,获取所有ID函数,ID的类型肯定是一致的,但是可能是数字可能是字符串
interface IdFn<T> {
  id: () => T;
  ids: () => T[];
}

const idObj: IdFn<number> = {
  id() { return 1 },
  ids() { return [1, 2] },
};

内置的泛型接口:

  • 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键) 去查看内置的泛型接口
const arr = [1, 2, 3];
// TS有自动类型推断,其实可以看做:const arr: Array<number> = [1, 2, 3]
arr.push(4);
arr.forEach((item) => console.log(item));
15.3 泛型函数
  • 泛型函数语法?
    • 函数名称后加上 <T>T是类型参数,是个类型变量,命名建议遵循大驼峰即可。
  • T什么时候确定?
    • 当你调用函数的时候,传入具体的类型,T 或捕获到这个类型,函数任何位置均可使用。
  • 泛型函数好处?
    • 让函数可以支持不同类型(复用),且保证类型是安全的。
  • 调用函数,什么时候可以省略泛型?
    • 传入的数据可以推断出你想要的类型,就可以省略。

代码示例:

// 函数的参数是什么类型,返回值就是什么类型
function getId<T>(id: T): T {
  return id
}

let id1 = getId<number>(1)
let id2 = getId('2')
// TS会进行类型推断,参数的类型作为泛型的类型 getId<string>('2')
// 我需要的类型 { name: string, age?: number } 但是推断出来是 { name: string}
let id2 = getId({name:'jack'})

TypeScript 与 组合式API

typescript 配合 Vue3 composition-api 使用

https://staging-cn.vuejs.org/guide/typescript/composition-api.html

  • script 加上 lang="ts" 才能写ts代码

    <script setup lang="ts"></script>

1. defineProps的TS写法

1 defineProps 的基本使用:

const props = defineProps({
  money: {
    type: Number,
    required: true
  },
  car: {
    type: String,
    required: false,
    default: '宝马车'
  }
})
console.log(props.money) // number
console.log(props.car) // string | undefined

2 defineProps 通过泛型参数来定义 props 的类型通常更直接:

const props = defineProps<{
  money: number
  car?: string
}>()

3 如果需要给 props 设置默认值,需要使用 withDefaults 函数:

const props = withDefaults(defineProps<{
  money: number;
  car?: string;
}>(),{
  car: '宝马车'
})

4 上面写法太笨拙,可以使用 响应式语法糖 解构 + defineProps 就行:

const { money, car = "宝马车" } = defineProps<{
  money: number
  car?: string
}>();

注意:目前需要 显式地选择开启 ,因为它还是一个实验性特性。

// vite.config.ts
export default defineConfig({
  plugins: [
    vue({
      reactivityTransform: true,
    }),
  ],
});

响应式语法糖 即将移除,建议使用 withDefaults 过度

2. defineEmits的TS写法

1. defineEmits 的基本用法:

const emit = defineEmits(['changeMoney', 'changeCar'])

2. defineEmits 通过泛型参数来定义,可以实现更细粒度的校验:

const emit = defineEmits<{
  (e: 'changeMoney', money: number): void
  (e: 'changeCar', car: string): void
}>()

了解:扩展TS语法 调用签名

3. ref的TS写法

ref() 会隐式的依据数据推导类型

1. 如果是简单类型,推荐使用类型推导:

// const money = ref<number>(10)

const money = ref(10)

2. 如果是复杂类型,推荐指定泛型:

type Todo = {
  id: number
  name: string
  done: boolean
}
const list = ref<Todo[]>([])

setTimeout(() => {
  list.value = [
    { id: 1, name: '吃饭', done: false },
    { id: 2, name: '睡觉', done: true }
  ]
}, 1000)

复杂数据一般是后台返回数据,默认值是空,无法进行类型推导。

4. reactive的TS写法

reactive() 也会隐式的依据数据推导类型

  • 官方:不推荐使用 reactive() 的泛型参数,因为底层和 ref() 实现不一样。

1. 默认值属性是固定的,推荐使用类型推导:

// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue3' })  

2. 根据默认值推导不出我们需要的类型,推荐使用接口或者类型别名给变量指定类型:

// 我们想要的类型:{ title: string, year?: number }
type Book = {
  title: string
  year?: number
}
const book: Book = reactive({ title: 'Vue3' })
book.year = 2022

5. computed和TS

1. computed() 会从其计算函数的返回值上推导出类型:

import { ref, computed } from 'vue'

const count = ref(100);
const doubleCount = computed(() => count.value * 2);

2. 可以通过泛型参数显式指定类型:

const doubleMoney = computed<string>(() => (count.value * 2).toFixed(2));

6. 事件处理与TS

1. 不加类型,event默认是any,类型不安全:

<script setup lang="ts">
// 提示:参数“event”隐式具有“any”类型。  
const handleChange = (event) => {
  console.log(event.target.value)
}
</script>

<template>
  <input type="text" @change="handleChange" />
</template>

2. 处理类型:

// `event` 隐式地标注为 `any` 类型,如何指定:event 类型?
// 1. @change="handleChange($event)"" 查看$event类型
// 2. 鼠标摸一下事件 @change 查看类型
const handleChange = (event: Event) => {
  // `event.target` 是 `EventTarget | null` 类型,如何指定具体类型?
  // document.querySelector('input') 查看返回值类型
  console.log((event.target as HTMLInputElement).value)
}

7. Template Ref与TS

模板 ref 需要通过一个显式指定的泛型参数,建议默认值 null

  • 注意为了严格的类型安全,有必要在访问 el.value 时使用可选链或类型守卫。
  • 这是因为直到组件被挂载前,这个 ref 的值都是初始的 null,并且在由于 v-if 的行为将引用的元素卸载时也可以被设置为 null
<script setup lang="ts">
import { ref, onMounted } from 'vue'

const el = ref<HTMLInputElement| null>(null)

onMounted(() => {
  el.value?.focus()
})
</script>

<template>
  <input ref="el" />
</template>

8. 非空断言

处理类型可能是 null 或 undefined 的值,下面的属性或函数的访问赋值:

1. 可选链

<script setup lang="ts">
import { onMounted, ref } from 'vue';

const input = ref< HTMLInputElement | null >(null)

onMounted(()=>{
  // 只能访问
  console.log(input.value?.value);
})

</script>

<template>
  <div>App组件</div>
  <input type="text" ref="input" value="abc">
</template>

2. 逻辑判断

  if (input.value) {
    console.log(input.value.value)
    input.value.value = '123'
  }

3. 非空断言

  // 一定要确定不为空!!!
  console.log(input.value!.value)
  input.value!.value = '123'

9. TypeScript类型声明文件

  • 在第三方库中的JS代码都有对应的 TS类型声明文件

什么是类型什么文件?

  • 在 TypeScript 中以 .d.ts 为后缀的文件,我们称之为 TypeScript 类型声明文件。它的主要作用是描述 JavaScript 模块内所有导出成员的类型信息。

TS 中有两种文件类型:.ts 文件 .d.ts 文件作用是啥?

  • .ts 文件:
    1. 既包含类型信息又可执行代码
    2. 可以被编译为 .js 文件,然后,执行代码
    3. 用途:编写程序代码的地方
  • .d.ts 文件:
    1. 只包含类型信息的类型声明文件
    2. 不会生成 .js 文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型
    3. 用途:为 JS 提供类型信息
9.1 内置类型声明文件

TypeScript 给 JS 运行时可用的所有标准化内置 API 都提供了声明文件,这个声明文件就是 内置类型声明文件

  • 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
  • 查看 forEach 的类型声明,在 VSCode 中会自动跳转到 lib.es5.d.ts 类型声明文件中
  • 像 window、document 等 BOM、DOM API 也都有相应的类型声明文件 lib.dom.d.ts
9.2 第三方库类型声明文件

首先,常用的第三方库都有相应的类型声明文件,只是使用的方式不同而已。

情况1:库本身自带类型声明文件

  • 比如:axios,安装后可查看 node_modules/axios 可发现对应的类型声明文件。
  • 导入 axios 后就会加载对应的类型文件,提供该库的类型声明。

情况2:由 DefinitelyTyped 提供

  • 比如:jquery,安装后导入,提示:需要安装 @types/jquery 类型声明包
  • DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
  • 当安装 @types/* 类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明

https://www.typescriptlang.org/dt/search 可以搜索是否有对应的 @types/*

9.3 自定义类型声明文件 【重要】
9.3.1 共享类型(重要)
  • 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
  • 操作步骤:
    1. 创建 index.d.ts 类型声明文件。
    2. 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
    3. 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。

src/types/data.d.ts

export type Person = {
  id: number;
  name: string;
  age: number;
};

App.vue

<script lang="ts" setup>
import { Person } from './types/data'
const p: Person = {
  id: 100,
  name: 'jack',
  age: 19
}
</script>
9.3.2 给JS文件提供类型
  • 在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。

  • declare 关键字:

    • 用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
    1. 对于 type interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
    2. 其他 JS 变量,应该使用 declare 关键字,明确指定此处用于类型声明。

add/index.js

const add = (a, b) => {
  return a + b;
};

const point = (p) => {
  console.log('坐标:', p.x, p.y);
};

export { add, point }

add/index.d.ts

declare const add: (a: number, b: number) => number;

type Position = {
  x: number;
  y: number;
};

declare const point: (p: Position) => void;

export { add , point};

main.ts

import { add , point} from './add';

add(3, 10)

point({x: 100, y: 200})

Pinia

  • Pinia 是一个状态管理工具,它和 Vuex 一样为 Vue 应用程序提供共享状态管理能力。
  • 语法和 Vue3 一样,它实现状态管理有两种语法:选项式API组合式API,我们学习组合式API语法。
  • 它也支持 Vue2 也支持 devtools,当然它也是类型安全的,支持 TypeScript
  • 可以创建多个全局仓库,不用像 Vuex 一个仓库嵌套模块,结构复杂。
  • 管理数据简单,提供数据和修改数据的逻辑即可,不像Vuex需要记忆太多的API。

知识点

1. 使用步骤:

1.1 安装
yarn add pinia
# or
npm i pinia
1.2 导入

导入实例化,当做插件使用,和其他插件使用套路相同

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')
1.4 创建仓库&使用仓库
import { defineStore } from "pinia"
import { computed, ref } from "vue"

export const useCounterStore = defineStore("counter", () => {
  return {}
})
<script setup lang="ts">
import { useCounterStore } from "./store/counter"
// store中有状态和函数
const store = useCounterStore()
</script>
1.5 进行状态管理
// state
const count = ref(100)
// getters
const doubleCount = computed(() => count.value * 2)
// mutations
const update = () => count.value++
// actions
const asyncUpdate = () => {
    setTimeout(() => {
        count.value++
    }, 1000)
}
return { count, doubleCount, update, asyncUpdate }
<template>
  APP {{ store.count }} {{ store.doubleCount }}
  <button @click="store.update()">count++</button>
  <button @click="store.asyncUpdate()">async update</button>
</template>
1.6 总结
  • 通过 const useXxxStore = defineStore('id',函数) 创建仓库得到使用仓库的函数
VuexPinia
staterefreactive创建的响应式数据
getterscomputed 创建的计算属性
mutations 和 actions普通函数,同步异步均可
  • 使用Pinia与在组件中维护数据大体相同,这就是 Pinia 的状态管理基本使用

2. storeToRefs的使用

问题:

  • 当我们想解构 store 提供的数据时候,发现数据是没有响应式的。

回忆:

  • 在学习 vue 组合式API创建的响应式数据的时候,使用 toRefs 保持结构后数据的响应式

方案:

  • 使用 storeToRefs 解决解构仓库状态丢失响应式的问题

代码:

import { storeToRefs } from 'pinia'

const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)

小结:

  • 当你想从 store 中解构对应的状态使用,需要使用 storeToRefs
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值