Prisma에서 JSON 컬럼을 사용하게 되면 JsonValue라는 타입으로 떨어지는데 이것을 타입추론을 해서 사용한다.
//Prisma.JsonValue
export declare type JsonValue =
| string
| number
| boolean
| null
| JsonObject
| JsonArray
위와 같이 JsonValue는 이렇다
interface Foo {
a : number
}
type Bar {
a : number
}
//가능
const foo1: Prisma.JsonValue = something as unknown as Foo;
//가능
const bar2 : Prisma.JsonValue = somthing as unknown as Bar
//불가능
const foo1: Prisma.JsonValue = something as Foo;
//가능
const bar2 : Prisma.JsonValue = somthing as Bar
이렇게 두개의 사용이 달랐는데 사내 사람들중 아무도 명확하게 왜 그런지 몰라서 내가 직접 해봐야겠다고 생각했다. 🤔
interface Foo {
a: string;
b: number;
c?: Foo;
}
type Bar = {
a: string;
b: number;
c?: Bar;
};
function main() {
const foo: Foo = {
a: 'foo1',
b: 1,
c: {
a: 'foo2',
b: 2,
},
};
const bar: Bar = {
a: 'bar2',
b: 3,
c: {
a: 'bar2',
b: 4,
},
};
console.log(foo);
console.log(bar);
}
main();
"use strict";
function main() {
const foo = {
a: 'foo1',
b: 1,
c: {
a: 'foo2',
b: 2,
},
};
const bar = {
a: 'bar2',
b: 3,
c: {
a: 'bar2',
b: 4,
},
};
console.log(foo);
console.log(bar);
}
main();
js결과물 에서는 interface, type이 없다. (tsconfig에서 declation을 true로 두면 d.ts로 빠진다)
interface Foo {
a: string;
b: number;
c?: Foo;
}
type Bar = {
a: string;
b: number;
c?: Bar;
};
class ExtenedFoo implements Foo {
a: string;
b: number;
c: Foo;
constructor() {
this.a = 'foo1';
this.b = 1;
this.c = {
a: 'foo2',
b: 2,
};
}
}
class ExtendedBar implements Bar {
a: string;
b: number;
c: Bar;
constructor() {
this.a = 'bar1';
this.b = 3;
this.c = {
a: 'bar2',
b: 4,
};
}
}
function main() {
const foo: Foo = new ExtenedFoo();
const bar: Bar = new ExtendedBar();
console.log(foo);
console.log(bar);
}
main();
"use strict";
class ExtenedFoo {
a;
b;
c;
constructor() {
this.a = 'foo1';
this.b = 1;
this.c = {
a: 'foo2',
b: 2,
};
}
}
class ExtendedBar {
a;
b;
c;
constructor() {
this.a = 'bar1';
this.b = 3;
this.c = {
a: 'bar2',
b: 4,
};
}
}
function main() {
const foo = new ExtenedFoo();
const bar = new ExtendedBar();
console.log(foo);
console.log(bar);
}
main();
interface , type도 다 없어지고 optional과 property type도 다 사라졌다. interface는 이름만 같으면 병합되고 type은 유니온 타입을 할 수 있다는 사용법 말고는 동일하다는 것인가 🧐
interface Foo {
a: string;
b: number;
c?: boolean;
}
type Bar = {
a: string;
b: number;
c?: boolean;
};
type JsonValue<T = unknown> =
| string
| number
| boolean
| null
| Record<string, T>
| Array<T>;
function main(arg: JsonValue) {
if (arg !== null && Array.isArray(arg) && 'a' in arg && 'b' in arg) {
//가능
const foo = arg as Foo;
}
//에러
const foo = arg as Foo;
//가능
const foo2 = arg as unknown as Foo;
//가능
const foo3 = arg as Foo | null;
//가능
const bar = arg as Bar;
}
main(null);
foo만 에러나야하는데 foo3은 에러가 안 나서 더 미스테리 해졌다 😇
Gemini와 ChatGPT 4o는 단순히 interface가 as 문법시 더 강력하게 확인한다라고만 하는데 그렇다면 foo3은 또 왜 되는것인지 알아내지 못했다
위에서 JsonValue에서 null을 빼고 테스트 해도 foo는 실패한다. 어딜 뒤져봐도 안 나와서 글을 쓰는 김에 새로 type과 interface를 동일하게 사용하는 방법을 작성해봐야겠다.
interface ITuple {
0: string;
1: number;
}
type TTuple = [string, number];
const a: ITuple = ['a', 1];
const b: TTuple = ['b', 2];
const c = b;
const d = a;
//에러
const [n1, s1] = a;
//가능
const [n2, s2] = b;
c 와 d를 만들때까지만 해도 상호호환 되는 것 같지만 구조분해를 하면 안된다고 한다.
'ITuple' 형식에는 반복기를 반환하는 '[Symbol.iterator]()' 메서드가 있어야 합니다.
interface ITuple {
0: string;
1: number;
[Symbol.iterator](): IterableIterator<string | number>;
}
type TTuple = [string, number];
const a: ITuple = ['a', 1];
const b: TTuple = ['b', 2];
const c = b;
const d = a;
//에러
const [n1, s1] = a;
for (const v of a) {
console.log(v);
}
//가능
const [n2, s2] = b;
for (const v of b) {
console.log(v);
}
ITuple은 object취급되어 Tuple로 사용하기 위해 반복기(iterator)가 필요하고 TTuple은 Array 취급되어 가능한 것이다.
interface IFunction {
(a: number, b: number): string;
}
type TFunction = (a: number, b: number) => string;
const fa: IFunction = (a, b) => `${a} + ${b}`;
const fb: TFunction = (a, b) => `${a} + ${b}`;
const fc = fa;
const fd = fb;
console.log(fa(1, 2));
console.log(fb(3, 4));
console.log(fc(5, 6));
console.log(fd(7, 8));
함수는 크게 특별한게 없이 형식만 맞춰주면 호한이 되는 것을 알 수 있다.
Type Alias와 Interface중 한개만 사용하라고 하면 Type Alias를 선택하겠지만
ESlint의 Stylistic을 적용해서 예제를 돌려보면
@typescript-eslint/prefer-function-type Interface only has a call signature, you should use a function type instead
@typescript-eslint/consistent-type-definitions Use an interface instead of a type
inteface와 type alias가 굉장히 비슷한 역할을 하지만 interface는 조금 엄격한 검사가 있기에 object를 위한건 interface 그 외에는 type으로 작성하는 것을 추천(?) 하고 있다. 줏대가 없다면 여기에 맞추는 것도 나쁘지 않겠다
