템플릿 문자열은 실제로 무엇을 위한 것입니까?

17124 단어 javascript
템플릿 문자열(일명 template literals)이 출시된 이후로 나는 그것들이 인정받지 못한다는 느낌을 받았습니다.

아니요, 물론 모두가 hello${world}와 같은 글쓰기 능력과 이런 트릭을 좋아했습니다.

escape`<html/>`

훌륭하게 작동했지만 몇 년 동안 나는 그들이 더 많은 것을 할 수 있다고 확신했습니다.

저는 잘 알려지지 않은 NoSQL 그래프 데이터베이스Neo4j 하나를 좋아하고 가끔 이 데이터베이스를 사용하여 프로젝트를 구축하고 있습니다.

좋았지만 쿼리 구문이 그리 좋지 않았기 때문에 다음과 같이 작성해야 했습니다.

s.run('MERGE (alice:Person {name : $nameParam, age : $ageParam})', {
    nameParam: 'Alice',
    ageParam: 21
})

그리고 문자 그대로 각 변수의 이름을 생각하느라 애를 먹었습니다.

추가 컨텍스트: 이것은 많은 기능과 스키마가 없는 연구를 위한 복잡한 쿼리 데이터베이스입니다. 단순히 ORM을 생성할 수 없으므로 원시 쿼리를 작성해야 했습니다. 할 일 목록 쿼리의 간단한 예: "이 작업에 이 작업의 다른 종속성에 의해 차단되지 않는 무한 깊이의 종속 작업이 있습니까?".

따라서 템플릿 문자열은 실제로 이 문제를 해결하고 지옥처럼 간단하게 만들 수 있습니다. 나는 이것을 위한 lib를 만들었습니다 - cypher-talker 그리고 지금 나는 이것에 대해 정말로 행복하다고 느끼며 이렇게 쓰고 있습니다:

s.run(...t`MERGE (alice:Person {name : ${'Alice'}, age : ${21})`)

나는 그것을 더 단순화하고 다음과 같이 작성하기 위해 원숭이 패치를 작성할 계획입니다.

s.run`MERGE (alice:Person {name : ${'Alice'}, age : ${21})`

그러나 트랜잭션 Realms 래퍼와 같은 다른 드라이버 확장이 필요하지만 작업이 끝나면 이에 대해 작성하겠습니다.

비결은 무엇입니까?



템플릿 문자열은 순수 함수로 예상됩니다. 이것은 중요합니다. 일반적으로 그 안에 있는 내용을 변경할 의도가 없습니다. 당신은 할 수 있지만 일반적으로 ESLint조차도 당신을 멈출 것입니다 - no-unused-expressions 규칙은 기본적으로 당신이 그것을하지 못하게합니다.

템플릿 리터럴(예, 호출된 템플릿 문자열과 함께 사용할 함수)에는 다음 서명이 있어야 합니다.

(literals: TemplateStringsArray, ...placeholders: string[]): any

멋진 점: typescript는 템플릿 함수의 서명을 완전히 이해하므로 여기에서 오류를 감지합니다.

const t = (literals: TemplateStringsArray, ...placeholders: string[]) => null

t`hello${'world'}${2}`

// and even here!

const t = (literals: TemplateStringsArray, ...placeholders: [string, number, ...string[]]) => null

t`hello${'world'}${true}`

typescript 4와 고급 튜플을 사용하면 이제 훌륭하게 작동합니다!
TemplateStringsArray가 무엇인지 궁금하시다면 - 그것은 단순히 ReadonlyArray<string> 특별한 것이 아닙니다.

리터럴 크기는 항상 자리 표시자 길이보다 1배 더 큽니다. 그것은 항상 문자열을 가질 것입니다 - 비어 있더라도, 그래서 그것의 감소는 약간 복잡할 수 있습니다.

두 번째 마법은 무엇이든 반환할 수 있다는 것입니다.



내 라이브러리를 위해 나는 퍼질 수 있는 것, 즉 반복 가능한 것을 생성해야 했습니다. 객체, 배열, WeakRef 또는 함수 등 무엇이든 반환할 수 있습니다. 그것은 단순히 작동합니다.

이것이 분명해 보이지만 그것이 의미하는 바를 진정으로 이해하면 가능성의 세계를 보게 될 것입니다.

NestJS를 상상해보십시오. 그러나 템플릿 문자열 데코레이터가 있습니다.



@Get`docs`
@Redirect`https://docs.nestjs.com`(302)
getDocs(@Query`version` version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

제 생각에는 놀랍습니다. 대괄호를 제거하기만 하면 이제 정말 선언적으로 보입니다. 이것은 함수 호출의 무리가 아니라 일종의 DSL처럼 보이기 시작합니다.

지금 어떻게 생겼는지 잊어버린 경우:

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

또는 테스트를 상상해보십시오. 그러나 데코레이터 fns를 사용하면



it`tests error with async/await and rejects`(async () => {
  expect.assertions(1);
  await expect(user.getUserName(3)).rejects.toEqual({
    error: 'User with 3 not found.',
  });
});

간단해 보이지만 매개변수화된 테스트를 일부 삭제하면 어떻게 될까요?

[2, 3, 5, 7].forEach((value) => {
  it(`should return true for prime number ${value}`, 
() => {
    expect(isPrime(value)).toEqual(true);
  });
});

//vs

it(`should return true for prime number ${[2, 3, 5, 7]}`, 
(value: number) => {
  expect(isPrime(value)).toEqual(true);
});

참고: 예, 일반적으로 DoneCallback이 있어야 한다는 것을 알고 있지만 특정 프레임워크가 아닌 일반적인 개념에 대해 말하고 있습니다.

유형으로 불가능하다고 생각하는 경우: 야간 TS 4.1에서 작동합니다. 최신 TS의 재귀 조건부 유형에 현재 문제가 있지만 수정 중입니다. TS playground

// a bit of functional magic
type car<T> = T extends [infer R, ...any[]] ? R : never
type cdr<T> = T extends [any, ...infer R] ? R : []

type pickType<T> = T extends Array<infer R> ? R : never

type pickFirst<T extends [...unknown[][]]> = T extends [] 
    ? [] 
    : [pickType<car<T>>, ...pickFirst<cdr<T>>]

const it = <T extends [...unknown[][]]>(
    literals: TemplateStringsArray, ...placeholders: T
) => {
    return (fn: (...args: pickFirst<T>) => void) => {

    }
}

it`hello${['world']} ${[true, 5]}`(
(v: string, g: number | boolean) => {
 // test it!
})

결론



템플릿 문자열이 정말 감사하지 않다고 생각합니다. 원하는 DSL을 제공하고 유형의 영광을 유지할 수 있습니다.

그들을 더 사랑하려고 노력하십시오!

좋은 웹페이지 즐겨찾기