文章目录
libraries
抖音前端UI框架:
https://github.com/DouyinFE/semi-design/
jsx的另一种选择:
https://markojs.com/
layout
src
- index.ts
- global.d.ts # 自己定义的各种声明应该放到独立的.d.ts(不会被编译为代码,仅存在于编译期)
- shims-vue.ts # 让ts正确理解vue文件; 删除的话,import-vue文件会报错
types
- foo
- index.d.ts
tsconfig.json
event loop
同步任务由js主线程依次执行
异步任务会被委托给宿主环境(node/explorer)去执行
- setTimeout: 宏任务,会被放到任务队列等待执行(会等待微任务执行完毕)
- new Promise(): 同步任务,与其他标准语句一样
- then: 这里面属于微任务
basic
- undefined/null
所有类型的子类型 - lib.d.ts
安装TypeScript时会自动附带一个lib.d.ts;
可通过如下禁用: tsconfig.json.noLib: true
JS Runtime + DOM Environments: window/Window, document/Document, math/Math - demos ➡️
export {b, s as ss}; // 一般会在文件最后一次性导出(并做重命名处理)
export * from './foo'; // 从其他模块导入后整体导出(也可像上面那样,部分导出)
export const foo = 1; // foo.ts
import {foo, xx as xx} from './foo'; // 导入其他模块export的变量; 若foo/则查找路径:./node_modules/foo; ../node_modules/foo; ...直到根目录
import * as $ from 'jquery'; // 实际上可以from一个css文件
import Ax from './axios';
const DAYS_IN_WEEK = 7;
let y; // default is any type; let声明的变量与其他语言一样:块级作用域(var则是违反尝试,外层也可访问)
let y: any = 'x'; // 顶级类型,可以假定它是任何类型(包括函数). 不安全,故不推荐使用,多数场合使用undefined替代都是合适的!
(y as string).length; // 类型断言
// 解决any的不安全问题,可以被赋值为任意类型! 但unknown变量仅可被赋值给'any/unknown'类型变量!
// unknown类型对象不许有各种奇怪的操作, 必须先做类型收窄: typeof/instanceof/as
// unknown类型仅能使用4个运算符: == === != !==
let x: unknown = y;
let h: number = 0xf00d; // 0b1010 0o744 NaN Infinity; 其实类型修饰可以省略,编译器可以类型推论!
let b: boolean = !!h; // 两次逻辑非可以在第2次时将null/undefined转换为boolean!
let s: string = `${n + 1}`; // let s = 'xx';
let a: number[] = [1, 2]; // 元素类型必须为number(除非使用any[]); <==> Array<number>
let t: [string, number] = [b.toString(), 1]; // tuple(注意:顺序相关的); t[0]=''; t=['',2];
let [a,b] = t; // 数组(或叫tuple)的解构! 也可以用来swap俩数! 对象object也可结构(且可只指定部分成员)
let u: string | number; // union,变量被赋值时会推断出其类型,否则仅能访问共有方法!
let u: "on" | "off" = "on";
const menuConfig = { // 内联类型(匿名接口)
title: 'defaultValue', // newconfig中有的字段是可选的,没有指定.此处希望有一个默认值
...newConfig, // 通常是传递进来的参数:要设置的最新配置(追加并替换上面的)
}
// a?表可选参数. b自动被识别为可选参数(但不用放在最后位置)! 剩余参数类型: any[]
async function say<U extends Leng, T=string, readonly d: Direction>(t: [T, U], a?: number, b: number = 2, ...items): [T, U] {
if (a && (typeof (name as Fish).swim == 'function')) {
items.forEach(function(item){
});
}
const user = await getUser<User>();
return '1' + name;
// 注意: 声明的变量只能定义它的类型,不要去定义实现,包括 declare class/enum
// 使用jQuery: 建议都置于jQuery.d.ts声明文件中,编译后自动被删除.
// 也可以不用这么麻烦:直接安装对应的声明模块即可: npm install @types/jquery --save-dev
declare const jQuery: (selector: string) => any;
for (let i=0; i<8; i++) {
setTimeout(function(){
console.log(i); // 若是js的var声明的i,则此处循环打印8!
}, 100*i);
}
if ('attr' in obj && +s===0) { // 是否存在属性; +:把变量转变为number类型
let x: typeof b; // 'boolean'
}
Object.defineProperty(obj, 'prop1', { // add a property to a object
value: 2
enumerable: true, // default can not be enumerated
writable: true, // default can not be wrote
configurable: true, // default can not be deleted
get: (){ // called when read obj.prop1
return hex // offen as a variable so prop1 can be changed along with the variable!
},
set: (){ // called when setting the property: obj.prop1
hex = 2
},
sum: sum, // 注意:value是一个变量(而不是字符串)才可以简写: 'sum,'
})
}
namespace Utility {
export type EventNames = 'click' | 'scroll' | 'mousemove'; // literal type
type OneToFile = 1|2|3|4|5;
type Direction = 'North' | 'East' | 'South' | 'West';
type Foo = { // 定义Foo为一个内敛类型(匿名接口)的别名
kind: "foo"; // 声明的时候直接初始化,以便在复合类型时可以判别其类型: arg.kind=="foo"
[key: string]: number; // 自定义索引签名:"动态属性". 注意: 其他确定与可选属性的返回类型都必须是索引签名的子类!
[key: number]: MyNumber; // 会自动把number换成string,故此时他们同时存在时,其返回类型必须时上面的子类!
readonly bar: number; // 也可于class中指定为只读; Readonly<Foo>也可将所有属性都标记为只读
}
type FooKeyName = keyof typeof foo; // 字符化每一个key,并将所有的key作为一个联合类型: 'kind' | 'bar'
let name: FooKeyName;
name = 'bar';
name = 'xx'; // error
}
// 枚举与数字类型互相兼容
enum Days {
Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat, // Days['Tue'] = 2; 若Mon=1.2,则Tue=2.2,Wed=3.2...
haha = 'ha' // 可为字符串! 'ha' as Days
}
namespace Days { // 给enum添加静态方法
function isBusinessDay(day: Days) {
switch(day) {
case Days.Saturday:
case Days.Sunday:
return false;
default:
return true;
}
}
}
Promise.resolve(123)
.then((res) => {
console.log(res); // 123
// 后续then会收到456(且会保持接收到的类型同返回类型:boolean),如果此处再throw异常,则后续then也不会被调用.直接进入.catch()
return true;
})
.then((res) => {
console.log(res); // true
return Promise.resolve(123);
})
.then((res) => {
console.log(res); // 123
return 123;
})
Promise.reject(new Error('xx'))
.then((res) => {
console.log(res); // not called
return 456;
})
.catch((err) => {
console.log(err.message); // xx
return 123; // 再向后传播链,注意catch捕获到上头的异常后就吞掉了,后续的catch不会再次捕获!
})
.then((res) => {
console.log(res); // 123
})
Interface
- 一种强制约束的类(也可创建实例): 所有成员必须强制初始化.
- 优点: 可以再被声明一遍以便添加扩展字段. 当作函数参数时可以避免参数名写错的漏洞!
- 注意: 包括class,只要里面的结构一样,名称无关紧要—视为同一种类型
interface Rsp<T = any> extends X,Y { // foo.ts
readonly code: number; // 只能在对象第1次创建时赋值! 属性使用时一般使用readonly,变量一般用const!
readonly message: string;
result: T;
age?: number; // 可选属性; 否则:tom初始化时属性还必须全,否则报错;多了也报错!
[propName: string]: any; // 任意属性. 此时,确定属性与可选属性的类型必须是它的类型的子集. 任意属性只能有一个!也可是联合类型!
(p1: T): boolean;
}
function getUser<T>() {
return Ax.get<Rsp<T>>('/path')
.then().catch(); // 若catch放在then前头则then肯定会被执行!
}
declare let person: Rsp; // 其他文件可以扩展person的属性
interface String { // 一般放到一个global.d.ts里面
endsWith(suffix: string): boolean;
}
String.prototype.endsWith = function(suffix: string): boolean {
const str: string = this;
return str && str.indexOf(suffix, str.length - suffix.length) != -1;
}
// 注意: 定义多个同名的类或接口时会合并里面的成员(适用于接口)
// 注意: 只要里面的成员一样(构造函数与静态成员除外),名称就无关紧要,对象之间可以相互赋值!
// 注意: T未泛化前相当于any,可以互相赋值.
// 实际上也同时声明了一个同名的接口(以下函数自动排除:构造函数,static成员)
class Cat<T> extends Animal implement XX,FF {
private readonly name = 'lei'; // 实例属性. 子类中也无法访问; readonly也可在构造函数种赋值
get name() { // automatic readonly
}
set name(value) {
}
constructor(name) { // 若未private则不许被继承或实例化
super(name);
}
abstract swim();
static isOk() {} // 仅可使用类名调用
static num = 2;
}
Utilities
common
// trueTypeOf(() => {}); // function
const trueTypeOf = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
// 检测对象是否为空
const isEmpty = obj => Reflect.ownKeys(obj).length === 0 && obj.constructor === Object;
// capitalize("hello world") // Hello world
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
// reverse('hello world'); // 'dlrow olleh'
const reverse = str => str.split('').reverse().join('');
// randomString();
const randomString = () => Math.random().toString(36).slice(2);
const stripHtml = html => (new DOMParser().parseFromString(html, 'text/html')).body.textContent || '';
// isNotEmpty([1, 2, 3]); // true
const isNotEmpty = arr => Array.isArray(arr) && arr.length > 0;
// const merge = (a, b) => [...a, ...b];
const merge = (a, b) => a.concat(b);
// average(1, 2, 3, 4, 5); // 3
const average = (...args) => args.reduce((a, b) => a + b) / args.length;
// random(1, 50);
const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
const randomBoolean = () => Math.random() >= 0.5;
// round(1.555, 2) //1.56
const round = (n, d) => Number(Math.round(n + "e" + d) + "e-" + d)
// rgbToHex(255, 255, 255); // '#ffffff'
const rgbToHex = (r, g, b) => "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
// 获取一个随机的十六进制颜色
const randomHex = () => `#${Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0")}`;
// 将文本复制到剪贴板
const copyToClipboard = (text) => navigator.clipboard.writeText(text);
// 清除所有cookie
const clearCookies = document.cookie.split(';').forEach(cookie => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`));
// 获取选中的文本
const getSelectedText = () => window.getSelection().toString();
// 检测是否是黑暗模式
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
// 滚动到页面顶部
const goToTop = () => window.scrollTo(0, 0);
// 判断页面是否已经底部
const scrolledToBottom = () => document.documentElement.clientHeight + window.scrollY >= document.documentElement.scrollHeight;
// 判断当前标签页是否激活
const isTabInView = () => !document.hidden;
const isAppleDevice = () => /Mac|iPod|iPhone|iPad/.test(navigator.platform);
datetime
// isDateValid("December 17, 1995 03:24:00"); // true
const isDateValid = (...val) => !Number.isNaN(new Date(...val).valueOf());
// dayDif(new Date("2021-11-3"), new Date("2022-2-1")) // 90
const dayDif = (date1, date2) => Math.ceil(Math.abs(date1.getTime() - date2.getTime()) / 86400000)
// dayOfYear(new Date()); // 307
const dayOfYear = (date) => Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
// timeFromDate(new Date(2021, 11, 2, 12, 30, 0)); // 12:30:00
const timeFromDate = date => date.toTimeString().slice(0, 8);
misc
// 华氏度和摄氏度之间的转换
const celsiusToFahrenheit = (celsius) => celsius * 9/5 + 32;
const fahrenheitToCelsius = (fahrenheit) => (fahrenheit - 32) * 5/9;