Vue3学习+ElementPlus应用+typescript(ts)的入门到熟练掌握

Vue3的学习+ElementPlus应用+TypeScript(ts)

vue3介绍

  • 2020 年 9 月 18 日发布,许多开发者还在观望
  • 2022 年 2 月 7 日称为默认版本
    vue3的组件库

vite 项目构建工具

  • vite是一种新型前端构建工具,能够显著提升前端开发体验。
  • 对比webpack:需要查找依赖先打包,然后启动开发服务器,HRM(热更新)更新速度会随着代码体积增加越来越慢,webpack刚开始启动的时候很快,因为是直接运行好了相关的文件再去到浏览器上,随着项目的复杂度越来越高,代码量越来越大,启动会很缓慢
  • vite的原理:直接启动开发服务器,使用原生ESModule通过script 标签动态导入,访问页面的时候加载到对应模块编译并响应 这样,在加载过的文件就不需要再进行编译了,在浏览器中会自动“静态”展示
  • 注明:项目打包的时候最终还是需要打包成静态资源的,打包工具 Rollup
    • 基于 webpack 构建项目,基于 vite 构建项目,谁更快体验更好?vite

vite创建一个项目

  • 使用vite构建工具创建项目的3种方式(直接终端运行):
    • npm create vue@latest
    • yarn create vue
    • pnpm create vue
      • 第一次使用此命令需要安装:npm install pnpm -g
  • 创建流程如下
    • 输入项目名称,默认是 vite-project
    • 选择前端框架
    • 选择语言类型
    • 创建完成
      目录结构目录结构图
  • 进入项目目录,安装依赖,启动项目即可
    • npm install(或者npm i)
    • npm run dev

Vue3对比Vue2的语法变化

  • Vue3提供两种组织代码逻辑的写法:
    • 第一种(选项式API写法):通过data、methods、watch 等配置选项组织代码逻辑,vue2常用
    • 第二种(组合式API写法):所有逻辑在setup函数中,使用 ref、watch 等函数组织代码,vue3常用
  • 对比如下:
    • option api:
<template>
  <button @click="add">隐藏显示图片</button>
  <img v-show="show" alt="Vue logo" src="/src/assets/vue.svg" />
  <hr/>
  计数器:{{ count }} <button @click="increment">累加</button>
</template>
<script>
export default {
  data() {
    return {
      show: true,
      count: 0,
    };
  },
  methods: {
    add() {
      this.show = !this.show;
    },
    increment() {
      this.count++;
    },
  },
};
</script>
<style>

</style>
  • composition api
<template>
  <button @click="add">隐藏显示图片</button>
  <img v-show="show" alt="Vue logo" src="/public/vite.svg" />
  <hr />
  计数器:{{ count }} <button @click="increment">累加</button>
</template>
<script>
// ref 就是一个组合式API
import { ref } from 'vue';
export default {
  setup () {
    // 显示隐藏
    const show = ref(true)
    const add = () => { 
      show.value = !show.value
    }
    // 计数器
    const count = ref(10)
    const increment = () => {
      count.value ++
    }

    return { show, add, count, increment }
  }
};
</script>
  • 对比可以看出:composition api格式的template中的跟组件可以有多个,script中的写法也不同

setup函数

  • setup函数是组合式API的入口函数
    • setup 函数是 Vue3 特有的选项,作为组合式API的起点
    • 从组件生命周期看,它在 beforeCreate 之前执行,可以自己试验一下
    • 函数中 this 不是组件实例,是 undefined,所以几乎不用this关键字
    • 如果数据或者函数在模板中使用,需要在 setup 返回(需要return变量名和函数名)
    • 在vue3的项目中几乎用不到 this , 所有的东西通过函数(例如ref、reactive、computed)获取。
      一段简单使用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() {  //生命周期函数,在创建之前执行,但这里setup先执行
    console.log('beforeCreate执行了')
    console.log(this)
  }
}
</script>

setup与options API之间的关系:

  • vue3是向下兼容,也就是包容vue2
  • vue2可以调用setup函数中定义的变量
  • setup不可以调用vue2中定义的变量,而且vue3的this是undefined,不用this
  • 如果与vue2冲突,setup是优先的,而且在vue3项目中一般不会用vue2的语法(可以去写代码测试一下,vue2的methods方法中能调用vue3的setup中的函数,但是setup中不能调用methods和data中定义的变量和方法)

setup语法糖

<script setup lang = "ts"></script> : setup语法糖的格式
  • 使用语法糖要有一下步骤:
    • 1.在vscode终端运行:npm i vite-plugin-vue-setup-extend -D
    • 2.在vite.config.ts中加import VueSetupExtend from ‘vite-plugin-vue-setup-extend’
      在里面的plugin函数中加VueSetupExtend()
    • 3.:可以为组件定义名称为App

Vue3的响应式:

  • 响应式:在组件模板中改变数据时,页面跟着发生变化
    • vue2是通过Object.defineProperty来实现响应式
    • vue3 是通过 proxy实现响应式, 不支持ie浏览器10以下的 ES6的新特性
<script>
	let data = {name : 'zhangsan'}
	new Proxy(data,{
	get(target,propkey){
	    //读取值的时候调用
	},
        set(target,propkey,val){
		//修改属性值的时候调用
               target[propokey] = val
	},
        deleteProperty(target,propkey){
		//删除属性时调用
                return dlelet target[propkey] //返回true或者false
	}
}
data.name = "lisi" //代理对象
_data.age = 15 //添加属性
delete _data.age //删除属性
</script>

reactive函数

  • 在Vue.js中,reactive函数是Vue 3中引入的一个函数,用于创建一个响应式对象。响应式对象是一种能够在数据发生变化时自动更新视图的对象。使用reactive函数可以将普通的JavaScript对象转换为响应式对象。
  • 简单来说就是为引用类型数据提供响应式处理
  • reactive函数的作用是将对象转换为响应式对象,这意味着当对象的属性发生变化时,相关的视图会自动更新
  • 进行重新赋值的时候:
     let state = reactive({count:}) //reactive进行的响应式赋值
     //进行重新赋值:
      state.count = 10  //可以
      state = {count:10}  //不可以 ,重新分配一个新对象,会失去响应式可以用Object.assign
     Object.assign(state{count:10})  //可以

姓名:{{state.name}}

年龄:{{state.age}}

## ref函数
- 在`Vue3`中,`ref`函数是用于创建响应式引用的函数,它有两种使用方式:基础用法和高级用法
- ref 函数可以为基本数据类型进行响应式处理 ,ref函数中的响应式数据在script中使用时要进行.value才行
- 如果用ref函数定义了一个应用数据类型 let state = ref({count:1})
在调用时要  例如:
     - 实现count加加:state.value.count += 1   必须要.value才能操作内部的值
   - 基础用法:

```javascript
<template>
  <div>
    <p>
      计数器:{{ count.valueOf() }}
      <button @click="count++">累加1</button>
      <button @click="increment">累加10</button>
    </p>
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    // 创建响应式引用的函数
    const count = ref(0);

    const increment = () => {
      // js中使用需要.value
      count.value += 10;
    };
    // 3. 返回数据
    return { count, increment };
  },
};
</script>
  • 高级用法(就是get和set方法):
<template>
  <div>
    <p>
      计数器:{{ count.valueOf() }}
      <button @click="count++">累加1</button>
      <button @click="increment">累加10</button>
    </p>
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    get(target,propkey){
	    //读取值的时候调用
	},
        set(target,propkey,val){
		//修改属性值的时候调用
               target[propokey] = val
	}
    // 3. 返回数据
    return { target, propkey,val };
  },
};
</script>

ref函数与reactive函数的区别

  • 在Vue 3中,refreactive 都是用于创建响应式数据的函数,但它们在用法和特性上有一些区别:
    基本用法
    ref 用于创建一个包装基本类型的响应式对象,例如数字、字符串等。
import { ref } from 'vue';

const count = ref(0);

reactive 用于创建一个包装对象的响应式代理,可以包含多个属性

import { reactive } from 'vue';

const state = reactive({
  count: 0,
  name: 'John',
});

访问内部值
使用 ref 创建的响应式对象,内部值通过 .value 来访问和修改。

console.log(count.value); // 0
count.value = 1;

使用 reactive 创建的响应式对象,直接访问对象属性即可。

console.log(state.count); // 0
state.count = 1;

适用场景:
ref 适用于包装基本类型,通常用于简单的变量。

reactive 适用于包装复杂对象,例如包含多个属性的对象。

响应式性质:
ref 创建的对象是透明的,可以直接访问 .value 属性。对于计算属性和监听属性等,需要使用 computedwatch 进行处理。

reactive 创建的对象是一个代理对象,访问和修改属性时不需要额外的 .value,Vue 会自动追踪属性的变化。
性能:
ref 在包装基本类型时,相对轻量,可能在某些场景下性能更好。
reactive 适用于包装复杂对象,其代理的对象可能会引入一些额外的性能开销。

  • 总的来说,选择使用 ref 还是 reactive 取决于你要处理的数据类型和场景。对于简单的数据,ref 更为轻量和直观,而对于复杂对象,reactive 提供了更自然的语法和更强大的响应式能力。

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>

watch函数

watch函数:监听数据的变化

  • 使用 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>
  • 使用 watch 监听多个响应式数据
<script setup>
  import { reactive, ref, watch } from "vue";
  const count = ref(0);
  const user = reactive({
    name: "tom",
    age:1
  });
  
  // 2. 监听多个响应式数据
  // watch([数据1, 数据2, ...], 改变后回调函数)
  watch([count, user], () => {  //使用数组式来为多个事件进行监听
    console.log("数据改变了");
  });
  
  // 2s改变数据
  setTimeout(() => {
    count.value++;
  }, 2000);
  
  // 4s改变数据
  setTimeout(() => {
    user.age++;
  }, 4000);
</script>

<template>
  <p>计数器:{{ count }}</p>
  <p>
    姓名:{{ user.name }} 年龄:{{ user.age }}
  </p>
</template>
  • 使用 watch 监听响应式(ref函数)对象数据
<script setup>
  import { ref, watch } from "vue";
  const obj = ref({a:1})
  // 1. 监听一个响应式数据
  watch(obj, () => {
    console.log("obj-a改变了");
  },{deep:true, immediate:true});
/*
	deep:深度监听
	immediate:立即监听	
*/
  // 2s改变数据
    
  setTimeout(() => {
    obj.value.a++
  }, 2000);
</script>

<template>
  <p>计数器:{{ count }}</p>
</template>
  • 使用 watch 监听响应式(reactive函数)对象数据
<script setup>
  import { reactive, watch } from "vue";
  const obj = reactive({a:1})
  // 1. 监听一个响应式数据
  watch(obj, () => {
    console.log("obj-a改变了");
  }); //不需要deep和immediate
  // 2s改变数据
    
  setTimeout(() => {
    obj.value.a++
  }, 2000);
</script>

<template>
  <p>计数器:{{ count }}</p>
</template>
  • 总结:
    • watch(需要监听的数据,数据改变执行函数,配置对象) 来进行数据的侦听
    • 数据:单个数据,多个数据,函数返回对象属性,属性复杂需要开启深度监听
    • 配置对象:deep 深度监听 immediate 默认执行

生命周期函数

  • 使用步骤:
      1. 先从vue中导入以on打头的生命周期钩子函数
      1. 在setup函数中调用生命周期函数并传入回调函数
      1. 生命周期钩子函数可以调用多次
  • Vue3和vue2的生命周期对比:
    vue2和vue3的生命周期对比变化
    使用的代码:
<script setup>
  import { onMounted } from "vue";
  // 生命周期函数:组件渲染完毕
  onMounted(()=>{
    console.log('onMounted触发了')
  })

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

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

自定义hook

  • hook:多个组件中通用代码放到hook中,例如一些计算的过程什么的,然后哪个组件需要用引入到哪个组件就可以了,hook文件都是use开头
  • 代码:
引入用到的vue函数:
   例如: import {ref,reactive,computed.....} form "vue"
 写要实现功能的代码并返回:
      例如:
useSum.ts/js:
<script>
 import {ref} form "vue"
  export default function(){
	let sun = ref(0)

	const increment = ()=>{
		sum.value +=1
	}
	return {sum,increment}  //向外部暴露数据
}
</script>
//App.vue要引用:
<script>
    import useSum from "./hooks/userSum"
   //结构赋值:
  let {sum,increment} = userSum()
  </script>

获取DOM元素

元素上使用 ref属性关联响应式数据,获取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>

其中:ref的属性值要和template中定义的ref(null)变量值一致

ref操作组件-defineExpose

  • 使用 <script setup> 的组件是默认关闭的,组件实例使用不到顶层的数据和函数。
  • 需要配合 defineExpose 暴露给组件实例使用,暴露的响应式数据会自动解除响应式。
    代码展示:
    Person.vue
<template>
    <div>
           我是被操作组件 
    </div>
</template>

<script setup>
import {ref,defineExpose} from "vue"
let name = "lisi"
let age = ref(20)
const transfer = ()=>{
    console.log("ref操作组件成功")
}

defineExpose({
    name,
    age,
    transfer
})

</script>

<style lang="scss" scoped>

</style>

App.vue

<template>
  <div>
    <person ref = "personRef"></person>
    <button @click = "fr">操作</button>
  </div>
</template>

<script setup>
import {ref} from "vue"
import person from "./Person.vue"

//创建一个ref
const personRef = ref(null) //需要一个变量去接受才能在script中使用
const fr = ()=>{
  console.log(personRef.value.age)
  personRef.value.transfer()
}

</script>

<style lang="scss" scoped>

</style>

父传子-defineProps函数&&子传父defineEmits函数

  • 父传子:
      1. 父组件提供数据
      1. 父组件将数据传递给子组件
      1. 子组件通过 defineProps 进行接收
      1. 子组件渲染父组件传递的数据
  • 子传父:
      1. 子组件通过 defineEmit 获取 emit 函数(因为没有this)
      1. 子组件通过 emit 触发事件,并且传递数据
      1. 父组件提供方法
      1. 父组件通过自定义事件的方式给子组件注册事件
        代码块:
        Parent.vue
<template>
    <div>
        <h1>父组件</h1>
        <children :money = "money" :carName = "carName" @changeMoney = "changeMoney"></children>
        <p>
            车型:{{money}}
            价格:{{carName}}
        </p>
    </div>
</template>

<script setup>
import {ref} from "vue"
import children from "./Children.vue"

const money = ref(100)
const carName = ref("兰博基尼")

const changeMoney = ()=>{
  money.value = 101
}
</script>

<style lang="scss" scoped>

</style>

children.vue

<template>
    <div>
        <h1>子组件</h1>
        <div>{{money}}----{{carName}}</div>
        <button @click = "change">change</button>
    </div>
</template>

<script setup>
import {ref} from "vue"
import parent from "./App.vue"
// let props = defineProps(['money','carName']) //数组方式接收
const props = defineProps({ //子传父
    money:Number,
    carName:String
})//定义一个变量来接收,才能使用

const emit = defineEmits(['changeMoney']) //父传子
const change = ()=>{
   emit('changeMoney',101)
}

</script>

<style lang="scss" scoped>

</style>
  • 总结:在进行传值的时候,接收的那边要定义一个变量来接收,才能在script中使用
    其实不管是子传父还是父传子,都是在子组件中进行接收/调用,父组件负责展示

兄弟组件的传递

  • vue2:全局事件总线
  • vue3:通过第三方插件mitt来进行创建emitter对象,实现传递

跨级组件通讯provide与inject函数

  • 通过provide和inject函数可以简便的实现跨级组件通讯
    祖先组件:
    App.vue:
<script setup>
import { provide, ref } from 'vue';
import Parent from './Parent.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
    <Parent><Parent />
  </div>
</template>

parent.vue:

<template>
  <div class="parent-page" style="padding: 50px">
    parent组件
    <children></children>
  </div>
</template>

<script setup>
import  children  from "./Children.vue";
</script>

<style lang="scss" scoped>

</style>

children.vue:

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

<script setup>
import {inject} from "vue"

const count = inject('count');
const updateCount = inject('updateCount');
</script>

<style lang="scss" scoped>

</style>

总结:

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

保持响应式toRefs函数

  • 在使用reactive创建的响应式数据被展开或解构的时候使用toRefs保持响应式
  • 当去解构和展开响应式数据对象使用 toRefs 保持响应式
  • 作用:把对象中的每一个属性做一次包装成为响应式数据
<template>
  <div>
    <p>姓名:{{name}}</p>
    <!-- 响应式数据丢失 -->
    <p>年龄:{{age}}</p>
    <button @click = "age++">长大一年</button>
  </div>
</template>

<script setup>
import {reactive,toRefs} from "vue"

const user = reactive({name:"zhangshan",age:20})

//处理响应式数据
const { name,age } = toRefs(user) //解构出引用数据类型的响应数据

</script>

<style lang="scss" scoped>

</style>

Vue3路由切换

  • npm i router 下载router
    router/index.ts中:
import {createRouter,createWebHashHistory} from ''vue-router"
 //指定路由类型为createWebHashHistory
const router = crateRouter({
	history:createWebHashHistory(),
        routers:[
		path:"/home",
  		component:()=>import()
	]
})
  • main.ts引入路由
  • App.vue
    路由路径:
<router-link to = "/home"></router-link>
		<router-link to = "/person"></router-link>

路由占位符:

<router></router>

上面是声明式路由导航

编程式导航:

<router-link to = "/goperson"></router-link>

App.vue引入 :

import {useRouter} from 'vue-router'
let router = useRouter() //路由实例对象
const goperson = ()=>{
	router.push('/home')// 无参
        router.push({
		path:'/home',query:{id:100}
	})  //带参式
}

基础案例练习

需求说明,使用组合式API实现:

界面原型

  • 创建项目
  • 安装element-plus组件库(vue3版本)
    elementPlus官网:https://s-test.belle.cn/zh-CN/component/table.html
npm install element-plus --save  //安装elementPlus
  • 引入element-plus组件库
main.ts中:
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
//elemenyPlus组件和样式的引入
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

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

全部引入完成后,再安装一个axios请求的依赖:npm install axios即可
获取表格中数据的链接:https://applet-base-api-t.itheima.net/api/cart
App.vue代码:

<template>
<div class="app">
 <el-table :data = "list" border>
   <el-table-column label = "ID" prop = "goods_id"></el-table-column>
   <el-table-column label = "商品" prop = "goods_name" width = "150"></el-table-column>
   <el-table-column label = "价格" prop = "goods_price"></el-table-column>
   <el-table-column label = "操作" width = "100">
     <template v-slot = "scope">
       <el-button type = "danger" link @click="deleteRow(scope)">删除</el-button>
     </template>
   </el-table-column>
 </el-table>
</div>
</template>

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

const list = ref([])
//表示在页面加载时执行getList()方法,要写在前面
  onMounted(() =>{
    getList()
  })
  //获取列表数据
const getList = async ()=>{
  const res = await axios.get('https://applet-base-api-t.itheima.net/api/cart')
  list.value = res.data.list
  //console.log(res)
}
/*
也可以这样写:
onMounted(()=>{
	let getList = async ()=>{
	  const res = await axios.get('https://applet-base-apit.itheima.net/api/cart')
	  list.value = res.data.list
	  //console.log(res)
	}
})
*/

  //删除数据功能实现
  const deleteRow = (scope) =>{
    list.value = list.value.filter(item =>item.goods_id !== scope.row.goods_id) //将选中的id取反,然后再把它filter了就可以实现删除了
  }
</script>

<style scoped>
.app{
  width: 980px;
  margin: 100px auto 0;
  margin: 20px
}
</style>

typeScript

  • TypeScript(缩写为TS)是一种由Microsoft(微软)开发的开源编程语言,它是JavaScript的一个超集(在Javascript的基础上开发和发展起来的)。这意味着任何有效的JavaScript代码都是有效的TypeScript代码,但反之则不一定成立。TypeScript通过添加静态类型和其他一些新特性,使得代码更易于维护、理解和调试。
  • 官网:https://www.typescriptlang.org/
  • TypeScript 的关键特性和概念:
    1. 静态类型系统: TypeScript引入了静态类型系统,允许在编写代码时定义变量的类型。这有助于在开发过程中捕获潜在的错误,并提供更好的代码补全和文档。
    2. 类型注解: 开发者可以为变量、函数参数和返回值等显式地添加类型注解,以明确指定变量的数据类型。
    3. 接口(Interfaces): TypeScript支持接口,允许定义代码结构的合同。这有助于在多人协作开发时确保代码的一致性和互操作性。
    4. 类(Classes): TypeScript支持类,使得面向对象编程更加直观,并且可以使用许多ES6+的类特性。
    5. 泛型(Generics): TypeScript支持泛型,使开发者能够编写灵活且可重用的代码。
    6. 编译时类型检查: TypeScript代码在运行之前会被编译成纯JavaScript代码。在这个过程中,编译器会执行类型检查,帮助开发者发现并修复潜在的问题。
    7. 工具支持: TypeScript具有强大的集成开发环境(IDE)支持,如Visual Studio Code,WebStorm等,提供智能代码补全、错误检测等功能。
    8. 社区和生态系统: TypeScript在社区中得到广泛采用,并且许多流行的JavaScript库和框架都提供了专门为TypeScript设计的类型定义。
    • 总体而言,TypeScript为JavaScript开发者提供了一种更强大、更可维护的工具,尤其是在大型项目中。通过引入静态类型和其他面向对象的概念,TypeScript有助于提高代码质量、可读性和可维护性。

TypeScript编译(这里我使用的是WebStorm)

  • 安装 TypeScript 编译器:首先,确保你已经安装了 Node.js 和 npm(Node.js 包管理器)。然后,使用以下(任选一种)命令安装 TypeScript:
    • npm install -g typescript
    • yarn global add typescript
  • 入门案例:
    新建一个demo01.ts
//静态类型,表示可以直接指定类型
let name1:string = "cheng_CSDN"
console.log(name1)

打开左下第二个的命令提示符
在这里插入图片描述

  • 注意:要将目录调到最终路径这里,不然找不到
    执行 tsc demo01.ts生成demo01.js文件,然后右键点击js文件运行就ok了

  • 如何直接执行TS代进行测试:

    • ts-node 是一个用于在 Node.js 环境中运行 TypeScript 脚本的工具。通过 npm 安装 ts-node
    • npm install -g ts-node
      • ts-node -v(验证是否成功)
    • 测试代码:可以使用如下指令执行:ts-node-esm demo1.ts

    TypeScript数据类型

    静态类型系统

    demo01.ts:

//静态类型,表示可以直接指定类型
let name1:string = "cheng_CSDN"
console.log(name1)

//联合类型
//  | 表示 or
let cheng:String | number; //指定了该数据既可以是String类型,也可以是number类
cheng = '123';
console.log(cheng)
cheng = 123;
console.log(cheng)
cheng = true

执行结果:
在这里插入图片描述
很显然,不能赋予没有联合的类型数据

数据类型的概括

  1. 基本数据类型

    • number: 用于表示数字,可以是整数或浮点数。

    • string: 表示文本字符串。

    • .boolean: 表示布尔值,即 truefalse

    • null: 表示一个空值或空引用。

    • undefined: 表示未定义的值。

    • symbol: 表示唯一的、不可变的值。

const cheng01:symbol = Symbol("symbol")
const cheng02:symbol = Symbol("symbol")
//测试cheng01和cheng02是否相等
console.log(cheng01 === cheng02) //false
  1. 复合数据类型:

    • array: 表示数组。可以使用泛型来定义数组元素的类型。
    • tuple: 表示一个元素数量和类型固定的数组。
    • object: 表示对象。在 TypeScript 中,你可以使用接口(interface)来定义对象的结构。
    • enum: 表示枚举类型。
  2. 特殊类型(了解):

    • any: 表示任意类型,取消了类型检查。
    • void: 表示没有任何类型,通常用于函数没有返回值的情况。
    • never: 表示永远不会返回值的类型,通常用于抛出异常或无限循环的函数。
  • 这只是 TypeScript 中一些基本和常见的数据类型。TypeScript 的类型系统相对灵活,还支持更多高级和复杂的类型用法,使得开发者能够更精确地定义和使用类型。

Array数据类型

  • 基本数据类型
let array1:number[] = [1,2,11,43,4435,35435]
let array2:String[] = ['cheng1','cheng2','cheng3']
//可修改
array1[3] = 1902
array2[2] = 'cheng_CSDN'
  • Readonly 数组:

let readonlyArray:readonly string[] = ['z','a','b','nbvc']
//不可修改
//readonlyArray[2] = 'asd'  //直接报错
  • for循环

//基本的for循环
let colors: string[] = ['red', 'green', 'blue'];

for (let i = 0; i < colors.length; i++) {
    console.log(colors[i]);
}


//forEach循环
let fruits: string[] = ['apple', 'banana', 'orange'];

fruits.forEach(fruit => {
    console.log(fruit);
});


//for let of 循环
let numbers: number[] = [1, 2, 3];

for (let num of numbers) {
    console.log(num);
}

Tuple类型

  • 元组(Tuple)是一种有序的、固定长度的数组类型。与普通数组不同的是,元组可以包含不同类型的元素,并且元素的类型在整个元组中是有序的。定义元组类型时,需要指定每个位置上元素的类型。
    演示代码:
let TupleArray = ['String','number'] //规定了第一个元素只能是String类型。第二个元素只能是number类型数据
TupleArray[0] = 'asd';
TupleArray[1] = 123
cosole.log(TupleArray[0]) //asd
cosole.log(TupleArray[1]) //123

TupleArray[0] = 123 //报错
TupleArray[1] = 'nba' //报错

// 错误示例,因为长度不匹配
myTuple = [10, "Hello", true]; // 错误,类型不匹配
  • 元组的使用场景包括从函数返回多个值、处理异构数据等情况。例如,一个函数可以返回一个包含多个不同类型值的元组:
function getUserInfo(): [string, number] {
  return ["John Doe", 30];
}

const userInfo = getUserInfo();
console.log(userInfo[0]); // 输出: John Doe
console.log(userInfo[1]); // 输出: 30
  • 需要注意的是,元组的每个位置上的类型是固定的,而且不能通过 push 等方法在运行时动态添加元素。如果需要处理可变长度的集合,应该使用普通数组。

  • 虽然在TypeScript中元组(Tuple)和数组(Array)都用于存储多个元素,但它们之间存在一些关键的区别:

    1. 元素类型约束:
      • 数组: 数组中的所有元素都可以是相同的类型,但也可以是不同类型。你可以使用泛型表示数组的元素类型,例如 number[] 表示元素类型为数字的数组。
      • 元组: 元组是一个固定长度的数组,每个位置上的元素有确定的类型。在元组中,每个位置的类型都是预定义的,并且元组的长度是固定的。
 // 数组
 let numbersArray: number[] = [1, 2, 3];

 // 元组
 let tuple: [string, number, boolean] = ["Hello", 42, true];
  • 2.长度约束:
    • 数组: 数组可以是任意长度,而且可以通过 push、pop 等方法在运行时动态改变数组的长度。
    • 元组: 元组的长度是固定的,一旦定义,就不能改变。试图在运行时修改元组的长度会导致类型错误。
 // 数组长度可以改变
 let dynamicArray: number[] = [1, 2, 3];
 dynamicArray.push(4);

 // 元组长度是固定的,下面的代码会导致类型错误
 let myTuple: [string, number] = ["Hello", 42];
 // 错误,不能将布尔值添加到元组
 // myTuple.push(true);
  • 3.访问元素:

    • 数组: 通过索引访问数组元素,可以使用索引值来读取或修改数组中的任何元素。
    • 元组: 同样可以通过索引访问元组中的元素,但需要确保索引在元组的有效范围内。元组还允许使用解构赋值来方便地获取元素。
 // 数组访问
 let numbersArray: number[] = [1, 2, 3];
 let firstNumber = numbersArray[0];

 // 元组访问
 let myTuple: [string, number] = ["Hello", 42];
 let firstElement = myTuple[0];
 // 使用解构赋值
 let [message, value] = myTuple;

Type关键字

  • 在 TypeScript 中,type 关键字用于创建类型别名(Type Aliases)。类型别名允许你为一个已存在的类型起一个新的名字,以提高代码可读性和维护性。通过 type,你可以为复杂的类型定义创建一个简洁、易懂的名称。
type Name:String = "cheng_CSDN";
type Age:number = 20;

let age:Age = 18;
let name:Name = 'lihong'
  • 通过使用 type 关键字,你可以更灵活地组织和管理你的类型定义,使得代码更具可读性和可维护性。类型别名在处理复杂的类型结构时尤其有用,可以简化代码并提高可理解性。

函数的使用

  • TypeScript中的函数使用与JavaScript类似,但可以通过类型注解提供更强的静态类型检查。下面是一些 TypeScript 中函数的详细用法的举例:
基本函数定义:
// 函数返回类型为number
function add(x: number, y: number): number {
  return x + y;
}

// 调用函数
let result: number = add(3, 7);
console.log(result); // 输出: 10
可选参数和默认参数:

//age?:number:在调用get函数时age可传参可不传参

function get(name:String,age?:number):String{
	if(age){
	//如果age被传实参
	return `${name},${age}!`;
	}else{
    //如果age没被传实参
	return `Hello,${name}!`;
	}
}
console.log(get("Jhon"));  //输出 Hello,Jhon
console.log(get("Jhon",29));  //输出 Jhon,29

// 默认参数
function multiply(x: number, y: number = 2): number {
  return x * y;
}

console.log(multiply(5));      // 输出: 10
console.log(multiply(5, 3));   // 输出: 15
可变参数:
// 可变参数
function concatenate(...args: string[]): string {
  return args.join(" ");
}

console.log(concatenate("Hello", "World", "!")); // 输出: Hello World !
函数重载:
  • 在TypeScript中,函数重载允许你为同一个函数提供多个函数签名(也就是函数名称相同),从而使函数能够以不同的方式调用。以下是一个简单的 TypeScript 函数重载的案例:
  • 优化了代码,可以相同的函数名称实现不同的逻辑。
    代码演示:
// 函数签名1:接受两个参数,均为数字
function add(a: number, b: number): number;

// 函数签名2:接受两个参数,均为字符串
function add(a: string, b: string): string;

// 实际的函数实现
function add(a: number | string, b: number | string): number | string {
  // 在实现中,需要根据不同的参数类型执行不同的逻辑
  if (typeof a === 'number' && typeof b === 'number') {
    return a + b;
  } else if (typeof a === 'string' && typeof b === 'string') {
  //typeof:判断此参数的数据类型
    return a + b;
  } else {
    // 如果参数类型不匹配,可以抛出错误或者返回一个默认值
    throw new Error('Invalid arguments');
  }
}

// 使用函数
console.log(add(2, 3));      // 输出: 5
console.log(add('Hello, ', 'World!'));  // 输出: Hello, World!

总结:其实就是说,在ts中,你可以用同样的函数名,但是形参类型不同就OK啦

箭头函数:
  • TypeScript中的箭头函数(Arrow Functions)是一种简洁的函数声明语法,它与传统的函数声明方式有一些不同,并且在一些场景下更为方便。以下是一些 TypeScript 箭头函数的应用场景和示例:
//传统函数声明
let traditionalFunction = function(x:number,y:number): number {
	return x+y;
}
//箭头函数(带参)
let arrowFunction = (x:number,y:number): number => x+y;
//不带参
let chengFunction: () => viod;

chengFunction= () => {
    console.log("Hello!");
};
//调用函数
chengFunction() //输出Heelo!
高阶函数:
  • 箭头函数在高阶函数中的使用也很常见。
  • 这里 multiplyBy 是一个接受一个参数并返回一个函数的高阶函数。
// 高阶函数
let multiplyBy = (factor: number) => (x: number) => x * factor;

let double = multiplyBy(2);
let triple = multiplyBy(3);

console.log(double(5)); // 输出: 10
console.log(triple(5)); // 输出: 15
  • 总的来说,TypeScript 中的箭头函数在简化语法、处理上下文的 this、作为回调函数以及高阶函数等场景下都有着广泛的应用。

  • 在 TypeScript 中,类(Class)是一种用于创建对象的模板,它允许你定义对象的结构和行为。通过使用类,你可以将对象的属性和方法组织在一起,从而更方便地创建和维护代码。
class Animal {

  // 方法
  makeSound(): void {
    console.log("汪汪");
  }
}

// 使用类
const myAnimal = new Animal();
myAnimal.makeSound();
    • 在上面的例子中,我们定义了一个名为 Animal 的类,该类一个方法(makeSound)。然后,我们创建了一个名为 myAnimal1 的实例,并使用它来调用方法。

类的特性:

封装(Encapsulation)

  • 在类中封装了属性和方法。
class Car {
    //属性
    brand:string

    //构造函数,创建对象的时候会被自动调用
    constructor(brand:string) {
        this.brand = brand
        console.log("该方法被调用了")
    }

    //方法
    run():void {
        //this:那个对象调用该方法这个this就指向那个对象
        console.log(this.brand + "骑车在路上")
    }
}

//创建对象
const tesila1 = new Car("特斯拉")
console.log(tesila1.brand)  //特斯拉
tesila1.run()

const xiaomi = new Car("小米")
console.log(xiaomi.brand)  //小米
xiaomi.run()

构造函数:

  • 类可以包含构造函数,用于在创建对象时进行对象属性的初始化操作,创建几个对象就会执行几次。
    • 会被自动调用
class Car {
    //属性
    brand:string

    //构造函数,创建对象的时候会被自动调用
    constructor(brand:string) {
        this.brand = brand
        console.log("该方法被调用了")
    }

    //方法
    run():void {
        //this:那个对象调用该方法这个this就指向那个对象
        console.log(this.brand + "骑车在路上")
    }
}
const car = new Car("特斯拉");
console.log(car.getBrand()); // 输出: 特斯拉

继承:

  • 类可以通过继承机制派生出新的类,从而实现代码的重用。子类可以继承父类的属性和方法,并且可以添加新的属性和方法,或者重写父类的方法。
  • TypeScript采用了单继承模型,每个类只能继承自一个父类。
  • 关键字:extends

抽象类:

  • TypeScript 中的类可以作为抽象类,其中的方法可以带有抽象修饰符 abstract,并且类本身不能被实例化(就是不能基于抽象类创建对象)。抽象类常常用于定义其他类的结构和行为的基础。
    关键字:abstract
abstract class Fu {
    abstract getArea(): number;
}

class Zi extends Fu {
    name: string;

    constructor(name: string) {
        super();
        this.name = name;
    }

    getName(): string {
        return this.name;
    }
}

const p1 = new Zi("zhangsan")
console.log(p1.getName())

访问修饰符:

  • 在 TypeScript 中,可以使用访问修饰符来限制类的成员的访问范围。常见的访问修饰符有 publicprivate、和 protected
    private(私有)
    • 属性被标记为 private,只能在类的内部访问。
      public(公共):
    • 如果不指定任何访问修饰符,默认为 public
    • public 成员可以在任何地方被访问,包括类的外部。
      protected(友好):
    • protected 成员与 private 类似,但在派生类(子类)中是可访问的。
    • 在类的外部是不可访问的。

接口

  • 接口是 TypeScript 中强大的类型工具,它们使代码更加清晰、可读,并提供了更严格的类型检查。通过使用接口,可以确保代码的健壮性和可维护性。
    基本语法:
  • 在下面的例子中,定义了一个名为 Person 的接口,该接口要求对象具有 firstNamelastName 两个字符串属性,以及一个可选的 age 属性。然后,创建了一个符合该接口要求的对象 myFriend,并将其传递给 greet 函数。
// 定义一个接口
interface Person {
  firstName: string;
  lastName: string;
}

// 创建符合接口要求的对象
let myFriend: Person = {
  firstName: "John",
  lastName: "Doe",
};

// 定义测试方法
function greet(person: Person): void {
  console.log(`Hello, ${person.firstName} ${person.lastName}!`);
}

// 调用函数
greet(myFriend); // 输出: Hello, John Doe!

可选属性和只读属性:

  • 在这个例子中,year 是一个可选属性,可以存在也可以不存在。而 vin 是一个只读属性,一旦赋值后就不能被修改。
interface Car {
  brand: string;
  model: string;
  year?: number; // 可选属性
  readonly vin: string; // 只读属性
}

let myCar: Car = {
  brand: "Toyota",
  model: "Camry",
  year: 2022,
  vin: "ABC123"
};

myCar.vin = "NewVin"; // 错误,只读属性不能被修改

函数类型接口:

  • 接口还可以描述函数类型,指定函数的参数和返回值类型。
interface MathFunction {
    (x: number, y: number): number;
}

let add: MathFunction = function(x, y) {
    return x + y;
};

let subtract: MathFunction = function(x, y) {
    return x - y;
};

console.log(add(10,20))

接口的继承

  • 接口可以继承其他接口,以便复用已有的接口定义。在这个例子中,Employee 接口继承了 Person 接口,因此 Employee 对象必须包含 firstNamelastName 属性。
interface Person {
  firstName: string;
  lastName: string;
}

interface Employee extends Person {
  jobTitle: string;
}

let employee: Employee = {
  firstName: "Alice",
  lastName: "Smith",
  jobTitle: "Software Engineer"
};

单继承多实现

  • 一个类可以实现多个接口,从而获得接口定义的属性和方法。这使得可以在一个类中组合多个行为。
interface Printable {
  print(): void;
}

interface Loggable {
  log(): void;
}

// 实现多个接口
class MyClass implements Printable, Loggable {
  print() {
    console.log("Printing...");
  }

  log() {
    console.log("Logging...");
  }
}

// 创建实例
const myObject = new MyClass();
myObject.print(); // 调用 Printable 接口的方法
myObject.log();   // 调用 Loggable 接口的方法


在这个例子中,`MyClass`类实现了`Printable``Loggable`接口,从而拥有了这两个接口定义的方法。尽管 TypeScript 不支持直接的多继承,但通过接口的使用,你可以在类中组合多个行为,达到类似多继承的效果。

接口和类的区别:

  • 接口 主要用于描述对象的结构,约定了属性和方法。
  • 用于创建对象,包含实例化和实际的实现逻辑。
  • 接口 更注重于约定,可以被多个类实现。
  • 更注重于具体的实现,包含了对象的结构和行为。

枚举

  • 在TypeScript中,枚举(enum)是一种用于命名一组命名常量的数据类型。枚举在代码中常用于表示有限集合的值,例如表示一周中的天、颜色、状态等。使用枚举可以提高代码的可读性和可维护性。
    代码:
// 定义一个枚举类型 Fruit
enum Fruit {
  Apple,
  Banana,
  Orange
}

// 使用枚举
let myFruit: Fruit = Fruit.Apple;
console.log("Selected fruit: " + myFruit); // 输出: Selected fruit: 0
  • 在这个例子中,我们定义了一个名为 Fruit 的枚举,包含了三个成员:AppleBananaOrange。每个成员都被分配了一个默认的数字值,从0开始递增。在使用枚举时,你可以通过成员名称来引用对应的值。
  • 自定义枚举成员的值:
// 自定义枚举成员的值
enum Fruit {
  Apple = 5,
  Banana = 10,
  Orange = 15
}

let myFruit: Fruit = Fruit.Banana;
console.log("Selected fruit value: " + myFruit); // 输出: Selected fruit value: 10
  • 在这个例子中,我们为 AppleBananaOrange 分别指定了自定义的数值。这样,Fruit.Banana 的值就是 10。

使用枚举的好处

  • 在TypeScript中,使用枚举可以提高代码的可读性、可维护性,并且可以更清晰地表示一组有限的命名常量。以下是一些情况,你可能考虑使用枚举:
    1.表示一组相关的命名常量: 当有一组相关的常量需要被命名时,使用枚举可以更清晰地表达这些常量之间的关系。
// 例:表示一周中的天
enum Weekday {
    Sunday = "星期日",
    Monday = "星期一",
    Tuesday = "星期二",
    Wednesday = "星期三" ,
    Thursday = "星期四" ,
    Friday = "星期五" ,
    Saturday = "星期六"
}

let day: Weekday = Weekday.Sunday;
console.log("今天是: " + day); //

字面量

  • 在 TypeScript 中,字面量类型是一种特殊的类型,用于表示一个具体的值。通过字面量类型,可以约束一个变量只能取特定的字面量值,而不能取其他任何值。这有助于提高代码的清晰度和安全性。

类型推断

  • TypeScript 的类型推断是指编译器在没有显式指定类型的情况下,根据变量的赋值情况自动推断出变量的类型。这使得在 TypeScript 中编写代码时,可以更少地手动声明类型,同时仍然能够获得类型安全的好处。

类型断言

  • 在TypeScript中,类型断言(Type Assertion)是一种开发者明确告诉编译器某个值的类型的方式。它类似于其他编程语言中的类型转换,但在 TypeScript 中,它更强调的是开发者对于某个值的了解。

泛型的应用

  • 泛型(Generics)是 TypeScript 中一个强大的特性,允许你编写灵活、可重用的函数和类,而不需要提前指定具体的类型。通过泛型,你可以编写适用于多种类型的代码,增加代码的通用性和灵活性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值