Ts的协变与逆变

200 阅读3分钟

typescript是一个类型安全的语言,任何情况下都需要保证类型安全,基于这一思想去理解协变和逆变。

协变:

interface Father {
  name: string;
}
interface Son extends Father {
  age: number;
}
let fa: Father = {
  name: '测试',
};
let son: Son = {
  name: '测试',
  age: 12,
};

fa = son;   // 不报错
son = fa;   // TS报错

将son赋值给fa:son的范围必定是大于fa范围的,并且son是继承自fa的,所以fa有的部分son必定会有,那么将son赋值给fa,就相当于把son中son和fa的公共部分给了fa,在未来使用fa的时候,如果就只能用fa和son的公共部分,此时fa是类型安全的,如果使用fa中没有的属性就会ts报错

将fa赋值给son:此时Ts就会报错,显示类型 "Father" 中缺少属性 "age",但类型 "Son" 中需要该属性。已知son的范围大于fa的范围,将fa赋值给son,那么后面使用son的时候默认可以是使用son的所有属性,但是son有一部分属性是fa没有的,这个时候就会类型不安全,因此Ts会报错。

逆变:逆变需要将入参和返回值拆开来看

入参:


interface Father {
  name: string;
}
interface Son extends Father {
  age: number;
}

let fa: (val: Father) => void = () => {};
let son: (val: Son) => void = () => {};

son = fa;  // 不报错

fa = son;  // TS报错

协变支持子类型赋值给父类型,不支持父类型给子类型,但是逆变的时候就不行了,为什么呢?

将fa赋值给son:fa函数内部逻辑中用到的参数是Father类型,如果将fa函数赋值给son,那么此时son函数调用时传递进来的参数就是Son类型(相当于函数逻辑没变,入参类型变了,变成了原本参数类型的超集),此时函数内部逻辑中使用的参数类型就成了Son类型,并且其一定包含Father类型。因此函数逻辑中用到的参数一定是存在的,类型安全所以不报错。

将son赋值给fa:son函数内部逻辑中用到的参数类型是Son类型,如果将son函数赋值给fa函数,那么此时son函数的入参类型就变成了Father类型,这会造成原本逻辑中需要用到的参数缺失(因为Son > Father的),类型不安全所以报错。

返回值:

interface Father {
  name: string;
}
interface Son extends Father {
  age: number;
}

let fa: () => Father = () => {
  return { name: '123' };
};

let son: () => Son = () => {
  return { name: '123', age: 1 };
};

son = fa; // Ts报错

fa = son;  // Ts不报错

将fa赋值给son:如果把fa给son,那么son函数原本的返回值是Son类型,用户用的时候也是当作Son类型去用的,此时真正的返回值类型是Father类型,那么用户可能会用到Son中Father没有的部分,此时类型不安全,Ts报错

将son赋值给fa: 如果把son给fa,fa原本的返回值类型是Father类型,用户用的时候也是当作Father用的,此时真正的返回值类型是Son类型,Son中一定包含Father的部分,所以类型安全,Ts不报错。