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

  本节介绍声明合并及JSX相关知识,ts中编译器会将同一个名字的两个独立声明合并为单一声明,合并后声明同时拥有原先两个声明的特性,合并时数量不限于两个。
  JSX是一种嵌入式的类似XML的语法,ts中支持内嵌、类型检查和将JSX直接编译为JS。

  1. 讲解视频

    TS学习笔记十六:声明合并


    TS学习笔记十七:JSX介绍

  2. B站视频

    TS学习笔记十六:声明合并


    TS学习笔记十七:JSX介绍

  3. 西瓜视频
    https://www.ixigua.com/7324495118084506164
    https://www.ixigua.com/7324496298202268196
    TS学习笔记九:声明合并及JSX_第1张图片

一、声明合并

  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的类型,上述实例中如果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>

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

你可能感兴趣的:(#,Ts基础知识及面试题汇总,学习,笔记,ts,TypeScript,jsx,前端,前端框架)