Type Script에 "뒤에 생성기에 전달되지 않은 매개변수가 있어야 함"이라는 유형 제한

이루어지다


/**
 * [α] T 型から undefined 不可キーの union を抽出
 */
type RequiredKeys<T> = {
  [K in keyof T]-?: Record<any, unknown> extends Pick<T, K>
    ? never
    : K;
}[keyof T];

/**
 * [β] α を利用し, Passed 型から Req 型の条件を満たさないものだけを抽出
 */
type MissingKeys<Req, Passed extends Partial<Req>> = {
  [K in keyof Pick<
    Req,
    RequiredKeys<Req>
  >]: Passed[K] extends Req[K] ? never : K;
}[keyof Pick<Req, RequiredKeys<Req>>];

/**
 * [γ] β を利用し, 「undefined 不可の不足しているサブセット」と「それ以外のサブセット」を抽出
 *     後者に対してのみ undefined を許可して型を合成する
 */
type RequireMissingSubset<Req, Passed extends Partial<Req>> = Pick<
  Req,
  MissingKeys<Req, Passed>
> &
  Partial<Omit<Req, MissingKeys<Req, Passed>>>;
  
/**
 * γ を利用し, 可変長リストで引数自体を省略可能か表現する
 */
export type RequireMissing<Req, Passed extends Partial<Req>> =
  Record<any, unknown> extends RequireMissingSubset<Req, Passed>
  ? [options?: RequireMissingSubset<Req, Passed>]
  : [options: RequireMissingSubset<Req, Passed>];
구조기의 파라미터를 엄격하게 제어하기 위해 아래의'여분의 열쇠가 있는지 없는지'를 동시에 검사하십시오.
export type Exact<T, Shape> = T extends Shape
  ? Exclude<keyof T, keyof Shape> extends never
    ? T
    : never
  : never;

사용법


// プロフィール作成に必要な項目
type Profile = {
  name: string;         // 名前(*必須*)
  age: number;          // 年齢(*必須*)
  url?: string;         // URL(任意)
  description?: string; // 自己紹介(任意)
}

// ↓ コンストラクタから推論される型パラメータを与える
class ProfileFactory<O extends Partial<Profile>> {
  // ↓ すべて Nullable にしておく
  private readonly name: string|null;
  private readonly age: number|null;
  private readonly url: string|null;
  private readonly description: string|null;

  // ↓ 推論させる対象
  constructor(options?: Exact<O, Partial<Profile>>) {
    this.name = options?.name ?? null;
    this.age = options?.age ?? null;
    this.url = options?.url ?? null;
    this.description = options?.description ?? null;
  }

  // ↓ 作成時に渡されなかった必須オプションを RequireMissing を用いたオーバーロードで要求
  public create(...args: RequireMissing<Profile, O>): Profile;

  // ↓ 実際は省略可能な Partial<Profile> 型として受け取る
  public create(options?: Partial<Profile>): Profile {
    return {
      name: options?.name ?? this.name,
      age: options?.age ?? this.age,
      url: options?.url ?? this.url,
      description: options?.description ?? this.description,
    } as Profile; // ←アサーションが必要
  }
}

// 必須パラメータはファクトリで埋められている
const factory1 = new ProfileFactory({
  name: 'Bob',
  age: 20,
});
// 空で作れる
factory1.create({});
// 引数自体を省略しても作れる
factory1.create();
// 部分的に必須パラメータをオーバーライドしても作れる
factory1.create({
  name: 'Bob',
  url: 'https://example.com/',
});
// 全部指定しても作れる
factory1.create({
  name: 'Bob',
  age: 20,
  url: 'https://example.com/',
  description: 'hello',
});

// 必須パラメータがファクトリで不足
const factory2 = new ProfileFactory({
  age: 20,
});
// 足りないものを補えば作れる
factory2.create({
  name: 'Bob',
});
// 部分的にオプションパラメータをオーバーライドしても作れる
factory2.create({
  name: 'Bob',
  url: 'https://example.com/',
});
// 全部指定しても作れる
factory2.create({
  name: 'Bob',
  age: 20,
  url: 'https://example.com/',
  description: 'hello',
});
// 空では不可
factory2.create({});
// 引数自体を省略すると不可
factory2.create();

// ファクトリに何もパラメータが与えられていない
const factory3 = new ProfileFactory();
// 必須パラメータをすべて指定すれば作れる
factory3.create({
  name: 'Bob',
  age: 20,
});
// 全部指定しても作れる
factory3.create({
  name: 'Bob',
  age: 20,
  url: 'https://example.com/',
  description: 'hello',
});
// 必須パラメータが足りなければ不可
factory3.create({
  name: 'Bob',
  url: 'https://example.com/',
});
// 空では不可
factory3.create({});
// 引数自体を省略すると不可
factory3.create();

// 余分なパラメータを与えるとコンストラクタでエラー
const factory4 = new ProfileFactory({
  name: 'Bob',
  age: 20,
  foo: 123,
});

// 余分なパラメータは作成時にもエラー
const factory5 = new ProfileFactory({
  name: 'Bob',
  age: 20,
});
factory5.create({
  foo: 123,
});
Playground를 통해 동작 확인
터널 말뚝 뒤가 자바스크립트에 이용된다고 가정한 경우 섣불리 결단하지 말고 실행 시 유형 검사를 꼼꼼히 적는 게 좋다.
public create(...args: RequireMissing<Profile, O>): Profile;
public create(options?: Partial<Profile>): Profile {
  const name = options?.name ?? this.name;
  if (name === null) {
    throw new Error('Missing parameter: name');
  }

  const age = options?.age ?? this.age;
  if (age === null) {
    throw new Error('Missing parameter: age');
  }

  const url = options?.url ?? this.url;
  const description = options?.description ?? this.description;
  
  return { name, age, url, description };
}
Special Thanks: @uhyo 선생(개량에 협조를 받았습니다)

좋은 웹페이지 즐겨찾기