CMeUp

记录一些学习心得,QQ:1139723651.

函数

1
2
3
4
5
function add(x: number, y: number): number {
return x + y;
}

let myAdd = function(x: number, y: number): number { return x + y; };

可选参数

1
2
3
4
5
6
7
8
9
10
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}

let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right

剩余参数

剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( …)后面给定的名字,你可以在函数体内使用这个数组。

1
2
3
4
5
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

this

this 参数

1
2
3
function f(this: void) {
// make sure `this` is unusable in this standalone function
}

重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

泛型

1
2
3
4
5
6
7
8
9
interface GenericIdentityFn {
<T>(arg: T): T;
}

function identity<T>(arg: T): T {
return arg;
}

let myIdentity: GenericIdentityFn = identity;

枚举

模块

1
2
3
4
5
6
7
8
9
10
11
12
13
export interface StringValidator { // 导出
isAcceptable(s: string): boolean;
}

declare module "event" { // 声明一个外部模块。不会有任何导出存在生成的资源中。
export const a = '1';
export const b = 2;
}


// 另一个文件通过下面这句话引入
/// <reference path="interface.d.ts" />

命名空间

1
2
3
4
5
// namespace.ts
namespace Test {
export const name: string = 'tzx';
}
// 打包生成一个对象

工作需要,开始学习 ts(只记点难的)。

基础类型

数组

1
2
let list1: number[] = [1, 2];
let list2: Array<number> = [1, 2];

元祖 Tuple

1
let x: [string, number] = ['hello', 10]; // OK

当访问越界的元素,使用联合类型替代:

1
2
x[3] = 'world'; // Ok , 可以是 string|number
x[6] = true; // Error

枚举

1
2
3
4
5
6
enum Color {
Red,
Green,
Blue,
};
let c: Color = Color.Green; // = 1

默认情况下,从 0 开始为元素编号。也可以手动指定起始、全部编号,也能用字符串等。

any

void

无返回值的函数

null 和 undefined

它们是所有类型的子类型。

类型断言

变量声明

解构

1
2
3
4
5
6
7
8
9
10
11
12
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f(input);

let {a, b}: {a: string, b: number} = o;

type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}

let mySquare = createSquare({color: "black"});


// 只读属性
interface Point {
readonly x: number;
readonly y: number;
}

TypeScript 具有 ReadonlyArray 类型,它与 Array 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:

1
2
3
4
5
6
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

上面代码的最后一行,可以看到就算把整个 ReadonlyArray 赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:

1
a = ro as number[];

额外的检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}

// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({ colour: "red", width: 100 });

// 绕开这些检查非常简单。 最简便的方法是使用类型断言:
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

然而,最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果 SquareConfig 带有上面定义的类型的 color 和 width 属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:

1
2
3
4
5
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}

我们稍后会讲到索引签名,但在这我们要表示的是 SquareConfig 可以有任意数量的属性,并且只要它们不是 color 和 width,那么就无所谓它们的类型是什么。

还有最后一种跳过这些检查的方式,这可能会让你感到惊讶,它就是将这个对象赋值给一个另一个变量: 因为 squareOptions 不会经过额外属性检查,所以编译器不会报错。

1
2
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

函数类型

1
2
3
interface SearchFunc {
(source: string, subString: string): boolean;
}

可索引的类型

1
2
3
4
5
6
7
8
interface StringArray {
[index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

你可以将索引签名设置为只读,这样就防止了给索引赋值:

1
2
3
4
5
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!

类类型

1
2
3
4
5
6
7
8
9
10
11
12
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}

class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}

类静态部分与实例部分的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

继承接口

和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Shape {
color: string;
}

interface PenStroke {
penWidth: number;
}

interface Square extends Shape, PenStroke {
sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

接口继承类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Control {
private state: any;
}

interface SelectableControl extends Control {
select(): void;
}

class Button extends Control implements SelectableControl {
select() { }
}

class TextBox extends Control {
select() { }
}

// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}

公共,私有与受保护的修饰符

默认为 public
在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用 public 来做修饰;例如,C# 要求必须明确地使用 public 指定成员是可见的。 在 TypeScript 里,成员都默认为 public。

1
2
3
4
5
6
7
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

当成员被标记成 private 时,它就不能在声明它的类的外部访问。比如:

1
2
3
4
5
6
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // 错误: 'name' 是私有的.

理解 protected

protected 修饰符与 private 修饰符的行为很相似,但有一点不同, protected 成员在派生类中仍然可以访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}

class Employee extends Person {
private department: string;

constructor(name: string, department: string) {
super(name)
this.department = department;
}

public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误

注意,我们不能在 Person 类外使用 name,但是我们仍然可以通过 Employee 类的实例方法访问,因为 Employee 是由 Person 派生而来的。

构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}

// Employee 能够继承 Person
class Employee extends Person {
private department: string;

constructor(name: string, department: string) {
super(name);
this.department = department;
}

public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.

readonly 修饰符

你可以使用 readonly 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

1
2
3
4
5
6
7
8
9
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.

参数属性

在 constructor 的参数前加上修饰符,能将修饰和赋值合并在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
// 转化为:
var Octopus = /** @class */ (function () {
function Octopus(name) {
this.name = name;
this.numberOfLegs = 8;
}
return Octopus;
}());

存取器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let passcode = "secret passcode";

class Employee {
private _fullName: string;

get fullName(): string {
return this._fullName;
}

set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
alert(employee.fullName);
}

静态属性

使用 static 定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}

let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
abstract class Department {

constructor(public name: string) {
}

printName(): void {
console.log('Department name: ' + this.name);
}

abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {

constructor() {
super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
}

printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}

generateReports(): void {
console.log('Generating accounting reports...');
}
}

let department: Department; // 允许创建一个对抽象类型的引用
department = new Department(); // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 方法在声明的抽象类中不存在

高级技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

在这里用了 typeof,它会取 Greeter 类的类型,而不是实例的类型。

把类当做接口使用

1
2
3
4
5
6
7
8
9
class Point {
x: number;
y: number;
}

interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

最近在做需求的时候,发现逻辑结构比较乱,整体思路不够清晰。于是便想用一些设计模式来抽象自己的逻辑,美化代码结构。

阅读全文 »

话说

话说我一个前端,为啥要跟时间扯上关系?PS:本来我开始的标题是 JS 中的时间,但一想,貌似跟开发语言没关系啊!确实没关系,我们开始正题。

引入

问题的开始,是运行了一个云函数(位于 FaaS 架构的微服务),在其中运行了类似如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
// ...
const [hours, minutes]: number[] = USER_INPUT_VALUE;
const newDate: number = new Date().setHours(hours, minutes);
db.add({
date: newDate,
openId,
// ...
});
FrontEnd.send({
newDate, // 将时间戳回传给前端
});
// ...

大致意思是,用户给个时、分的值,服务端构造一个时间戳,存入数据库,回送前端。好了,发现问题没?

阅读全文 »

这篇讲 GraphQL 的其他内容。

验证

通过使用类型系统,能够预先确定一个 GraphQL 请求是否合法。这让服务器和客户端能有效地通知开发者一个 query 是否有效,而不用依赖运行时的检查。即在本地调试的时候,就能知道该请求是否符合 schema。代码详见 https://graphql.github.io/learn/validation/ , 可以直接改变代码,来查看效果。

执行

GraphQL 的执行依赖于类型系统。来个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Query {
human(id: ID!): Human
}

type Human {
name: String
appearsIn: [Episode]
starships: [Starship]
}

enum Episode {
NEWHOPE
EMPIRE
JEDI
}

type Starship {
name: String
}

为了描述当 query 执行时发生什么,用这个例子:
req:

1
2
3
4
5
6
7
8
9
{
human(id: 1002) {
name
appearsIn
starships {
name
}
}
}

res:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"data": {
"human": {
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"starships": [
{
"name": "Millenium Falcon"
},
{
"name": "Imperial shuttle"
}
]
}
}
}

根字段和 resolver

每一个字段都用一个函数(就是 resolver 的概念,有点类似于 redux 的 reducer 的意味)来生成:

1
2
3
4
5
6
7
Query: {
human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
}
  • obj 先前的对象,一般是上一层的值。对于根字段没有这个值。
  • args 传递进来的参数
  • context 环境信息
  • info 当前请求的详细信息和 schema 信息。

异步 resolver

GraphQL 会等待异步的 resolver 返回后,再返回数据给前端。

普通 resolvers

当 Human 对象可访问后,GraphQL 的执行会继续下去:

1
2
3
4
5
Human: {
name(obj, args, context, info) {
return obj.name
}
}

此时的 obj 是 Human 对象的值,因此简单返回该对象的 name 值即可。事实上,许多 GraphQL 库会让你忽略该简单实现:如果你没有定义该字段的 resolve,库会返回 obj [field]。

标量强制

如果默认地使用普通 resolver 的话,

1
2
3
4
5
Human: {
appearsIn(obj) {
return obj.appearsIn // returns [ 4, 5, 6 ]
}
}

appearsIn 返回的数字数组,而不是枚举值。怎么操作?

这是标量强制的例子。类型系统知道需要什么,会将值转换所需的类型。

列表 resolves

1
2
3
4
5
6
7
8
9
Human: {
starships(obj, args, context, info) {
return obj.starshipIDs.map(
id => context.db.loadStarshipByID(id).then(
shipData => new Starship(shipData)
)
)
}
}

GraphQL 会等待 Promise List 完成后,才将数据返回。

生成结果

在每一个字段完成后,结果值将被放入一个键值对。

内省

能够去查看 GraphQL 的 Schema 能返回什么类型的数据是非常有用的。GraphQL 允许我们使用内省系统。

0%