Vue3的学习+ElementPlus应用+TypeScript(ts)
vue3介绍
- 2020 年 9 月 18 日发布,许多开发者还在观望
- 2022 年 2 月 7 日称为默认版本
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中,
ref
和reactive
都是用于创建响应式数据的函数,但它们在用法和特性上有一些区别:
基本用法:
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
属性。对于计算属性和监听属性等,需要使用 computed
和 watch
进行处理。
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
默认执行
生命周期函数
- 使用步骤:
-
- 先从vue中导入以
on打头
的生命周期钩子函数
- 先从vue中导入以
-
- 在setup函数中调用生命周期函数并传入回调函数
-
- 生命周期钩子函数可以调用多次
-
- Vue3和vue2的生命周期对比:
使用的代码:
<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元素
步骤:
-
创建 ref =>
const hRef = ref(null)
-
模板中建立关联 =>
<h1 ref="hRef">我是标题</h1>
-
使用 =>
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函数
- 父传子:
-
- 父组件提供数据
-
- 父组件将数据传递给子组件
-
- 子组件通过
defineProps
进行接收
- 子组件通过
-
- 子组件渲染父组件传递的数据
-
- 子传父:
-
- 子组件通过
defineEmit
获取emit
函数(因为没有this)
- 子组件通过
-
- 子组件通过
emit
触发事件,并且传递数据
- 子组件通过
-
- 父组件提供方法
-
- 父组件通过自定义事件的方式给子组件注册事件
代码块:
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提供的依赖
- 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 的关键特性和概念:
- 静态类型系统: TypeScript引入了静态类型系统,允许在编写代码时定义变量的类型。这有助于在开发过程中捕获潜在的错误,并提供更好的代码补全和文档。
- 类型注解: 开发者可以为变量、函数参数和返回值等显式地添加类型注解,以明确指定变量的数据类型。
- 接口(Interfaces): TypeScript支持接口,允许定义代码结构的合同。这有助于在多人协作开发时确保代码的一致性和互操作性。
- 类(Classes): TypeScript支持类,使得面向对象编程更加直观,并且可以使用许多ES6+的类特性。
- 泛型(Generics): TypeScript支持泛型,使开发者能够编写灵活且可重用的代码。
- 编译时类型检查: TypeScript代码在运行之前会被编译成纯JavaScript代码。在这个过程中,编译器会执行类型检查,帮助开发者发现并修复潜在的问题。
- 工具支持: TypeScript具有强大的集成开发环境(IDE)支持,如Visual Studio Code,WebStorm等,提供智能代码补全、错误检测等功能。
- 社区和生态系统: 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
执行结果:
很显然,不能赋予没有联合的类型数据
数据类型的概括
-
基本数据类型
-
number: 用于表示数字,可以是整数或浮点数。
-
string: 表示文本字符串。
-
.boolean: 表示布尔值,即
true
或false
。 -
null: 表示一个空值或空引用。
-
undefined: 表示未定义的值。
-
symbol: 表示唯一的、不可变的值。
-
const cheng01:symbol = Symbol("symbol")
const cheng02:symbol = Symbol("symbol")
//测试cheng01和cheng02是否相等
console.log(cheng01 === cheng02) //false
-
复合数据类型:
- array: 表示数组。可以使用泛型来定义数组元素的类型。
- tuple: 表示一个元素数量和类型固定的数组。
- object: 表示对象。在 TypeScript 中,你可以使用接口(interface)来定义对象的结构。
- enum: 表示枚举类型。
-
特殊类型(了解):
- 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'
let readonlyArray:readonly string[] = ['z','a','b','nbvc']
//不可修改
//readonlyArray[2] = 'asd' //直接报错
//基本的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)都用于存储多个元素,但它们之间存在一些关键的区别:
- 元素类型约束:
- 数组: 数组中的所有元素都可以是相同的类型,但也可以是不同类型。你可以使用泛型表示数组的元素类型,例如
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 中,可以使用访问修饰符来限制类的成员的访问范围。常见的访问修饰符有
public
、private
、和protected
private(私有)- 属性被标记为
private
,只能在类的内部访问。
public(公共): - 如果不指定任何访问修饰符,默认为
public
。 public
成员可以在任何地方被访问,包括类的外部。
protected(友好):protected
成员与private
类似,但在派生类(子类)中是可访问的。- 在类的外部是不可访问的。
- 属性被标记为
接口
- 接口是 TypeScript 中强大的类型工具,它们使代码更加清晰、可读,并提供了更严格的类型检查。通过使用接口,可以确保代码的健壮性和可维护性。
基本语法: - 在下面的例子中,定义了一个名为
Person
的接口,该接口要求对象具有firstName
和lastName
两个字符串属性,以及一个可选的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
对象必须包含firstName
和lastName
属性。
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
的枚举,包含了三个成员:Apple
、Banana
和Orange
。每个成员都被分配了一个默认的数字值,从0开始递增。在使用枚举时,你可以通过成员名称来引用对应的值。 - 自定义枚举成员的值:
// 自定义枚举成员的值
enum Fruit {
Apple = 5,
Banana = 10,
Orange = 15
}
let myFruit: Fruit = Fruit.Banana;
console.log("Selected fruit value: " + myFruit); // 输出: Selected fruit value: 10
- 在这个例子中,我们为
Apple
、Banana
和Orange
分别指定了自定义的数值。这样,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 中一个强大的特性,允许你编写灵活、可重用的函数和类,而不需要提前指定具体的类型。通过泛型,你可以编写适用于多种类型的代码,增加代码的通用性和灵活性。