Typescript에서 옵셔널(Optional) 사용하기

Posted by , August 27, 2024
TypescriptVSCode
Series ofJS_TS

thumbnail

Dart에서 자주 쓴 Optional

간만에 글을 남긴다.
최근 마와셀 앱 업데이트 후 와인 정보를 위해 웹쪽을 다시 개발하고 있다.

코로나 걸려서 잠시 쉬다가, 침대에서 빈둥거리기도 시간이 아까워서 누워있다가,
옵시디언에 정리해둔 내용을 올려둔다.

웹(Typescript)을 개발하다가 앱(Dart)로 넘어갔을 때 Typescript와 매우 흡사해서 바로 사용하는데 큰 문제는 없었다.
그 중 Dart의 유용한 기능 중 하나가 옵셔널(Optional)이었다.

Dart의 경우 버전이 올라가면서 Null-Safe한 특성이 추가되었는데,
아래와 같이 사용된다.

WineModel getWineData(String? wineItemId) {
    ...
}

그렇다.
이 기능은 Typescript에도 존재하는 기능이다.

Javascript를 사용할 때는 뭐 그냥 null이던 undefined던 타입 체킹을 안하고,
무식하게(?) 그냥 쓰기에 볼 일이 없지만, Typescript를 사용한다면 타입 명시가 중요하다.

근데 사용하다 보면 특정 값의 속성이 있어도, 없어도 무관한 값이 있다.
그 경우 옵셔널(Optional)을 사용하게 된다.


옵셔널 속성 (Optional Property)

나같은 경우 타입을 지정할 때 인터페이스를 사용한다.
그 안의 속성에 옵셔널을 지정할 때 위에서 보인 Dart와 동일하게 ?를 사용한다.

export interface WineryInfo {
  wineryName: string
  description: string
  country: string
  address?: string
}

이렇게 선언된 타입을 사용한다면 아래와 같이 사용된다.

const mollydookerWineryInfo: WineryInfo = {
  wineryName: "Mollydooker",
  description: "The best of australia winery.",
  country: "Australia",
  address: "McLaren Vale",
}

몰리두커라고 내가 좋아하는 와이너리가 있는데 이렇게 표현할 수 있다.
근데 만약 Address 정보가 불명인 와이너리 정보가 있다고 가정해보자.

const krugWineryInfo: WineryInfo = {
  wineryName: "krug",
  description:
    "The House of Krug was founded in Reims by Joseph Krug, a visionary non-conformist with an uncompromising philosophy. His dream was to go beyond the notion of vintage and craft the very best Champagne he could offer, every single year, regardless of climate variations",
  country: "France",
}

샴페인의 끝판왕인 크룩 와이너리 정보다.
크룩 와이너리의 경우 주소(address) 속성이 없다.
하지만 address의 경우 옵셔널(Optional) 값이기에 없어도 무방하다.


옵셔널 매개변수 (Optional Parameter)

함수나 메서드에 전달되는 매개변수(Parameter)에도 옵셔널을 사용할 수 있다.
이 경우 값이 없을 때 undefined로 처리된다.

약간 억지스러운 예제이긴 한데.. -_-;;
와인 정보를 가져오는 함수가 있다고 하자.

const getWineInfo = async (wineName: string, wineDes?: string): WineInfo => {
  const wineData = await getWineData(wineName)

  return { wineData, description: wineDes || "-" }
}

저기서 보면 wineDes라는 파라메터가 옵셔널하여 주는 값에 따라 반환 값이 변한다.
만약 wineDes 값이 없다면 하이픈(-) 표시로 대체해서 적용된다.


옵셔널 체이닝 (Optional Chaining)

이 문법은 Typescript 3.7 부터 도입되었다.
해당 객체의 속성 또는 함수(또는 메서드)가 없는 경우 undefined를 반환하는 방법을 제공한다.

아래의 예제 코드를 보자.

const someWineData = {
  itemId: "france_wine_0000001",
  wineName: "Blanc d'Assemblage Brut Nature Champagne",
  wineType: "Champagne",
  wineryInfo: {
    wineryName: "Chavost",
    wineryDes:
      "Our Champagnes are singular and atypical wines, their own identity comes from a mastered vinification, and the absolute absence of added sulphites.",
  },
}

const wineName = someWineData.wineName
const wineryAddress = someWineData.wineryInfo.address

여기서 wineName속성의 경우 값이 있어서 Blanc d'Assemblage Brut Nature Champagne 라는 값이 정상적으로 출력된다.

하지만 wineryAddress속성의 경우 wineryInfo안에 address 값은 존재하지 않기에 undefined가 반환된다.

옵셔널 체이닝은 다음과 같은 상황에서 유용하다.

  • 중첩된 객체의 속성에 접근할 때 해당 속성이 존재하는지 알 수 없는 경우
  • 함수(메서드)가 존재하지 않을 수 있는 객체에서 메서드를 호출할 때

널 병합 연산자 (??) (Nullish Coalescing Operator)

?를 하나만 사용하면 삼항연산자인데 하나 더 붙이면 널 병합 연산자가 된다.
dart에서 아주 잘 쓰고 있는 문법 중 하나인데 아래와 같이 사용할 수 있다.

function getWineInfo(wineModel: WineModel): WineInfo {
  return {
    wineName: wineModel.wineName,
    wineDes: wineModel?.wineDes ?? "some wine.",
  }
}

저 함수를 보면 WineInfo라는 객체를 반환한다.
근데 wineDes속성의 경우 모델 값이 없다면 기본 값을 대신 넣어준다.


옵셔널 반환 타입 (Optional Return Type)

데이터베이스에서 값을 조회할 때 해당 값이 없을 수도 있다.
React 예제를 가져올까 했는데 딱 떠오르는게 없고 해서 백엔드 쪽으로...
(이것도 약간 어거지 코드다.)

export class MyWineFindWineNameRepository
  implements MyWineFindWineNameOutboundPort
{
  constructor(
    @InjectModel(MyWine.name)
    private readonly myWineEntity: Model<MyWine>
  ) {}

  async execute(
    params: MyWineFindWineNameInputDto
  ): Promise<MyWineEntity | null> {
    if (params.wineNameKr !== undefined) {
      return this.myWineEntity.findOne({ wineNameKr: params.wineNameKr }).exec()
    }
    return this.myWineEntity.findOne({ wineNameEn: params.wineNameEn }).exec()
  }
}

/// 기타 작업 후
///...

const wines = await this.myWineFindWineNameRepository.execute({ wineNameEn })
if (wines) {
  console.log("Found wines:", wines.wineNameEn)
} else {
  console.log("Not found wine")
}

약간 어거지 코드이긴 한데..
와인 이름을 검색해서 특정 조건에서 값을 찾을 수 없다면, 반환 타입에 undefined를 포함시킬 수 있다.

사실 이렇게 안히고, Repository에서는 저렇게 반환하고,
Inbound 서비스 단에서 와인 데이터 유무를 확인하여 처리할 수 있다.

요약

옵셔널을 요약하면 다음과 같다.

  • ?를 사용해 옵션 속성 또는 매개변수를 선언할 수 있다.
  • 옵셔널 체이닝(?.)으로 안전하게 객체의 속성에 접근할 수 있다.
  • ?? 연산자를 사용해 null 또는 undefined인 경우 기본값을 설정할 수 있다.
  • 옵셔널 속성이나 매개변수를 사용할 때 기본값 설정, 조건문, 옵셔널 체이닝 등을 활용해 안전하게 처리해야 한다.

옵시디언에 썼던 자료에 살 붙이고 하니 글이 늘었다.
게다가 코로나 약 기운이 돌아서 나중에 다시 검수해야 할듯...