前端同学们!跟着我一起开卷吧!

1,292 阅读19分钟

Hello,大家好,我是disguiseFish,坚持早起每天每日一题有一段时间啦,收获良多,每天会做些题来储备自己的知识量,在这里把它们分享记录下来,我们一起学习进步吧! 这是一个日更帖哟!每日做的题都贴出了对应的理解,或许你还有其他理解~欢迎来骚扰哟!

67.:DAY22(泛型与泛型接口)【2022.07.28】

定义接口的时候也可以指定泛型

// 定义了接口传入的类型 T 之后返回的对象数组里面 T 就是当时传入的参数类型
interface Cart<T> {
  list: T[];
}
let cart: Cart<{ name: string; price: number }> = {
  list: [{ name: "hello", price: 10 }],
};
console.log(cart.list[0].name, cart.list[0].price);

66.:DAY21(泛型与泛型约束)【2022.07.27】

//泛型 T 不一定包含属性 length,所以编译的时候报错了
function loggingIdentity<T>(arg: T): T {
  console.log(arg.length);
  return arg;
}

这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

65.跟着鲨鱼哥学ts:DAY20(泛型与多个参数)【2022.07.26】

如果 有多个未知的类型占位 那么我们可以定义任何的字母来表示不同的类型参数

如下图所示

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
// 通过<>传入类型
swap<number,string>([7, "seven"]); // ['seven', 7]

64.跟着鲨鱼哥学ts:DAY19(泛型)【2022.07.25】

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

我们想要的效果是 我们预先不知道会传入什么类型 但是我们希望不管我们传入什么类型 我们的返回的数组的指里面的类型应该和参数保持一致 那么这时候 泛型就登场了

// 数组扁平化
const arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
    // 不停循环 找出数组中是数组的进行...
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}
console.log(flatten(arr)); 

function createArray<T>(length: number, value:T):Array<T>{
    // 初始化为空数组--类型为T
    let result:T[] = []
    for (let i = 0; i < length; i++) {
        result[i] = value
    }
    return result
}
createArray<string>(3, 'x')//使用的时候确定定义的类型T是啥,直接传入即可

可以使用 <> 的写法 然后在前面传入一个变量 T 用来表示后续函数需要用到的类型 当我们真正去调用函数的时候再传入 T 的类型就可以解决很多预先无法确定类型相关的问题

63.跟着鲨鱼哥学ts:DAY18(接口和类型别名的区别)【2022.07.22】

大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别

1.基础数据类型: 与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组

// primitive
type Name = string;

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

// dom
let div = document.createElement("div");
type B = typeof div;

2.重复定义:接口可以定义多次 会被自动合并为单个接口 类型别名不可以重复定义

interface Point {
  x: number;
}
interface Point {
  y: number;
}
const point: Point = { x: 1, y: 2 };

3.扩展:接口可以扩展类型别名,同理,类型别名也可以扩展接口。但是两者实现扩展的方式不同

接口的扩展就是继承,通过 extends 来实现。类型别名的扩展就是交叉类型,通过 & 来实现。

// 接口扩展接口
interface PointX {
  x: number;
}

interface Point extends PointX {
  y: number;
}

// ----
// 类型别名扩展类型别名
type PointX = {
  x: number;
};

type Point = PointX & {
  y: number;
};
// ----
// 接口扩展类型别名
type PointX = {
  x: number;
};
interface Point extends PointX {
  y: number;
}
// ----
// 类型别名扩展接口
interface PointX {
  x: number;
}
type Point = PointX & {
  y: number;
};

4.实现:这里有一个特殊情况 类无法实现定义了联合类型的类型别名

type PartialPoint = { x: number } | { y: number };

// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint {
  // Error
  x = 1;
  y = 2;
}


62.跟着鲨鱼哥学ts:DAY17(接口与构造函数的类型接口)【2022.07.21】

使用特殊的 new()关键字来描述类的构造函数类型

class Animal {
  constructor(public name: string) {}
}
//不加new是修饰函数的,加new是修饰类的
interface WithNameClass {
  new (name: string): Animal;
}
function createAnimal(clazz: WithNameClass, name: string) {
  return new clazz(name);
}
let a = createAnimal(Animal, "hello");
console.log(a.name);

当我们需要把一个类作为参数的时候,我们需要对传入的类的构造函数类型进行约束 所以需要使用 new 关键字代表是类的构造函数类型 用以和普通函数进行区分

61.跟着鲨鱼哥学ts:DAY16(接口与函数类型接口)【2022.07.20】

可以用接口来定义函数类型

// 定义一个discount类型,
interface discount {
  (price: number): number;
}
// 声明cost属性的 参数和返回值都是定义好的number类型
let cost: discount = function (price: number): number {
  return price * 0.8;
};

60.跟着鲨鱼哥学ts:DAY15(接口与继承)【2022.07.19】

类和接口均可以继承,都能用到 extends 关键字

interface Speakable {
  speak(): void;
}
// 声明一个接口 继承Speakable
interface SpeakChinese extends Speakable {
  speakChinese(): void;
}
// 继承SpeakChinese的类
class Person implements SpeakChinese {
  speak() {
  console.log("Person");
}
speakChinese() {
  console.log("speakChinese");
}

59.跟着鲨鱼哥学ts:DAY14(接口与定义任意属性)【2022.07.18】

如果我们在定义接口的时候无法预先知道有哪些属性的时候,

可以使用 [propName:string]:any ,propName 名字是任意的

interface Person {
  id: number;
  name: string;
  [propName: string]: any;
}

let p1 = {
  id: 1,
  name: "hello",
  age: 10,
};


这个接口表示 必须要有 id 和 name 这两个字段 然后还可以新加其余的未知字段

58.跟着鲨鱼哥学ts:DAY13(接口与行为的抽象)【2022.07.15】

接口可以把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类

一个类可以实现多个接口,一个接口也可以被多个类实现

我们用 implements关键字来代表 实现

//接口可以在面向对象编程中表示为行为的抽象
interface Speakable {
  speak(): void;
}
interface Eatable {
  eat(): void;
}

//一个类可以实现多个接口
class Person implements Speakable, Eatable {
  speak() {
    console.log("Person说话");
  }
  
  //   eat() {} //需要实现的接口包含eat方法 不实现会报错,因为implements了两个
}


57.跟着鲨鱼哥学ts:DAY12(接口与对象的形状)【2022.07.14】

通常我们可以通过interface关键字来定义接口,在接口中可以用个逗号或者分号或者什么都不加来分割每一项

接口既可以表示行为的抽象,也可以用来描述对象的形状

// 接口可以用来描述`对象的形状`
interface Speakable {
  speak(): void;
  readonly lng: string; //readonly表示只读属性 后续不可以更改
  name?: string; //?表示可选属性
}

// speakman对象根据Speakable定义的形状去被描述
let speakman: Speakable = {
  //   speak() {}, //少属性会报错
  name: "shelly",
  lng: "en",
  age: 111, // 在Speakable 中没有age 所以 多属性也会报错
};

56.跟着鲨鱼哥学ts:DAY11(ts与抽象类和抽象方法)【2022.07.13】

抽象类:

无法被实例化,只能被继承并且无法创建抽象类的实例,可用于增加代码的可维护性和复用性

子类可以对抽象类进行不同的实现

抽象方法只能出现在抽象类中并且抽象方法不能在抽象类中被具体实现,只能在抽象类的子类中实现(必须要实现)

使用场景:

我们一般用抽象类和抽象方法抽离出事物的共性,之后所有继承的子类必须按照规范去实现自己的具体逻辑,这样就增加代码的可维护性和复用性,使用 abstract 关键字来定义抽象类和抽象方法

// 定义一个抽象类 Animal- 有name 有抽象方法speak
abstract class Animal {
  name!: string;
  abstract speak(): void;
}
// 子类Cat 继承 Animal
class Cat extends Animal {
  speak() {
    console.log("shelly喵喵喵");
  }
}
let animal = new Animal(); //直接报错 无法创建抽象类的实例 -- 无法直接创建 abstract 定义的抽象类实例
let cat = new Cat(); // 可以直接创建继承了abstract 抽象类的子类实例
cat.speak(); // 子类可以直接使用抽象方法

思考 1:重写(override)和重载(overload)的区别

思考 2:什么是多态

// 解答:
// 思考1:重写是指子类重写继承自父类中的方法,重载是指为同一个函数提供多个类型定义


// 举个栗子:
// 重写:
class People {
  speak(word: string): string {
    return "人:" + word;
  }
}
class Beuty extends People {
  // 重写:子类重写继承来的方法speak
  speak(word: string): string {
    return "美女:" + word;
  }
}
let beuty = new Beuty();
console.log(beuty.speak("我不是美女"));

// 重载:为1个方法double 提供了多个类型定义
function double(val: number): number;
function double(val: string): string;
function double(val: any): any {
  if (typeof val == "number") {
    return val * 2;
  }
  return val + val;
}

let r = double(1);
console.log(r);
// 解答:
// 思考2: 多态指的是,在父类中定义一个方法,在子类中这个方法有多个实现,在运行的时候,
// 会根据不同的对象执行不同的操作,实现运行时的绑定

// 举个栗子
abstract class Animal {
  // 声明抽象的方法,让子类去实现
  abstract sleep(): void;
}
// 狗继承动物父类
class Dog extends Animal {
  // 狗给抽象方法定义具体实现
  sleep() {
    console.log("dog sleep");
  }
}
let dog = new Dog();// 狗实例
class Cat extends Animal {
  // 猫咪给抽象方法定义具体实现
  sleep() {
    console.log("cat sleep");
  }
}
let cat = new Cat();// 猫实例
// 把猫狗塞进animals中 循环执行实例中的sleep方法
let animals: Animal[] = [dog, cat];
animals.forEach((i) => {
  i.sleep();
});

55.跟着鲨鱼哥学ts:DAY10(ts与静态方法)【2022.07.12】

类的静态属性和方法是直接定义在类本身上面的 所以也只能通过直接调用类的方法和属性来访问

// 声明一个父类
class Parent {
  static mainName = "Parent";
  static getmainName() {
    console.log(this); //注意静态方法里面的this指向的是类本身 而不是类的实例对象 所以静态方法里面只能访问类的静态属性和方法
    return this.mainName;
  }
  public name: string;
  constructor(name: string) {
    //构造函数
    this.name = name;
  }
}
console.log(Parent.mainName);
console.log(Parent.getmainName());// 静态方法里面只能访问类的静态属性和方法

54.跟着鲨鱼哥学ts:DAY9(ts与修饰符)【2022.07.11】

public 类里面 子类 其它任何地方外边都可以访问 

protected 类里面 子类 都可以访问,其它任何地方不能访问

private 类里面可以访问,子类和其它任何地方都不可以访问


  class Parent {
        
        public name: string;// 其它任何地方
        protected age: number;//子类和内部可访问
        private car: number;//只有内部可以访问
        
        constructor(name: string, age: number, car: number) {
          //构造函数
          this.name = name;
          this.age = age;
          this.car = car;
        }
        getName(): string {
          return this.name;
        }
        setName(name: string): void {
          this.name = name;
        }
      }
      // 声明一个子类继承父类 记得super
      class Child extends Parent {
        constructor(name: string, age: number, car: number) {
          super(name, age, car);
        }
        desc() {
          console.log(`${this.name} ${this.age} ${this.car}`); //car访问不到 会报错
        }
      }
      
      let child = new Child("hello", 10, 1000);
      console.log(child.name);// 可访问
      console.log(child.age); //age访问不到 会报错
      console.log(child.car); //car访问不到 会报错

53.跟着鲨鱼哥学ts:DAY8(ts与继承)【2022.07.08】

子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性

将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑

super 可以调用父类上的方法和属性

在 TypeScript 中,我们可以通过 extends 关键字来实现继承

class Person {
  name: string; //定义实例的属性,默认省略public修饰符
  age: number;
  constructor(name: string, age: number) {
    //构造函数
    this.name = name;
    this.age = age;
  }
  getName(): string {
    return this.name;
  }
  setName(name: string): void {
    this.name = name;
  }
}
// 子类通过extends继承父类
class Student extends Person {
  no: number;
  constructor(name: string, age: number, no: number) {
    super(name, age);
    this.no = no;
  }
  getNo(): number {
    return this.no;
  }
}
let s1 = new Student("hello", 10, 1);
console.log(s1);

52.跟着鲨鱼哥学ts:DAY7(ts与readonly 只读属性)【2022.07.07】

终于周四了!!

readonly 修饰的变量只能在构造函数中初始化 TypeScript 的类型系统同样也允许将 interface、type、 class 上的属性标识为 readonly readonly 实际上只是在编译阶段进行代码检查。

class Animal {
  // 声明只读:字符串
  public readonly name: string;
  // 在构造函数内初始化
  constructor(name: string) {
    this.name = name;
  }
  changeName(name: string) {
    this.name = name; //这个ts是报错的
  }
}

let a = new Animal("hello");

51.跟着鲨鱼哥学ts:DAY6(ts与类中的存取器)【2022.07.6】

TypeScript 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为


class User {
  myname:string;
  constructor(myname:string) {
    this.myname = myname
  }
  get name(){
    return this.myname
  }
  set name(value) {
    this.myname = value
  }
}

let user = new User('shelly')
user.name = 'hellow shelly'
console.log(user.name)


// 把class翻译成es5语法再看看:
var User = /** @class */ (function () {
  function User(myname) {
    this.myname = myname;
  }
  // 对方法User用Object.defineProperty进行劫持
  // 通过内部的get 和 set方法
  Object.defineProperty(User.prototype, "name", {
    get: function () {
      return this.myname;
    },
    set: function (value) {
      this.myname = value;
    },
    enumerable: false,
    configurable: true,
  });
  return User;
})();
// 使用:
var user = new User("hello");
user.name = "world";
console.log(user.name);


50.跟着鲨鱼哥学ts:DAY5(ts与类的定义)【2022.07.5】

TypeScript 中,我们可以通过 Class 关键字来定义一个类

class Person {
  name!: string; //如果初始属性没赋值就需要加上!
  constructor(_name: string) {
    this.name = _name;
  }
  getName(): void {
    console.log(this.name);
  }
}
let p1 = new Person("hello");
p1.getName();

当然 如果我们图省事 我们也可以把属性定义直接写到构造函数的参数里面去(不过一般不建议这样写 因为会让代码增加阅读难度)

class Person {
  constructor(public name: string) {}
  getName(): void {
    console.log(this.name);
  }
}
let p1 = new Person("hello");
p1.getName();

注意:当我们定义一个类的时候,会得到 2 个类型 一个是构造函数类型的函数类型(当做普通构造函数的类型) 另一个是类的实例类型(代表实例)

class Component {
  static myName: string = "静态名称属性";
  myName: string = "实例名称属性";
}
//ts 一个类型 一个叫值
//放在=后面的是值
let com = Component; //这里是代表构造函数
//冒号后面的是类型
let c: Component = new Component(); //这里是代表实例类型
let f: typeof Component = com;

49.跟着鲨鱼哥学ts:DAY4(ts与函数)【2022.07.4】

// 1. 函数的定义
// 可以指定 参数的类型 和  返回值的类型
function hello(name: string): void {
  console.log("hello", name);
}
hello("hahaha");
// 2. 函数表达式:定义函数类型
type SumFunc = (x: number, y: number) => number;
// 指定类型后 使用的时候不用再指定类型
let countNumber: SumFunc = function (a, b) {
  return a + b;
};

// 3. 可选参数
// 在 TS 中函数的形参和实参必须一样,不一样就要配置可选参数,而且必须是最后一个参数
function print(name: string, age?: number): void {
  console.log(name, age);
}
print("hahaha");
// 4. 默认参数
function ajax(url: string, method: string = "GET") {
  console.log(url, method);
}
ajax("/users");
// 5. 剩余参数
function sum(...numbers: number[]) {
  return numbers.reduce((val, item) => (val += item), 0);
}
console.log(sum(1, 2, 3));
// 6. 函数重载
// 函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。
// 在 TypeScript 中,表现为给同一个函数提供多个函数类型定义
let obj: any = {};
function attr(val: string): void;
function attr(val: number): void;
function attr(val: any): void {
  if (typeof val === "string") {
    obj.name = val;
  } else {
    obj.age = val;
  }
}
attr("hahaha");
attr(9);
attr(true);
console.log(obj);


// 注意:函数重载真正执行的是同名函数最后定义的函数体 
// 在最后一个函数体定义之前全都属于函数类型定义 不能写具体的函数实现方法 只能定义类型

48.跟着鲨鱼哥学ts:DAY3(ts类型)【2022.07.1】

// 11.void
// void 表示没有任何类型 当一个函数没有返回值时 TS 会认为它的返回值是 void 类型
function hello(name:string): void{}
// 12.never 类型
// never 一般表示用户无法达到的类型 例如never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
function neverReach(): never {
  throw new Error("an error");
}

思考:never 和 void 的区别

  1. void 可以被赋值为 null 和 undefined 的类型。 never 则是一个不包含值的类型。

  2. 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。

// 13 BigInt 大数类型
// 使用 BigInt 可以安全地存储和操作大整数
// 我们在使用 BigInt 的时候 必须添加 ESNext 的编译辅助库 需要在 tsconfig.json 的 libs 字段加上ESNext
// 要使用1n需要 "target": "ESNext"
// number 和 BigInt 类型不一样 不兼容
const max1 = Number.MAX_SAFE_INTEGER; // 2**53-1
console.log(max1 + 1 === max1 + 2); //true

const max2 = BigInt(Number.MAX_SAFE_INTEGER);
console.log(max2 + 1n === max2 + 2n); //false

let foo: number;
let bar: bigint;
foo = bar; //error
bar = foo; //error
// 14. object, Object 和 {} 类型
// object 类型用于表示非原始类型
let objectCase: object;
objectCase = 1; // error
objectCase = "a"; // error
objectCase = true; // error
objectCase = null; // error
objectCase = undefined; // error
objectCase = {}; // ok

// 大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)
let ObjectCase: Object;
ObjectCase = 1; // ok
ObjectCase = "a"; // ok
ObjectCase = true; // ok
ObjectCase = null; // error
ObjectCase = undefined; // error
ObjectCase = {}; // ok

// {} 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合
let simpleCase: {};
simpleCase = 1; // ok
simpleCase = "a"; // ok
simpleCase = true; // ok
simpleCase = null; // error
simpleCase = undefined; // error
simpleCase = {}; // ok
// 15 类型推论
// 指编程语言中能够自动推导出值的类型的能力 它是一些强静态类型语言中出现的特性
// 定义时未赋值就会推论成 any 类型
// 如果定义的时候就赋值就能利用到类型推论
let flag; //推断为any
let count = 123; //为number类型
let hello = "hello"; //为string类型
// 16 联合类型
// 联合类型(Union Types)表示取值可以为多种类型中的一种
// 未赋值时联合类型上只能访问两个类型共有的属性和方法
let name: string | number;
console.log(name.toString());
name = 1;
console.log(name.toFixed(2));
name = "hello";
console.log(name.length);
// 17 类型断言
// 有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。
// 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
// 其实就是你需要手动告诉 ts 就按照你断言的那个类型通过编译(这一招很关键 有时候可以帮助你解决很多编译报错)
// 类型断言有两种形式:

// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as 语法
let someValue1: any = "this is a string";
let strLength: number = (someValue1 as string).length;

// 以上两种方式虽然没有任何区别,但是尖括号格式会与 react 中 JSX 产生语法冲突,因此我们更推荐使用 as 语法。


//非空断言
//在上下文中当类型检查器无法断定类型时 一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型
let flag: null | undefined | string;
flag!.toString(); // ok
flag.toString(); // error
// 18 字面量类型
// 在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。
// 目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型,具体示例如下:
let flag1: "hello" = "hello";
let flag2: 1 = 1;
let flag3: true = true;
// 19 类型别名
// 类型别名用来给一个类型起个新名字
type flag = string | number;

function hello(value: flag) {}

// 20 交叉类型
// 交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type Flag1 = { x: number };
type Flag2 = Flag1 & { y: string };

let flag3: Flag2 = {
  x: 1,
  y: "hello",
  henb,
};
// 21 类型保护
// 类型保护就是一些表达式,他们在编译的时候就能通过类型信息确保某个作用域内变量的类型 其主要思想是尝试检测属性、方法或原型,以确定如何处理值


// (1)typeof 类型保护
function double(input: string | number | boolean) {
  if (typeof input === "string") {
    return input + input;
  } else {
    if (typeof input === "number") {
      return input * 2;
    } else {
      return !input;
    }
  }
}


// (2)in 关键字
interface Bird {
  fly: number;
}

interface Dog {
  leg: number;
}

function getNumber(value: Bird | Dog) {
  if ("fly" in value) {
    return value.fly;
  }
  return value.leg;
}


// (3)instanceof 类型保护
class Animal {
  name!: string;
}
class Bird extends Animal {
  fly!: number;
}
function getName(animal: Animal) {
  if (animal instanceof Bird) {
    console.log(animal.fly);
  } else {
    console.log(animal.name);
  }
}


// (4)自定义类型保护
// 通过 type is xxx这样的类型谓词来进行类型保护
// 例如下面的例子 value is object就会认为如果函数返回 true 那么定义的 value 就是 object 类型
function isObject(value: unknown): value is object {
  return typeof value === "object" && value !== null;
}

function fn(x: string | object) {
  if (isObject(x)) {
    // ....
  } else {
    // .....
  }
}

47.跟着鲨鱼哥学ts:DAY2(ts类型)【2022.06.30】

// 1.boolean类型
const beuty: boolean = true;
// 2.number类型
const number: number = 1
// 3.string类型
const string1: string = 'shelly'
// 4.enum类型:枚举类型,可以描述一些特定业务场景,比如状态信息,代码错误
// 普通枚举 初始值默认为 0 其余的成员会会按顺序自动增长 可以理解为数组下标
enum Color {
    RED,
    PINK,
    BLUE
}
const pink: Color = Color.PINK

// 设置初始值
enum Color1 {
    RED = 10,
    PINK,
    BLUE,
}
const pink1: Color1 = Color1.PINK;
console.log(pink1); // 11

// 字符串枚举 每个都需要声明
enum Color2 {
    RED = "红色",
    PINK = "粉色",
    BLUE = "蓝色",
}

const pink2: Color2 = Color2.PINK;
console.log(pink2); // 粉色

// 常量枚举 它是使用 const 关键字修饰的枚举,
// 常量枚举与普通枚举的区别是:整个枚举会在编译阶段被删除,我们只能看到结果,编译阶段被删除

const enum Color3 {
    RED,
    PINK,
    BLUE,
}

const color3: Color3[] = [Color3.RED, Color3.PINK, Color3.BLUE];

//编译之后的js如下:
var color31 = [0 /* RED */, 1 /* PINK */, 2 /* BLUE */];
// 可以看到我们的枚举并没有被编译成js代码 只是把color这个数组变量编译出来了
// 5.数组类型:
const number1:number[] = [1,2,3]
const number2:Array<number> = [1,2,3]
// 6.元组类型(tuple)
// 在 TypeScript 的基础类型中,元组( Tuple )表示一个已知数量和类型的数组 可以理解为是一种特殊的数组
const tuple1:[string, number] = ['hello', 1]
// 7.Symbol
// 如字面意思,当我们想让某个值是唯一的时候,可以使用它
const sym1 = Symbol('hello')
const sym2 = Symbol('hello')
console.log(Symbol('hello') === Symbol('hello') )// false
// 8.任意类型(any)
// 任何类型都可以被归为 any 类型 这让 any 类型成为了类型系统的 顶级类型 (也被称作 全局超级类型) TypeScript 允许我们对 any 类型的值执行任何操作 而无需事先执行任何形式的检查
// 比如你不知道预定哪个类型, 第三方库没有提供类型文件时,就可以使用any;不过不要太依赖 any 否则就失去了 ts 的意义了
const flag:any = document.getElementById('root')
// 9.null 和 undefined
let u: undefined = undefined;
let n: null = null;
// 10.Unknown 类型
// unknown 和 any 的主要区别是:unknown 类型会更加严格,
// 在对 unknown 类型的值执行大多数操作之前 我们必须进行某种形式的检查 
// 而在对 any 类型的值执行操作之前 我们不必进行任何检查

// 所有类型都可以被归为 unknown ;
// 但unknown类型只能被赋值给 any 类型和 unknown 类型本身; 
// 而 any 啥都能分配和被分配
let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK ---任何值都能分配给unknown

let exampleUnk1: unknown = value; // OK-- unknown可以赋值给unknown和any
let exampleUnk2: any = value; // OK
let exampleUnk3: boolean = value; // Error--- 但是! unknown不能赋值给 unknown和any 以外的值
let exampleUnk4: number = value; // Error
let exampleUnk5: string = value; // Error
let exampleUnk6: object = value; // Error

46.跟着鲨鱼哥学ts:DAY1(安装及基本使用)【2022.06.29】

  • 第一步 新建一个空文件夹用来学习 ts
  • 第二步 全局安装 ts 和 ts-node
    cnpm i typescript -g //全局安装ts 

    cnpm i -g ts-node //全局安装ts-node ,可以监听ts中的变化,自动生成js文件

  • 第三步 生成 tsconfig.js 配置文件
tsc --init
  • 第四步 在项目下新建一个index.ts 直接写入
const a: string = "hello"; 
console.log(a);
  • 第五步 编译 ts 为 js 在控制台(终端)输入命令
tsc index.ts

发现在和ts文件同目录下会编译成js文件,ts-node可以帮助我们不需要编译成js的前提下就直接执行ts文件,每次更新

ts-node index.ts

如果不想每次都手动去执行编译,可以加一个参数,使每次ts百年更就自动编译成js文件

tsc --watch index.ts

45.ts的简单入门【2022.06.28】

直接npm下载依赖项

npm install -g typescript

简单运行:

tsc xxxxx.ts

function greeter(person: string) { 
    return "Hello, " + person; 
} 
let user = "Jane User"; 
document.body.innerHTML = greeter(user);

TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。就算你的代码里有ts相关错误,你仍然可以使用TypeScript。但在这种情况下,TypeScript会警告你代码可能不会按预期执行。

其中,Implements后面跟interface  是实现接口:使用接口来描述一个拥有firstNamelastName字段的对象。 在TypeScript里,只在两个类型内部的结构兼容那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements语句。

接口中的使用:

// 通常Implements后面跟interface  是实现接口
// 这里实现接口的时候 包含了要求的结构就可以,不必明确使用Implements
interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = { firstName: "Jane", lastName: "User" };

document.body.innerHTML = greeter(user);

class中的使用:

TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。在构造函数的参数上使用public等同于创建了同名的成员变量。

class Student {
    fullName: string;
    // 接收参数 定义类型 public等同于创建了同名的成员变量
    constructor(public firstName, public middleInitial, public lastName) {
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
}
// 定义一个类型 person
interface Person {
    firstName: string;
    lastName: string;
}

// 使用了person类型定义的函数
function greeter(person : Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

// 根据接收参数创建实例
let user = new Student("Jane", "M.", "User");
// 调用函数
document.body.innerHTML = greeter(user);

代码中,鼠标悬停在标识符上查看它们的类型。 注意在某些情况下它们的类型可以被自动地推断出来。 重新输入一下最后一行代码,看一下自动补全列表和参数列表,它们会根据DOM元素类型而变化。

44. vue中为什么 data 是一个函数【2022.06.27】

兜兜转转我又来看vue了.....

组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果

43. 事件冒泡、捕获(委托)【2022.06.24】

  • 事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。
  • 事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理

其实时间冒泡和委托都是一件事情,都是为了减少代码,节约内存,动态监听,通过给父元素绑定监听子元素事件去给子元素添加事件

event.stopPropagation() 阻止事件冒泡

42. 无痛刷新Token【2022.06.23】

对于需要前端实现无痛刷新Token,无非就两种:

  1. 请求前判断Token是否过期,过期则刷新
  2. 请求后根据返回状态判断是否过期,过期则刷新

实现起来也没多大差别,只是判断的位置不一样,核心原理都一样:

  1. 判断Token是否过期

    1. 没过期则正常处理

    2. 过期则发起刷新Token的请求

      1. 拿到新的Token保存
      2. 重新发送Token过期这段时间内发起的请求
  • 保持Token过期这段时间发起请求状态(不能进入失败回调)
  • 把刷新Token后重新发送请求的响应数据返回到对应的调用者

实现:

  • 创建一个flag isRefreshing 来判断是否刷新中

  • 创建一个数组队列retryRequests来保存需要重新发起的请求

  • 判断到Token过期

    1. isRefreshing = false 的情况下 发起刷新Token的请求

    2. 刷新Token后遍历执行队列retryRequests

    3. isRefreshing = true 表示正在刷新Token,返回一个Pending状态的Promise,并把请求信息保存到队列retryRequests

参考:juejin.cn/post/707534…

41. css3动画实现轮播图【2022.06.22】

实现原理:

  1. 设置大的div:

    设置绝对定位,设置图片高度宽度,设置超出部分隐藏overflow:hidden

  2. 设置小的div:

    宽度设置为图片宽度,设置position:relative / position:absolute  来让它可以实现轮播的功能

  3. 给图片设置浮动 设置左浮,保证图片在同一行展示

  4. 加入动画效果,每次左偏移一个图片的宽度

  5. 将最后一张图片和第一张图片设为一样的,可以实现视觉上的无缝连接

40. 前端水印的实现【2022.06.21】

  1. 显性水印 + dom元素直接遮盖: 直接将水印文字直接通过一层dom元素覆盖到需要添加水印的图片上

  2. 显性水印+canvas:算法和显性水印+dom元素直接覆盖,性能和安全性略优于方案一(直接通过canvas绘画,避免了在水印密度较大的情况下大量dom元素的创建添加,并且canvas在部分环境和浏览器下能GPU加速,所以性能较大)

  3. 保护程序+dom元素直接覆盖:方案二中,将资源绘制在canvas中虽然可行,但对于普通的非图片dom元素,虽然有可行方案例如html2canva来将DOM转化为·Canvas,但是实现过于繁杂 并且DOM将失去其事件处理响应功能,故而并不推荐这么使用,除非需要保护的资源没有任何交互 使用浏览器新增的MutationObserver特性(主流浏览器都已支持,参考资料中有具体文档链接) 用来监视需要保护的DOM元素及其子代的更改(包括监视DOM及其子代的删减、Style的变化,标签属性变化等等),一旦回调函数通知出现了任何更改 我们可以做出提示,提醒用户操作违法,并且删除掉水印,并且重新生成水印DOM 或者在用户更改了水印DOM的时候,将需要显示的保护资源DOM一并删除

  4. base64传输:将资源文件通过Base64编码并且通过request请求返回(或是直接后端保存Base64) 而对于Img来说,Base64只需要一些小小的的处理就可以在Web中使用(Base64字符串可以直接作为imgurl,但建议使用Js Image对象,这样避免了暴露原始URL到HTML中

  5. 加料的base64:

参考:juejin.cn/post/708814…

39. 为什么说js是单线程,而不是多线程呢?【2022.06.20】

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以是单线程的

38.如何实现 shouldComponentUpdate??【2022.06.17】

你可以用 React.memo 包裹一个组件来对它的 props 进行浅比较:

const Button = React.memo((props) => {
  // 你的组件
});

这不是一个 Hook 因为它的写法和 Hook 不同。React.memo 等效于 PureComponent,但它只比较 props。(你也可以通过第二个参数指定一个自定义的比较函数来比较新旧 props。如果函数返回 true,就会跳过更新。)

React.memo 不比较 state,因为没有单一的 state 对象可供比较。但你也可以让子节点变为纯组件,或者 [用 useMemo 优化每一个具体的子节点]

37.从Class迁移到 Hook项目,生命周期要如何对应到hook?【2022.06.16】

  • constructor:函数组件不需要构造函数。通过调用 [useState]来初始化 state。如果计算的代价比较昂贵,可以传一个函数给 useState
  • getDerivedStateFromProps:改为 [在渲染时]安排一次更新。
  • shouldComponentUpdate:详见 React.memo.
  • render:这是函数组件体本身。
  • componentDidMountcomponentDidUpdatecomponentWillUnmount:[useEffect Hook]可以表达所有这些(包括 [不那么] [常见] 的场景)的组合。
  • getSnapshotBeforeUpdatecomponentDidCatch 以及 getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法,但很快会被添加。

36.多次commit合并到一个commit【2022.06.15】

有时候,多次commit的提交其实都是在处理一件事,这个时候,为了commit好看点,后期易于维护,可以把这多个commit合并成一个

  1. git log 查看当前提交的commit

image.png 2. 合并多个commit

image.png

git [rebase] -i commit_id1 commit_id

这里的commit_id是待合并的多个commit之前的那个commit ID,这里也就是msg A的commit ID。 或者是想合并前3个到一个可以直接这样输入:

git rebase -i HEAD~3

执行完命令后就进入到vi的编辑模式:

image.png

  1. 进入vi编辑模式

image.png pick表示使用当前的commit,squash表示这个commit会被合并到前一个commit。

我们这里需要将msg C,msg D合并到msg B中,因为msg B是最靠近msg A的,因此这里选择将msg C,msg D合并到msg B中。

在键盘上敲i键进入insert模式,然后将msg C,msg D前面的pick修改成squash

image.png

按esc键退出insert模式,输入冒号,输入wq进行保存。

接下来会进入commit message 的编辑界面:

image.png 将上图中画线的内容删掉或者注释,然后写一个新的commit信息作为这3个commit的log信息

image.png 然后按esc键退出insert模式,输入冒号,输入wq进行保存。就会跳转到最初的命令界面:

image.png

参考连接:blog.csdn.net/fujian9544/…

35.useInsertionEffect【2022.06.14】

useInsertionEffect(didUpdate);

它在所有 DOM 突变之前同步触发。 在读取useLayoutEffect. 由于此挂钩的范围有限,因此此挂钩无法访问 refs 并且无法安排更新。

34.useSyncExternalStore【2022.06.13】

const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);

用于从外部数据源读取和订阅的钩子,其方式与时间切片等并发渲染功能兼容。

此方法返回 store 的值并接受三个参数:

  • subscribe: 注册回调的函数,每当商店更改时调用该回调。
  • getSnapshot:返回当前值的函数。
  • getServerSnapshot:返回服务器渲染期间使用的快照的函数。
// 订阅整个商店
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
// 订阅特定字段:
const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField,
);

服务端渲染时,必须将服务端使用的存储值序列化,并提供给useSyncExternalStore. React 将在 hydration 期间使用此快照来防止服务器不匹配:

const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField,
  () => INITIAL_SERVER_SNAPSHOT.selectedField,
);

getSnapshot必须返回一个缓存值。如果连续多次调用 getSnapshot,则它必须返回相同的确切值,除非两者之间有存储更新。

33.查看修改环境配置【2022.06.10】

一般我们刚入职一家新公司的时候,公司内部会有文档告诉你应该安装什么版本的node,npm,以及代理的镜像还有代理应该是代理哪个?还是比较简单的一个配置,不过也是我们必须掌握的技能

一、镜像源
查询当前镜像源

npm get registry 
设置为淘宝镜像

npm config set registry http://registry.npm.taobao.org/
设置回默认的官方镜像

npm config set registry https://registry.npmjs.org/
设置electron淘宝镜像:

npm config set ELECTRON_MIRROR "https://npm.taobao.org/mirrors/electron/"
查看和删除electron淘宝镜像设置:

npm config get ELECTRON_MIRROR
npm config delete ELECTRON_MIRROR
二、代理
查看当前代理

npm config get proxy
npm config get https-proxy
设置代理

npm config set proxy http://server:port
 
npm config set https-proxy http://server:port
删除代理

npm config set proxy null
npm config set https-proxy null

查看自己当前的代理配置情况:

npm config list

注意一点~如果是项目发布后突然换了依赖后,换完后记得测试一下构建有没有问题,没问题的话部署的脚本也要和运维说下,使用新的这个代理!

32.useId【2022.06.09】

const id = useId();

用于生成在服务器和客户端之间稳定的唯一 ID,同时避免水合不匹配

useId不适用于在列表中生成[键]]。密钥应该从数据中生成。

function Checkbox() {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Do you like React?</label>
      <input id={id} type="checkbox" name="react"/>
    </>
  );
};
// 同一组件中的多个 ID,请使用相同的 附加后缀`id`:
function NameFields() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id + '-firstName'}>First Name</label>
      <div>
        <input id={id + '-firstName'} type="text" />
      </div>
      <label htmlFor={id + '-lastName'}>Last Name</label>
      <div>
        <input id={id + '-lastName'} type="text" />
      </div>
    </div>
  );
}

useId生成一个包含:令牌的字符串。这有助于确保令牌是唯一的

31.useTransition【2022.06.08】

const [isPending, startTransition] = useTransition();

返回转换的挂起状态的有状态值,以及启动它的函数。

startTransition允许将提供的回调中的更新标记为转换:

startTransition(() => {
  setCount(count + 1);
})
// `isPending`指示转换何时处于活动状态以显示挂起状态:
function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);
  
  function handleClick() {
    startTransition(() => {
      setCount(c => c + 1);
    })
  }

  return (
    <div>
      {isPending && <Spinner />}
      <button onClick={handleClick}>{count}</button>
    </div>
  );
}

过渡中的更新会产生更紧急的更新,例如点击。

转换中的更新不会显示重新暂停内容的回退。这允许用户在呈现更新时继续与当前内容交互。

30.useDeferredValue【2022.06.07】

const deferredValue = useDeferredValue(value);

接受一个值并返回该值的新副本,该副本将推迟到更紧急的更新。如果当前渲染是紧急更新的结果,比如用户输入,React 将返回之前的值,然后在紧急渲染完成后渲染新的值。

这个钩子类似于用户空间钩子,它使用去抖动或节流来延迟更新。使用的好处useDeferredValue是 React 将在其他工作完成后立即进行更新

useDeferredValue仅延迟传递给它的值。如果想防止子组件在紧急更新期间重新渲染,还必须使用[React.memo]or记住该组件[React.useMemo]:

function Typeahead() {
  const query = useSearchQuery('');
  const deferredQuery = useDeferredValue(query);

  // Memoizing tells React to only re-render when deferredQuery changes,
  // not when query changes.
  const suggestions = useMemo(() =>
    <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">
        {suggestions}
      </Suspense>
    </>
  );
}

记忆孩子告诉 React 它只需要在更改时重新渲染它们

29.useDebugValue【2022.06.06】

useDebugValue(value)

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // 在开发者工具中的这个 Hook 旁边显示标签 
  // e.g. "FriendStatus: Online"  
  useDebugValue(isOnline ? 'Online' : 'Offline');
  return isOnline;
}

某些情况下,格式化值的显示可能是一项开销很大的操作。除非需要检查 Hook,否则没有必要这么做。

useDebugValue 接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。

一个返回 Date 值的自定义 Hook 可以通过格式化函数来避免不必要的 toDateString

useDebugValue(date, date => date.toDateString());

28.useLayoutEffect【2022.06.2】

useLayoutEffect:

函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。 尽可能使用 useEffect 以避免阻塞视觉更新,推荐一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect

服务端渲染:

如果使用服务端渲染,无论 useLayoutEffect 还是 useEffect 都无法在 Javascript 代码加载完成之前执行

解决这个问题,需要将代码逻辑移至 useEffect 中(如果首次渲染不需要这段逻辑的情况下),或是将该组件延迟到客户端渲染完成后再显示(如果直到 useLayoutEffect 执行之前 HTML 都显示错乱的情况下)。

若要从服务端渲染的 HTML 中排除依赖布局 effect 的组件,可以通过使用 showChild && <Child /> 进行条件渲染,并使用 useEffect(() => { setShowChild(true); }, []) 延迟展示组件。这样,在客户端渲染完成之前,UI 就不会像之前那样显示错乱了。

27.useImperativeHandle【2022.06.1】

useImperativeHandle(ref, createHandle, [deps])

在使用 ref 时,子组建自定义暴露给父组件的实例值,useImperativeHandle 应当与 forwardRef 一起使用:

// 父组件
import { useRef } from React

const Test = (props:any) => {
  const childRef = useRef<any>()
  function useChild(){
      // 使用子组件方法
      childRef.current.getList()
  }
  return (
    <div>
      <Child ref = {childRef} />
    <div/>
  )
}
export default Test
// 子组件
// forwardRef  和 useImperativeHandle 搭配使用 向父组件暴露对应实例方法
import { forwardRef, useImperativeHandle } from React
// 用forwardRef包一层
const Child = forwardRef((props, ref)=>{
// 对外暴露
  useImperativeHandle(ref, ()=>{
    getList
  })
  function getList(){
    
  }
})
export default Child

26.useRef【2022.05.31】

const refContainer = useRef(initialValue);

返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

如果将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象

当 ref 对象内容发生变化时,useRef 并不会通知我们。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

25.useMemo【2022.05.30】

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值

24.useCallback【2022.05.27】

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本, 该回调函数仅在某个依赖项改变时才会更新。避免非必要渲染的子组件时,它将非常有用。依赖项数组不会作为参数传给回调函数。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

还可以在react中使用防抖的时候可以包一层它就可以直接使用:

image.png

23.useReducer【2022.05.26】

const [state, dispatch] = useReducer(reducer, initialArg, init);

[useState]的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。

使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为可以向子组件传递 dispatch 而不是回调函数

reducer 写一个计数器:

// 初始化一个state
const initialState = {count: 0};

// 写一个reducer函数 
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

// 计数器 
function Counter() {
// 使用useReducer初始化state 且 传入reducer函数以及初始值  -- 深层监听更新
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 dispatch

有两种不同初始化 useReducer state 的方式,根据使用场景选择。将初始 state 作为第二个参数传入 useReducer 是最简单的方法:

  const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}  );

也可以选择惰性地创建初始 state,可以将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
// 给useReducer传入reducer执行函数,initialCount,初始值设置为init
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行

如果在渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化。

22.useContext【2022.05.25】

Context 设计目的是为了共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或首选语言。

const value = useContext(MyContext);

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。useContext 的参数必须是 context 对象本身调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,可以 通过使用 memoization 来优化

useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>

它只是让我们能够读取 context 的值以及订阅 context 的变化。我们仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。

// 初始一个想传递给子组件的对象
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};
// 将要传递的数据转换成context
const ThemeContext = React.createContext(themes.light);

// 在父级组件中使用Provider将数据向下传递
function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 在子组件中继续使用组件
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

// 在使用的时候  直接useContext取到一个context值,可以直接使用~
function ThemedButton() {
  const theme = useContext(ThemeContext);  
  return (    
  <button style={{ background: theme.background, color: theme.foreground }}> 
      I am styled by theme context!   
  </button>  );
}

21.addEventListener函数的第三个参数【2022.05.24】

第三个参数涉及到冒泡和捕获,是`true`时为捕获,是`false`则为冒泡。

或者是一个对象`{passive: true}`,针对的是`Safari`浏览器,禁止/开启使用滚动的时候要用到。

20.数组去重【2022.05.23】

  1. Array.from(new Set(arr))
  2. [...new Set(arr)]
  3. for循环嵌套,利用splice去重
  4. 新建数组,利用indexOf或者includes去重
  5. 先用sort排序,然后用一个指针从第0位开始,配合while循环去重

当然还有很多,例如用filter、reduce、Map、Object等,具体可以看: Array.from(new Set(arr))[...new Set(arr)]

var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(Array.from(new Set(arr)))
// console.log([...new Set(arr)])

for循环嵌套,利用splice去重

function unique (origin) {
  let arr = [].concat(origin);
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] == arr[j]) {
        arr.splice(j, 1);
        j--;
      }
    }
  }
  return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))


新建数组,利用includes去重:

function unique (arr) {
  let res = []
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) {
      res.push(arr[i])
    }
  }
  return res;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))

先用sort排序,然后用一个指针从第0位开始,配合while循环去重

function unique (arr) {
  arr = arr.sort(); // 排序之后的数组
  let pointer = 0;
  while (arr[pointer]) {
    if (arr[pointer] != arr[pointer + 1]) { // 若这一项和下一项不相等则指针往下移
      pointer++;
    } else { // 否则删除下一项
      arr.splice(pointer + 1, 1);
    }
  }
  return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))

19.Git Flow规范【2022.05.20】

在 Git Flow 中,有两个长期存在且不会被删除的分支:master 和 develop

  1. master 主要用于对外发布稳定的新版本,该分支时常保持着软件可以正常运行的状态,通常工作进展到发版的时候才会与master进行合并,发布的时候再打个tag标签即可
  2. develop 用来存放我们最新开发的代码,通常这个分支不允许开发者直接进行修改和提交,开发者要以 develop 分支为起点新建 feature 分支,在 feature 分支中进行新功能的开发或者代码的修正,也就是说 develop 分支维系着开发过程中的最新代码,以便程序员创建 feature 分支进行自己的工作。

除了这两个永久分支,还有三个临时分支:feature branches、hotfixes 以及 release branches

  1. feature功能分支,当你需要开发一个新的功能的时候,可以新建一个 feature-xxx 的分支,在里边开发新功能,开发完成后,将之并入 develop 分支中
  2. hotfixes修复 BUG 的,项目上线后,发现有 BUG 需要修复,那么就从 Master 上拉一个名为 fixbug-xxx 的分支,然后进行 BUG 修复,修复完成后,再将代码合并到 Master 和 Develop 两个分支中,然后删除 hotfix 分支
  3. release发版的时候拉的分支,当我们所有的功能做完之后,准备要将代码合并到 master 的时候,从 develop 上拉一个 release-xxx 分支出来,这个分支一般处理发版前的一些提交以及客户体验之后小 BUG 的修复(BUG 修复后也可以将之合并进 develop),不要在这个里边去开发功能,在预发布结束后,将该分支合并进 develop 以及 master,然后删除 release

其实我们自己开发不必这么死板,结合自己的项目来就行了,master、develop 以及 release 三个分支是固定的,这三个分支的作用跟前面介绍的 Git Flow 也是一致的,在此基础之上,其他的基本上没有太多限制,比较自由。

18.关于发布打tag【2022.05.19】

在发布项目的时候,通常是要确定发布的分支以及打个tag,打tag可以通过jenkins自动打也可以gitlab配置自动打,当然也可以手动git打tag,鱼姐在这里提供一个手动tag的方式:

// 创建标签 -a表示的是annotated的缩写 指定标签类型 -m是标签说明
git tag -a v1.0.0 -m "release v1.0.0"
// 将标签推到远程 -- 提交到服务器 -- tag名要和之前提交的一致
git push origin v1.0.0
// 查看所有标签
git tag
// 查看标签版本信息
git show v1.0.0
//切换标签
git checkout 标签名
// 删除标签--修改标签的时候要先删除标签再打新标签
git tag -d 1.0.0
// 删除线上版本标签
git push origin :refs/tags/v1.0.0
// 补打标签 -- 可以通过git log 获取
git tag -a 1.0.0 12sdsdhsaddsdada
// 还原到打标签的版本
git reset --hard[commit id]
// 查看所有tag 
git ls-remote

17.react-codemirror【2022.05.18】

最近在做代码编辑器,用的这个插件,不会很重,然后今天遇到个问题,发现有的回显数据不展示,第一次打开要点一下编辑框才回显,之后的编辑器就不需要再点击就能正常展示数据了

首猜就是没清除其他编辑器的问题导致,试图通过重新渲染codemirror来解决这个问题,然并卵....

接着百度出了一些解决方法

image.png

最后尝试了手动刷新和自动刷新均无用。。。

最后我选择开个定时器再赋值~ 终于解决了这个问题

16.react踩坑记【2022.05.17】

今天踩了个坑,在项目里引进lodash使用防抖的时候,发现报错,报错信息说找不到lodash,其实之前我引入组件库中的某组件的时候也提示找不到对应的style,然后我删了安装包又重新安装依赖项,还是没用。

找报错原因最后找到因为开启了预编译(mfsu)和webpack5。更底层原因后续我翻翻...

关掉后,不报错了,但是使用lodash的debounce没报错也没起作用,然后自己手写了一个防抖也一样的结果,最后找到原因:react不能像vue那样直接使用,react会重复执行这个debounce,存在闭包问题。得找找hook适用的防抖方式,最后找到两种适用的

//
// util.ts
export const useDebounce(msg) {
    const [postMsg, setPostMsg] = useStat(msg)
    useEffect(()=>setPostMsg(msg), [msg])
    return postMsg
}

// 组件.tsx
import {useDebounce} from '../util'
...
const [msg, setMsg] = useState('')
const postMsg = useDebounce(msg)
useEffect(()=>{
    执行postMsg网络请求
}, [postMsg])
...
return <div>
    <input type="text" onInput={(e)=>setMsg(e.target.value)} />
    <p>你输入了{msg}</p >
</div>

// 这个版本比较简单,,,就是套了一层useCallback,,button调接口版
// 要注意的是,hook中useXXX都得写在函数内调用,不能在函数内的函数内调用 这样会报错的!
// 比如下面的debounceF得const debounceF = useXXX,不能写成function debounceF (){....}
const fn = () => {
    console.log("执行了");
  };

  function debounce(callback, delay) {
    let timer = null;
    return function (...args) {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout((...args) => {
        callback.apply(this, args, delay);
      }, delay);
    };
  }

  const debounceF = useCallback(debounce(fn, 1000), []);

  return (
    <div className="App">
      <button onClick={() => debounceF() }>点我触发防抖</button>
    </div>
  );

15.关于hook的api 你知道哪些?【2022.05.16】

  • [基础 Hook]

    • [useState]
    • [useEffect]
    • [useContext]
  • [额外的 Hook]

    • [useReducer]
    • [useCallback]
    • [useMemo]
    • [useRef]
    • [useImperativeHandle]
    • [useLayoutEffect]
    • [useDebugValue]

接下来一个api 一个api学习吧。。。。。

14.自定义 Hook 必须以 “use” 开头吗?【2022.05.13】

必须如此。这个约定非常重要。不遵循的话,无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则

在两个组件中使用相同的 Hook 不会共享 state ,每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。

每次调用 Hook,它都会获取独立的 state。由于我们直接调用了 useFriendStatus,从 React 的角度来看,我们的组件只是调用了 useState 和 useEffect。我们可以在一个组件中多次调用 useState 和 useEffect,它们是完全独立的。

Hook 本身就是函数,可以用用参数的方式去进行传值,也可以使用reducer 的方式来管理组件的内部 state ~

// useReducer简易版
function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}

// 使用:
function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, []);

  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...
}

13.Effect Hook【2022.05.12】

Effect Hook 可以让我们在函数组件中执行副作用操作

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  // 和 componentDidMount and componentDidUpdate 的作用相似
  useEffect(() => {     
    document.title = `You clicked ${count} times`;  
  });
  // 在 effect 中获取到最新的 `count` 值,因为他在函数的作用域内。
  // 当 React 渲染组件时,会保存已使用的 effect,并在更新完 DOM 后执行它。
  // 这个过程在每次渲染时都会发生,包括首次渲染。
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

1.什么是副作用?

数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用;而useEffect Hook 可以看成是componentDidMountcomponentDidUpdate 和 componentWillUnmount 这三个函数的组合;

React 组件中有两种常见副作用操作:需要清除的和不需要清除的

2.不需要清除的effect

只想在 React 更新 DOM 之后运行一些额外的代码。 比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。

3.useEffect做了什么?

可以告诉 React 组件需要在渲染后执行某些操作,并且在执行 DOM 更新之后调用我们传入的函数

4.为什么在组件内部调用 useEffect

将 useEffect 放在组件内部让我们可以在 我们传入的函数 中直接访问 count state 变量(或其他 props),让它们已经保存在函数作用域中,这样我们就不用写this.xxx去访问了,Hook 使用了 JavaScript 的闭包机制引入了React API。

5.useEffect 会在每次渲染后都执行吗?

默认情况下,它在第一次渲染之后 和 每次更新之后都会执行,这个可以控制;

我们可以关注effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

每次重新渲染,都会生成新的effect(代指我们传入的函数),替换掉之前的effect 更像是渲染结果的一部分

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。

每次渲染更新都会effect(代指我们传入的函数),这个其实可以帮助我们减少bug

举个例子~:

// 1. 在组件挂载后订阅好友的状态,并在卸载组件的时候取消订阅:
// 2. 这是一个 bug。而且我们还会因为取消订阅时使用错误的好友 ID 导致内存泄露或崩溃的问题。
  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  
  // 3. 在 class 组件中,我们需要添加 `componentDidUpdate` 来解决这个问题
    componentDidUpdate(prevProps) {
    // 取消订阅之前的 friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // 订阅新的 friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

而在hook中几行搞定!useEffect 默认就会处理。它会在调用一个新的 effect 之前对前一个 effect 进行清理。

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

6.需要清除的effect-- return

需要清除的副作用:例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露! 在hook中, useEffect 的设计是在同一个地方执行,因为添加和删除订阅的代码的紧密性。如果我们写的 effect 返回一个函数,React 将会在执行清除操作时调用它~

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {    
     function handleStatusChange(status) {     
          setIsOnline(status.isOnline);    
     }    
     return function cleanup() { 
         ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); 
     };  
 });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

effect 可选的清除机制就是在每个 effect 中都可以返回一个清除函数,所以添加和移除订阅的逻辑可以放在一起。它们都属于 effect 的一部分。 React 会在组件卸载的时候执行清除操作,React 会在执行当前 effect 之前对上一个 effect 进行清除。并不是必须为 effect 中返回的函数命名,也可以返回一个箭头函数或者给起一个别的名字。

Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。

function FriendStatusWithCounter(props) {
// 多次调用useEffect 会按顺序执行
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

Hook 允许我们按照代码的用途分离他们,  而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。

7.useEffect 监听特定值进行性能优化

useEffect的第二个参数是可选参数,可以放入我们想要监听的特定值!如果某些特定值在两次重渲染之间没有发生变化,则React跳过对 effect 的调用,如果有变化则调用effect!对于有清除操作的 effect 同样适用

注意:此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。

如果只想运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

我们有更好的方式(FAQ)来避免过于频繁的重复调用 effect。除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便。

12.State hook【2022.05.11】

import React, { useState } from 'react';

function Example() {
  // 声明了一个叫 `count` 的 state 变量,然后把它设为 `0`,且可以通过setCount来更改这个变量值
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      // 点击按钮后,React 会重新渲染 `Example` 组件,并把最新的 `count` 传给它。
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Hook 在 class 内部是起作用的。但我们可以使用它们来取代 class 。

Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook

1. 调用 useState 方法的时候做了什么?

它定义一个 “state 变量”。我们的变量叫 count, 我们可以叫他任何名字。这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同,让我们在函数组件中存储内部 state。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。

2. useState 参数是什么?返回了什么?

里面唯一的参数就是初始 state;返回值为:当前 state 以及更新 state 的函数。这就是我们写 const [count, setCount] = useState() 的原因。

3. 为什么叫 useState 而不叫 createState?

Create可能不是很准确,因为 state 只在组件首次渲染的时候被创建。在下一次重新渲染时,useState 返回给我们当前的 state。这也是 Hook 的名字总是以 use 开头的一个原因,它不仅仅具有初始化功能还具有更新功能

4.读取State?

读取State:可以直接用 count,调用方法不用写this

  <p onClick={() => setCount(count + 1)}>You clicked {count} times</p>

5.方括号有什么用?


  const [fruit, setFruit] = useState('banana');

他其实是一个数组解构,等价于下面的形式

  var fruitStateVariable = useState('banana'); // 返回一个有两个元素的数组
  var fruit = fruitStateVariable[0]; // 数组里的第一个值
  var setFruit = fruitStateVariable[1]; // 数组里的第二个值

当我们使用 useState 定义 state 变量时候,它返回一个有两个值的数组。使用 [0] 和 [1] 来访问有点令人困惑,因为它们有特定的含义。这就是我们使用数组解构的原因。

思考:React 怎么知道 useState 对应的是哪个组件,我们并没有传递 this 给 React?

11.关于自定义hook【2022.05.10】

假如我们想要在多个组件中重复使用一些状态或者逻辑,以class模式的话通常有两个方案,高阶函数或者render props,而使用自定义hook的话可以让我们在不增加组件的情况下达到同样目的!

假如此刻我们想在某个组件内重复使用一个订阅逻辑

我们可以先把这部分订阅逻辑抽取到useFriendStatus这个方法里

import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {  
// 初始化
const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
// 在hook中订阅 解绑事件
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });
// 返回 isOnline 值
  return isOnline;
}

然后我们在另外两个组件中使用这个组件

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

是不是很简单!这两个组件的 state 是完全独立的。Hook 是一种复用状态逻辑的方式,它不复用 state 本身。事实上 , Hook 的每次调用都有一个完全独立的 state , 因此你可以在单个组件中多次调用同一个自定义 Hook。

通常函数的名字以 “use” 开头并调用其他 Hook,我们就说这是一个自定义 Hook。 useSomething 的命名约定可以让我们的 linter 插件在使用 Hook 的代码中找到 bug。

useContext 可以让我们不使用组件嵌套就可以订阅 React 的 Context。

useReducer 可以让我们通过 reducer 来管理组件本地的复杂 state。

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...
 }

等等,还有很多hook~

10.讲讲网络OSI七层模型,TCP/IP和HTTP分别位于哪一层【2022.05.09】

image.png

9.关于react hooks的简单理解【2022.05.07】

hooks是react18.6加的新特性,可以让我们不写class的情况下使用state以及react其他特性,

import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量 ,用setCount改变该值,初始值为0
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

那么为什么会有hooks呢?

1 class写法中组件之间的复用状态逻辑复杂,由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”,使用hooks的话可以不修改组件结构就复用状态逻辑

2 class写法中,有的情况复杂组件是不能拆分为更小的粒度,而hooks能把组件中相互关联的部分拆分成更小的函数,比如设置订阅或者请求数据

3 hooks采用渐进策略,还是可以使用class组件的,理解起来更容易

  • useState相当于class组件的this.setState,useState它会返回一对值,状态名以及改变该状态的函数,而useState中唯一的参数为初始值,且可以多次使用useState

  • 而hook可以让我们在函数组件中勾入react state 以及 生命周期等特性的函数。

  • 其中useEffect是一个Effect Hook,这个方法是在对dom完成更改后运行,可多次使用,可用于订阅状态和取消订阅

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {

  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {   
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    
  return () => {      
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    
  };  });
  
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

最后总结一下,其实hook就是一个js函数,只能在函数最外层调用hook,不要在循环,条件判断或者子函数中调用!只能在react函数组件中使用

8.react为什么使用jsx?【2022.05.06】

以下就是一个简单的jsx变量声明

const element = <h1>Hello, world!</h1>;
  1. jsx是一个js的语法扩展,通常我们在使用react库的时候会搭配使用jsx,因为jsx可以很好的描述ui呈现,jsx还会使人联想到模板语言,它具备js的全部功能,jsx还能生成react的一部分

    其实我们在开发react的过程中会发现,react的渲染逻辑是比较耦合的,会在ui中绑定处理事件,在某些状态发生改变的时候要通知到ui,要在ui展示的时候准备好需要的数据,jsx和ui放一起会有视觉上的辅助作用,不过react中不强制要求jsx~

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

  1. 其实jsx在编译后会被转为普通的js函数调用,对它取值的时候会得到一个js对象,也就是说我们可以在if或者for循环代码块中使用jsx,将jsx赋值给变量,把jsx变量当做参数传入,然后在编译的时候变量会被编译成js对象,所以也可以把jsx理解为是一个表达式
function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;  }
  return <h1>Hello, Stranger.</h1>;}
  1. jsx可以防止注入攻击,因为react Dom在渲染的时候会默认进行转义,它可以确保在我们的应用中不会注入那些不是我们编写的内容,所有的内容在渲染之前都会被转换成字符串,转义就可以防止xss攻击啦!

  2. jsx也表示一个对象,因为在转义的过程中babel会把jsx转义给React.createElement()调用,以下两种写法等效:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement()会预先执行一些检查帮助我们编写无错的代码,实际他创建了一个这样的对象:

// 注意:这是简化过的结构,这些对象也被称为Reach元素~
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

8.初识react【2022.05.05】

因为鱼姐最近从vue转react了,最近每天会抽点时间来看一下关于react的知识。

vue是一个框架,而react是一个声明式的js库,react中有很多不同类型的组件,来个简单的看看


class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// 用法: <ShoppingList name="Mark" />

其中ShoppingList是一个react组件类,通过render方法返回要展示在屏幕上的层次结构,render内可以通过props拿到传入的属性,render返回了一个Reach元素;

react中使用的是jsx语法,该语法会把

编译成React.createElement('div');ShoppingLis组件只会渲染内置的DOM组件,比如
  • 等等,我们也可以通过多个这样的组件组合渲染成一个复杂页面。在学习react过程中,可以关注一下组件之间的传值,交互等等等

    7. V8引擎如何实现一段js代码【2022.04.29】

    1. 预解析:检查我们写入的代码是否有语法错误
    2. 生成AST:经过语法分析,生成抽象语法树
    3. 生成字节码:编译器把ast转成字节码
    4. 生成机器码:把字节码转成机器码,这个过程,如果一段代码经常被执行那就会被缓存起来下次执行就直接取缓存,优化了执行速度

    6. react的生命周期【2022.04.28】

    新版react:

    image.png

    1. 创建时

    (1)constructor: 初始化属性和状态

    (2)getDerivedStateFromProps:根据属性对象派生状态对象

    • 静态方法
    • 参数:新的属性对象,旧的状态对象
    • 用途:在没有这个生命周期函数之前,我们使用的数据来源可能是属性对象,也可能是状态对象;通过这个生命周期函数将属性对象派生到状态对象上,使我们在代码中只通过this.state.XXX来绑定我们的数据。示例如下
      constructor(props) {
        super(props);
        this.state = {
          number:0
        }
      }
      
      static getDerivedStateFromProps(nextProps, prevState) {
        if (nextProps.number % 2 === 0) {
          return {number:nextProps.number*2}
        } else {
          return {number:nextProps.number*3}
        }
      }
      
      render() {
        console.log(this.state);
        return (
          <div>
            <p>{this.state.number}</p>
          </div>
        );
      }
    

    (3)render: 挂载(渲染)组件

    (4)componentDidMount:组件挂载(渲染)完成

    2. 更新时

    (1)getDerivedStateFromProps:根据属性对象派生状态对象

    • 静态方法
    • 参数:新的属性对象,旧的状态对象

    (2)shouldComponentUpdate:询问组件是否可以更新

    • 参数:新的状态对象

    • 返回:boolean

      • true允许更新继续向下执行
      • false不允许更新,停止执行,不会调用之后的生命周期函数

    (3)render:根据新的状态对象重新挂载(渲染)组件

    (4)getSnapshotbeforeUpdate:获取更新前的快照

    • 举例:在开发中可能会遇到一个问题,异步加载资源,当浏览器处于非第一屏的时候并且所在屏之前的模块并没有加载出来,你肯定不希望在所在屏之前的模块加载出来之后浏览器显示窗口仍然处于当前的高度,而是希望仍然处于我们所浏览屏的高度,在没有这个生命周期之前react处理这个问题是很棘手的。

    使用getSnapshotbeforeUpdate代码:

    import React, { Component } from 'react';
    class GetSnapshotBeforeUpdate extends Component {
      ...
      getSnapshotBeforeUpdate() {
        // 返回更新内容的高度
        return this.wrapper.current.scrollHeight;
      }
      // 组件更新完毕
      componentDidUpdate(prevProps,prevState,prevScrollHeight) {
        console.log(prevProps,prevState,prevScrollHeight);
        this.wrapper.current.scrollTop = this.wrapper.current.scrollTop +
        (this.wrapper.current.scrollHeight - prevScrollHeight);
      }
      ...
    export default GetSnapshotBeforeUpdate;
    
    

    (5)componentDidUpdate:组件更新完成

    3.卸载时

    (1)componentWillUnmount: 组件卸载之前

    5. 同一个页面内监听storage事件会怎么样【2022.04.27】

    会不起作用!有的时候,某些情况下,会想到在同一个页面中往localstorage里存值,然后想监听到localstorage中的某个值发生改变后再触发某些事件,这个时候你就会发现监听事件是在创建了这个Item之后才会触发, 测试发现,storage事件是无法在Chrome同一个页面触发的,只能是其他tap页面中的才能。 Chrome 下必须由其他页面触发,测试了FireFox发现也不行。

    后来查资源发现触发storage事件的条件:

    • 同一浏览器打开了两个同源页面
    • 其中一个页面修改了localStorage
    • 另一个网页注册了这个事件

    然后我找到一个解决方案,其实就是重写这个方法 很容易犯的错误是,在同一个网页修改本地存储,又在同一个网页监听,这样是没有效果的。

    1. 在同源的两个页面中,可以监听 storage 事件
    window.addEventListener("storage", function (e) {
            alert(e.newValue);
    });
    
    1. 在同一个页面中,对 localStoragesetItem 方法进行重写
    var orignalSetItem = localStorage.setItem;
    localStorage.setItem = function(key,newValue){
          var setItemEvent = new Event("setItemEvent");
          setItemEvent.newValue = newValue;
          window.dispatchEvent(setItemEvent);
          orignalSetItem.apply(this,arguments);
    }
    window.addEventListener("setItemEvent", function (e) {
    // 监听重写后的这个方法~
        alert(e.newValue);
    });
    localStorage.setItem("name","wang");
    

    2022.04.26

    4. nodejs开发命令行是用哪个库?

    • commander 自定义指令
    • inquirer 命令交互
    • figlet logo打印
    • chalk log输出字体样式

    2022.04.25

    3.了解过npm、npx、nvm、cnpm、yarn吗?说说有什么区别

    npm、cnpm、pnpm、yarn都是包管理工具,支持根据package.json安装相应依赖,以npm为标准:

    • cnpm是换了源,
    • pnpm利用硬链接和符号链接避免复制本地缓存源文件,具备清晰的pnpm.local文件,解决了yarn的性能弱点,
    • yarn最早是为了解决npm的依赖树问题,(现在的npm版本也解决了),yarn有一个yarn.lock文件
    • npx其实就是运行node_modules里的包 比如你install lerna但是你不想全局,但是你需要lerna的命令那你可以 npx lerna xxx
    • nvm是node版本管理工具

    2.按需加载实现原理?

    按需加载说的是webpack按需加载还是react按需加载(懒加载),还是页面按需加载(图片)?emmmm...

    按需加载原理:不要一次加载完所有文件,只加载当前页面需要的文件(更准确来说应该是此时需要的文件),需要更多文件的时候再去做相应的加载

    webpack按需加载需要对配置文件做改动指定对应的chunkFilename

    然后在页面路由利用require.ensure配置修改使这个路由页面的加载变成一个异步的操作

    其他的实现诸如:

    1. 使用() => import();import 是ensure的语法糖,核心到webpack对chunk的promise,import(‘xxxx/xxx.vue’)
    2. 使用resolve => require([’./_account’], resolve);
    3. 动态import: import(*);
    4. require.ensure:require.ensure([’./tab0.vue’], () => { resolve(require(’./tab0.vue’)) }, ‘tab0’)

    1.nginx location 规则

    • =是精确匹配,
    • ^~是最佳匹配,匹配到了不再往后查找.
    • ~正则匹配,区分大小写
    • ~*正则匹配,不区分大小写

    image.png

    • root就是以配置的地址作为根;
    • alias有别名的意思,别名就是把location配置的地址直接重命名; localtion ^~/api/ { root /anotherApi/xxxxx; // 最后就是 /anotherApi/xxxxx/实际请求地址/api/xxxx alias /aliasApi/xxxx; // 最后就是 /aliasApi/xxxx/原来api后缀的地址 }