angular
March 1

Дженерики ReadT/WriteT в Angular Input Signals

В версии 17.1.0 появились типы ReadT и WriteT. Рассмотрим их подробнее.

@Component({ })
export class UserProfileComponent {
  skills = input<ReadT, WriteT>()
}

Для чего предназначен ReadT?

@Component({ })
export class UserProfileComponent { 
  // `ReadT` inferred as `string` from the initial value         
  id = input('');

  // Explicitly setting `ReadT` as (string | number)
  foo = input<string | number>(1)
   
   constructor() {
    effect(() => {
      // `this.id()` type is string
      console.log(this.id());

     // `this.foo()` type is (string | number)
      console.log(this.foo()); 
    });
  }
}

В ситуациях, когда начальное значение не установлено, и явный тип не передан, тип по умолчанию устанавливается как unknown. А если определен тип без значения по умолчанию, автоматически включается тип undefined:

@Component({ })
export class UserProfileComponent { 
  // `ReadT` inferred as `unknown` because we didn't provide initial value          
  id = input();

  // Explicilty sets ReadT as (string | number) with no initial value
  foo = input<string | number>();

  // `ReadT` inferred as `unknown`
  bar = input.required();

  // `ReadT` inferred as `string`
  baz = input.required<string>();
   
  constructor() {
   effect(() => {
     // `this.id()` type is `unknown`
     console.log(this.id());

     // `this.foo()` type is (string | number | undefined)
     console.log(this.foo());

     // `this.bar()` type is `unknown`
     console.log(this.bar());

     // `this.baz()` type is string
     console.log(this.baz());
   });
  }
}

Для чего предназначен WriteT?

WriteT применяется преимущественно при работе с transform. Он определяет типы значений, которые могут быть записаны в Input Soignal. Функция transform получает тип WriteT и должна возвращать значение типа ReadT.

Рассмотрим пример:

@Component({ })
export class UserProfileComponent {
  skills = input<string[], string | string[]>([], {
    // `value` can be either `string` or `string[]`
    transform(value) {
      // Should return `string[]`
      return Array.isArray(value) ? value : [value];
    }
  });
}

Здесь наш тип сигнала - string[], но мы можем передать ввод как string, так и string[]. Тип значения переданного в transform - string | string[], и она возвращает тип ReadT, который в данном случае также является string[].


Если установить только ReadT, не определив WriteT, можно столкнуться с проблемами:

@Component({ })
export class UserProfileComponent {
  skills = input<string[]>([], {
    // Results in a compilation error
    transform(value) {
      return Array.isArray(value) ? value : [value];
    }
  });
}

В этом случае попытка использовать transform приведет к ошибке компиляции, потому что WriteT не определен.

Кроме того, когда явные типы не передаются, WriteT по умолчанию устанавливается как unknown:

@Component({ })
export class UserProfileComponent {
  // `ReadT` = `number`, `WriteT` = `unknown`
  foo = input(1, {
    // `value` is `unknown`
    transform(value) {
      // Must return a number
      return 2;
    },
  });
}

В случаях, когда используется required без передачи каких-либо обобщений, ReadT будет получен из возвращаемого типа функции transform, а WriteT будет undefined:

@Component({ })
export class UserProfileComponent {
  // `ReadT` = `number`, `WriteT` = `unknown`
  foo = input.required({
    // `value` is `unknown`
    transform(value) {
      return 1;
    },
  });
}


Следует всегда указывать типа при использовании transform. Это поможет избежать ошибок связанных с типами.

Это перевод оригинального материала