blog's People
blog's Issues
Typescript与React结合的最佳实践
TypeScript
工具
- typescript playground:用于实时编写ts并且可以查看编译后的js代码。
基础备忘
enum
- 数字枚举:
// 源输入:
enum test {
ONE,
TWO,
THREE
}
// 实际释义:
declare enum test {
ONE = 0,
TWO = 1,
THREE = 2
}
// 解析成JavaScript
"use strict";
var test;
(function (test) {
test[test["ONE"] = 0] = "ONE";
test[test["TWO"] = 1] = "TWO";
test[test["THREE"] = 2] = "THREE";
})(test || (test = {}));
-
字符串枚举:
// 源输入 enum Direction { NORTH = "NORTH", SOUTH = "SOUTH", EAST = "EAST", WEST = "WEST", } // 解析成JavaScript "use strict"; var Direction; (function (Direction) { Direction["NORTH"] = "NORTH"; Direction["SOUTH"] = "SOUTH"; Direction["EAST"] = "EAST"; Direction["WEST"] = "WEST"; })(Direction || (Direction = {}));
数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射:
enum Direction { NORTH, SOUTH, EAST, WEST, } let dirName = Direction[0]; // NORTH let dirVal = Direction["NORTH"]; // 0
类型断言
你比 TypeScript 更了解某个值的详细信息,通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用
-
尖括号语法
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length; const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
-
as语法
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length; const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即
值 as 类型
。形如
<Foo>
的语法在 tsx 中表示的是一个ReactNode
,在 ts 中除了表示类型断言之外,也可能是表示一个泛型。
非空断言(!)
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 !
可以用于断言操作对象是非 null 和非 undefined 类型。**具体而言,x! 将从 x 值域中排除 null 和 undefined 。**使用场景如下:
-
忽略 undefined 和 null 类型
function myFunc(maybeString: string | undefined | null) {
// Type 'string | null | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
const onlyString: string = maybeString; // Error
const ignoreUndefinedAndNull: string = maybeString!; // Ok
}
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
-
调用函数时忽略 undefined 类型
type NumGenerator = () => number;
function myFunc(numGenerator: NumGenerator | undefined) {
// Object is possibly 'undefined'.(2532)
// Cannot invoke an object which is possibly 'undefined'.(2722)
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}
类型守卫
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。
-
in关键字
interface Admin { name: string; privileges: string[]; } interface Employee { name: string; startDate: Date; } type UnknownEmployee = Employee | Admin; function printEmployeeInformation(emp: UnknownEmployee) { console.log("Name: " + emp.name); if ("privileges" in emp) { console.log("Privileges: " + emp.privileges); } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate); } }
-
typeof关键字
"string"
"number"
"bigint"
"boolean"
"symbol"
"undefined"
"object"
"function"
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof
类型保护只支持两种形式:typeof v === "typename"
和 typeof v !== typename
,"typename"
必须是 "number"
, "string"
, "boolean"
或 "symbol"
。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
-
instanceof
-
自定义类型保护的类型谓词
function isNumber(x: any): x is number { return typeof x === "number"; } function isString(x: any): x is string { return typeof x === "string"; }
可辨识联合
如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
- 可辨识。可辨识要求联合类型中的每个元素都含有一个单例类型属性。
enum CarTransmission {
Automatic = 200,
Manual = 300
}
interface Motorcycle {
vType: "motorcycle"; // discriminant
make: number; // year
}
interface Car {
vType: "car"; // discriminant
transmission: CarTransmission
}
interface Truck {
vType: "truck"; // discriminant
capacity: number; // in tons
}
在上述代码中,我们分别定义了 Motorcycle
、 Car
和 Truck
三个接口,在这些接口中都包含一个 vType
属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
- 联合类型
基于前面定义了三个接口,我们可以创建一个 Vehicle
联合类型。
type Vehicle = Motorcycle | Car | Truck;
- 类型守卫
const EVALUATION_FACTOR = Math.PI;
function evaluatePrice(vehicle: Vehicle) {
return vehicle.capacity * EVALUATION_FACTOR;
}
const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);
以上的代码编译器会报错。
Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.
使用类型守卫改造:
function evaluatePrice(vehicle: Vehicle) {
switch(vehicle.vType) {
case "car":
return vehicle.transmission * EVALUATION_FACTOR;
case "truck":
return vehicle.capacity * EVALUATION_FACTOR;
case "motorcycle":
return vehicle.make * EVALUATION_FACTOR;
}
}
接口
- 任意属性
有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
const p1 = { name: "semlinker" };
const p2 = { name: "lolo", age: 5 };
const p3 = { name: "kakuqo", sex: 1 }
- extend
接口和类型别名都能够被扩展,但语法有所不同。此外,接口和类型别名不是互斥的。接口可以扩展类型别名,而反过来是不行的。
Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX {
y: number;
}
Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
泛型
T
代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T
可以用任何有效名称代替。除了 T
之外,以下是常见泛型变量代表的意思:
- K(Key):表示对象中的键类型;
- V(Value):表示对象中的值类型;
- E(Element):表示元素类型。
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U
,用于扩展我们定义的 identity
函数:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
never
TypeScript 将使用 never 类型来表示不应该存在的状态。
穷举检查
never 类型可分配给每种类型;但是,没有类型可以分配给 never(除了 never 本身)。这意味着您可以使用缩小并依靠从不出现在 switch 语句中进行详尽的检查。
例如,向我们的 getArea 函数添加一个默认值,该函数试图将形状分配为 never 在尚未处理所有可能的情况时将引发。
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
向 Shape union 添加新成员会导致 TypeScript 错误:
interface Triangle {
kind: "triangle";
sideLength: number;
}
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
// Type 'Triangle' is not assignable to type 'never'.
return _exhaustiveCheck;
}
}
函数类型
类型表达式
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
// 可以改为:
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
泛型函数
在 TypeScript 中,当我们想要描述两个值之间的对应关系时,会使用泛型。我们通过在函数签名中声明一个类型参数来做到这一点:
function firstElement<Type>(arr: Type[]): Type {
return arr[0];
}
通过向这个函数添加一个类型参数 Type 并在两个地方使用它,我们在函数的输入(数组)和输出(返回值)之间创建了一个链接。现在当我们调用它时,会出现一个更具体的类型:
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
良好泛型函数编写指南
- 使用type类型
- 使用更少的参数
- 类型参数应该出现两次
使用type
function firstElement1<Type>(arr: Type[]) {
return arr[0];
}
function firstElement2<Type extends any[]>(arr: Type) {
return arr[0];
}
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
乍一看,两种写法似乎相同,但 firstElement1 是编写此函数的更好方法。它的推断返回类型是 Type
,但是 firstElement2 的推断返回类型是 any
,因为 TypeScript 必须使用约束类型解析 arr[0] 表达式,而不是在调用期间“等待”解析元素。
使用更少的参数
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
return arr.filter(func);
}
function filter2<Type, Func extends (arg: Type) => boolean>(
arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}
我们创建了一个类型参数Func
,它不关联两个值。这总是一个危险信号,因为这意味着想要指定类型参数的调用者必须无缘无故地手动指定额外的类型参数。Func
没有做任何事情,只是使函数更难阅读和推理!
类型参数应该出现两次
有时我们会忘记一个函数可能不需要是泛型的:
function greet<Str extends string>(s: Str) {
console.log("Hello, " + s);
}
greet("world");
我们只需要简单的写成:
function greet(s: string) {
console.log("Hello, " + s);
}
函数重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
比如,我们需要实现一个函数 reverse
,输入数字 123
的时候,输出反转的数字 321
,输入字符串 'hello'
的时候,输出反转的字符串 'olleh'
。
利用联合类型,我们可以这么实现:
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个 reverse
的函数类型:
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
上例中,我们重复定义了多次函数 reverse
,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
参数解构
// Same as prior example
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}
高级语法
type VS interface
Aspect | Type | Interface |
---|---|---|
Can describe functions | ✅ | ✅ |
Can describe constructors | ✅ | ✅ |
Can describe tuples | ✅ | ✅ |
Interfaces can extend it | ✅ | |
Classes can extend it | 🚫 | ✅ |
Classes can implement it (implements ) |
✅ | |
Can intersect another one of its kind | ✅ | |
Can create a union with another one of its kind | ✅ | 🚫 |
Can be used to create mapped types | ✅ | 🚫 |
Can be mapped over with mapped types | ✅ | ✅ |
Expands in error messages and logs | ✅ | 🚫 |
Can be augmented | 🚫 | ✅ |
Can be recursive | ✅ |
使用建议:
- always use
interface
for public API's definition when authoring a library or 3rd party ambient type definitions, as this allows a consumer to extend them via declaration merging if some definitions are missing. - consider using
type
for your React Component Props and State, for consistency and because it is more constrained.
Pick,添加某个对象类型的属性
type Pick<T, K extends keyof T> = {
[key in k]: T[key]
}
// 原始类型
interface TState {
name: string;
age: number;
like: string[];
}
// 如果我只想要name和age怎么办,最粗暴的就是直接再定义一个(我之前就是这么搞得)
interface TSingleState {
name: string;
age: number;
}
// 这样的弊端是什么?就是在Tstate发生改变的时候,TSingleState并不会跟着一起改变,所以应该这么写
interface TSingleState extends Pick<TState, "name" | "age"> {};
Omit:剔除某个对象类型的属性
type Omit<T, K extends keyof T> = {
[key in k]: T[key]
}
type UserProps = {
name?:string;
age?:number;
sex?:string;
}
// 但是我不希望有sex这个属性我就可以这么写
type NewUserProps = Omit<UserProps,'sex'>
// 等价于
type NewUserProps = {
name?:string;
age?:number;
}
extends:用于接口,表示继承⭐️
interface T1 {
name: string,
}
interface T2 {
sex: number,
}
/**
* @example
* T3 = {name: string, sex: number, age: number}
*/
interface T3 extends T1, T2 {
age: number,
}
注意,接口支持多重继承,语法为逗号隔开。如果是type实现继承,则可以使用交叉类型type A = B & C & D
。
/**
* @example
* type A1 = 1
*/
type A1 = 'x' extends 'x' ? 1 : 2;
/**
* @example
* type A2 = 2
*/
type A2 = 'x' | 'y' extends 'x' ? 1 : 2;
/**
* @example
* type A3 = 1 | 2
*/
type P<T> = T extends 'x' ? 1 : 2;
type A3 = P<'x' | 'y'>
代码引用外部资源时,添加参考注释:
/**
* sourced from: https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd
*/
const useCombinedRefs = (...refs: ReactCropperRef[]): React.RefObject<ReactCropperElement> => {
const targetRef = useRef<ReactCropperElement>(null);
React.useEffect(() => {
refs.forEach((ref) => {
if (!ref) return;
if (typeof ref === 'function') {
ref(targetRef.current);
} else {
ref.current = targetRef.current;
}
});
}, [refs]);
return targetRef;
};
React & Typescript应用
useState
const [user, setUser] = React.useState<IUser>({} as IUser);
useRef
-
DOM element ref:返回一个只读的current
function Foo() { // - If possible, prefer as specific as possible. For example, HTMLDivElement // is better than HTMLElement and way better than Element. // - Technical-wise, this returns RefObject<HTMLDivElement> const divRef = useRef<HTMLDivElement>(null); useEffect(() => { // Note that ref.current may be null. This is expected, because you may // conditionally render the ref-ed element, or you may forgot to assign it if (!divRef.current) throw Error("divRef is not assigned"); // Now divRef.current is sure to be HTMLDivElement doSomethingWith(divRef.current); }); // Give the ref to an element so React can manage it for you return <div ref={divRef}>etc</div>; }
-
Mutable alue ref
参考资料
- typescript-cheatsheets:介绍了如何把 React 和 TypeScript 结合,并且给出了一些进阶用法的示例,非常值得过一遍。
JavaScript代码混淆常见方案
主要目的
类似于加密处理,避免JavaScript抓取
方案理念
- 十六进制字符串编码
console.log("Hello, world! " + 123);
// 处理成十六进制编码
console["\x6C\x6F\x67"]("\x48\x65\x6C\x6C\x6F\x2C\x20\x77\x6F\x72\x6C\x64\x21\x20"+ 123)
// console["\x6C\x6F\x67"] 相当于 console['log']、console.log
- 字符串映射
var _0x8b75=["Hello, world! ","log"];console[_0x8b75[1]](_0x8b75[0]+ 123)
-
Dead code 注入
缺点是会让JavaScript代码体积变大且更难阅读 -
scope混淆
-
控制流混淆
-
压缩代码
这本身并不是什么混淆技术,从代码中删除缩进、换行符和空格通常是混淆过程的一部分,与其他技术结合使用。
参考文章
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.