TypeScript를 사용하다가 마주친 에러에 대해 이야기해보려고 한다.
"'string | undefined' 형식은 'string' 형식에 할당할 수 없습니다"라는 에러인데, 이 에러가 발생하는 이유와 해결 방법에 대해 알아보자.
NestJS에서 ConfigService
를 사용해 환경 변수 값을 가져오는 코드를 작성하다가 이 에러를 만났다. 코드를 보면서 에러의 원인을 파악해 보자
constructor(private readonly configService: ConfigService) {
this.REST_API_KEY = this.configService.get('REST_API_KEY');
this.REDIRECT_URI = this.configService.get('REDIRECT_URI');
}
여기서 configService.get()
메서드는 string
또는 undefined
를 반환하는데, this.REST_API_KEY
와 this.REDIRECT_URI
는 string
타입으로 선언되어 있다.
TypeScript의 strictNullChecks
옵션이 활성화된 경우, undefined
를 string
타입에 할당할 수 없다고 판단하기 때문에 에러가 발생한다.
이 에러를 해결하는 방법은 네 가지 정도 있다.
this.REST_API_KEY = this.configService.get('REST_API_KEY') as string;
this.REDIRECT_URI = this.configService.get('REDIRECT_URI') as string;
as string
을 사용하면 TypeScript 컴파일러에게 해당 값이 확실히 string
이라고 알려줄 수 있다.
하지만!! 런타임에서 undefined
값이 할당될 수 있으므로 주의해야 한다.
this.REST_API_KEY = this.configService.get('REST_API_KEY') || '';
this.REDIRECT_URI = this.configService.get('REDIRECT_URI') || '';
||
연산자를 사용해서 configService.get()
메서드가 undefined
를 반환하면 빈 문자열(''
)을 할당하도록 할 수 있다.
이렇게 하면 this.REST_API_KEY
와 this.REDIRECT_URI
는 항상 string
타입의 값을 가지게 된다.
하지만, 역시 의도하는 기능(유효한 config 값을 불러오는 역할)이 수행됐는지 확인되는 시점은 해당 값을 사용하는 메서드가 호출될 때
라는 점에서 매력적인 선택지가 아니다.
this.REST_API_KEY = this.configService.get('REST_API_KEY') ?? '';
this.REDIRECT_URI = this.configService.get('REDIRECT_URI') ?? '';
configService.get()
메서드가 null
또는 undefined
를 반환하면 ??
연산자로 빈 문자열(''
)을 할당하도록 할 수 있다.
하지만, 이 방식 역시 1, 2번 방식과 마찬가지의 문제점을 가지고 있다.
constructor(private readonly configService: ConfigService) {
const restApiKey = this.configService.get('REST_API_KEY');
const redirectUri = this.configService.get('REDIRECT_URI');
if (!restApiKey || !redirectUri) {
throw new Error('REST_API_KEY or REDIRECT_URI is missing');
}
this.REST_API_KEY = restApiKey;
this.REDIRECT_URI = redirectUri;
}
configService.get()
메서드의 반환값을 먼저 변수에 할당한 뒤, 해당 값들이 존재하는지 확인한다. 만약 누락된 값이 있다면 에러를 던진다.
이렇게 하면 런타임에서 환경 변수의 존재를 보장할 수 있게 된다.
가장 맘에 드는 방식이다. 유효한 config 값을 불러오는 역할이 수행됐는지 확인되는 시점이 해당 값을 사용하는 메서드가 호출될 때
가 아닌 서버가 구동되는 시점(정확히는 NestJS IoC 컨테이너에서 Provider 인스턴스를 생성하는 시점)
이기 때문이다!!!
이 방식을 채택했고, 1차로 문제를 해결해 작성한 코드는 아래와 같다.
getConfigValue 메서드를 ConfigService 클래스로 이동한다면, 더 재사용성이 높은 코드가 완성될 것이다 🤩
...
@Injectable()
export class UsersService {
private readonly restApiKey: string;
private readonly redirectUri: string;
constructor(private readonly configService: ConfigService) {
this.restApiKey = this.getConfigValue('REST_API_KEY');
this.redirectUri = this.getConfigValue('REDIRECT_URI');
}
private getConfigValue(key: string): string {
const value = this.configService.get(key);
if (!value) throw new Error(`${key} is missing`);
return value;
}
...
}