프론트엔드 데브코스 5기 TIL 34 - TS, BEM 방법론

김영현·2023년 11월 13일
1

TIL

목록 보기
41/129

제네릭(Generic)

//함수 오버로딩 사용시
function toObj(a:string, b:string): {a:string, b:string}
function toObj(a:number, b:number): {a:number, b:number}
function toObj(a:string | number, b:string | number): {a:string | number, b:string | number}{
	return {a, b}
}

toObj("a","b");
toObj(1,2);
toObj(true,false) //불리언마저 추가하려면 함수 오버로딩이 너무 길어지게 된다...

//제네릭 사용시!

//함수 이름과 괄호 사이에 꺽쇠와 지정할 이름을 넣어준다.
function toObj<T>(a: T, b: T): {a:T, b:T}{
	return {a, b}
}
//바깥에서 string타입을 넣어주면 toObj옆 꺽쇠<T>로 들어간다.
toObj<string>('A', 'B') 
toObj<number>(1,2) 
toObj<boolean>(true, false) 

//제네릭 제약 사용시!
function toObj<T extends string | number | boolean>(a: T, b: T): {a:T, b:T}{
	return {a, b}
}
toObj('A', 'B') 
toObj(1,2) 
toObj(true, false)
toObj(null,null) //타입 오류

또한 interface나 타입 별칭등에서도 사용 가능하다.

interface ToObj<T>{
	a: T,
  	b: T
}
function toObj<T extends string | number | boolean>(a: T, b: T): ToObj<T>{
	return {a, b}
}

결국 꺽쇠로 흘러들어간 타입의 흐름이 어떻게 작동하는지 잘 알면 될것 같다!

제네릭 안에 여러속성(타입 변수)를 사용할 수도 있다.

//매개변수처럼 순서대로 들어온다!
interface User<T, U, V>{
	name:T,
  	age:U,
  	isValid: V
}

const heropy: User<string, number, boolean> = {name: 'heropy', age:85, isValid:true};
const kim: User<string, number, boolean> = {name: 'kim', age:11, isValid:false};
const lee: User<string, number, boolean> = {name: 'lee', age:43, isValid:true};
//만약 튜플로도 받고싶다면...
type User<T,U,V> = { name: T, age:U, isVaolid: V} | [T, U, V];
//반복되는 타입 또한 타입 내부에 넣어버릴 수 있다.
type U = User<string, number, boolean>;

const evan: U = ["Evan", 77, false];

클래스 내부의 제네릭

class Basket<T> {
	public items: T[]
  	//바깥에서 T라는 변수를 주지않는다. 
  	//타입 추론을 이용하여 T는 string이되고, string-array 타입이 된다.
  	//명시적으로 바깥에서 작성할 수도 있다.
  	constructor(...rest: T[]){
    	this.items = rest
    }
  	putItem(item: T) {
  		this.items.unsfhit(item)
  	}
  	takeOutItems(count:number){
    	return this.items.splice(0, count)	
    }
}

const fruitsBasket = new Basket('apple', 'banana', 'cherry');
fruitsBasket.putItem('orange');
const fruits = fruitsBasket.takeOutItems(2);
console.log(fruits) // ['orange', apple']
console.log(fruitsBasket.items) // ['banana', 'cherry']

다만 이렇게 추론을 사용했을 경우 타입스크립트의 장점이 퇴색될수 있음.
명시적으로 지정해주는 편이 옳다.
이 부분은 고민거리구먼?


제네릭의 조건부 타입

//타입을 지정할때 삼항연산자 사용 가능하다.
//string이나 number이면 boolean타입이고 아니면 never타입이다.
type MyType<T> = T extends string | number ? boolean : never;

const a: MyType<string> = true
const b: MyType<number> = true
const c: MyType<null> = true // never타입이기에 할당이 불가능하다.

개념 자체는 쉽지만 어떻게 활용할지 생각나질 않는다!
강사님께서 보여주셨다

type MyExclude<T,U> = T extends U ? never : T
type MyUnion = string | number | boolean | null

//string, number이 들어오면 할당해주고 boolean | null이 들어오면 never타입을 넣는다.
const a: MyExclude<MyUnion, boolean | null> = 123

사실 Exclude유틸리티 타입은 이미 내장으로 존재한다!
방법은 쉬운데 용법이 이해가질 않는다. 다음을 잘 보자.

type IsPropertyType<T, U extends keyof T, V> = T[U] extends V ? true : false

type Keys = keyof User // 'name' | 'age'. 각각 키값 추출해서 유니온 타입을 만들어줌
interface User{
	name: string
  	age: number
}

const n: IsPropertyType<User, 'name', number> = true

infer

// infer의 기능은 I가 추론 가능한(infer)타입이면 참. 아니면 거짓.
type ArrayItemType<T> = T extends (inferI)[] ? I : never;

const numbers = [1,2,3];
const a: ArrayItemType<typeof numbers> = 123;
const b: ArrayItemType<boolean> = 123;

추론 가능한 타입이라면 할당해주는 것이구나?
실제로 써봐야 잘 알것같다.

type SecondArgumentType<t> = T extends (f: any, s: infer S) => any ? S : never

function hello(a: string, b: number){};
const a: SecondArgumentType<typeof hello> = 123;
                            
//typeof hello는 (a: string, b: number) => void 타입을 반환.T라는 타입변수로 들어가서...
//두번째 매개변수를 추론한다. 따라서 숫자가 들어온다. 삼항연산자가 참으로 판단되어 S(숫자)타입이 할당된다.

BEM 방법론이란?

Block, Element, Modifier로 나누어서 클래스를 작명하는 기법이다.
왜 나왔나?
=> 겹침 방지. 일관성. 보기 편해서..등등.

여러 규칙이있지만 대표적인 4가지만 소개해보겠음.

  • 기본 : block__element_modname_modval
  • two__dash--style: block-name__elem-name--mod-name--mod-val
    1. 이름은 소문자로 짓는다
    2. B.E.M내부의 단어(block내부 단어, element 내부 단어...)는 -하이픈으로 구분한다.
    3. element는 __이중밑줄로 block과 구분됨
    1. modifier는 __이중밑줄로 element와 구분됨
    2. modifier-value는 -하이픈으로 구분됨.
  • camelCase : blockName-elemName_modName_modVal
    1. 이름 내부의 각 단어는 대문자로 시작(headerLeft같이).
    2. block, element 및 modifier의 구분 기호는 표준 체계와 동일.
  • ReactStyle-Case : BlockName-ElemName_modName_modVal
    1. block, element이름만 대문자로 시작
    2. 이름안의 각 단어는 대문자로 시작(카멜 케이스)
    3. element는 -로 block과 구분됨
    4. modifier와 값 사이는 표준 구성과 동일

이번 CSS과제에 적용할때 대충 훑어보고 적용했다...(반성반성...😥)
그래서 아예 공식문서에서 확인하였음!

그러면 block, element, modifier를 구분하는 방법은 뭘까?

block

웹 구성요소와 동일하다고 보면된다.

  • 중첩 가능
    블록은 내부에 블록을 포함할 수 있어야 한다


    출처

  • 독립적
    블록은 독립적인 개체기에 이동하여도 CSS나 JS의 수정이 필요없어야 한다.


  • 재사용 가능
    블록은 독립적인 개체이며 동시에 재사용이 가능하다.

컴포넌트의 단위와 비슷하다.

element

블록 외부에서 사용할 수 없는 블록의 구성품이다.

위치를 바꾸면 CSS의 수정이 필요하게되겠지?
또한 element내부에 element가 존재해선 안된다.

<!--
    Correct. The structure of the full element name follows the pattern:
    `block-name__element-name`
-->
<form class="search-form">
    <div class="search-form__content">
        <input class="search-form__input">

        <button class="search-form__button">Search</button>
    </div>
</form>

<!--
    Incorrect. The structure of the full element name doesn't follow the pattern:
    `block-name__element-name`
-->
<form class="search-form">
    <div class="search-form__content">
        <!-- Recommended: `search-form__input` or `search-form__content-input` -->
        <input class="search-form__content__input">

        <!-- Recommended: `search-form__button` or `search-form__content-button` -->
        <button class="search-form__content__button">Search</button>
    </div>
</form>

그러면 헤더를 좌,우로 나누었을때 header__left보단 header-left로 짓는게 더 낫겠구나?

modifier

같은 block이라도 modifier에 따라 다르게 보인다.
이는 선택사항. 마치 상태와 유사함.


느낀점

뭐든 잘 알고써야한다. 규칙은 특히 그렇다. 잘 알고 쓰지 못하면 혼선만 가중할 뿐이다.
내일은 코드리뷰 받은걸 정리해야지...

profile
모르는 것을 모른다고 하기

0개의 댓글