Typescript Handbook

zmin·2022년 8월 15일
0

https://www.typescriptlang.org/docs/handbook/intro.html

여기서 말하는 불가능은 실행조차 안되는 불가능이 아니라 그냥 타입스크립트 컴파일러가 오류를 뱉어낸다는 의미

Type Annotation

기본 타입

자바스크립트의 기본 primitives인 string, number, boolean

Array( 타입[] || Array<타입>)이라고 표현

any : 말 그대로 any 아무거나 넣을 수 있음 → 그런데 아무거나 넣을 수 있으며 사실 타입스크립트를 쓰게 되는 의미가 없기 때문에 최대한 지양하는게 좋음

let, var을 사용한 변수 선언시 타입을 정해주면 해당 타입의 값 말고는 재할당이 불가능함

const는 어차피 재할당이 안되지만 배열할당시 Array<type>으로 타입을 지정해주면 다른 타입의 값을 요소로 대입할 수 없음
→ 그런데 이건 안해도 타입스크립트가 초깃값을 이용하여 알아서 추측해서 경고를 날려줌

그런데 실제 값을 넣어줘도 동작함 → 그 값이 아니라면 안됨 Unit types 라고 부름

Function

!! 파라미터에 타입을 지정해줄 수 있다 !!

반환 값에도 타입을 지정해줄 수 있다. 이때 반환 값의 타입은 아래와 같이 파라미터 리스트 이후, 함수 몸체 이전에 표시한다.

function test(a:number) : number {
	return a + 1;
}

익명함수

는 보통 콜백함수로 많이 쓰이는데 이때 이 익명함수의 파라미터의 타입을 지정해주지 않아도 타입스크립트는 알아서 추측한 뒤 오류를 뱉어줌 → contextual typing

const arr = [23, 35, 26];

arr.each((n) => console.log(n)) // 알아서 n이 number라고 추측

Object

파라미터가 객체인 경우 각 프로퍼티에 대해 타입을 지정해줄 수 있다

const obj = {
	type: string,
	age: number,
	props: CustomPropsType,
	onClick: () => void,
}

Optional….

object의 속성이든 그냥 일반 파라미터든 Function의 파라미터에서 없을 수도 있는 값은 ?연산자를 통해 optional하다는 것을 알려야함

function add (a: number, b: number, c?: number) {
	// c는 있을 수도 없을 수도..
	// 이를 인지하고 함수 내용을 작성할 수 있도록 해야함
}

→ 자바스크립트의 경우 없으면 없는대로 undefined를 알아서 할당하여 연산하지만 이는 예상치못한 오류를 불러올 때가 있어 타입스크립트에선 이를 명시하도록 유도함

→ 개발자가 이런 것들을 인지하는 것이 중요

Union

| 을 이용해 이 값이 가질 수 있는 타입을 모두 명시한다.

하나 중요?한건… 이런 파라미터에 을 넣는 것은 크게 상관없다. 나열된 type중 하나라도 맞으면 문제없이 실행되는데 method의 반환값을 바로 파라미터로 전달하는 경우는 해당 반환값이 나열된 type 모두를 만족시켜야한다.

를 해결하기 위해서는 타입 스크립트를 쓰지 않을 때처럼 내부동작을 분기시켜주는 것이 필요하다. → 모든 타입에 오류가 전혀 나지 않을 동작이라면 상관 없음

Type Aliases

사용자가 직접 타입을 만들 수 있다.

위의 객체 타입을 계속 똑같이 써야할 때, 그런데 만약 그 객체 타입이 복잡하다면 이를 매번 쓸 수 없으니 Type으로 만들어서 이를 대신 쓰는 것이다

type Obj = { x: number; y: number; }; 
type NumOrStr = number | string;

function test(obj: Obj) {...};

이때 덕타이핑을 하기 때문에 무조건 Obj 타입으로 만들어진 객체가 아니라 그냥 { x: 9, y: 0}처럼 구조적으로 동일하기만 하면 된다. 똑똑한 타입스크립트

그리고 서브셋도 가능하다 명시된 프로퍼티만 가지고 있으면 문제없이 실행 가능

Interfaces

type Aliasses와 생긴것도 비슷하고 하는 것도 비슷함.

그럼 왜 다른거지?

두 개는 거의 비슷하기 때문에 보통 개발자가 자유롭게 선택해서 사용할 수 있다.

하지만 다른점이 있다면 …

  • 타입을 확장할 때 interface의 경우는 extends를 사용하는 반면에 type aliases는 & 을 이용하여 확장할 수 있다.
  • type aliases의 경우 이미 선언되어있는 type에 추가적으로 값을 넣어줄 수 없다. 위처럼 타입 확장을 이용해야함. 하지만 interface의 경우는 기존에 존재하는 타입의 이름을 선언할 때 그대로 사용하면 이미 존재하는 인터페이스에 필드를 추가로 넣어줄 수 있다.

위에서 보여준 것처럼 인터페이스를 이용해도 되고, 객체형태의 type alias로도 설정할 수 있다. 사실 공식 문서에서 말하는 것들은 잘못된게 많음. 두 값은 거의 비슷하다고 봐야함 → 인터페이스와 타입 모두 상속받을 수 있고 섞어서도 가능 → 상속이 가능하니 abstract하게 적어둔 것들을 구현하여 나타낼 수도 있음

interface와 type의 차이점은

  • interface의 경우 두개의 다른 인터페이스를 선언한 규칙?(값)을 합쳐 하나의 인터페이스처럼 쓸 수 있다.
  • 타입 정의에 union 연산자가 있는 type alias는 클래스, 인터페이스에서에서 상속받아 구현할 수 없다.

Type Assertion

변수의 타입을 지정해주는 것이 아니라 오른쪽의 표현식과 값에 대해 ~ 였으면 좋겠다 조언 해주기 → 타입스크립트가 보고 이거 아닌데 싶으면 오류 뱉어냄

as 키워드 이용, <> 이런 표기법을 사용하기도 했으나 jsx구문과 구별하기 어려워서 as 키워드를 사용하는 것이 좋음

그니까 얘를 이 타입처럼 생각하고 컴파일하라고 말해주는 것 → 이것도 많이 쓰게되면 typescript를 이용하여 타입을 제한하는 이유가 조금 사라짐

하지만 그렇다고 number에 string을 assertion 해줄 수는 없고 보통 any 와 같이 뭔지 알 수 없는 값에 대해 이 값을 특정 타입이라고 생각해라~ 는 식으로 사용하는 경우가 많음

Literal Types/Interfaces

let과 const 의 차이를 여기서 확인할 수 있는데 ..

let의 경우 그냥 값을 할당하면 해당 값의 타입이 변수의 타입으로 암묵적으로 지정 → 타입에만 맞으면 어떤 값이든 재할당 가능

근데 const는 해당 타입이 아니라 그 값 자체가 타입으로 지정이 된다 → 그래서 그 값이 아니면 문을 못 열어주는것이다.
그 값 자체가 타입 → 리터럴 타입

function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

인터페이스로 나타내는 경우에도 동일

보통 union 연산자나 keyof 키워드를 이용해서 어떤 객체의 key값만을 입력받기 위해 타입을 설정하는 등의 작업을 할 때 많이 사용한다.

Narrowing

계~~속 얘기하다시피 타입스크립트는 어떤 새로운 것을 하기위한 아주 새로운 언어가 아니라 자바스크립트를 좀 더 안전하고 bending over backwards 없게 만들어주기 위한 것, 특별한 기능이 있는 것은 아님! 그냥 오류 생길 가능성이 있다면 오류 뱉어주는 형태, 원한다면 그냥 컴파일된 js가지고 빌드할 수는 있음(하지만 오류가 날수도 있겠지)

narrowing은 특히 함수 파라미터 타입이 union일 때 해줘야하는 작업 중 하나

여러 타입의 값이 들어올 수 있으니 해당 타입에 맞는 연산을 따로따로 적용해줘야한다는 것…!!! 잘 통과할 수 있게 좁게 길을 만들어주자~

  • typeof를 이용해서 받은 파라미터의 타입을 검사할 수도 있겠지만 이 typeof에 걸리지 않는 애들도 있음, null 같은 경우 null 이 아니라 그냥 object로 검출
    (자바스크립트에서 지원하는 typeof return value는 string, number, bigint, boolean, symbol, undefined, object, function)
    • Array는 Array.isArray(arr) = 'array' 로 인증 가능
    • 직접 만든 것을 이용하고 싶다면 instanceof를 이용할 수 있는데 이의 경우 상속이 영항을 미칠 수 있어 유의해서 사용해야함
  • truthiness 이용하여 null, undefined 제외하기 → 그런데 이 경우는 falsy한 값이 들어왔을 때의 처리를 주의해서 해줘야함 0(또는 “”)이 들어왔을 때 숫자로 판단해야하는 로직에서 false라고 판단할 수 있기 때문
  • equality 이용 → 값이 같다는 건 타입이 같다 → narrowing
    typeof를 이용하는 것도 일종의 equality narrowing
  • is 를 이용한 type predicate
    • 타입 확인을 위한 함수를 만들 때 주로 쓰이는 것 같음

      function isFish(pet: Fish | Bird): pet is Fish { // 여기서 명시한 pet is Fish일때 참을 반환할 수 있도록
        return (pet as Fish).swim !== undefined;       // return문을 작성
      }
  • 객체가 파라미터로 들어오는 경우 특정 property가 있는지 없는지 in 연산자를 이용하여 타입을 narrowing할 수도 있음
    • 비슷한 구조의 타입이라면 특정 속성값을 비교할 수도 있겠다~~ switch같은거 이용해서
  • never type은 그 어떤 타입의 값도 들어갈 수 있다 → 대신 타입이란걸 가지지 않게 됨 → 타입이 없다고 판단. switch의 마지막즈음에 사용하며 뭔가 잘못된 것을 나타낼때 사용하기도 함. 정상적으로 작동하지 않을 때 사용

Function with Type

함수의 파라미터로 함수를 받을 때, 해당 함수의ㅡ 생김새를 타이핑 할 수 있다.

여기서 함수의 생김새란…? 파라미터가 어떻고 이걸로 나오는 return이 어떻고…

function greet(say : (a: string) => void) { ... }
// 파라미터로 함수를 받아올 것이고 그 함수를 greet내부에서 say라고 부르겠다.
// 그 함수는 string을 파라미터로 받아서 void를 내놓는 함수이다.

물론 저런 함수타입 표현식도 type으로 나타낼 수 잇음

type Fn = (a: string) => void;
... say: Fn ...

Call Signature

화살표 함수 사용하지 않고 : 이용하여 나타낼 수도 있음

이는 함수도 객체이기 때문인데 함수가 실행되는 부분 이외의 프로퍼티를 가질 때 유용하게 사용가능

type Fn = {
	property: string,         // 함수의 프로퍼티
	(parameter: string): void // 함수 호출시에 사용되는 부분
}

Construct signatures

constructor의 경우 new 키워드를 앞에 붙여서 선언 가능

type fn = {
	new (): object,      // 생성자
	(a: string): string, // 그냥 호출시,
}

Optional Parameters

object에서도 그랬지만 없어도 괜찮은 파라미터의 경우 그냥 두지 말고 앞에 optional 이라는 의미의 ? 를 붙여줘야한다. 예기치 못한 값이 나오는 것을 방지해준다.

또한 이런 optional parameters로 설정하면 undefined는 넘겨줄 수 있다 → 값을 사용하지 않겠다는 것을 명시해줄 수 있음

원하는 것보다 많은 개수의 파라미터는 자바스크립트처럼 무시한다.

Function Overload

같은 함수이름과 반환 값을 가지지만 파라미터에 차이가 있는 경우……………….

이 경우 body를 제외한 함수의 선언부만 나열해주고 마지막 함수를 선언할 때 body를 같이 적어준다.

그 중 선언부만 적은 부분을 function signature이라고 함

함수 시그니처를 오버로드 하려면 그 시그니처를 무조건 포함해야함 시그니처의 파라미터는 한 개인데 오버로드하여 implementation하려는 파라미터는 0개일 수 없음, 위 시그니처의 경우를 모두 포함해야함 갑자기 다른 타입을 받아도 안됨 (any는 가능)

아래 구현된 함수의 계산 경우의 수에 위 시그니처들이 포함되어야함

// 얘는 잘 만든 오버로드
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

Declaring this in a Function

this의 경우 해당 함수가 호출되는 곳에 따라 달라지게 된다. 아무리 객체의 메소드더라도 객체.메소드() 의 형태로 호출하지 않고 const f = 객체.메소드; f();의 형태로 호출한다면 그 메소드의 this에는 전역객체가 담기게 될 것이다.

그래서 이런 예상치 못한 오류를 막기 위해 this로 사용될 객체의 타입을 파라미터 부분에 명시해줄 수 있다.

→ 이것이 가능한 이유는 자바스크립트 자체가 this라는 것을 키워드로 사용 중이기 때문에 이것이 매개변수가 아니라는 확신이 있고, 따라서 this: T 의 형태로 작성된 구문은 매개변수를 의미하는 것이 아니라 this에 바인딩 될 객체의 타입을 말하는 것이라는 것을 말해줄 수 있다.

→ 그리고 이 경우에는 인수로 함수를 넘겨줄 때 function 키워드를 이용하여 익명함수를 전달해야한다. 화살표 함수는 this 바인딩이 일어나지 않기 때문

other type with function

  • void: 함수 내부에 return문이 없을때. 함수가 무사히 잘 끝냈다는 것을 명시
  • object: (≠Object)
  • unknown: 함부로 접근하려하면 오류 뱉음, any의 경우 number에 length 접근을 하려한다거나 하면 그냥 undefined를 뱉지만 얘는 아예 해당 연산을 불가능하게 함 →어떤 타입이어도 문제없이 실행할 수 있도록 해야함
  • never: 아무것도 안 뱉음, void와 다른 점은 never를 반환하는 함수는 에러를 던지는 것 (중간에서 끝나서 never)임
  • Function: 말 그대로 function을 내뱉음

void도 엄연히 값의 타입중 하나, 우리가 뭘 사용하고 할 수 있는 것은 아니지만 사용할 수 없는 어떠한 값을 반환한다고 생각할 수 있음, 잘 끝났다~ 는 표시 정도로 이해할 수 있겠네요

그 반면에 never는 정말 정말 아무 값도 반환하지 않음 → 이 말은 function이 비정상 적으로 종료됐다는 의미와 같음 왜냐면 제대로 끝났다면 void를 뱉었을 테니까
에러를 뱉었거나 갑자기 중간에 종료됐거나 하는 것들

Rest Parameter → 보통 any[ ]

const [a:number, b: string, …c: any[]] = dest; 와 같이 사용

압도적으로 any가 많이 사용됨 → 뭐가 있을지 모르기 때문

Assignability of Functions

type voidFunc = () => void;
 
const f1: voidFunc = () => {
  return true;
};

void는 정말 특별한 친구인데요 다른 애들(number, string 등)은 저렇게 적어두면 반환값이 그게 아니라면서 오류를 뱉음

근데 쟤는 아님

반환값이 void인 함수 타입은 그 어떤 값도 return하는 것으로 덮어 씌울 수 있다. 타입 검사를 하지 않겠다는 말과 같다.

Object Type

앞선 애들 중 함수 파라미터로 객체를 받는다거나,
객체 인터페이스를 정의한다거나
객체 alias를 정해주는 방법으로 객체 타입을 정해줄 수 있었다.

앞서 본 optional properties의 경우를 살펴보자.

type Person = {
	name: string;
	age?: number;
	address?: string;
}

이렇게 되면 age: number | undefined , address: string | undefined 와 같다.

사실 계속 명심해야할건 저렇게 표시해준다고 모든 처리가 끝나는 것이 아니다

타입스크립트는 문제가 생길 만한 부분을 짚어주는 역할을 하는 거지 ? 같은 것들 안 적어도 실행되고 타입스크립트에서 오류를 아무리 뱉어내봤자 자바스크립트로 컴파일된 파일은 잘 작동할 수도 있다. 하지만 이제 좀 더 예기치 못한 오류에 한 발 더 가까워지는 격…

아무튼

undefined에 대한 처리를 해줘야한다. es6에서 나온 파라미터 기본값 설정도 있겠고 아니면 정말 값이 undefined인지 아닌지 검사하는 로직을 추가할 수도 있겠다

근데 기묘하다 파라미터로 객체를 넘겨주면서 속성을 타이핑해서 비구조화할당 문법으로 받으면 이름이 바뀐다

function aLittleBitWeird({x: number, user: Person}) {
	console.log(x); // 를 하면 x를 못 찾는다
}

이건 자바스크립트 부터 있던 특징이라 변경이 안되는 것같음 → 이 경우는 타이핑을 해준게 아니라 파라미터로 틀어오는 x 프로퍼티의 이름을 number로 바꿔서 사용하겠다는 것임 아예 다른 의미입니다.

readonly properies

오직 읽기만 가능하고 재할당이 불가능한 프로퍼티 설정

하지만 역시 참조형이 되는 순간 그 안의 요소는 수정할 수 있다.(당연함 재할당이 불가함)

type Person = {
    name: string;
    readonly books: Array<string>;
}

const zmin : Person = {
    name: 'zmin',
    books: ['refactoring','deepdive javascript','core javascript']
};

function test (user:Person) {
    user.books[2] = 'hi';
}

test(zmin);
console.log(zmin.books);

// [ 'refactoring', 'deepdive javascript', 'hi' ]

프로퍼티 어트리뷰트를 직접 설정하지 않아도 쉽게 할 수 있음

한가지 특이한건 readonly가 있든지 없든지 구조는 달라지지 않는다는 점 → 그래서 같은 구조에 readonly가 없는 경우를 있는 경우에 할당할 수 있음

// 이건 공식문서 예제
interface Person {
  name: string;
  age: number;
}
 
interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}
 
let writablePerson: Person = {
  name: "Person McPersonface",
  age: 42,
};
 
let readonlyPerson: ReadonlyPerson = writablePerson;

writablePerson.age++;  // 가능

const vs readonly

'readonly' type modifier is only permitted on array and tuple literal types.

참조형이 아닌 변수에는 설정 불가 ⇒ const로 지정 가능하니까 굳이 할 필요 없음

아니면 아래와같이 assertion을 이용해서 설정할 수도 있음

const a = [1, 2, 3] as const;
-> 참조형 안에 참조형이 있어도 전부 readonly 가 됨

Index Signatures

아래와 같이 인덱스 시그니처를 나타낼 수 있다. 보통 Array 나 객체를 표현할 때 많이 사용한다.

객체의 경우 해당 표현이 key와 value의 type을 명시해주는 것과 같다.

interface NumberDictionary {
  [index: string]: number;
 
  length: number;
  name: string;   // string타입은 위에서 명시한 value type인 number와 다르므로 오류
}

Extends

타입 상속이 됨, 여러 타입을 한 번에 같이 받을 수 있음 → 자바 클래스 상속 하듯이

interface NewType extends type1, type2 {
	상속받은 타입 둘 다 쓸 수 있음
}

Intersections

타입의 일종

타입과 타입의 곱을 말하는 것 같은데.. 교차라곤 하지만 두 타입을 전부 포함하는 새로운 타입을 만들어내는 것

중요한건 얘는 type alias에 속한다는 점 → 그래서 union연산이 포함되어 있으면 상속이 안됨

인터페이스로 만들 수도 있지만 인터페이스와 다름

Generic Object Type

제네릭을 사용할 수 있습니다! → 반복되는 코드 줄이기

interface Box<T> {
	contents: T;
}

const stringBox: Box<string> = {
	contents: 'this is a string box'
}

// 이때 저 제네릭에는 사용자가 만든 새로운 타입도 들어갈 수 있음
interface Apple {..사과..}

const appleBox: Box<Apple> = {
	contents: {..사과..}
}

이쯤 다시 말하지만 interface 말고 type alias 써도 됨!!!ㅁ

Array Type

둘다 가능, Array 라고 명시해두긴 했지만 자바스크립트 내부의 data structure모두 사용가능

Array<string>
string[]

Map<K, V>

ReadonlyArray

readonly인 Array 타입, 객체 명처럼 생겨서 생성자가 있을 것 같지만 없음

글고 리드온리를 안리드온리에 대입하는 거 안됨

안 리드온리를 리드온리에 대입하는건 됨

tuple type

도 사용방법 동일

근데 튜플?

자바스크립트에 튜플 없음 (그래 그래서 이상햇어) → 배열비구조화 할당과 같아보임

굳이 없어도 되는 이유라하면 자바스크립트는 타입이 없기 때문에 배열 하나에 다양한 타입의 값들을 넣을 수 있어서 굳이 튜플이 필요하지 않았음

C++ 생각만 해봐도 항상 타입 지정해서 사용하니까 다양한 타입을 넣으려면 튜플, 벡터같은거 이용해서 각 타입을 미리 지정해줬어야했지않니??

근데 갑자기 이런 자바스크립트에 타입이 생기니까 그런 타입스크립트에도 자연스럽게 튜플이 생기게 된 것이에요

그래서 tuple 이라는 자료형이 있는 것이 아니라 type alias 등을 이용하여 지정 해줘야함(물론 그냥 사용할 수도 있음)

type BooleansStringNumber = [...boolean[], string, number];

const a: BooleansStringNumber
const a: [...boolean[], string, number]

→ 사실 그냥 하나하나 정해줘도 될 텐데 이렇게 묶는 이유는 관련있는 정보를 좀 더 묶어서 나타내기 위한 것이겠지?

Type manipulation

Generic

<Type>으로 타입을 변수로 지정하여 사용할 수 있다. 로직이 동일한데 다른 타입을 사용해야 하는 경우에 많이 사용된다 → 반복되는 코드를 줄일 수 있음

function identity<Type>(arg: Type): Type {
  return arg;
}

let output = identity<string>("myString"); // <string>을 명시하지 않아도 "myString"으로 추론하여 사용!!

좀더 general하게 타입을 지정해주는 것. 이후 사용할 때 <string>등을 이용해 실제 타입을 적용할 수 있다. 아주 작은 범위의 변수 역할을 하는 셈 narrow에서도 나왔지만 해당 타입이 제너릭으로 주어진 변수의 경우 확실하게 있다고 판단되지 않는 프로퍼티는 사용할 수 없다.

특정 타입을 꼭 포함하는 type이어야 한다면 extends 를 이용해서도 나타낼 수 있다.

interface Person {
  name: string;
}
 
function getInfoPerson<Type extends Person>(arg: Type) {
  console.log(arg.name);
}

getInfoPerson({name: 'abc', age: 30}); // name 프로퍼티를 꼭 가지고 있어야함
// <>로 타입을 명시해 주지 않아도 해당 매개변수가 Person을 extends한 구조라는 것을 알아서 잘 확인함

keyof

해당 타입, 객체의 키가 가질 수 있는 타입을 유니언으로 나타냄

단순히 말해서 객체의 키들을 리터럴 타입 유니언으로 사용하겠다는 것

변수로 받은 값을 이용하여 객체의 프로퍼티에 접근하고 싶을 때 유용하게 사용 가능

type Animal = {
	type: string,
	age: number,
}
function animalInfo(property: keyof Animal){
	const a: Animal = ...;
	console.log(a[property]);  // property가 Animal타입 객체 안에 존재하는 것이 확실하기 때문에 오류 발생하지 않음
}

typeof

실제 값의 type을 나타냄, 정의된 타입 X

Animal 타입으로 정의된 변수 a 에 대해 typeof를 하더라도 Animal 타입의 구조대로 나오는 것이 아니라 실제로 a의 값들에 대한 type을 알려줌

condition types

SomeType extends OtherType ? TrueType : FalseType;

someType이 OtherType에 포함되면 TrueType을, 포함되지 않으면 FalseType을 사용하겠다.

보통 someType에 제네릭을 사용하여 구현하는 경우가 많은 것 같음

distributive conditional types

someType이 유니온 타입으로 전달되었을 때 그 condition types도 union 각각에 대해 실행한다.

readonly & optional 추가/삭제

각 조건을 가지는 프로퍼티들에 대해 +, - 를 사용하여 조건을 빼고 더해줄 수 있다

type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];  // readonly 삭제
}

type Option<Type> = {
  [Property in keyof Type]+?: Type[Property];   // ? 옵셔널 조건 추가
};

template Literal type

type Animal = "tiger" | "puppy" | "bear";

type AnimalId = `${Animal}_id`; // "tiger_id" | "puppy_id" | "bear_id"

이외에도 타입스크립트 자체에서 string literal type으로 정의된 타입들에 대해 연산을 수행해주는 타입이 있다

  • Uppercase<T>
  • Lowercase<T>
  • Capitalize<T>
  • Uncapitalize<T>
profile
308 Permanent Redirect

0개의 댓글