这是该系列最后一篇文章,核心讲解TypeScript
。
内容如下:
- 什么是TypeScript?
- 为什么使用TypeScript?
- 可以不使用TypeScrip?
- TypeScript优势
- TypeScript劣势
- TypeScript基础知识
- 给变量加约束
- 原始类型
- 联合类型
- 任意类型
- 枚举类型
- 对象类型
- 数组类型
- 给函数加约束
- 给函数加约束
- 给类加约束
- 类型断言作用
- 类型断言 vs 类型转换
- 类型断言 vs 泛型
- 举例
1.什么是TypeScript?
TypeSciprt是JavaScript的超集,简单理解就是加了约束的JavaScript
。
JavaScript可以在哪些地方加约束呢?
无非就两个地方:变量
和函数
(类
也是函数)
2.为什么使用TypeScript?
加了约束后的JavaScript,可读性有很大的提升,将弱类型的语言变成了强类型的语言,当然就包括强类型的所有优点。
3.可以不使用TypeScript?
当然可以,只是目前越来越多的开发使用ts去写各种类库,想看其实现,学习ts是不错的选择。
其次,公司需要持续维护的前端项目,ts可以提高代码可读性,让陆陆续续离职入职的前端小伙伴们快速熟悉项目,交接接手更方便。
4.TypeScript优势
1.写通用组件类库
2.解读开源第三方ts库
3.拥于持续维护的前端项目
5.TypeScript劣势
优势说完,再来说说ts的劣势。
首先,ts需要一定的学习成本,得花时间和实践去理解其语法。
然后,ts导致项目代码量增大,编译会稍慢点,因为最后的构建部分ts最终还是会编译成js。
接着,ts比其他js项目开发周期会拉长,毕竟每个类每个函数每个参数都需要自定义。
最后,ts规范很重要,对于新手而言,如果项目参数全部用any声明,那还不如直接用js呢,切记不能滥用any。
总结一下:
1.有学习成本
2.代码增多,编译变慢、开发周期变长
3.ts如果使用不规范,还不如用js实在
6.TypeScript基础知识
1.给变量加约束
- 原始类型
- 联合类型
- 任意类型
- 枚举类型
- 对象类型
- 数组类型
1.原始类型:
1 2 3 4 5 6
| let str: string = 'hello'; let num: number = 1; let bol: boolean = true; let nul: null = null; let un: undefined = undefined;
|
2.联合类型:
1 2 3
| let muchtype:string|number = '1'; muchtype = 2;
|
3.任意类型:
1 2 3 4 5
| //允许赋值任意类型 //方式一: let an: any = 'test'; //方式二: let anyThing;
|
4.枚举类型:
1 2 3 4 5 6 7 8
| enum ChatCmdType { init = 0, // 课程初始化 chat = 1, // 群聊消息 addAnnounce = 2, // 发布公告 deleteAnnounce = 3, // 删除公告 startCourse = 4, // 开始上课 endCourse = 5, // 结束上课 }
|
5.对象类型:
用interface
声明:
1 2 3 4 5 6 7 8
| interface ChatMessage { account: string headImg?: string local: boolean readonly role: number text: string [k: string]: any }
|
用type
声明:
1 2 3 4 5 6 7 8
| type ChatMessage = { account: string headImg?: string local: boolean readonly role: number text: string [k: string]: any }
|
解释下声明的对象类型:
1 2 3 4 5 6 7 8
| const message: ChatMessage = { account: 'Tony', local: true, role: 1, text: 'Hello, Tim' } message.role = 2 // 报错 message.link = 'http://baidu.com'
|
声明一个聊天对象:
1.除了头像,其他都必填
2.角色是数字类型且只读
3.可以自定义其他属性
6.数组类型:
1 2 3 4 5
| let arr: number[] = [1, 2, 3]; let str: string[] = ['1', '2', '3']; let an: any[] = ['1', 2, true]; let messages: ChatMessage = [{xxx}, {xxx}, ...]
|
2.给函数加约束
1.给函数加约束
给函数加类型,简单理解就是给函数的参数
声明类型。
1 2 3
| const on = (name: string, cb: (...args: any[]) => void) => { cb(true, name, 1) }
|
声明一个on函数:
1.第一个参数string
2.第二个参数回调callback函数
3.callback函数的参数类型是多个的任意类型
1 2 3 4
| on('Tony', (bool, name, age) => { console.log(bool, name, age) }) // 输出 true Tony 1
|
注意cb: (...args: any[]) => void
和cb: (args: any[]) => void
的区别
2.给类加约束
严格意义上讲JavaScript是没有类的,只是在ES6时引入的一个语法糖class。
新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
。
函数是面向过程的写法,类是面向对象的写法。
1 2 3 4 5 6 7 8 9 10 11
| class Person { private name: string; protected age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } public wark() { console.log(this.name); } }
|
3.类型断言作用
1.将一个联合类型断言为其中一个类型
1 2 3 4 5
| const muchtype:string|number = 1; (muchtype as number).toString()
const muchtype:string|number = '1'; (muchtype as string).length
|
2.将任何一个类型断言为 any
会报错:Property 'ipc' does not exist on type 'Window & typeof globalThis'. TS2339
。
方案一,as:
1
| const ipc = (window as any).ipc;
|
方案二,加忽略注释:
1 2
| const ipc = window.ipc;
|
4.类型断言 vs 类型转换
类型断言只会影响 TypeScript 编译时的类型
,类型断言语句在编译结果中会被删除:
1 2 3 4 5 6
| function toBoolean(something: any): boolean { return something as boolean; }
toBoolean(1);
|
在上面的例子中,将 something
断言为 boolean
虽然可以通过编译,但是并没有什么用,代码在编译后会变成:
1 2 3 4 5 6
| function toBoolean(something) { return something; }
toBoolean(1);
|
所以类型断言不是类型转换,它不会真的影响到变量的类型
。
若要进行类型转换,需要直接调用类型转换的方法:
1 2 3 4 5 6
| function toBoolean(something: any): boolean { return Boolean(something); }
toBoolean(1);
|
5.类型断言 vs 泛型
举个例子:
1 2 3 4 5 6 7 8 9 10 11
| function getCacheData(key: string): any { return (window as any).cache[key]; }
interface Cat { name: string; run(): void; }
const tom = getCacheData('tom') as Cat; tom.run();
|
我们使用泛型:
1 2 3 4 5 6 7 8 9 10 11
| function getCacheData<T>(key: string): T { return (window as any).cache[key]; }
interface Cat { name: string; run(): void; }
const tom = getCacheData<Cat>('tom'); tom.run();
|
通过给 getCacheData 函数添加了一个泛型 ,我们可以更加规范的实现对 getCacheData 返回值的约束,这也同时去除掉了代码中的 any,是最优的一个解决方案。
7.举例
最后看一个完整案例:
一个简单的react组件:
1 2 3 4 5 6 7 8
| import React from 'react'; interface ChildProps { onClick: (evt: any) => void } const Child: React.FC<ChildProps> = ({ onClick }) => { return (<span style={{ cursor: 'pointer' }} onClick={onClick}>Child</span>) } export default Child
|
一个复杂的react组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| import React, { useEffect, useRef, useCallback } from 'react'; import logo from 'logo.svg'; import { connect } from 'dva'; import { FunCompProps } from 'utils/types' import Child from './Child'; import Child2 from './Child2'; import Context from 'hooks/useContext' import useTitle from 'hooks/useTitle'
type Login = { current: number record: number } type Loading = { global: boolean models: Object effects: { [k: string]: boolean } } interface LoginProps extends Login, FunCompProps { loading: boolean } type LoginModel = { login: Login loading: Loading }
const Login: React.FC<LoginProps> = ({ dispatch, loading, current }) => { const preProps = useRef<number>() const inputElement = useRef<HTMLInputElement>(null) const lock = useRef<boolean>(false)
useTitle('Login') useEffect(() => { preProps.current = current }, [current])
const change = async (type: string) => { if (lock.current) return const delay = (timeout: number) => new Promise((resolve) => { setTimeout(resolve, timeout); }) lock.current = true await delay(2000) await dispatch({ type: `login/${type}` }) lock.current = false }
const onClick = useCallback(() => { inputElement.current?.focus() }, []) return ( <Context.Provider value={current}> <div className="App" > <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> {loading ? <p>Loading</p> : <p>{current} Page</p>} <div>Child: <Child /></div> <div>Child: <Child2 onClick={onClick} /></div> <div className="App-operate"> <button className="App-btn" disabled={loading} onClick={() => change('addASync')}>Add</button> <button className="App-btn" onClick={() => change('minus')}>Minus</button> </div> </header> </div > </Context.Provider> ) }
export default connect(({ login, loading }: LoginModel) => ({ current: login.current, loading: loading.effects['login/addASync'] }))(Login)
|
复杂的组件中,引入了一个FunCompProps的ts声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import H from 'history'; import { Dispatch, } from 'redux';
export interface match<Params extends { [K in keyof Params]?: string } = {}> { params: Params; isExact: boolean; path: string; url: string; }
export interface StaticContext { statusCode?: number; }
export interface FunCompProps< Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.LocationState > { dispatch: Dispatch<any> history: H.History<S>; location: H.Location<S>; match: match<Params>; staticContext?: C; }
|
最后总结:
TypeScript并非适用于任何项目,知道即可。
TS可以当成装逼神器,如果JS语法写厌倦了,想试试新的语法,TS还是不错的选择哈。