本节介绍声明合并及JSX相关知识,ts中编译器会将同一个名字的两个独立声明合并为单一声明,合并后声明同时拥有原先两个声明的特性,合并时数量不限于两个。
JSX是一种嵌入式的类似XML的语法,ts中支持内嵌、类型检查和将JSX直接编译为JS。
讲解视频
TS学习笔记十六:声明合并
TS学习笔记十七:JSX介绍
B站视频
TS学习笔记十六:声明合并
TS学习笔记十七:JSX介绍
西瓜视频
https://www.ixigua.com/7324495118084506164
https://www.ixigua.com/7324496298202268196
ts中声明会创建三种实体之一:命名空间、类型和值。创建命名空间的声明会新建一个命名空间,包含了用.符号来访问时使用的名字,创建类型的声明是:用声明的模型创建一个类型并绑定到给定的名字上,创建值的声明会创建在js输出中看到的值。
合并接口时会把双方的成员放到一个同名的接口中,接口的非函数成员必须是唯一的,如果两个接口中同时声明了同名的非函数成员编译器则会报错。
interface B{
height: number;
width: number;
}
interface B{
scale: number;
}
let box: B= {height: 5, width: 6, scale: 10};
对于函数成员,每个同名函数声明都会被当成这个函数的一个重载,同时当接口A与后来的接口A合并时,后面的接口具有更高的优先级:
interface A{
clone(animal: Animal): Animal;
}
interface A{
clone(animal: Sheep): Sheep;
}
interface A{
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
//合并后的接口如下:
interface A{
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}
每组接口中的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。但有一个例外是当出现特殊的函数签名时,如果签名中有一个参数的类型是单一的字符串字面量,如字符串字面量的联合类型,那么它将会被提升到重载列表的最顶端。
interface D{
createElement(tagName: any): Element;
}
interface D{
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface D{
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
}
//合并后
interface D{
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
同名的命名空间也会进行合并,声明命名空间的时候会创建出命名空间和值,有不同的合并规则。
对于命名空间的合并:模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口。
对于命名空间值的合并:如果当前已存在给定名字的命名空间,那么后来的命名空间的导出成员会被加到已经存在的那个模块中。
声明合并实例:
namespace A{
export class A1{}
}
namespace A{
export interface L{
leg:number
}
export class D{}
}
合并后:
namespace A{
export interface L{
leg:number
}
export class A1{}
export class D{}
}
对于非导出成员合并时,仅在其原有的命名空间中是可见的,即从其它命名空间合并进来的成员无法访问非导出成员,如下:
namespace A{
let s = true;
export function a(){
return s;
}
}
namespace A{
export function ab(){
return s;//此处将报错,因为s属性不是在此处定义的,且并没有导出,合并后无法访问未合并且未导出的成员
}
}
命名空间可以和其它类型的声明进行合并,只要命名空间的定义符合将要合并类型的定义,合并结果包含两者的声明类型。
命名空间和类的合并规则和命名空间之间合并的规则类似,合并结果是一个类并带有一个内部类,可以使用命名空间为类增加一些静态属性,如下:
class A{
label:A.B;
}
namespace A{
export class B{}
}
上述实例中B类必须导出,除了内部类的模式,也可以创建一个函数后扩展它增加一些属性,ts使用声明合并来达到同样的效果并保证类型安全,如下:
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
export let suffix = "";
export let prefix = "Hello, ";
}
alert(buildLabel("Sam Smith"));
命名空间也可以扩展枚举类型,如下:
enum Color {
red = 1, green = 2,blue = 4
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
}else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
}else if (colorName == "magenta") {
return Color.red + Color.blue;
}else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
}
类和其它的类及变量无法进行合并。
js本身不支持合并,但可以通过给对象的原先添加属性进行对象的扩展,如下:
a.js
export class A{}
b.js
import {A} from “./a”;
A.prototype.a = function(f){}
上述实例可以在ts中运行,但是编译器无法知道A.prototype.a的具体内容,可以使用扩展模块告诉编译器对应的信息,如下:
b.ts
import {A} from “./a”
declare module “./A”{
interface A{
a(f:(x)=>U):A
}
}
A.prototype.a = function(f){}
c.ts
import {A} from “./a”
import “./b”
let c:A;
c.a(x=>return s);
模块解析名和使用import和export解析模块标识符的方式是一致的,当这些声明被合并时,就像在原始位置被声明了一样,但不能在扩展中声明新的顶级声明,仅可以扩展模块中已经存在的声明。
可以在模块内部添加声明到全局作用域中:
export class A<T>{}
declare global{
interface Array<T>{
toA():A<T>
}
}
Array.prototype.toA = function{
return new A();
}
全局模块与模块扩展的行为和限制是相同的。
JXS是一种嵌入式类似XML的语法,可以被转换为合法的js,转换的语义是根据不同的实现而定,ts支持内嵌,类型检查和将jsx直接编译为js。
使用jsx需要启用jsx选项,并且文件扩展名需为.tsx。ts具有两种txs模式:preserve和react,此处的模式只在代码生成阶段有用,类型检查不受影响,preserve模式生成的代码会保留jsx以便后续的转换操作,输出文件会带有.jsx扩展名,react模式会直接生成js代码,如React.createElement,不需要进行转换,输出的文件扩展名为.js。
可以通过–jxs或jsconfig.json中的选项指定具体的模式。
类型断言可以使用<>或者as语法,但是在jxs中无法使用<>,只能使用as语法。若用jxs时<>会和类型断言造成混乱,无法区分具体是jxs的代码还是类型断言。
jxs中进行类型检查时需要区分固有元素和基于值的元素,两者的区别是固有元素总是以一个小写字母开头,基于值的元素总是以一个大写字母开通,如自定义组件等。因为对于固有元素和基于值的元素会有不同的处理方式:
固有元素使用特殊的接口JSX.IntrinsicElements进行查找,如果这个接口没有指定,则会全部通过,不对固有元素进行类型检查,如果存在,固有元素的名字需要在此接口的属性中查找,如:
declare namespace JSX {
interface IntrinsicElements {
foo: any
}
}
<foo />; // 正确
<bar />; // 错误
基于值的元素会简单的在它的作用域中按标识符查找,可以限制基于值的元素的类型:
import MyComponent from "./myComponent";
<MyComponent />; // 正确
<SomeOtherComponent />; // 错误
若现在有,元素类的类型为Expr的类型,上述实例中如果MyComponent是ES6的类,则它的类类型就是这个类,如果是个工厂函数,类类型就是函数。
一旦建立起了类类型,示例类型就确定了,为类类型调用签名的返回值与构造签名的联合类型,在ES6类的情况下,实例类型为这个类的实例的类型,如果是工厂函数,实例类型为这个函数返回值类型。
class MyComponent {
render() {}
}
// 使用构造签名
var myComponent = new MyComponent();
// 元素类的类型 => MyComponent
// 元素实例的类型 => { render: () => void }
function MyFactoryFunction() {
return {
render: () => {
}
}
}
// 使用调用签名
var myComponent = MyFactoryFunction();
// 元素类的类型 => FactoryFunction
// 元素实例的类型 => { render: () => void }
元素的类型必须赋值给JSX.ElementClass或抛出一个异常,默认的JSX.ElementClass为{},但可以被扩展用来限制JSX的类型以符合对应的接口。
declare namespace JSX {
interface ElementClass {
render: any;
}
}
class MyComponent {
render() {}
}
function MyFactoryFunction() {
return { render: () => {} }
}
<MyComponent />; // 正确
<MyFactoryFunction />; // 正确
class NotAValidComponent {}
function NotAValidFactoryFunction() {
return {};
}
<NotAValidComponent />; // 错误
<NotAValidFactoryFunction />; // 错误
属性类型检查时需要确定元素属性类型,和固有元素和基于值的元素之间不同,对于固有元素对应的是JSX.IntrinsicElements属性的类型:
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean }
}
}
// `foo`的元素属性类型为`{bar?: boolean}`
<foo bar />;
对于值的元素,取决于先前确定的在元素实例类型上的某个属性的类型,具体使用那个属性来确定类型取决于JSX.ElementAttributesProperty。
declare namespace JSX {
interface ElementAttributesProperty {
props; // 指定用来使用的属性名
}
}
class MyComponent {
// 在元素实例类型上指定属性
props: {
foo?: string;
}
}
// `MyComponent`的元素属性类型为`{foo?: string}`
<MyComponent foo="bar" />
元素属性类型用于的JSX里进行属性的类型检查,支持可选属性和必选属性。
declare namespace JSX {
interface IntrinsicElements {
foo: { requiredProp: string; optionalProp?: number }
}
}
<foo requiredProp="bar" />; // 正确
<foo requiredProp="bar" optionalProp={0} />; // 正确
<foo />; // 错误, 缺少 requiredProp
<foo requiredProp={0} />; // 错误, requiredProp 应该是字符串
<foo requiredProp="bar" unknownProp />; // 错误, unknownProp 不存在
<foo requiredProp="bar" some-unknown-prop />; // 正确, `some-unknown-prop`不是个合法的标识符
若一个属性不是合法的JS标识符,并没有出现在元素属性类型里时不会报错。也可以使用延展操作符:
var props = { requiredProp: 'bar' };
<foo {...props} />; // 正确
var badProps = {};
<foo {...badProps} />; // 错误
JSX表达式默认的类型为any,可以自定义这个类型,通过JSX.Element接口,但不能够从接口中检索元素,属性或JSX的子元素的类型信息。
JSX允许使用{}标签来内嵌表达式,如下:
var a= <div>
{[‘a’,’b’].map(i=><span>{i/2}</span>)}
</div>
上述代码中的会有错误,因为不能用数字来除以一个字符串。