스칼라 값이란?
순수한 데이터 값을 나타내며,
프리미티브primitive 자료형과 비슷하다고 볼 수 있습니다.
정수 Int
문자열 String
불리언 Boolean
소수형 숫자 Float
아이디 ID
스칼라 값을 반환하는 필드는
더 이상 쿼리를 뻗어나갈 수 없기 때문에 리프 노드leaf node라고 부릅니다.
필드가 아니라 노드인 이유는,
GraphQL 쿼리가 내부적으로는 트리구조로 표현되기 때문입니다.
사용자 정의 스칼라 Custom Scalar
프로그래머는 임의의 스칼라 타입을 만들 수 있습니다.
먼저 스키마 정의 언어SDL로 표현하면 다음과 같습니다.
다만, 이대로는 사용할 수 없으며
스칼라 값을 어떻게 표현하고 어떻게 해석할건지에 대한 로직이 필요합니다.
스칼라 타입에 로직을 할당하는 방법은 언어마다 다르기 때문에 공식문서를 참고하세요.
여기서는 예제로 RGB를 다룹니다.
내부적인 표현으로는 number[3] 이고,
외부적인 표현으로는 string을 사용할 것 입니다.
여기서,
내부적인 표현이란, 컴퓨터 안에서 어떻게 그 값을 구현할건지 나타내는 표현이고.
외부적인 표현이란, 사용자에게 어떻게 그 값을 표현할건지 나타내는 표현입니다.
아래 그림을 볼까요,
위의 숫자 3개가 내부적인 표현이고, 아래 문자열이 외부적인 표현입니다.
내부적인 표현을 사용하여 외부적인 표현이 만들어지고 클라이언트에게 반환됩니다.
Attention!
RGB를 문자열로 다루는게 더 쉬운 분들은,
문자열을 내부적인 표현으로 사용해도 상관없습니다.
코드에서 더 다루기 쉽도록 내부적인 표현을 결정하는 것이 좋습니다.
스칼라 타입을 만들기 위해서는 3가지 함수를 구현해야 합니다.
serialize (value : any)
내부적인 표현을 외부적인 표현으로 바꾸는 부분.
이 함수의 결과가 쿼리 응답에 포함됩니다.parseValue (value : any)
외부적인 표현을 내부적인 표현으로 바꾸는 부분.parseLiteral (valueAST : any)
외부적인 표현을 내부적인 표현으로 바꾸는 부분.
serialize 함수는 내부to외부이고,
parseValue와 parseLiteral함수는 외부to내부 입니다.
두 함수의 차이점은 다다음 절에서 다루겠습니다.
필수 로직부터 구현하자
parseValue와 parseLiteral의 차이점이 무엇인지 궁금하시겠지만 본질은 같습니다.
외부 표현을 내부 표현으로 만드는 것 뿐이죠.
일단, 두 표현간의 변환함수를 구현해야 합니다.
먼저 내부 표현을 외부 표현으로 만드는 함수부터 만들어볼까요?
/**
* RGB의 채널값 3개가 담긴 배열을 전달받아,
* HEX 문자열로 반환한다.
*/
function arr2str(arr: number[]): string {
if (arr.length != 3) {
throw Error(`arr length is must 3, but got ${arr.length}`);
}
let hex: string = "#";
for (let i = 0; i < 3; i++) {
let channel = arr[i].toString(16);
if (channel.length == 1) hex += "0" + channel;
else if (channel.length == 2) hex += channel;
else throw Error(`${arr[i]} is not 0~255`);
}
return hex;
}
외부 표현을 내부 표현으로 만드는 함수도 만들어야 합니다.
/**
* RGB HEX 문자열을 전달받아,
* 채널값 3개가 담긴 배열로 반환한다.
*/
function str2arr(str: string): number[] {
if (!/^#[0-9a-f]{6}/.test(str)) {
throw Error(`${str} is not hex RGB`);
}
let arr: number[] = [];
arr.push(parseInt(str.substring(1, 3), 16));
arr.push(parseInt(str.substring(3, 5), 16));
arr.push(parseInt(str.substring(5, 7), 16));
return arr;
}
필수로직 구현은 끝났습니다.
이제 아래처럼 적당히 뭉치면 스칼라 타입이 완성됩니다.
/**
* RGB 값을 대표하는 스칼라 타입.
*
* 내부적 표현 : number[3]
* 외부적 표현 : hex string
*/
let RGB = new GraphQLScalarType({
name: "RGB",
serialize: (value: any) => {
console.log("serialize");
return arr2str(value);
},
parseValue: (value: any) => {
console.log("parseValue");
return str2arr(value);
},
parseLiteral: (valueAST: any) => {
console.log("parseLiteral");
return str2arr(valueAST.value);
}
});
parseValue는 str2arr에 value를 넘겼지만,
parseLiteral은 str2arr에 valueAST.value를 넘긴것에 주목해야 합니다.
차이점은 바로 다음 절에서 다룹니다.
리터럴과 값
GraphQL에서 value는 QueryVariables에 선언된 것을 value라고 하며,
쿼리에 직접적으로 나온 값을 literal이라고 합니다.
이럼에도 헷갈릴 수 있습니다.
아래 그림을 보면서 이해하면 쉽습니다.
Value :
Literal :
즉, 변수를 value로 선언했는지 literal로 선언했는지에 따라
호출되는 parse함수가 다릅니다.
value로 선언했다면 옆에 주어진 값이 value로 그대로 전달되며,
literal로 선언했다면 valueAST.value로 전달됩니다.
전체 코드는 아래의 링크에서 볼 수 있습니다.
참고 링크
https://github.com/graphql/graphql-js/issues/500
'# GraphQL > GraphQL.js' 카테고리의 다른 글
아마존 AWS AppSync 시작하기 (0) | 2019.12.26 |
---|---|
타입스크립트 GraphQL Cursor Based Pagination (0) | 2019.12.16 |
타입스크립트 GraphQL Enum Type 클래스 (0) | 2019.12.15 |
타입스크립트 GraphQL 재귀호출 recursion (0) | 2019.12.15 |
타입스크립트 GraphQL Resolve Parameter 정보 (0) | 2019.12.15 |