プライベートクラスプロパティを使用してtypescriptの入力を強化する

これは私がタイプスクリプトについて好きなことです。数値等の長さを測定します。もちろん、最初は、私はあらゆる種類の愚かな手続きに悩まされていたことに憤慨しました。しかし、それから私は関与し、より激しく恋に落ちました。まあ、もう少し厳密な意味で。プロジェクトでstrictNullChecksオプションをオンにして、発生したエラーを修正するのに3日を費やしました。そして彼は満足して喜び、リファクタリングがいかに簡単で制約のないものになったかを指摘しました。





しかし、あなたはもっと何かが欲しいのです。そしてここで、タイプスクリプトはあなたがあなた自身にどのような制限を課すかを説明する必要があり、あなたはこれらの制限の遵守を監視する責任を彼に委任します。さあ、私を完全に壊してください。





例1

少し前に、サーバー上のテンプレートエンジンとしてreactを使用するというアイデアに捕らえられました。もちろん、タイピングの可能性によってキャプチャされます。はい、あらゆる種類のパグ、口ひげ、その他のものがあります。ただし、開発者は、テンプレートに渡された引数を新しいフィールドで拡張するのを忘れたかどうかを自分で覚えておく必要があります。(そうでない場合は、訂正してください。しかし、一般的には気にしません。仕事の性質上、テンプレートの生成に対処する必要がないことを神に感謝します。他の例についても説明します)。





そして、ここでは通常、コンポーネントに渡された小道具を入力し、テンプレートを編集するときに適切なIDEヒントを取得できます。しかし、これはコンポーネントの内部にあります。ここで、このコンポーネントに左翼を移していないことを確認しましょう。





import { createElement, FunctionComponent, ComponentClass } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';

export class Rendered<P> extends String {
  constructor(component: FunctionComponent<P> | ComponentClass<P>, props: P) {
    super('<!DOCTYPE html>' + renderToStaticMarkup(
      createElement(component, props),
    ));
  }
}
      
      



これで、小道具を注文からユーザーコンポーネントに転送しようとすると、この誤解がすぐに通知されます。涼しい?涼しい。





しかし、これはhtml生成時です。今後の使用はどうですか?なぜなら Renderedをインスタンス化した結果は単なる文字列であり、typescriptは、たとえば次の構造では誓約しません。





const html: Rendered<SomeProps> = 'Typescript cannot into space';
      
      



したがって、次のように書くと、次のようになります。





@Get()
public index(): Rendered<IHelloWorld> {
  return new Rendered(HelloWorldComponent, helloWorldProps);
}
      
      



これは、HelloWorldComponentのコンパイル結果がこのメソッドから返されることを保証するものではありません





, :)





export class Rendered<P> extends String {
	_props: P;
	constructor(component: FunctionComponent<P> | ComponentClass<P>, props: P)
...
      
      



'cannot into space' , _props. . - . - _props, js , .. "" .









Object.assign('cannot into space', {_props: 42})
      
      



, . .





export class Rendered<P> extends String {
  // @ts-ignore -       noUnusedParameters
  private readonly _props: P;
  constructor(component: FunctionComponent<P> | ComponentClass<P>, props: P)
...
      
      



Object.assign , .. Rendered



_props , .





, , , . , , . .





2

, , , . - -. . .





. , . .





, -, .





ApiResponse. - , .





export interface IApiResponse {
	readonly scenarioSuccess: boolean;
	readonly systemSuccess: boolean;
	readonly result: string | null;
	readonly error: string | null;
	readonly payload: string | null;
}

export class ApiResponse implements IApiResponse {
	constructor(
		public readonly scenarioSuccess: boolean,
		public readonly systemSuccess: boolean,
		public readonly result: string | null = null,
		public readonly error: string | null = null,
		public readonly payload: string | null = null,
	) {}
}
      
      



scenarioSuccess true. , ( ) - scenarioSuccess false. - systemSuccess false. / result/error. . , scenarioSuccess true error.





, ApiResponse , :





export class ScenarioSuccessResponse extends ApiResponse {
  constructor(result: string, payload: string | null = null) {
    super(true, true, result, null, payload);
  }
}
      
      



.





- ApiResponse, " " , . .





const SECRET_SYMBOL = Symbol('SECRET_SYMBOL');

export abstract class ApiResponse implements IApiResponse {
  // @ts-ignore
  private readonly [SECRET_SYMBOL]: unknown;
  
  constructor(
    public readonly scenarioSuccess: boolean,
    public readonly systemSuccess:   boolean,
    public readonly result:  string | null = null,
    public readonly error:   string | null = null,
    public readonly payload: string | null = null,
  ) {}
}
      
      



Rendered



_props, , , . "" . . ( , ?)





. , , any. .





ここで、システムのコンポーネント間の通信をインターフェースを介して分離する必要があることにも気付くでしょう。ただし、受信側がIApiResponseを期待するように規定することは十分に可能ですが、ドメインロジック層からのサービスはそうです。ApiResponseの特定の実装を返すようにします。





上手 ...

この資料がおもしろかったと思います。一部の人にとっては、このアプローチは冗長に見えるかもしれません。私は、そのような「警備員」をプロジェクトに緊急に追加することをすべての人に促しません。しかし、私の記事であなたが思考の糧を見つけたことを願っています。





お時間をいただきありがとうございます。建設的な批判をいただければ幸いです。








All Articles