龙空技术网

TS学习笔记九:声明合并及JSX

军军君06 49

前言:

而今小伙伴们对“object htmlspanelement”大概比较关注,姐妹们都想要分析一些“object htmlspanelement”的相关文章。那么小编也在网摘上收集了一些关于“object htmlspanelement””的相关文章,希望看官们能喜欢,小伙伴们一起来了解一下吧!

本节介绍声明合并及JSX相关知识,ts中编译器会将同一个名字的两个独立声明合并为单一声明,合并后声明同时拥有原先两个声明的特性,合并时数量不限于两个。

JSX是一种嵌入式的类似XML的语法,ts中支持内嵌、类型检查和将JSX直接编译为JS。

一、声明合并

ts中声明会创建三种实体之一:命名空间、类型和值。创建命名空间的声明会新建一个命名空间,包含了用.符号来访问时使用的名字,创建类型的声明是:用声明的模型创建一个类型并绑定到给定的名字上,创建值的声明会创建在js输出中看到的值。

1. 合并接口

合并接口时会把双方的成员放到一个同名的接口中,接口的非函数成员必须是唯一的,如果两个接口中同时声明了同名的非函数成员编译器则会报错。

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;}

2. 合并命名空间

同名的命名空间也会进行合并,声明命名空间的时候会创建出命名空间和值,有不同的合并规则。

对于命名空间的合并:模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口。

对于命名空间值的合并:如果当前已存在给定名字的命名空间,那么后来的命名空间的导出成员会被加到已经存在的那个模块中。

声明合并实例:

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属性不是在此处定义的,且并没有导出,合并后无法访问未合并且未导出的成员  }}

3. 命名空间与类和函数和枚举类型合并

命名空间可以和其它类型的声明进行合并,只要命名空间的定义符合将要合并类型的定义,合并结果包含两者的声明类型。

4. 合并命名空间和类

命名空间和类的合并规则和命名空间之间合并的规则类似,合并结果是一个类并带有一个内部类,可以使用命名空间为类增加一些静态属性,如下:

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;        }    }}

5. 非法的合并

类和其它的类及变量无法进行合并。

6. 模块扩展

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解析模块标识符的方式是一致的,当这些声明被合并时,就像在原始位置被声明了一样,但不能在扩展中声明新的顶级声明,仅可以扩展模块中已经存在的声明。

7. 全局扩展

可以在模块内部添加声明到全局作用域中:

export class A<T>{}declare global{  interface Array<T>{    toA():A<T>  }}Array.prototype.toA = function{  return new A();}

全局模块与模块扩展的行为和限制是相同的。

二、JSX

JXS是一种嵌入式类似XML的语法,可以被转换为合法的js,转换的语义是根据不同的实现而定,ts支持内嵌,类型检查和将jsx直接编译为js。

1. 基本使用

使用jsx需要启用jsx选项,并且文件扩展名需为.tsx。ts具有两种txs模式:preserve和react,此处的模式只在代码生成阶段有用,类型检查不受影响,preserve模式生成的代码会保留jsx以便后续的转换操作,输出文件会带有.jsx扩展名,react模式会直接生成js代码,如React.createElement,不需要进行转换,输出的文件扩展名为.js。

可以通过--jxs或jsconfig.json中的选项指定具体的模式。

2. as操作符

类型断言可以使用<>或者as语法,但是在jxs中无法使用<>,只能使用as语法。若用jxs时<>会和类型断言造成混乱,无法区分具体是jxs的代码还是类型断言。

3. 类型检查

jxs中进行类型检查时需要区分固有元素和基于值的元素,两者的区别是固有元素总是以一个小写字母开头,基于值的元素总是以一个大写字母开通,如自定义组件等。因为对于固有元素和基于值的元素会有不同的处理方式:

1. 对于React,固有元素会生成字符串React.createElement(‘div’),但自定义的组件不会生成React.createElement(myComponent)。

2. 传入JSX元素中的属性类型的查找方式不同,固有元素属性本身就支持,但自定义组件需要指定它们具有哪些属性。

4. 固有元素

固有元素使用特殊的接口JSX.IntrinsicElements进行查找,如果这个接口没有指定,则会全部通过,不对固有元素进行类型检查,如果存在,固有元素的名字需要在此接口的属性中查找,如:

declare namespace JSX {    interface IntrinsicElements {        foo: any    }}<foo />; // 正确<bar />; // 错误

5. 基于值的元素

基于值的元素会简单的在它的作用域中按标识符查找,可以限制基于值的元素的类型:

import MyComponent from "./myComponent";<MyComponent />; // 正确<SomeOtherComponent />; // 错误

若现在有<Expr>,元素类的类型为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 />; // 错误

6. 属性类型检查

属性类型检查时需要确定元素属性类型,和固有元素和基于值的元素之间不同,对于固有元素对应的是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} />; // 错误

7. JSX结果类型

JSX表达式默认的类型为any,可以自定义这个类型,通过JSX.Element接口,但不能够从接口中检索元素,属性或JSX的子元素的类型信息。

8. 嵌入的表达式

JSX允许使用{}标签来内嵌表达式,如下:

var a= <div>{[‘a’,’b’].map(i=><span>{i/2}</span>)}</div>

上述代码中的会有错误,因为不能用数字来除以一个字符串。

标签: #object htmlspanelement