Giter Site home page Giter Site logo

blog's People

Contributors

ktfwycj avatar

Stargazers

 avatar

Watchers

 avatar

blog's Issues

Typescript与React结合的最佳实践

TypeScript

img

工具

基础备忘

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
}

在上述代码中,我们分别定义了 MotorcycleCarTruck 三个接口,在这些接口中都包含一个 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; };

泛型

image-20210829222831171

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"));

image-20210829223005896

除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:

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.

typescript-cheatsheets

Interface vs Type alias in TypeScript 2.7.

Differences Between Type Aliases and Interfaces.

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抓取

方案理念

  1. 十六进制字符串编码
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
  1. 字符串映射
var _0x8b75=["Hello, world! ","log"];console[_0x8b75[1]](_0x8b75[0]+ 123)
  1. Dead code 注入
    缺点是会让JavaScript代码体积变大且更难阅读

  2. scope混淆

  3. 控制流混淆

  4. 压缩代码
    这本身并不是什么混淆技术,从代码中删除缩进、换行符和空格通常是混淆过程的一部分,与其他技术结合使用。

参考文章

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.