主要整理了 effective typescript 裏列出的一些技巧,列出了一些本身以爲有用的,有的簡單的就一筆帶過。更加友好的排版見 blog.staleclosure.com/effective-t…javascript
function setLightSwitch(value:boolean){
switch(value){
case true:
turnOn();
break;
case false:
turnOff();
break;
default:
console.log('dead code')
}
}
複製代碼
上述代碼的default條件在TS裏會被標記爲dead code
,但在運行時仍然可能執行,如setLightSwitch('boom')
,因此 代碼裏不能徹底依賴於類型檢查,必要時仍是須要進行防護性編程java
如不支持以下靜態重載react
function add(a:number,b:number): number {
return a + b;
}
function add(a:string,b:string): string{
return a + b + '!'
}
複製代碼
只能支持函數簽名加函數實現的方式重載c++
function add(a:number,b:number):number;
function add(a:string,b:string):string;
function add(a:any,b:any){
if(typeof a === 'string'){
return a + b + '!'
}else{
return a + b
}
}
複製代碼
下述代碼,即便vector3D不是vector2D的子類,仍然不會報錯, 由於Typescript不是經過繼承來實現子類型,而是經過structual typing來實現子類型, 即雖然vector3D不是vector2D的子類可是是其子類型。web
class vector2D{
constructor(public x:number, public y: number){
this.x = x;
this.y = y;
}
}
class vector3D{
constructor(public x:number,public y:number,public z:number){
this.x = x;
this.y = y;
this.z = z;
}
}
function calculateLength(v:vector2D){
return Math.sqrt(v.x*v.x + v.y*v.y)
}
const point = new vector3D(0,1,2)
const dist = calculateLength(point)
複製代碼
type A= 'A' // 單值集合 { 'A' }
type B= 'B' // 單值集合 { 'B' }
type AB = 'A' | 'B' // 集合的並集 { 'A', 'B' }
type twoInt = 2 | 4 | 5 ... // 無限元素集合 { 1,2,3,4}
type threeInt = 3 | 6 | 9 // 無限集合
type twoIntersectThreeInt = twoInt & threeInt // 無限集合的交集
type twoUnionThreeInt = 2| 3 | 4 | 6 ... // 無限集合的並集
keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)
複製代碼
Typescript術語 | 集合術語 |
---|---|
never | 空集 |
literal type | 單值集合 |
value 可賦值給 T | value ∈T |
T1 assignable to T2 | T1是T2的子集 |
T1 extends T2 | T1是T2的子集 |
T1 | T2 |
T1 & T2 | T1 和T2的交集 |
unknown | universal set |
Type space
或者 value Space
,也能夠同時屬於type space
和value space
class
和enum
同時屬於type space
和value space
以下的左邊的Cylinder
是實例的類型,而右邊的Cylinder
是construtortypescript
class Cylinder {
radius = 1;
height = 1
}
const instance: Cylinder = new Cylinder();
複製代碼
而且typeof Cylinder
並不是是Cylinder
類型,而InstanceType<typeof Cylinder>
纔是Cylinder
類型 這裏着重說一下class,class其實是兩類對象的合體,一類是做爲構造函數及其原型屬性一類是類對象自己 考察以下的classexpress
class Test {
constructor(x:number){
this.instanceMember = x;
}
static staticMember = 1;
instanceMember = 2;
static staticMethod1(){
}
static staticMethod2(){
this.staticMethod1();
}
instanceMethod1(){
}
instanceMethod2(){
this.instanceMethod1()
}
}
複製代碼
實際上能夠將Test拆分爲兩部分編程
class Test {
instanceMember = 1;
instanceMethod1(){
}
instanceMethod2(){
}
}
object Test {
new(x:number): Test{
}
staticMethod1(){
}
staticMethod2(){
}
staticMember = 1
}
複製代碼
這裏的object Test
在scala中被稱爲伴生對象,而這裏的class Test
實際是用來生成實例對象的 伴生對象和實例對象經過構造函數關聯 咱們能夠從伴生類型中獲取實例類型,也能夠從實例類型獲取伴生類型api
const test = new Test()
type instanceType = typeof test; // 獲取實例對象的類型即這裏class Test定義的類型
type companionType = typeof Test // 獲取伴生對象的類型即這裏的object Test定義的類型
type c = InstanceType<companionType> // 根據伴生類型推倒實例類型
複製代碼
雖然能夠經過實例的__proto__來獲取伴生對象可是Typescript並無提供支持數組
const
在value space
修飾變量時表示變量不能從新賦值,而as const
修飾則修改字面量的類型推導extends
能夠用來定義繼承關係(class A extends B
)或者定義子類型(interface A extends B
)或者定義泛型約束Generic<T extends number>
in
可用於檢測屬性存在key in object
也能夠用於mapped type({[key in keyof T]:string}
)const a = new String('ss');
const b: string = a; // String沒法賦值給string
const c:String = '123' // string能夠賦值給String
複製代碼
當將對象字面量賦值給變量時會觸發額外的屬性檢查,以保證沒有傳入多餘的屬性
interface Point {
x: number;
y: number;
}
const point : Point = {
x:1,
y:2,
z:3 // 報錯,多餘的屬性
}
複製代碼
這個按照strutual typing的設計是不合理的,有幾種繞過Excess Property Checking方式 這裏是Typescript對對象字面量額外添加的檢查,
interface Point {
x: number;
y: number;
}
const tmp = {
x:1,
y:2,
z:3
}
const point:Point= tmp; // 不報錯
複製代碼
interface Point {
x: number;
y: number;
}
const point : Point = {
x:1,
y:2,
z:3
} as Point
複製代碼
function add(a:number,b:number){
return a+b;
}
function sub(a:number,b:number){
return a-b;
}
function mult(a:number,b:number){
return a*b;
}
function div(a:number,b:number){
return a/b;
}
複製代碼
提取出公共的函數類型,可簡化以下
type Binary = (a:number,b:number) =>number;
const add : Binary = (a,b) => a+b;
const sub: Binary = (a,b) => a-b;
const mult: Binary = (a,b) => a*b;
const div: Binary= (a,b) => a-b;
複製代碼
typeof fn
來標註加強的函數類型const checked: typeof fetch = (...args) => {
return fetch(...args).then(resp=> {
if(!resp.ok){
throw new Error('failed')
}
})
}
checked('/api') // 能夠繼續獲取類型檢查
複製代碼
// 普通對象
type TState = {
name: string;
capital: string;
}
interface TState {
name: string;
capital: string;
}
// index signature
type TDict = {[key:string]: string }
interface IDict {
[key:string]: string
}
type TFn = (x:number) => string;
interface IFn {
(x:number):string;
}
// function with props
type TFnWithProps = {
(x:number):number;
prop: string;
}
interface IFnWithProps {
(x:number):number;
prop: string;
}
// constructor
type TConstructor = new(x:number) => {x:number}
interface IConstructor{
new(x:number): {x:number}
}
// generic
type TPair<T>= {
first: T;
second: T;
}
interface IPair<T> {
first: T;
second: T;
}
// extends
type TStateWithProps = IState & { population : number}
interface IStateWithProp extends TState {
population: number;
}
// implements
class StateT implements TState {
name = '';
capital = '';
}
class StateI implements IState {
name='';
capital = ''
}
複製代碼
type AorB = 'A' | 'B'
type NamedVariable = (Input | Output) & { name: string}
type Pair = [number,number]
複製代碼
// inner
interface IState {
name :string;
capital: string;
}
// outer
interface IState {
population: number
}
const wyoming: IState = {
name: 'Wyoming',
capital: 'Cheyenne',
population: 500_000
}
複製代碼
interface ButtonProps {
type: string;
size: 'large' | 'middle'| 'small'
}
interface ButtonPropsWithChildren{
type: string;
size: 'large' | 'middle'| 'small',
children: React.ReactNode
}
複製代碼
使用PropsWithChildren簡化
import { PropsWithChildren } from 'react';
type ButtonPropsWithChildren = PropsWithChildren<ButtonProps>
複製代碼
interface State {
userId: string;
pageTitle: string;
recentFiles: string[]
pageContents: string;
}
interface TopNavState {
userId: string;
pageTitle: string;
recentFiles: string[]
}
複製代碼
上述代碼可經過lookup type簡化
interface TopNavState = {
userId: State['userId'];
pageTitle: State['pageTitle']
recentFiles: State['recentFiles']
}
複製代碼
使用mapped type 可進一步簡化
type TopNavState = {
[k in 'userId' | 'pageTitle' | 'recentFiles'] : State[k]
}
複製代碼
再使用工具類進一步簡化
type TopNavState = Pick<State, 'userId', 'pageTitle', 'rencentFiles'>
複製代碼
咱們也能夠利用typeof來進行類型傳遞
function getUserInfo(userId:string){
return {
userId,
name,
age,
height,
weight,
favoriteColor
}
}
type UserInfo = ReturnType<typeof getUserInfo>
複製代碼
utility type
時,多多使用generic constraint
保證明例化時的類型安全interface Name {
first: string;
last: string
}
type Pick1<T, K>{
[k in K]: T[k]
}
type FirstLast = Pick1<Name, 'first'| 'last'>
type FirstMiddle = Pick1<Name, 'first', 'middle'> // 應該報錯但沒報錯
type Pick2<T, K extends keyof T> = { // 添加泛型約束
[k in K]: T[K]
}
type FirstMiddle = Pick2<Name, 'first', 'middle'> // 正確的報錯了
複製代碼
function parseCSV(input:string): {[columnName:string]: string}[]{
// xxx
}
複製代碼
能夠經過Record簡化
function parseCSV(input:string): Record<string,string>[] 複製代碼
function safeParseCSV(input:string): Record<string,string|undefined>[] const result =safeParseCSV('input') for(const x of result){
console.log(x.name?.toUpperCase()) // 應該使用optiona chain訪問,防止屬性不存在
}
複製代碼
interface Row1 { [column:string]: number} // 太寬泛了,容許訪問不該該容許的屬性了
interface Row2 { a:number, b?:number, c?:number, d?:number} // 不容許訪問不存在的屬性了
interface Row3 = | {a:number} | { a: number; b:number } | {a:number;b:number;c:number} | {a: number; b: number; c:number; d:number}
// 更細化了,不容許{ a:1, c: 2}這種不容許的對象
複製代碼
即便以下代碼x[0]和x['0']的行爲在運行時徹底一致,可是隻有x[0]才能正確的推倒出類型。
let a : string[] = []
let x = a[0] // x類型爲string
let y = a['0'] // 可是y類型爲any
複製代碼
以下所示,當聲明一個函數的參數爲readonly時
function arraySum(arr:readonly number[] ){
let sum=0,num = 0;
// check error
while((num = arr.pop()) !== undefined){
sum += num;
}
return sum;
}
複製代碼
若是一個函數沒有聲明一個函數參數爲readonly,那麼將沒法傳遞一個readonly的數組, 即便函數實現沒有修改參數
function arraySum2(arr: number[]) {
}
const arr: readonly number[] = [];
arraySum2(arr)
複製代碼
因此爲了保證函數能夠同時接受readonly和非readonly的數組,應儘可能聲明參數爲readonly(這裏和c++的const reference 和reference的限制很相似)
const
和readonly
假若有一天你實現了一個組件,而且實現了shouldComponentUpdate來進行性能優化
class App extends React.Component<{
x: number,
y: number
}> {
shouldComponentUpdate(props){
return props.x !== this.props.x || props.y !== this.props.y
}
}
複製代碼
忽然有一天你的組件添加了個新的z props,雖然你擴展了你的props類型,可是你忘記修改了 shouldComponentUpdate,致使組件該從新渲染的時候沒從新渲染,此時Typescript並不會 幫你作檢查
type AppProps = {
x: number,
y: number,
z: number,
onClick: () => {} // 不須要檢查它
}
class App extends React.Component< AppProps> {
shouldComponentUpdate(props){
return props.x !== this.props.x || props.y !== this.props.y
}
}
複製代碼
經過Mapped Type咱們能夠創建這種檢查,下面的[k in keyof AppProps]
保證了 每次添加新的屬性,都須要在REQUIRED_UPDATE進行添加
{
x: number,
y: number,
z: number,
onClick: () => {} // 不須要檢查它
}
const REQUIRED_UPDATE: {[k in keyof AppProps]: boolean} = {
x: true,
y: true,
z: true
onClick: false,
}
class App extends React.Component<AppProps> {
shouldComponentUpdate(props){
for(const k in this.props){
if(this.props[k] !== props[k] && REQUIRED_UPDATE[k]){
return true;
}
}
return false;
}
}
複製代碼
const a: number = 10; // 不建議
const a = 10 // 可自行推導
const obj: {name: string, age: number} = {name:'yj', age: 20} // 不建議
const obj = { name: 'yj', age: 20} // 自動推導
複製代碼
以下代碼雖然在javascript裏是合法的,可是在typescript裏會報錯
let id = '123456';
id = 123456; // 123456 not assignable to string
複製代碼
解決方式1: 聲明類型爲union
let id: string| number = '123456';
id = 123456 // works
複製代碼
雖然上述代碼能經過類型檢查,但更好的方式是避免使用union, 而是從新定義一個新的變量
let id= '123456';
let idInt = 123456;
複製代碼
進一步的能夠將變量聲明爲const
const id= '123456';
let idInt = 123456;
複製代碼
當你使用一個常量初始化一個變量而且沒提供類型標註時, typescript須要爲你的變量肯定一個類型,這個過程就叫widening 考慮以下代碼
const mixed = ['x', 1]
複製代碼
上述代碼中的mixed的應該被推導爲何類型
上述答案彷佛都合理,事實上Typescript只能根據你的使用狀況 進行猜想推導,並不能徹底知足你的需求。
const mixed = ['x',1]
//使用方式1
mixed.push(1) //(string|number)[] 更爲合理
//使用方式二
function test(a:string,b:number){
}
test(...mixed) // [string,number] 更爲合理
//使用方式三
function test2(a:'x',b:1){
}
test2(...mixed) // ['x',1] 更合理
複製代碼
咱們發現不一樣的使用場景須要的類型是不一樣的,事實上Typescript只能根據 大部分使用場景進行類型推斷
當發生literal widening 時,'foo',1,ColorEnum.RED等unit type會被視爲其base type即string,number, ColorEnum 觸發literal widening的條件爲
let x = 3 // widening,類型爲number
const x = 3 // 不觸發weidening,類型爲3
複製代碼
對於primitive type,const可以控制其不會觸發widening,可是 對於object和array這些複合對象,const並不能控制屬性的widening
const obj = {
name: 'yj'
} // 推導類型爲 { name: string}
const arr = [1,'x'] // 推導類型爲(string|number)[]
複製代碼
上述類型推導大部分狀況下是合理的
const obj = {
name
}
obj.name = 'zrj' // 後續修改props
const arr = [1,'x']
arr.push(3)
複製代碼
但有時候咱們須要進一步的控制屬性的widening,此時有兩種方式
const arr1: [1,'x'] = [1,'x'] // 類型爲[1,'x']
arr[0] = 2; // check error
const arr: readonly [number, string] = [1, 'x']
arr.push(3); // check error
複製代碼
as const
const arr = [1,'x'] as const
arr.push(3) // check error
複製代碼
type narrowing與type widening相反,其負責收窄類型
對於大部分類型使用內置的類型收窄便可,支持的類型收窄操做包括
對於更加複雜的對象則須要使用自定義類型收窄和tagged union來支持類型收窄
interface Point {
type: 'point',
x: number,
y: number
}
interface Radius{
type: 'radius',
r: number
}
function distance(p: Point | Radius){
if(p.type === 'point'){
return Math.sqrt(p.x*p.x + p.y*p.y)
}else {
return p.r
}
}
複製代碼
對於更加的複雜的類型,可使用自定義類型判斷
function isInputElement(el: HTMLElement): el is HTMLInputElement {
return 'value' in el;
}
function getElementContent(el: HTMLElement){
if(isInputElement(el)){
return el.value // el 爲HTMLInputElement類型
}else {
return el.textContent // el爲HTMLElement類型
}
}
複製代碼
考慮以下代碼,雖然是合法的js代碼,可是TS仍然會報錯
const pt = {}
pt.x = 3; // check error
pt.y = 4
複製代碼
這是由於pt定義是類型被推導爲{} 正確的作法應該是
const pt = {x :3, y: 4}
複製代碼
若是須要經過一系列對象構造出新對象,應儘可能使用spread 操做, 能夠保證生成的對象類型安全
const pt = { x:3,y:4}
const id = {name: 'point'}
const namedpoint = {}
Object.assign(namedpoint, pt, id)
namedpt.name // check error
複製代碼
正確的作法應該是
const pt = { x:3, y: 4}
const id = { name: 'point'}
const namedpoint = {...pt, ...id}
namedpoint.name // 正常
複製代碼
若是是須要合併部分屬性,則須要配合Partial使用
const pt = { x:3, y: 4}
const id = { name: 'point'}
function merge<T extends object, U extends object>(x: T, y: U): T & Partial<U> {
return {...x,...y}
}
const p = merge(pt, id)
p.name // 類型爲string | undefined
複製代碼
當對變量進行narrowing時,並不會同步的對其alias進行narrowing
interface Test {
name?: string;
}
const obj: Test = {}
const name = obj.name
if (obj.name) {
obj.name.toLowerCase(); // ok
name.toLowerCase(); // check error
}
複製代碼
雖然這裏的name和obj.name是一致的可是,name並不受obj.name影響, 所以當使用alias和narrow時得注意保持一致
考慮下面的組件
const App = () => {
const [content,setContent] = useState('')
const [loading, setLoading] =useState(false);
const [error, setError] = useState(null);
function load(){
setLoading(true);
try {
const resp = await fetch(getUrlForPage());
if(!resp.ok){
throw new Error('unable to load')
}
const text = await resp.text();
setLoading(false);
setContent(text);
}catch(e){
setError(e);
}
}
if(error){
return 'Error';
}else if(loading){
return 'loading'
}
return <h1>{content}</h1>
}
}
複製代碼
上面的代碼明顯存在一些問題
因爲Error, Loading,Content等狀態其實是互斥的,所以能夠用一個變量經過tagged union來建模狀態 重構代碼以下
interface RequestPending {
state: 'pending'
}
interface RequestError {
state: 'error',
error: string;
}
interface RequestSuccess {
state: 'ok',
content: string;
}
type RequestState = RequestError | RequestPending | RequestSuccess
const App = () => {
const [state, setState] = useState<RequestState>({
state: 'ok',
content: ''
})
function load(){
setState({
state: 'pending'
})
try {
const resp = await fetch(getUrlForPage());
if(!resp.ok){
throw new Error('unable to load')
}
const text = await resp.text();
setState({
state: 'ok',
content: text
})
}catch(error){
setState({
state: 'error',
error
})
}
}
switch(state.type){
case 'pending':
return 'pending',
case 'error':
return state.error
case 'ok':
return <h1>{state.content}</h1>
}
}
複製代碼
此時就徹底避免了上面存在的幾個問題,並且後續每次增長新的狀態 Typescript均可以幫咱們進行類型檢查
/** * Return a string with the backgroudColor * */
function getBackgroundColor(){
// return 'red' // 老代碼
return (255,255,255); // 重構後的代碼
}
複製代碼
重構時會忘記更改文檔裏的類型信息,致使不一致,對於量綱的信息, 因爲難以使用類型進行標記,因此能夠在文檔裏標註
/** * duration: timeMs 表示ms * */
function sleep(duration: number){
}
複製代碼
考慮下面代碼,實現了一個肯定數組範圍的函數
function extent(nums: number[]) {
let min, max;
for (const num of nums) {
if (!min) {
min = num;
max = num;
} else {
min = Math.min(min, num);
max = Math.max(max, num);
}
}
return [min, max];
}
複製代碼
上述代碼存在一些問題
的排除非咱們本意)
當開啓了strictNullCheck下上述代碼會報錯
function extent(nums: number[]) {
let min, max;
for (const num of nums) {
if (!min) {
min = num;
max = num;
} else {
min = Math.min(min, num);// number|undefined is not assignable to 'number'
max = Math.max(max, num);
}
}
return [min, max];
}
複製代碼
重構上述代碼以下
function extent(nums:number[]){
let result: [number,number] | null = null;
for(const num of nums){
if(!result){
result = [num, num]
}else {
result = [Math.min(num,result[0]), Math.max(num,result[1])]
}
}
return result;
}
複製代碼
上述代碼解決了以前的問題,其最大的區別在於,保證了循環裏的 result[0]和result[1]都不含有undefined|null,防止其影響了 正常的代碼判斷
考慮下述類型定義
interface Layer {
layout: FillLayout | LineLayout | PointLayout;
paint: FillPaint | LinePaint | PointPaint
}
複製代碼
這樣設計的類型很難關聯layout和對應的paint,重構以下
interface FillLayer {
type: 'fill',
layout: FillLayout,
paint: FillPaint
}
interface LineLayer {
type: 'line',
layout: LineLayout,
paint: LinePaint
}
interface PointLayer {
type: 'paint',
layout: PointLayout,
paint: PointPaint
}
type Layer = FillLayer | LineLayer |PointLayer
複製代碼
這實際上就是tagged union,能夠經過type進行narrowing操做
function drawLayer(layer: Layer) {
if (layer.type === 'fill') {
const {paint} = layer; // Type is FillPaint
const {layout} = layer; // Type is FillLayout
} else if (layer.type === 'line') {
const {paint} = layer; // Type is LinePaint
const {layout} = layer; // Type is LineLayout
} else {
const {paint} = layer; // Type is PointPaint
const {layout} = layer; // Type is PointLayout
}
}
複製代碼
考慮下面的case,Point是使用直角座標表示的點, 而RadiusPoint則是使用極座標表示的點
interface Point {
x: number,
y: number
}
interface RadiusPoint{
x: number // radius
y: number // theta
}
function PointDistance(p:Point){
return Math.sqrt(p.x**2 + p.y**2)
}
let p1: Point;
let p2: RadiusPoint
PointDistance(p1);
PointDistance(p2); // 應該報錯但不報錯
複製代碼
雖然這裏的PointDistance要求類型是Point,但因爲是Typescript使用的是structual typing,致使 實際上能夠將RadiusPoint類型的變量也能夠傳遞進去,致使計算錯誤 咱們能夠經過添加一個brand標記區分二者
interface Point {
_brand: 'point',
x: number,
y: number
}
interface RadiusPoint{
_brand: 'radius',
x: number // radius
y: number // theta
}
function PointDistance(p:Point){
return Math.sqrt(p.x**2 + p.y**2)
}
PointDistance(p1);
PointDistance(p2); // 正常報錯
複製代碼
function f1(){
const x: any = expressionReturningFoo(); // 不建議,後續的x都是any了
processBar(x)
}
function f2(){
const x = expressionReturningFoo();
processBar(x as any) // 建議,只有這裏是any
}
複製代碼
function getLengthBad(arr:any){
return array.length; // 不推薦
}
function getLength(array:any[]){
return array.length //推薦
}
const numArgsBad = (...args:any) => args.length //Return any 不推薦
const numArgs = (...args: any[]) => args.length // Return number 推薦
複製代碼
有時候不使用any想編寫一個徹底類型安全的實現並不是易事,可是通常對於使用者 並不關心內部的實現是否安全,只關心對外暴露的簽名是否安全,此時咱們能夠將函數簽名和 函數實現相分離,以簡化內部的類型實現。這個技巧充分利用了當使用重載時,只有函數簽名對外可見, 而函數實現對外不可見 use-immer裏即便用了該技巧
// 類型安全的簽名
export function useImmer<S = any>( initialValue: S | (() => S) ): [S, (f: (draft: Draft<S>) => void | S) => void];
// 沒那麼安全的實現
export function useImmer(initialValue: any) {
const [val, updateValue] = useState(initialValue);
return [
val,
useCallback(updater => {
updateValue(produce(updater));
}, [])
];
}
複製代碼
Typescript中的any並非一成不變的,會隨着用戶的操做,Typescript會猜想更加合理的類型
const output = [] // any[]
output.push(1)
output // number[]
output.push('2')
output // (number|string)[]
複製代碼
考慮下述代碼
function parseYAML(yaml:string):any{
}
const book = parseYAML(` name: effective typescript author:yj `)
console.log(book.title) // no error
book('read') // no error
複製代碼
咱們發現上述代碼在該報錯的地方並無報錯, 更加安全的是使用unknown和配合自定義type guide
function parseYAML(yaml:string):unknown{
}
const book = parseYAML(`name: effective typescript author:yj`)
console.log(book.title) // 報錯
book('read') // 報錯
interface Book {
name: string;
author: string;
}
function isBook(val:unknown): val is Book {
return (
typeof val === 'object' && val !== null && 'name' in val && 'author' in val
)
}
if(isBook(booke)){
console.log(book.title)
}
複製代碼
同時須要區分{}和object和unknown
在引入unknown以前,多使用{},在引入unknown以後,基本上不須要再使用{}類型
考慮下面函數
class C {
vals = [1, 2, 3];
logSquares() {
for (const val of this.vals) {
console.log(val * val);
}
}
}
const c = new C();
c.logSquares();
const c2 = new C();
const method = c.logSquares;
method(); // check ok, 可是運行時報錯
複製代碼
上面的method函數調用Typescript並未檢查到其錯誤使用,致使其在運行時報錯, 咱們能夠爲logSquares提供this的類型杜絕錯誤的使用
class C {
vals = [1, 2, 3];
logSquares(this: C) { // 顯示代表要求的this類型
for (const val of this.vals) {
console.log(val * val);
}
}
}
const c = new C();
c.logSquares();
const c2 = new C();
const method = c.logSquares;
method(); // check ok, 可是運行時報錯
複製代碼
Typescript獨有的一些語言特性包括
enum Color {
RED,
BLUE
}
複製代碼
class Person {
constructor(public name: string)
}
複製代碼
namespace foo {
function bar(){}
}
/// <reference path="other.ts">
foo.bar()
複製代碼
class Greeter {
@logged
greet(){
return 'hello'
}
}
複製代碼
interface ABC{
a:string;
b:string;
c:string;
}
function foo(abc:ABC){
for(const [k,v] of Object.entries(abc)){
console.log(k,v)
}
}
複製代碼