TypeScript -仅提取接口成员-可能吗?

kmbjn2e3  于 2023-03-24  发布在  TypeScript
关注(0)|答案(7)|浏览(123)

有没有一种方法可以动态地从属于接口的对象中提取成员(即不显式地再次指定它们),就像这样:

let subset = { ...someObject as ISpecific };

目前我得到了someObject碰巧拥有的所有成员。所以spread运算符在这里不起作用。有什么方法可以做到这一点吗?
示例:

interface ISpecific { A: string; B: string; }
class Extended implements ISpecific { public A: string = '1'; public B: string = '2'; public C: string = '3'; }

let someObject = new Extended(); 
let subset = { ...someObject as ISpecific }; 
console.log(subset);  // -> { A, B, C } but want { A, B }

TypeScript强制转换仅仅是编译器的提示,而不是运行时的真实的转换。

3mpgtkmj

3mpgtkmj1#

由于typescript接口在运行时不存在,我们不能使用它们来指导任何运行时行为,只是编译时的类型检查。然而,我们可以创建一个与接口具有相同属性的对象(例如,使用true类型的所有属性来简化初始化),并使编译器在此对象的字段比接口多或少时触发错误。我们可以使用这个对象作为我们提取哪些属性的指南:

function extract<T>(properties: Record<keyof T, true>){
    return function<TActual extends T>(value: TActual){
        let result = {} as T;
        for (const property of Object.keys(properties) as Array<keyof T>) {
            result[property] = value[property];
        }
        return result;
    }
}

interface ISpecific { A: string; B: string; }
const extractISpecific = extract<ISpecific>({ 
    // This object literal is guaranteed by the compiler to have no more and no less properties then ISpecific
    A: true,
    B: true
})
class Extended implements ISpecific { public A: string = '1'; public B: string = '2'; public C: string = '3'; }

let someObject = new Extended(); 
let subset = extractISpecific(someObject);
nwlls2ji

nwlls2ji2#

如果你想限制你 * 使用 * 的类型,你可以简单而安全地做到这一点:

let subset = someObject as ISpecific;

这些属性仍然存在于subset上,但编译器会阻止你依赖它们,即subset.age将在下面失败,尽管属性 does 仍然存在。

interface ISpecific {
    name: string;
}

const someObject = {
    name: 'Fenton',
    age: 21
};

let subset = someObject as ISpecific; 

console.log(subset.age);

你真的可以通过这样的解构来抛弃属性,危险在于你需要在...subset之前的列表中包括“所有我不想要的东西”。

interface ISpecific {
    name: string;
}

const someObject = {
    name: 'Fenton',
    age: 21
};

let { age, ...subset } = someObject;   

console.log(JSON.stringify(someObject));
console.log(JSON.stringify(subset));
nwnhqdif

nwnhqdif3#

我遇到的另一个简单的选择是使用lodash的pick函数,它有点乏味,但工作做得很好。
首先,定义一个代表你的接口的类。你以后会需要它来轻松地创建这个类的对象。

class Specific {
  constructor(readonly a?: string, readonly b?: string) {}
}
interface ISpecific extends Specific {}
interface IExtended extends ISpecific {
  c: string;
}

然后假设这是您要从中提取数据的原始对象:

const extended: IExtended = { a: 'type', b: 'script', c: 'is cool' };

现在有趣的部分来了,基于类的新示例化获取Specific键的列表,并从原始对象中获取pick这些成员。
换句话说:

const specificMembers: string[] = Object.keys(new Specific());
const specific: ISpecific = lodash.pick(extended, specificMembers);
console.log(specific); // {a: "type", b: "script"}

瞧!:)

cwdobuhd

cwdobuhd4#

很遗憾不是,你需要详细说明并重复这些部分。

export interface Machine {
  id: string;
  name: string;
  logo: string;
  location: Location;
  products: Item[];
  restriction: string;
  categories: string[];
  star: number;
  distance: number;
}

export interface MachineIdentification {
  id: string;
  name: string;
  logo: string;
  location: Location;
}

//Object Destructuring fat object in parts
const {id, name, logo, location} = machine; 

//Compose a ligth object from parts and apply interface type
const mi: MachineIdentification = {id, name, logo, location};
mzaanser

mzaanser5#

@titian是正确的,typescript接口/类型在运行时不存在,但可执行代码DOES在编译时存在!
如果你不需要一个类,这里有一个更短,更少类型的解决方案,它采用一个类型或示例和一个简单的字符串数组作为键:

const pickSafely = <ObjectType>(keys: readonly `${string & keyof ObjectType}`[]) => {
    return (object: any) => {
      const resultObject: ObjectType = {} as unknown as ObjectType;
      for (let index = 0; index < keys.length; index += 1) {
        const key = keys[index] as unknown as keyof ObjectType;
        resultObject[key] = object[key];
      }

      return resultObject as ObjectType;
    }
  }

这种方法将保存您的击键时,它的时间来使用它:

// Imagine this came from your database.
  const user = {
    firstName: 'Bill',
    favouriteColor: 'green',
    creditCard: 'what is this doing here?',
  };

  // Imagine this is your model.
  type User = {
    firstName?: string;
    favouriteColor?: string;
  }
  const userKeys = ['firstName', 'favouriteColor'] as const;
  const pickUser = pickSafely<User>(userKeys); // No type error.

  // And here's your application usage.
  const safeUser = pickUser(user);

但更重要的是,它可以防止您选择类型不允许的键。如果您想使用pickSafely来清理来自用户的数据或在通过网络发送数据库响应之前从数据库响应中删除字段,这很有用。

// Imagine this came from your database.
  const user = {
    firstName: 'Bill',
    favouriteColor: 'green',
    creditCard: 'what is this doing here?',
  };

  // Imagine this is your model.
  type User = {
    firstName?: string;
    favouriteColor?: string;
  }
  const userKeysWhoopsie = ['firstName', 'favouriteColor', 'creditCard'] as const;
  const pickUserUhOh = pickSafely<User>(userKeysWhoopsie); // Shows a type error - hmm, picking a property you shouldn't?

  // In your application
  const pwndUser = pickUser(user); // This won't execute, it won't compile.

这个解决方案的神奇之处在于使用Template Literal Types从普通Type动态生成文字类型的联合。
这并不能防止您将属性添加到ObjectType并忘记将其添加到keys-可以将其视为一个白名单,该白名单本身由类型列入白名单;)
下面是这段代码在VSCode中产生的类型错误:

ghhaqwfi

ghhaqwfi6#

**可以使用装饰器实现(见最后的需求)。**只能与方法一起使用(复制属性get/set访问器只会产生其瞬时返回值,而不是访问器函数)。

// define a decorator (@publish) for marking members of a class for export: 
function publish(targetObj: object, memberKey: string, descriptor: PropertyDescriptor) { 
    if (!targetObj['_publishedMembers']) 
        targetObj['_publishedMembers'] = []; 
    targetObj['_publishedMembers'].push(memberKey); 
}

// this function can return the set of members of an object marked with the @publish decorator: 
function getPublishedMembers(fromObj: object) {
    const res = {}; 
    const members = fromObj['_publishedMembers'] || []; 
    members.forEach(member => { res[member] = fromObj[member].bind(fromObj); }); 
    return res; 
}

// this is for making sure all members are implemented (does not make sure about being marked though): 
interface IPublishedMembers {
    A(): string; 
    B(): number; 
    C(): void; 
}

// this class implements the interface and has more members (that we do NOT want to expose): 
class Full implements IPublishedMembers {
    private b: number = 0xb; 

    @publish public A(): string { return 'a'; }
    @publish public B(): number { return this.b; }
    @publish public C(): boolean { return true; }
    public D(): boolean { return !this.C(); } 
    public E(): void { } 
}

const full = new Full(); 
console.log(full);  // -> all members would be exposed { A(), B(), b, C(), D(), E() }

const published = getPublishedMembers(full) as IPublishedMembers; 
console.log(published);  // -> only sanctioned members { A(), B(), C() }
console.log(published.B());  // -> 11 = 0xb (access to field of original object works)
kb5ga3dv

kb5ga3dv7#

更新:这似乎不起作用,所以不是一个答案。

let subset = Object.create(someObject as ISpecific);

Object.create使用指定的原型创建一个新对象,但TypeScript强制转换只是对编译器的提示。它可能使用具体的类层次结构,然后引用原型,但这可能包含比接口声明的更多的实际成员。

相关问题