TypeScript의 캔디코드

클래스 이름으로 해당 클래스의 인스턴스 생성

TypeScript에서 클래스 이름을 인자로 받아 해당 클래스의 인스턴스를 생성해 주는 코드입니다.

function create<T>(ClassName: { new(z: number): T }, i:number): T {
    return new ClassName(i);
}

위의 함수를 사용하는 코드는 다음과 같습니다.

class CLZ {
    i: number;
    constructor(i: number) {
        this.i = i;
    }
}

const o = create(CLZ, 100);
console.log(o.i);

실행 결과는 100

어떤 함수의 반환값 타입 얻기

어떤 함수가 있을 때.. 그 함수의 반환값에 대한 타입을 얻기 위한 코드입니다.

function func(x: number): string {
    return `value: ${x}`;
}

type K = ReturnType<typeof func>

위의 K 타입 별칭이 func 함수의 반환값에 대한 타입인데.. 결과는 string입니다.

배열 구성 요소의 타입 얻기

const MyArray = [
  { name: "Alice", age: 15 },
  { name: "Bob", age: 23 },
  { name: "Eve", age: 38 }
];

type Person = typeof MyArray[number];

사실 위의 코드 중 7번째의 number를 지정해 배열의 인덱스 값을 지정했는데, 이 곳에 어떤 숫자값이든 지정이 가능한데.. 이 부분이 애매모호해서 그냥 number로 지정하는 것으로 이해합니다. 여튼 위 코드에서 Person의 결과는 다음과 같습니다.

type Person = { age: number, name: string };

추론된 타입 얻기

TypeScript는 타입을 자동으로 추론하게 되는데 추론된 타입을 얻기 위해 infer를 사용합니다. 예를들어 배열을 구성하는 요소의 타입을 추론하고자 하는 예는 다음과 같습니다.

type Flatten<T> = T extends Array<infer E> ? E : T 
type Str = Flatten<string[]> // Str의 값은 string 타입
type Num = Flatten<number> // Num의 값은 number 타입
// 위의 코드는 다음과 동일함 : type Flatten<T> = T extends unknown[] ? T[number] : T

다른 예로 어떤 함수의 결과값의 타입에 대해 추론이 가능하다면.. 추론된 타입을 얻는 예는 다음과 같습니다.

type GetReturnType<T> = T extends (...args: never[]) => infer R ? R : never
type R1 = GetReturnType<() => number> // R1 값은 number 타입
type R2 = GetReturnType<(x: string) => string> // R2 값은 string 타입
type R3 = GetReturnType<(a: boolean, b: boolean) => boolean[]> // R3 값은 boolean[] 타입
function func(x: number): string { return `value: ${x}`; }
type R4 = GetReturnType<typeof func> // R4 값은 string 타입

생성자의 인자를 필드화 시키기(Parameter Properties)

클래스의 필드를 정의하는 코드를 작성하는 것은 때로 지루한데, 특히나 생성자로 초기화할 값을 받아 필드들의 값을 설정할때 그렇습니다. TypeScript는 다음처럼 이러한 지루함을 줄여줍니다.

class Params {
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {}
}

const a = new Params(1,2,3) // x, y, z는 각각 1, 2, 3으로 설정됨
console.log(a.x)
console.log(a.y) // ERROR
console.log(a.z) // ERROR
a.x = 100 // ERROR

인덱스 프로퍼티

interface User {
    name: string,
    [grade: number]: string,
    [key: string]: string,
}

const user: User = {
    name: "Dip2K",

    // [grade: number]: string로 인해 가능함
    0: "A",
    2: "B",
    5: "C",
    
    // [key: string]: string로 인해 가능함
    asdf: "adsf",
    axcv: "scvg",
}

console.log(user[2], user.asdf)

타입 도구 : Partial

Partial은 타입을 구성하는 항목 중 일부만으로 구성된 타입을 정의합니다. 아래의 코드를 이해한다면 TypeScript 프로그래머로써 중급 이상의 수준입니다.

interface Todo {
  title: string;
  description: string;
}
 
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}
 
const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};
 
const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

console.log(todo2)

todo2를 출력해보면 {title: ‘organize desk’, description: ‘throw out trash’} 입니다.

리터널 타입으로 변경하기

handleRequest의 두번째 인자는 “GET” | “POST” 타입이므로 아래의 코드에서 req.method는 이 유니온 타입을 충족시키지 못한다고 타입스크립트는 해석합니다. 이에 대한 해결책은 아래 주석으로 막아 놓은 코드 2가지입니다.

const req = { url: "https://example.com", method: "GET" }
// SOLUTION I  : const req = { url: "https://example.com", method: "GET" } as const
// SOLUTION II : const req = { url: "https://example.com", method: "GET" as "GET"}
handleRequest(req.url, req.method)

function handleRequest(url: string, req: "GET" | "POST") {
  // .
}

any

어떤 타입의 값이든 할당을 허용하는 타입으로 TypeScript의 타입 시스템을 무력화시키는 타입입니다. 부득이한 경우 사용하되 점진적으로 이 타입의 사용을 제거해 나가야 합니다.

아래의 코드는 모두 정상적으로 처리됩니다.

let a: any = 10
a = "Hello"
a.TEST()
let b: number = a
console.log(a, b)

unknown

어떤 타입의 값이든 할당을 허용하지만 타입이 지정된 다른 타입에 할당등과 같은 혼용시 unknwon 타입 변수의 타입을 as 키워드를 사용하여 명확히 명시해줘야 합니다.

let c: unknown = "Hello"
// [ERROR] c.length 
if(typeof c === "string") {
  c.length
}
c = 10
let d: number = c as number

never

어떤 타입의 값도 할당되는 것을 허용하지 않는 타입입니다.

let e: never
// [ERROR] let f: never = 10

활용예로 2가지를 살펴볼 수 있는데, 첫번째는 실행될 수 없는 함수에 대한 반환값을 지정할때로 다음 코드와 같습니다.

const foo = (name: string): never => {
  throw new Error("Not Implemented")
}

또 하나는 generic 타입으로 지정되는 타입의 사용을 막기 위한 용도입니다.

type NotString<T> = T extends string ? never : T
// [ERROR] let f: NotString<string> = "Hello"
let g: NotString<boolean> = true

변경할 수 없는 배열 타입 만들기

배열의 구성 값들을 변경하거나 삭제할 수 없도록 하기 위한 코드입니다.

const a = [1, 2, 3]
a.push(4)

const b: readonly number[] = [1, 2 ,3]
b.push(4) // ERROR

주변 환경을 비추는 객체 표현

환경맵(envmap)을 사용해서 주변 환경의 모습을 비추는 객체를 표현할 때 적용할 수 있는 코드입니다. 먼저 아무런 재질도 적용되지 않은 검정색 구체입니다.

위의 검정색 구에 주변 환경 이미지를 환경 맵 소스로 활용하는 코드는 다음과 같습니다.

const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 1024 );
this._mirrorSphereCamera = new THREE.CubeCamera( 0.05, 50, cubeRenderTarget );
this._scene.add( this._mirrorSphereCamera );
const mirrorSphereMaterial = new THREE.MeshBasicMaterial( { envMap: cubeRenderTarget.texture } );
this._sphere.material = mirrorSphereMaterial;

환경맵을 생성하는 CubeCamera 객체의 상태가 변경될 경우… 예를들어 카메라의 위치 등이 변경될 경우 다음처럼 update 매서드를 호출해줘야 합니다.

this._mirrorSphereCamera.update( this._renderer, this._scene );

이 글에 사용된 코드와 결과는 Three.js에서 제공되는 예제 코드 중 webgl_animation_skinning_ik.html에서 환경맵 적용에 대한 내용만을 정리한 것입니다.

three.js의 행렬 변환

Three.js는 이동(position), 회전, 크기변환과 같은 3차원 변환을 위해 행렬을 사용함. Object3D의 파생 클래스의 모든 객체는 matrix 속성을 가지고 있고 이 속성은 객체의 위치, 회전, 크기 변환값이 저장되어 있음. 이 글은 객체의 변환을 어떻게 업데이트 하는지 기술함.

객체의 변환을 업데이트하기 위해서는 2가지 방법이 있음.

1. 객체의 position, quaternion, scale 속성을 변경하고 이 속성들로부터 객체의 행렬을 다시 계산시킴.

object.position.copy( start_position );
object.quaternion.copy( quaternion );

기본적으로 matrixAutoUpdate 속성이 true이므로 행렬은 자동적으로 재계산됨. 만약 객체가 정적이거나 행렬 재계산을 수동으로 제어하고자 한다면 matrixAutoUpdate를 false로 지정하여 더 나은 퍼포먼스를 얻을 수 있음. 이때 다음처럼 수동으로 행렬을 재계산할 수 있음.

object.updateMatrix();

2. 객체의 행렬을 직접 변경해도 됨. Matrix4 클래스는 행렬 변경을 위한 다양한 매서드를 제공함.

object.matrix.setRotationFromQuaternion( quaternion );
object.matrix.setPosition( start_position );
object.matrixAutoUpdate = false;

윗 코드의 경우 matrixAutoUpdate의 값은 false로 지정했고 updateMatrix 매서드가 호출되지 않도록 보장해야 함. updateMatrix 매서드를 호출하면 position, scale 등의 속성값으로 행렬이 재계산되어 버림.

객체의 행렬에는 부모 객체와 연관된 객체의 변환값이 저장되어 있음. 월드 좌표계에 대한 객체의 변환을 얻기 위해서는 객체의 Object3D.matrixWorld에 접근해야 함.

부모 또는 자식 객체의 변환이 변경될 때, 자식 객체의 matrixWorld를 업데이트되도록 updateMatrixWorld 매서드를 호출할 수 있음.

Three.js는 3차원 회전을 위해 2가지 방식을 제공: 오일러(Euler) 각도와 쿼터니안(Quaternions). 이 2가지 방식은 상호간의 변환이 가능함. 오일러 각도 방식은 짐벌락(Gimbal Lock)이라고 불리우는 문제가 있음으로 객체의 회전은 반드시 쿼터니안 방식을 위한 quaternion 속성을 이용해야 함.

이 글의 원문

three.js로 체스, 볼링 게임 웹 만들기

three.js를 이용해 웹에서 어떠한 플러그인 없이도 멋진 3차원 그래픽 웹페이지를 개발할 수 있는데요. 게임 느낌의 3D 웹 개발에 대한 컨텐츠입니다. 내용이 다소 길지만 차근 차근 따라하시다보면 금새 완성하실 수 있습니다.


node.js로 server 개발 프로젝트 생성 절차

프로젝트 폴더를 만들어 VS.Code에서 오픈 후 package.json 파일 생성을 위해 터미널에서 다음 명령을 실행한 뒤에 생성된 파일을 편집함

npm init -y

{
  "name": "tstServer",
  "version": "1.0.0",
  "description": "",
  "keywords": [],
  "author": "",
  "license": "ISC"
}

nodemon 설치를 위해 다음 명령 실행

npm i nodemon -D

babel.config.json 파일과 nodemon.json 파일을 생성하고 각각 다음 내용으로 입력

{
    "presets": [ "@babel/preset-env" ]
}
{
    "exec": "babel-node src/index.js"
}

babel 설치를 위해 다음 명령 실행

npm i @babel/core @babel/cli @babel/node @babel/preset-env -D

express 설치를 위해 다음 명령 실행

npm i express

필요할 경우 pug 설치를 위해 다음 명령 실행

npm i pug

package.json에 설치된 항목에 대한 종속성(Dependency)가 존재하는지 확인하고 다음처럼 scripts 속성을 추가(또는 변경)

{
  "name": "tstServer",

  ..

  "scripts": {
    "dev": "nodemon"
  },

  "devDependencies": {
    "@babel/cli": "^7.20.7",
    "@babel/core": "^7.20.7",
    "@babel/node": "^7.20.7",
    "@babel/preset-env": "^7.20.2",
    "nodemon": "^2.0.20"
  },
  "dependencies": {
    "express": "^4.18.2",
    "pug": "^3.0.2"
  }
}

src 폴더 만들어 주고 index.js 파일 생성하고 다음처럼 입력

import express from "express";

const app = express();


const port = 7777;
const handleListen = () => console.log(`Listening on http://localhost:${port}`)
app.listen(port, handleListen);

다음 명령을 실행하여 서버 실행하고 웹브라우저에서 http://localhost:7777/로 접속해서 확인

npm run dev