sourcetip

TypeScript의 생성자 오버로드

fileupload 2023. 3. 14. 21:50
반응형

TypeScript의 생성자 오버로드

TypeScript에서 컨스트럭터 오버로드를 수행한 사람이 있습니까?언어 사양(v 0.8)의 64페이지에는 생성자 오버로드를 설명하는 문장이 있지만 주어진 샘플 코드는 없습니다.

저는 지금 아주 기본적인 계급 선언을 시도하고 있어요. 이렇게 생겼는데,

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj: IBox) {    
        this.x = obj.x;
        this.y = obj.y;
        this.height = obj.height;
        this.width = obj.width;
    }   

    constructor() {
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
    }
}

tsc Box Sample.ts와 함께 실행하면 중복된 생성자 정의가 삭제됩니다.이것은 명백합니다.어떤 도움이라도 감사합니다.

TypeScript를 사용하면 오버로드를 선언할 수 있지만 구현은 1개뿐이며 해당 구현에는 모든 오버로드와 호환되는 시그니처가 있어야 합니다.이 예에서는 에서와 같은 옵션 파라미터를 사용하면 쉽게 이 작업을 수행할 수 있습니다.

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}
    
class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj?: IBox) {    
        this.x = obj?.x ?? 0
        this.y = obj?.y ?? 0
        this.height = obj?.height ?? 0
        this.width = obj?.width ?? 0;
    }   
}

더 일반적인 컨스트럭터로 과부하를 두 번 당하면

interface IBox {    
    x : number;
    y : number;
    height : number;
        width : number;
}
    
class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox); 
    constructor(obj?: IBox) {    
        this.x = obj?.x ?? 0
        this.y = obj?.y ?? 0
        this.height = obj?.height ?? 0
        this.width = obj?.width ?? 0;
    }   
}

Playground 참조

컨스트럭터 오버로드와 관련하여 한 가지 좋은 대안은 추가 오버로드를 정적 공장 방식으로 구현하는 것입니다.생성자에서 가능한 모든 인수 조합을 확인하는 것보다 더 읽기 쉽고 쉽다고 생각합니다.

다음 예제에서는 값을 다르게 저장하는 보험 제공업체의 데이터를 사용하여 환자 개체를 생성할 수 있습니다.환자 인스턴스화를 위한 또 다른 데이터 구조를 지원하기 위해 제공된 데이터를 정규화한 후 기본 생성자를 최대한으로 호출하는 정적 방법을 추가할 수 있습니다.

class Patient {
    static fromInsurance({
        first, middle = '', last,
        birthday, gender
    }: InsuranceCustomer): Patient {
        return new this(
            `${last}, ${first} ${middle}`.trim(),
            utils.age(birthday),
            gender
        );
    }

    constructor(
        public name: string,
        public age: number,
        public gender?: string
    ) {}
}

interface InsuranceCustomer {
    first: string,
    middle?: string,
    last: string,
    birthday: string,
    gender: 'M' | 'F'
}


const utils = { /* included in the playground link below */};

{// Two ways of creating a Patient instance
    const
        jane = new Patient('Doe, Jane', 21),
        alsoJane = Patient.fromInsurance({ 
            first: 'Jane', last: 'Doe',
            birthday: 'Jan 1, 2000', gender: 'F'
        })

    console.clear()
    console.log(jane)
    console.log(alsoJane)
}

TS Playground에서 출력을 확인할 수 있습니다.


예를 들어, TypeScript의 메서드 오버로드는 컴파일러에 의해 생성된 코드가 너무 많이 필요하고 TS는 어떤 대가를 치르더라도 이를 회피하도록 설계되어 있기 때문에 실제는 아닙니다.메서드 오버로드의 주요 사용 사례는 API에 매직인수가 있는 라이브러리의 선언을 작성하는 것입니다.여러 가지 가능한 주장을 처리하는 작업은 모두 당신이 하기 때문에 각 시나리오에 대해 애드혹 방식보다는 오버로드를 사용하는 것이 더 유리하다고 생각하지 않습니다.

오브젝트 파라미터는 옵션이며 오브젝트 내의 각 속성도 옵션인 것 같습니다.이 예에서는 제시된 바와 같이 오버로드 구문은 필요하지 않습니다.여기 답변 중 몇 가지에서 몇 가지 나쁜 관행을 지적하고 싶습니다.으로 물, 것, 것, 그, 그, 그, 그, 그, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, granted, , grantedbox = { x: 0, y: 87, width: 4, height: 0 }단, 이것은 설명에 따라 클래스에서 원하는 모든 코드 힌트를 제공합니다.이 예에서는 파라미터가 1개, 일부, 전부 또는 전혀 없는 함수를 호출하여 기본값을 얻을 수 있습니다.

 /** @class */
 class Box {
     public x?: number;
     public y?: number;
     public height?: number;
     public width?: number;     

     constructor(params: Box = {} as Box) {

         // Define the properties of the incoming `params` object here. 
         // Setting a default value with the `= 0` syntax is optional for each parameter
         let {
             x = 0,
             y = 0,
             height = 1,
             width = 1
         } = params;
         
         //  If needed, make the parameters publicly accessible
         //  on the class ex.: 'this.var = var'.
         /**  Use jsdoc comments here for inline ide auto-documentation */
         this.x = x;
         this.y = y;
         this.height = height;
         this.width = width;
     }
 }

메서드를 추가해야 합니까?상세하지만 확장성이 뛰어난 대안:Box위의 클래스는 동일하기 때문에 인터페이스로서 더블 듀티로 동작할 수 있습니다.클래스를 " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "" 에서 하여 참조해야 .Box클래스는 더 이상 수신 파라미터와 완전히 달라지게 됩니다. 「」 「」 「」 「」)의 위치에 해 주세요.?:이 경우 옵션의 속성이 이동됨을 나타냅니다.클래스 내에서 기본값을 설정하므로 이러한 값은 반드시 존재하지만 incoming parameters 개체 내에서는 임의입니다.

    interface BoxParams {
        x?: number;
         // Add Parameters ...
    }

    class Box {
         public x: number;
         // Copy Parameters ...
         constructor(params: BoxParams = {} as BoxParams) {
         let { x = 0 } = params;
         this.x = x;
    }
    doSomething = () => {
        return this.x + this.x;
        }
    }

클래스를 정의하는 방법에 관계없이 이 기술은 유형 안전의 가드레일을 제공하지만 다음 중 하나를 유연하게 쓸 수 있습니다.

const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({x:0});
const box4 = new Box({x:0, height:10});
const box5 = new Box({x:0, y:87,width:4,height:0});

 // Correctly reports error in TypeScript, and in js, box6.z is undefined
const box6 = new Box({z:0});  

컴파일하면되지 않은 경우에만 알 수 , 「」의 이 널리 가 발생하기 쉬운) 것을 피할 수 .이것에 의해, 널리 사용되는(그러나 에러가 발생하기 쉬운) 폴백 구문의 함정이 회피됩니다.var = isOptional || default; void 0undefined:

컴파일된 출력

var Box = (function () {
    function Box(params) {
        if (params === void 0) { params = {}; }
        var _a = params.x, x = _a === void 0 ? 0 : _a, _b = params.y, y = _b === void 0 ? 0 : _b, _c = params.height, height = _c === void 0 ? 1 : _c, _d = params.width, width = _d === void 0 ? 1 : _d;
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
    return Box;
}());

부록:기본값 설정: 잘못된 방법

||) 연산자

||/또는 연산자가 기본 폴백 값을 설정하는 경우 다른 답변에 나와 있습니다.다음 코드는 잘못된 기본값 설정 방법을 보여 줍니다.0, ' , null , undefined , false , NaN 의 false 값에 대해 평가하면 예기치 않은 결과를 얻을 수 있습니다.

var myDesiredValue = 0;
var result = myDesiredValue || 2;

// This test will correctly report a problem with this setup.
console.assert(myDesiredValue === result && result === 0, 'Result should equal myDesiredValue. ' + myDesiredValue + ' does not equal ' + result);

Object.assign(이것, params)

테스트에서는 es6/typescript destructured object를 사용하는 이 Object.assign보다 15~90% 더 빠를 수 있습니다.destructured 매개 변수를 사용하면 개체에 할당한 메서드와 속성만 허용됩니다.예를 들어 다음 방법을 생각해 보겠습니다.

class BoxTest {
    public x?: number = 1;

    constructor(params: BoxTest = {} as BoxTest) {
        Object.assign(this, params);
    }
}

TypeScript를 TypeScript에 않는 를 배치하려고 했을 TypeScript를 하려고 할 수 있습니다.z

var box = new BoxTest({x: 0, y: 87, width: 4, height: 0, z: 7});

// This test will correctly report an error with this setup. `z` was defined even though `z` is not an allowed property of params.
console.assert(typeof box.z === 'undefined')

TypeScript의 디폴트 파라미터에 의해 구현레벨에서 오버로드가 발생하지 않는 것을 회피할 수도 있습니다.예를 들어 다음과 같습니다.

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj : IBox = {x:0,y:0, height:0, width:0}) {    
        this.x = obj.x;
        this.y = obj.y;
        this.height = obj.height;
        this.width = obj.width;
    }   
}

편집: 16년 12월 5일 현재 Benson의 답변을 참조하여 보다 유연한 솔루션을 제공합니다.

업데이트 2 (2020년 9월 28일) :이 언어는 끊임없이 발전하고 있기 때문에Partial이하기 위한 (v2.1에서 도입)

class Box {
   x: number;
   y: number;
   height: number;
   width: number;

   public constructor(b: Partial<Box> = {}) {
      Object.assign(this, b);
   }
}

// Example use
const a = new Box();
const b = new Box({x: 10, height: 99});
const c = new Box({foo: 10});          // Will fail to compile

업데이트(2017년 6월 8일): guyarad와 snolflake는 내 답변에 대한 아래 코멘트에서 유효한 포인트를 제시한다.나는 독자들에게 나보다 더 나은 답을 가진 벤슨, , 그리고 스놀플레이크의 답을 보라고 권하고 싶다.*

답변 (2014년 1월 27일)

컨스트럭터 오버로드를 실현하는 또 다른 예시는 다음과 같습니다.

class DateHour {

  private date: Date;
  private relativeHour: number;

  constructor(year: number, month: number, day: number, relativeHour: number);
  constructor(date: Date, relativeHour: number);
  constructor(dateOrYear: any, monthOrRelativeHour: number, day?: number, relativeHour?: number) {
    if (typeof dateOrYear === "number") {
      this.date = new Date(dateOrYear, monthOrRelativeHour, day);
      this.relativeHour = relativeHour;
    } else {
      var date = <Date> dateOrYear;
      this.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
      this.relativeHour = monthOrRelativeHour;
    }
  }
}

출처 : http://mimosite.com/blog/post/2013/04/08/Overloading-in-TypeScript

오래된 질문인 것은 알지만 1.4에서는 유니언 타입이 새로 추가되었습니다.모든 함수 과부하(컨스트럭터 포함)에 사용합니다.예:

class foo {
    private _name: any;
    constructor(name: string | number) {
        this._name = name;
    }
}
var f1 = new foo("bar");
var f2 = new foo(1);

사실 이 답변에 너무 늦을 수 있지만, 이제 이 작업을 수행할 수 있습니다.

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox);
    constructor(obj?: IBox) {    
        this.x = !obj ? 0 : obj.x;
        this.y = !obj ? 0 : obj.y;
        this.height = !obj ? 0 : obj.height;
        this.width = !obj ? 0 : obj.width;
    }
}

따라서 정적 방법 대신 위의 작업을 수행할 수 있습니다.도움이 되었으면 좋겠습니다!!!

이것은, 다음과 같이 처리할 수 있습니다.

class Box {
  x: number;
  y: number;
  height: number;
  width: number;
  constructor(obj?: Partial<Box>) {    
     assign(this, obj);
  }
}

Partial은 필드(x,y,높이,폭)를 옵션으로 만들어 여러 컨스트럭터를 허용합니다.

들어, 이렇게 하면 됩니다.new Box({x,y})높이와 폭은 없습니다.

의 ★★★★★★★★★★★★★★★★★.Box클래스가 여러 생성자 구현을 정의하려고 합니다.

클래스 컨스트럭터 구현으로 사용되는 은 마지막 컨스트럭터 오버로드 시그니처뿐입니다.

다음 예에서는 컨스트럭터 구현의 어느 오버로드 시그니처에도 모순되지 않도록 정의되어 있습니다.

interface IBox = {
    x: number;
    y: number;
    width: number;
    height: number;
}

class Box {
    public x: number;
    public y: number;
    public width: number;
    public height: number;

    constructor() /* Overload Signature */
    constructor(obj: IBox) /* Overload Signature */
    constructor(obj?: IBox) /* Implementation Constructor */ {
        if (obj) {
            this.x = obj.x;
            this.y = obj.y;
            this.width = obj.width;
            this.height = obj.height;
        } else {
            this.x = 0;
            this.y = 0;
            this.width = 0;
            this.height = 0
        }
    }

    get frame(): string {
        console.log(this.x, this.y, this.width, this.height);
    }
}

new Box().frame; // 0 0 0 0
new Box({ x:10, y:10, width: 70, height: 120 }).frame; // 10 10 70 120



// You could also write the Box class like so;
class Box {
    public x: number = 0;
    public y: number = 0;
    public width: number = 0;
    public height: number = 0;

    constructor() /* Overload Signature */
    constructor(obj: IBox) /* Overload Signature */
    constructor(obj?: IBox) /* Implementation Constructor */ {
        if (obj) {
            this.x = obj.x;
            this.y = obj.y;
            this.width = obj.width;
            this.height = obj.height;
        }
    }

    get frame(): string { ... }
}

옵션인 입력 파라미터로 충분할 경우 속성을 반복하거나 인터페이스를 정의하지 않고 동일한 기능을 수행하는 다음 코드를 고려합니다.

export class Track {
   public title: string;
   public artist: string;
   public lyrics: string;

   constructor(track?: Track) {
     Object.assign(this, track);
   }
}

에 된 모든 된다는 점에 하십시오.track되어 있지 eveTrack.

interface IBox {
    x: number;
    y: number;
    height: number;
    width: number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj: IBox) {
        const { x, y, height, width } = { x: 0, y: 0, height: 0, width: 0, ...obj }
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
}

가드를 사용하여 컨스트럭터 과부하를 시뮬레이션할 수 있습니다.

interface IUser {
  name: string;
  lastName: string;
}

interface IUserRaw {
  UserName: string;
  UserLastName: string;
}

function isUserRaw(user): user is IUserRaw {
  return !!(user.UserName && user.UserLastName);
}

class User {
  name: string;
  lastName: string;

  constructor(data: IUser | IUserRaw) {
    if (isUserRaw(data)) {
      this.name = data.UserName;
      this.lastName = data.UserLastName;
    } else {
      this.name = data.name;
      this.lastName = data.lastName;
    }
  }
}

const user  = new User({ name: "Jhon", lastName: "Doe" })
const user2 = new User({ UserName: "Jhon", UserLastName: "Doe" })

디폴트/옵션 파라미터 및 파라미터 수가 가변적인 "kind-of-overloaded" 컨스트럭터를 취득하려면 다음 대안을 사용합니다.

private x?: number;
private y?: number;

constructor({x = 10, y}: {x?: number, y?: number}) {
 this.x = x;
 this.y = y;
}

가장 예쁜 암호는 아니지만 익숙해질 거야추가 인터페이스는 필요하지 않으며 개인 구성원을 허용합니다.이것은 인터페이스를 사용할 때는 불가능합니다.

은 작업 로, 가 더 모든 는 추가 를 '보다 더 많이'로 . 추가 필드가 있는 모든 생성자는 추가 필드를 다음과 같이 표시해야 합니다.optional.

class LocalError {
  message?: string;
  status?: string;
  details?: Map<string, string>;

  constructor(message: string);
  constructor(message?: string, status?: string);
  constructor(message?: string, status?: string, details?: Map<string, string>) {
    this.message = message;
    this.status = status;
    this.details = details;
  }
}

@Benson answer에서 코멘트한 바와 같이, 이 예를 코드에 사용하고, 매우 유용하다고 생각했습니다. 나는 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★Object is possibly 'undefined'.ts(2532)가 발생합니다.AssignedType | undefined. 되어도 강제 적용.<AssignedType>에러를 제거할 수 없었기 때문에 args를 옵션으로 할 수 없었습니다.물음표 파라미터와 물음표 없이 클래스 변수를 사용하여 인수 구분 유형을 만드는 문제를 해결했습니다.★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

클래스 메서드()의 에러를 나타내는 원래의 코드를 다음에 나타냅니다.아래를 참조해 주세요.

/** @class */

class Box {
  public x?: number;
  public y?: number;
  public height?: number;
  public width?: number;

  // The Box class can work double-duty as the interface here since they are identical
  // If you choose to add methods or modify this class, you will need to
  // define and reference a new interface for the incoming parameters object 
  // e.g.:  `constructor(params: BoxObjI = {} as BoxObjI)` 
  constructor(params: Box = {} as Box) {
    // Define the properties of the incoming `params` object here. 
    // Setting a default value with the `= 0` syntax is optional for each parameter
    const {
      x = 0,
      y = 0,
      height = 1,
      width = 1,
    } = params;

    //  If needed, make the parameters publicly accessible
    //  on the class ex.: 'this.var = var'.
    /**  Use jsdoc comments here for inline ide auto-documentation */
    this.x = x;
    this.y = y;
    this.height = height;
    this.width = width;
  }

  method(): void {
    const total = this.x + 1; // ERROR. Object is possibly 'undefined'.ts(2532)
  }
}

const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({ x: 0 });
const box4 = new Box({ x: 0, height: 10 });
const box5 = new Box({ x: 0, y: 87, width: 4, height: 0 });

따라서 클래스 메서드에서 변수를 사용할 수 없습니다.예를 들어 다음과 같이 수정되는 경우:

method(): void {
    const total = <number> this.x + 1;
}

다음의 에러가 표시됩니다.

Argument of type '{ x: number; y: number; width: number; height: number; }' is not 
assignable to parameter of type 'Box'.
Property 'method' is missing in type '{ x: number; y: number; width: number; height: 
number; }' but required in type 'Box'.ts(2345)

인수 번들 전체가 더 이상 옵션이 아닌 것처럼.

따라서 옵션의 args를 가진 유형이 생성되고 클래스 변수가 옵션에서 삭제된 경우 원하는 것을 달성합니다.인수는 옵션이며 클래스 메서드에서 사용할 수 있습니다.솔루션 코드 아래:

type BoxParams = {
  x?: number;
  y?: number;
  height?: number;
  width?: number;
}

/** @class */
class Box {
  public x: number;
  public y: number;
  public height: number;
  public width: number;

  // The Box class can work double-duty as the interface here since they are identical
  // If you choose to add methods or modify this class, you will need to
  // define and reference a new interface for the incoming parameters object 
  // e.g.:  `constructor(params: BoxObjI = {} as BoxObjI)` 
  constructor(params: BoxParams = {} as BoxParams) {
    // Define the properties of the incoming `params` object here. 
    // Setting a default value with the `= 0` syntax is optional for each parameter
    const {
      x = 0,
      y = 0,
      height = 1,
      width = 1,
    } = params;

    //  If needed, make the parameters publicly accessible
    //  on the class ex.: 'this.var = var'.
    /**  Use jsdoc comments here for inline ide auto-documentation */
    this.x = x;
    this.y = y;
    this.height = height;
    this.width = width;
  }

  method(): void {
    const total = this.x + 1;
  }
}

const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({ x: 0 });
const box4 = new Box({ x: 0, height: 10 });
const box5 = new Box({ x: 0, y: 87, width: 4, height: 0 });

시간을 들여서 읽고 내가 말하고자 하는 요점을 이해하려고 노력하는 모든 사람에게 감사한 코멘트.

잘 부탁드립니다.

일반적으로 N개의 오버로드에 대해서는 다음을 사용하는 것이 좋습니다.

constructor(obj?: {fromType1: IType1} | {fromType2: IType2}) {    
    if(obj){
      if(obj.fromType1){
        //must be of form IType1
      } else if(obj.fromType2){
        //must have used a IType2
      } else {
        throw "Invalid argument 1"
      }
    } else {
      //obj not given
    }
}   

적어도 이제 우리는 어떤 경로로 내려갈지 확인하고 그에 따라 행동할 수 있다.

chuckj가 말했듯이 간단한 답변은 옵션 파라미터입니다만, 둘 이상의 파라미터로 컨스트럭터를 오버로드하거나 파라미터 순서를 변경하는 경우는 어떻게 해야 합니까?

컨스트럭터는 다음과 같이 과부하가 될 수 있습니다.

class FooBar {
  public foo?: number;
  public bar?: string;

  // Constructor A
  constructor(foo: number, bar?: string);
  // Constructor B
  constructor(bar: string, foo?: number);
  // Constructor C
  constructor(bar: string);
  // Constructor D
  constructor(foo: number);
  // Constructor E
  constructor();

  constructor(...args: any[]) {
    switch (args.length) {
      case 2:
        if (typeof args[0] === "number") {
          this.foo = args[0];
          this.bar = args[1];
        } else {
          this.bar = args[0];
          this.foo = args[1];
        }
        break;
      case 1:
        if (typeof args[0] === "number") {
          this.foo = args[0];
        } else {
          this.bar = args[0];
        }
    }

    console.log(this.foo, this.bar);
  }
}

const fooBarConstructorA = new FooBar("150", 25);
const fooBarConstructorB = new FooBar(25, "150");
const fooBarConstructorC = new FooBar("150");
const fooBarConstructorD = new FooBar("150");
const fooBarConstructorE = new FooBar();

넌 그걸 명심했어야 했어...

contructor()

constructor(a:any, b:any, c:any)

하다와 new() ★★★★★★★★★★★★★★★★★」new("a","b","c")

따라서

constructor(a?:any, b?:any, c?:any)

위와 같으며 유연성이 더 높습니다.

new() ★★★★★★★★★★★★★★★★★」new("a") ★★★★★★★★★★★★★★★★★」new("a","b") ★★★★★★★★★★★★★★★★★」new("a","b","c")

언급URL : https://stackoverflow.com/questions/12702548/constructor-overload-in-typescript

반응형