강의

멘토링

로드맵

인프런 커뮤니티 질문&답변

프린이님의 프로필 이미지
프린이

작성한 질문수

실전 연습으로 익히는 고급 타입스크립트 기술

[빈객체{}의 타입정의, 타입단언(as)] 클래스 멤버변수에 초깃값을 빈 객체{}로 할때, 빈 객체{} 타입을 어떻게 정의해야할까요?

해결된 질문

작성

·

423

·

수정됨

1

안녕하세요,

질문 :

타입스크립트, 클래스에서 사용하는 변수에 초기값을 빈 객체({})로 정의할 경우,

해당 빈 객체의 타입을 정의할 때, 타입단언, as를 사용하지 않고,

다른 방법으로 정의할 수 있을까요?

 

다음과 같은 코드가 있습니다.

interface Props{
 email:string;
 password:string
}

class Store<State>{
// state에 초깃값으로 빈객체{}를 설정할때,
// 빈 객체{}에 대한 타입이슈
 public state = {} as State
 ..중략..
}


export default new Store<Props>({
  email:'hello@hello.com',
  password:'hello'
})


Store란 클래스에서 state라는 변수를 빈 객체를 값으로 초기값으로 정의하였습니다.

즉, 멤버변수 state는 빈객체{}를 초기값으로 사용할 것이며

멤버변수 state는 인터페이스 Props 형태를 따르도록 규정하고 싶습니다.

 

public state = {} as State

빈객체{}의 타입을 정의할때,

타입단언 as를 사용하지 않고 다른 방법이 있을까요?

 

어떤 값을 빈 객체를 초기값으로 시작한다고 코드 작성할때,

as, 타입단어의 유혹?을 받곤 합니다.

 

{}객체에 대한 타입정의에 대해 검색해보니

Record타입으로 정의하는 내용이 나와 다음과 같은 코드로 변경해보았는데,

public state:Record<State,string> = {}

다음과 같은 에러가 발생했습니다.

스크린샷 2023-11-07 오전 2.04.18.png

reduce 함수를 사용할 때도, 초깃값을 빈객체{}로 사용할 경우에도 as를 사용해,

누산기(acc)의 타입을 as로 단언 한 코드를 종종 보곤 하는데요,

 

위와 같이 어떤 변수에 빈 객체{}를 초기값으로 혹은 값으로 할당하고 타입을 정의해야 할때,

타입단언을 쓰지 않고, 다른 방법이 있는지 문의 드립니다!

 

답변 1

0

애프터캠프님의 프로필 이미지
애프터캠프
지식공유자

안녕하세요 프린이님

interface Props {
 email:string;
 password:string
}

class Test<State> {
  public store: State
  constructor(initialState: State) {
    this.store = initialState;
  }
}

new Test<Props>({
  email: 'hello',
  password: "hello",
})

위와 같이 처리하면 될 것 같은데 굳이 아래와 같이 미리 초기화 하는 특별한 이유가 있을까요?

public store: State = {}

 

프린이님의 프로필 이미지
프린이
질문자

안녕하세요!
위의 내용에 관련해, 현재 작업중인 코드로 연결해서 문의 드립니다!

빈객체{}의 타입을 지정하려는 이유가,

해당 빈객체{}에 내용을 다르게 할당하여

종류/목적별로 다양한 객체를 생성할 목적입니다.


다음의 코드가 있습니다.

import { StoreObservers, SubscribeCallback } from '../types/store';

class Store<S> {
// state를 빈객체{}로 정의하여
// 목적, 종류에 맞는 값을 할당하여, 해당하는 내용이 포함된 state를 만들기 위해서 입니다.
  state = {} as S; 
  private observers = {} as StoreObservers; 

  constructor(state: S) {
    for (const key in state) {
      Object.defineProperty(this.state, key, {
        get: () => state[key],
        set: (val) => {
          state[key] = val;
          if (Array.isArray(this.observers[key])) {
            this.observers[key].forEach((observer) => observer(val));
          }
        },
      });
    }
  }

}

export default Store;

Store 클래스를 new 호출로 사용해서 Store 클래스에 전달한 인자의 종류대로 state를 만들 수 있습니다.

다음은 클래스 Store를 사용하는 경우 입니다.

코드1.

import Store from './store';

interface State {
  photo: string;
  name: string;
  email: string;
  blog: string;
  github: string;
  repository: string;
}

export const aboutStore = new Store<State>({
  photo: '',
  name: '',
  email: '',
  blog: '',
  github: '',
  repository: '',
});


코드2.

import Store from './store';
import { DetailedMovieType, MovieStateType } from '../types/movie';

const store = new Store<MovieStateType>({
  searchText: '',
  page: 1,
  pageMax: 1,
  movies: [],
  movie: {} as DetailedMovieType,
  loading: false,
  message: 'Search for the movie title!',
});

export default store;

코드1에 해당하는 state 객체를 만들 수 있고,

코드2에 해당하는 state 객체를 만들 수 있습니다.

즉 Store클래스로 다양한 내용물을 담은 state를 만들려 하다보니
우선 Store 클래스의 state를 빈객체{}로 정의하게 됐습니다!


as로 단언을 하면 에러 없이 해결되나,

!할당단언 같은 눈가리고 아웅?하는 문법이 위와 같이 빈객체{} 타입정의에 좋은건지 궁금합니다!

혹시 as 말고 Record유틸리티 타입으로도 위와 같은 상황을 정의 가능할까요?

위의 내용처럼 key:value 형식의 내용말고
함수 타입 같은 경우도 빈객체{}의 타입을 지정해야하는 경우도 있는데,
이것도 어떻게 하면 좋을까요?

export interface StoreObservers {
  [key: string]: SubscribeCallback[];
}
export interface SubscribeCallback {
  (arg: unknown): void;
}

private observers = {} as StoreObservers; 

질문의 포인트는 빈 객체{}에 대한 타입정의 라서 혹시 좋은 의견 있으시면 조언을 요청드립니다!!


image
감사합니다!

 

 

 

애프터캠프님의 프로필 이미지
애프터캠프
지식공유자

네 의도를 파악했습니다

그런데 제공해주신 코드의 특수성이 있는 것 같네요. 원래 넘어온 state로

this.state = state 하면 타입 에러가 발생하지 않는데 에러가 발생하는 이유는 특수한 목적을 갖고 Object.definePropertythis.state를 초기화하고 계셔서 에러가 발생하고 있습니다.

 

이럴 땐 제가 생각했을 땐 as 외엔 특별한 방법이 없을 것 같은데 만약에 제가 지금 작성한 코드를 수정해보라고 한다면 아래와 같이 Proxy 사용해서 수정할 것 같습니다.

interface State {
  photo: string;
  name: string;
  email: string;
  blog: string;
  github: string;
  repository: string;
}

type StoreObservers<S> = {
  [K in keyof S]?: Array<(value: S[K]) => void>;
};

class Store<S extends Record<string, any>> {
  private state: S;
  private observers: StoreObservers<S> = {};

  constructor(initialState: S) {
    this.state = new Proxy(initialState, {
      get: (target, prop: string) => {
        return target[prop];
      },
      set: (target, prop: string, value) => {
        this.notify(prop, value);
        return true;
      }
    });
  }

  private notify(prop: keyof S, value: any) {
    if (Array.isArray(this.observers[prop])) {
      this.observers[prop]?.forEach(observer => observer(value));
    }
  }
}

const aboutStore = new Store<State>({
  photo: '',
  name: '',
  email: '',
  blog: '',
  github: '',
  repository: '',
});
프린이님의 프로필 이미지
프린이
질문자

안녕하세요, 애프터캠프님! 답변 남겨주셔 감사드립니다.

알려주신 코드로 변경해 사용하겠습니다!
다만 궁금한 사항이 있어 몇가지 추가 문의 남깁니다!

#1. object.definePropertythis.state를 매번 초기화 하는 코드가 안 좋은 코드인가요?

#2. proxy를 사용해 기존 코드를 변경하는 이유가 무엇일까요? proxy에 어떤 특징/특성 때문에 proxy로 코드를 변경 하신걸까요?

 

#1. object.defineProperty로 this.state를 매번 초기화 하는 코드가 안 좋은 코드인가요?

초기에 문의 드린 클래스 Store 코드에서,

매번 object.defineProperty로 this.state를 매번 초기화 하는 것이 안 좋은 코드일까요?
음,, 뭐 그런 객체를 변경가능한/변경하지 못하게 하는 그런 이슈 때문인건지요?

proxy로 코드를 추천해주시면서, 기존 코드가 갖는 문제점이 있는건지 문득 궁금해서 문의 드립니다.

 

#2. proxy를 사용해 기존 코드를 변경하는 이유가 무엇일까요? proxy에 어떤 특징/특성 때문에 proxy로 코드를 변경하신걸까요?

텍스트 적으로 proxy의 개념을 공부한 적은 있는데, 직접 proxy를 코드에서 사용해본 적이 없어서 문의 드립니다!
아는 내용만 사용하고 익숙하다보니, 새로운 개념(proxy)이 있어도 적용할 수 없어서 (proxy)학습 차원에서 문의 드립니다!
어떤 맥락에서 proxy의 특징과 이유가 있어서 사용해주셨는지 알려주시면,
추천해주신 코드를 뜯어보고 (proxy를) 학습하는데 ,(이후 proxy를 코드에 적용해보는데) 큰 도움이 될 듯합니다!

 

귀한 시간 내어, 읽어주시고 답글 남겨주셔 감사드립니다!

애프터캠프님의 프로필 이미지
애프터캠프
지식공유자

1,2번 답변을 한꺼번에 드리면,

작성하신 코드 자체는 문제는 없는데, 가독성 측면에서 변경했습니다. 가독성이라는 것도 주관적일 수 있어서 제 논리를 말씀드려보면...

MDN 문서를 보면 Proxy 설명이 아래와 같이 되어있습니다.

The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.

기존의 object의 기능을 인터셉트해서 기존의 동작을 재정의한다고 되어있는데 프린이님이 작성하신 코드가 딱 그런 코드라서 Proxy를 사용하면 그 의도가 분명해지지 않나 생각합니다.

 

프린이님의 프로필 이미지
프린이

작성한 질문수

질문하기