likes
comments
collection
share

React实战精讲(React_TS/API)

作者站长头像
站长
· 阅读数 22

英国诗人 萨松在诗歌 《与我,过去、现在以及未来》中写道:"In me the tiger sniffs the rose" 诗人余光中将其翻译为:"心有猛虎,细嗅蔷薇"

大家好,我是柒八九

今天,我们继续前端面试的知识点。我们来谈谈关于React实战的相关知识点和具体的算法。

该系列的文章,大部分都是前面文章的知识点汇总,如果想具体了解相关内容,请移步相关系列,进行探讨。

如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。

文章list

  1. CSS重点概念精讲
  2. JS_基础知识点精讲
  3. 网络通信_知识点精讲
  4. JS_手写实现
  5. 前端工程化_知识点精讲
  6. 前端框架_React知识点精讲

好了,天不早了,干点正事哇。 React实战精讲(React_TS/API)

你能所学到的知识点

  1. TS_React:使用泛型来改善类型
  2. TS_React:Hook类型化
  3. TS_React:类型化事件回调
  4. React API

TS_React:使用泛型来改善类型

TypeScript 是什么

TypeScript 是⼀种由微软开源的编程语⾔。它是 JavaScript 的⼀个超集。 本质上向JS添加了可选的

  1. 静态类型
  2. 基于类的⾯向对象编程

TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来⾃ 2015 年的 ECMAScript 和未来的提案中的特性,⽐如异步功能和 Decorators,以帮助建⽴健壮的组件。

React实战精讲(React_TS/API)


TypeScriptJavaScript 的区别

TypeScriptJavaScript
JavaScript 的超集⽤于解决⼤型项⽬的代码复杂性⼀种脚本语⾔⽤于创建动态⽹⻚
可以在编译期间发现并纠正错误作为⼀种解释型语⾔只能在运⾏时发现错误
强类型,⽀持静态和动态类型弱类型,没有静态类型选项
最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使⽤
⽀持模块、泛型和接⼝不⽀持泛型或接⼝

典型 TypeScript ⼯作流程

React实战精讲(React_TS/API)

在上图中包含 3 个 ts ⽂件:a.tsb.tsc.ts。这些⽂件将被 TypeScript 编译器,根据配置的编译选项编译成 3 个 js ⽂件,即 a.jsb.jsc.js。对于⼤多数使⽤ TypeScript 开发的 Web 项⽬,我们还会对编译⽣成的 js ⽂件进⾏打包处理,然后在进⾏部署。

TypeScript的特点

TypeScript 主要有 3 大特点:

  1. 始于JavaScript,归于JavaScript TypeScript 可以编译出纯净、 简洁的 JavaScript 代码,并且可以运行在任何浏览器上、Node.js 环境中和任何支持 ECMAScript 3(或更高版本)的JavaScript 引擎中。
  2. 强大的类型系统 类型系统允许 JavaScript 开发者在开发 JavaScript 应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构。
  3. 先进的 JavaScript TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。

{泛型| Generics} 是个啥?

泛型指的是类型参数化:即将原来某种具体的类型进⾏参数化

在像 C++/Java/Rust 这样的 OOP 语⾔中,可以使⽤泛型来创建可重⽤的组件,⼀个组件可以⽀持多种类型的数据。 这样⽤户就可以以⾃⼰的数据类型来使⽤组件

设计泛型的关键⽬的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的⽅法、函数参数和函数返回值。

TypeScript类型JavaScript对象进行比较。

主要的区别

  • JavaScript 中,关心的是变量的
  • TypeScript 中,关心的是变量的类型

但对于我们的User例子来说,使用一个泛型看起来是这样的。

// `User` 现在是泛型类型
const user: User<'在线' | '离线'>;

// 我们可以手动新增一个新的类型 (空闲)
const user: User<'在线' | '离线' | '空闲'>;

上面说的是 user变量是类型为User的对象

我们继续来实现这个类型

// 定义一个泛型类型
type User<StatusOptions> = {
  name: string;
  status: StatusOptions;
};

StatusOptions 被称为 {类型变量| type variable},而 User 被说成是 {泛型类型|generic type }


泛型有啥用?

通常的情况是,当你想让一个类型在多个实例中共享,而每个实例都有一些不同:即这个类型是动态的。

⾸先我们来定义⼀个通⽤的 identity 函数,函数的返回值的类型与它的参数相同

我们的⽬标是让 identity 函数可以适⽤于任何特定的类型,为了实现这个⽬标,我们可以使⽤泛型来解决这个问题,具体实现⽅式如下:

function identity <T>(value: T) : T {
 return value;
}
console.log(identity<Number>(1)) // 1

看到 <T> 语法,就像传递参数⼀样,上面代码传递了我们想要⽤于特定函数调⽤的类型。

React实战精讲(React_TS/API)

也可以引⼊希望定义的任何数量的类型变量。⽐如我们引⼊⼀个新的类型变量 U ,⽤于扩展我们定义的 identity 函数:

function identity <T, U>(value: T, message: U) : T {
 console.log(message);
 return value;
}
console.log(identity<Number, string>(68, "TS真的香喷喷"));

React实战精讲(React_TS/API)


泛型约束

有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作⽤。下⾯我们来举⼏个例⼦,介绍⼀下如何使⽤泛型约束。

确保属性存在

有时候,我们希望类型变量对应的类型上存在某些属性。这时,除⾮我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。

例如在处理字符串或数组时,我们会假设 length 属性是可⽤的。让我们再次使⽤ identity 函数并尝试输出参数的⻓度:

function identity<T>(arg: T): T {
 console.log(arg.length); // Error
 return arg;
}

在这种情况下,编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends ⼀个含有我们所需属性的接⼝,⽐如这样:

interface Length {
 length: number;
}
function identity<T extends Length>(arg: T): T {
 console.log(arg.length); // 可以获取length属性
 return arg;
}

T extends Length ⽤于告诉编译器,我们⽀持已经实现 Length 接⼝的任何类型。


箭头函数在jsx中的泛型语法

在前面的例子中,我们只举例了如何用泛型定义常规的函数语法,而不是ES6中引入的箭头函数语法。

// ES6的箭头函数语法
const identity = (arg) => {
  return arg;
};

如果想要在处理箭头函数,需要使用下面的语法。

// 方式1
const identity = <ArgType,>(arg: ArgType): ArgType => {
  return arg;
};

// 方式2
const identity = <ArgType extends unknown>(arg: ArgType): ArgType => {
  return arg;
};

出现上述问题的根源在于:这是TSX(TypeScript+ JSX)的特定语法。在正常的 TypeScript 中,不需要使用这种变通方法。


TS_React:Hook类型化

类型推断

在绝大部分,TS都可以根据hook中的值来推断它们的类型:也就是我们常说的类型推断

何为类型推断,简单来说:类型推断就是基于赋值表达式推断类型的能⼒ts采用将类型标注声明放在变量之后(即类型后置)的方式来对变量的类型进行标注。而使⽤类型标注后置的好处就是编译器可以通过代码所在的上下⽂推导其对应的类型,⽆须再声明变量类型。

  • 具有初始化值的变量
  • 默认值的函数参数
  • 函数返回的类型

都可以根据上下⽂推断出来。

例如,下面的代码可以在ts环境中正常运行,且能够通过类型推断推导出name的类型为string类型。

const [name, setName] = useState('前端柒八九');

何时不能依赖类型推断

下面的两种情境下,类型推断有点力不从心

  • ts推断出的类型过于宽松
  • 类型推断错误

推断出的类型过于宽松

我们之前的例子--有一个字符串类型的name。但是我们假设这个name只能有两个预定的值中的一个。

在这种情况下,我们会希望name有一个非常具体的类型,例如这个类型。

type Name = '前端柒八九' | '前端工程师' ;

这种类型同时使用联合类型字面类型

在这种情况下,推断的类型过于宽松(是string,而不是我们想要的2个字符串的特定子集),这种情况下就必须自己指定类型。

const [name, setName] = useState<Name>('前端柒八九');

类型推断错误

有时,推断的类型是错误的(或者限制性太强不是你想要的类型)。这种情况经常发生在ReactuseState 默认值中。比方说,name 的初始值是null

const [name, setName] = useState(null);

在这种情况下,TypeScript 会推断出namenull类型的(这意味着它总是null)。这显然是错误的:我们以后会想把 name 设置成一个字符串。

此时你必须告诉 TypeScript,它可以是别的类型。

const [name, setName] = useState<string | null>(null);

通过这样处理后,TypeScript 会正确理解name可以是null也可以是string

这里要提到的一件事是,当类型推断不起作用时,应该依靠泛型参数而不是类型断言

  • const [name, setName] = useState<Name>('前端柒八九'); 推荐使用
  • const [name, setName] = useState('前端柒八九' as Name); 不推荐使用

类型化 useState

在前面,我们已经通过类型推断讲过了,如何处理useState的各种情况。这里就不在赘述了。

const [name, setName] = useState<string | null>(null);

类型化 useReducer

useReducer 的类型比 useState 要复杂一些。其实useState就是useReducer简化版

针对useReducer有两样东西要类型化处理stateaction

这里有一个useReducer的简单例子。针对input做简单的数据收集处理。

import { useReducer } from 'react';

const initialValue = {
  username: '',
  email: '',
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'username':
      return { ...state, username: action.payload };
    case 'email':
      return { ...state, email: action.payload };
    case 'reset':
      return initialValue;
    default:
      throw new Error(`未定义的action: ${action.type}`);
  }
};

const Form = () => {
  const [state, dispatch] = useReducer(reducer, initialValue);
  return (
    <div>
      //....
    </div>
  );
};

export default Form;

类型化 reducer 的state

我们有两个选择来类型化reducer-state

  • 使用初始值(如果有的话)和 typeof 操作符
  • 使用类型别名

使用typeof 操作符

const initialValue = {
  username: '',
  email: '',
};

+ const reducer = (state: typeof initialValue, action) => {
    ///....
};

使用类型别名

+type State = {
+  username: string;
+  email: string;
+};

+ const reducer = (state: State, action) => {
    // ....
};

类型化 reducer 的action

reducer-action的类型比reducer-state要难一点,因为它的结构会根据具体的action而改变

例如,对于 username-action,我们可能期望有以下类型。

type UsernameAction = {
  type: 'username';
  payload: string;
};

但对于 reset-action,我们不需要payload字段。

type ResetAction = {
  type: 'reset';
};

我们可以借助联合类型区别对待不同的action

const initialValue = {
  username: "",
  email: ""
};

+type Action =
+  | { type: "username"; payload: string }
+  | { type: "email"; payload: string }
+  | { type: "reset" };

+ const reducer = (state: typeof initialValue, action: Action) => {
    //....
};

Action类型表示的是,它可以接受联合类型中包含的三种类型中的任何一种。因此,如果 TypeScript 看到 action.typeusername,它就会自动知道它应该是第一种情况,并且payload应该是一个string

通过对state/action类型化后,useReducer能够从reducer函数的type中推断出它需要的一切。


类型化 useRef

useRef 有两个主要用途

  • 保存一个自定义的可变值(它的值变更不会触发更新)。
  • 保持对一个DOM对象的引用

类型化可变值

它基本上与 useState 相同。想让useRef保存一个自定义的值,你需要告诉它这个类型。

function Timer() {
+  const intervalRef = useRef<number | undefined>();

  useEffect(() => {
    const id = setInterval(() => {
      // ...
    });
    intervalRef.current = id;
    return () => {
      clearInterval(intervalRef.current);
    };
  });

  // ...
}

类型化 DOM 节点

在DOM节点上使用useRef的一个经典用例是处理input元素的focus

mport { useRef, useEffect } from 'react';

const AutoFocusInput = () => {
+  const inputRef = useRef(null);

  useEffect(() => {
+    inputRef.current.focus();
  }, []);

+  return <input ref={inputRef} type="text" value="前端柒八九" />;
};

export default AutoFocusInput;

TypeScript内置的DOM元素类型。这些类型的结构总是相同的:

如果name是你正在使用的HTML标签的名称,相应的类型将是HTML${Name}Element。 这里有几个特例

  • <a>标签的类型为HTMLAnchorElement
  • <h1>标签的类型为HTMLHeadingElement

对于<input>,该类型的名称将是HTMLInputElement

mport { useRef, useEffect } from 'react';

const AutoFocusInput = () => {
+  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
+    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} type="text" value="前端柒八九" />;
};

export default AutoFocusInput;

注意:在inputRef.current?.focus()上加了一个?。这是因为对于 TypeScriptinputRef.current可能是空的。在这种情况下,我们知道它不会是空的,因为它是在 useEffect 第一次运行之前由 React 填充的。


类型化 forwardRef

有时想把ref转发给子组件。要做到这一点,在 React 中我们必须用 forwardRef包装组件

import { ChangeEvent } from 'react';

type Props = {
  value: string,
  handleChange: (event: ChangeEvent<HTMLInputElement>) => void,
};

const TextInput = ({ value, handleChange }: Props) => {
  return <input type="text" value={value} onChange={handleChange} />;
};

例如,存在一个组件TextInput而我们想在父组件的调用处,通过ref来控制子组件input

此时,就需要用forwardRef来处理。

import { forwardRef, ChangeEvent } from 'react';

type Props = {
  value: string;
  handleChange: (event: ChangeEvent<HTMLInputElement>) => void;
};

+const TextInput = forwardRef<HTMLInputElement, Props>(
+  ({ value, handleChange }, ref) => {
    return (
+      <input ref={ref} type="text" value={value} onChange={handleChange} />
    );
  }
);

此语法只需要向 forwardRef 提供它应该期待的HTMLElement(在这种情况下是HTMLInputElement)。

有一点,需要指出:组件参数refprops的顺序与泛型的<HTMLInputElement, Props>不一样。


类型化 useEffect 和 useLayoutEffect

你不必给他们任何类型

唯一需要注意的是隐式返回useEffect里面的回调应该是什么都不返回,或者是一个会清理任何副作用的Destructor函数(析构函数,这个词借用了C++中类的说法)


类型化 useMemo 和 useCallback

你不必给他们任何类型


类型化 useContext

context提供类型是非常容易的。首先,为context创建一个类型,然后把它作为一个泛型提供给createContext函数。

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

+type User = {
+  name: string;
+  email: string;
+  freeTrial: boolean;
+};

+type AuthValue = {
+  user: User | null;
+  signOut: () => void;
+};

+const AuthContext = createContext<AuthValue | undefined>(undefined);

type Props = {
  children: ReactNode;
};

const AuthContextProvider = ({ children }: Props) => {
  const [user, setUser] = useState(null);

  const signOut = () => {
    setUser(null);
  };

  useEffect(() => {
    // 副作用处理
  }, []);

  return (
+    <AuthContext.Provider value={{ user, signOut }}>
      {children}
+    </AuthContext.Provider>
  );
};

export default AuthContextProvider;

一旦你向createContext提供了泛型,剩余的事,都由ts为你代劳。

上述实现的一个问题是,就TypeScript而言,context的值可以是未定义的。也就是在我们使用context的值的时候,可能取不到。此时,ts可能会阻拦代码的编译。

如何解决context的值可能是未定义的情况呢。我们针对context的获取可以使用一个自定义的hook

export const useAuthContext = () => {
  const context = useContext(AuthContext);

  if (context === undefined) {
    throw new Error('useAuthContext必须在AuthContext上下文中使用');
  }

  return context;
};

通过类型保护,使得我们在使用context的时候,总是有值的。


类型化自定义hook

类型化自定义hook基本上和类型化普通函数一样


TS_React:类型化事件回调

  1. 类型化事件处理程序的参数
  2. 类型化事件处理程序本身
  3. 依靠类型推断

类型化事件处理程序的参数(event)

先处理onClick事件。React 提供了一个 MouseEvent 类型,可以直接使用!

import { 
    useState, 
+   MouseEvent,
} from 'react';

export default function App() {
    
  // 省略部分代码
  
+  const handleClick = (event: MouseEvent) => {
    console.log('提交被触发');
  };

  return (
    <div className="App">
      <h1>前端柒八九</h1>
      <button onClick={handleClick}>提交</button>
    </div>
  );
}

onClick 事件实际上是由React维护的:它是一个合成事件。合成事件是React浏览器事件的一种包装,以便不同的浏览器,都有相同的API

handleInputChange函数与 handleClick 非常相似,但有一个明显的区别。不同的是,ChangeEvent 是一个泛型,你必须提供什么样的DOM元素正在被使用

import { 
    useState, 
+   ChangeEvent
} from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

+ const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value);
  };

  // 省略部分代码

  return (
    <div className="App">
      <h1>前端柒八九</h1>
      <input value={inputValue} onChange={handleInputChange} />
    </div>
  );
}

在上面的代码中需要注意的一点是,HTMLInputElement 特指HTML的输入标签。如果我们使用的是 textarea,我们将使用 HTMLTextAreaElement 来代替。

注意,MouseEvent 也是一个泛型,你可以在必要时对它进行限制。例如,让我们把上面的 MouseEvent 限制为专门从一个按钮发出的鼠标事件。

const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
  console.log('提交被触发');
};

还需要提示的是,React为我们提供了很多 Event 对象的类型声明。

Event 事件对象类型

事件类型解释
ClipboardEvent<T = Element>剪切板事件对象
DragEvent<T =Element>拖拽事件对象
ChangeEvent<T = Element>Change事件对象
KeyboardEvent<T = Element>键盘事件对象
MouseEvent<T = Element>鼠标事件对象
TouchEvent<T = Element>触摸事件对象
WheelEvent<T = Element>滚轮时间对象
AnimationEvent<T = Element>动画事件对象
TransitionEvent<T = Element>过渡事件对象

类型化事件处理程序本身

React 声明文件所提供的 EventHandler 类型别名,通过不同事件的 EventHandler类型别名来定义事件处理函数的类型,更方便定义其函数类型。

React实战精讲(React_TS/API)

import { 
   useState, 
+  ChangeEventHandler, 
+  MouseEventHandler 
} from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

 
+ const handleInputChange: ChangeEventHandler<HTMLInputElement> = (event) =>{
    setInputValue(event.target.value);
  };

+  const handleClick: MouseEventHandler = (event) => {
    console.log('提交被触发');
  };

  return (
   // ...省略....
  );
}


依赖类型推断

你也可以依靠类型推断,而不需要自己处理函数。但是,你需要将回调函数内联处理

import { useState } from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

  return (
    <div className="App">
      <h1>前端柒八九</h1>
      <input 
        value={inputValue} 
+        onChange={(event) => setInputValue(event.target.value)}
      />
      <button 
+        onClick={(event) => console.log('提交被触发')}
      >
        提交
      </button>
    </div>
  );
}


React API

  1. 组件类
  2. 工具类
  3. 生命周期
  4. Hook
  5. ReactDom

组件类

Component

React 中提供两种形式,

  1. 一种是类组件
  2. 另一种是函数式组件

而在类组件组件中需要继承 Component

class Welcome extends React.Component {
  render() {
    return <h1>Hello, 前端柒八九</h1>;
  }
}

PureComponent

PureComponent:会对 propsstate 进行浅比较,跳过不必要的更新,提高组件性能。

可以说 PureComponentComponent 基本完全一致,但 PureComponent 会浅比较,也就是较少render渲染的次数,所以PureComponent一般用于性能优化。

class Welcome extends React.PureComponent {
  render() {
    return <h1>Hello, 前端柒八九</h1>;
  }
}

与shouldComponentUpdate的关系如何

在生命周期中有一个 shouldComponentUpdate() 函数,shouldComponentUpdate()如果被定义,就会对新旧 propsstate 进行 shallowEqual 比较,新旧一旦不一致,便会触发 update

也可以这么理解:PureComponent 通过自带propsstate 的浅比较实现了shouldComponentUpdate(),这点Component并不具备。

PureComponent 可能会因深层的数据不一致而产生错误的否定判断,从而导致shouldComponentUpdate结果返回false,界面得不到更新,要谨慎使用。

memo

memo:结合了 pureComponent 纯组件和 componentShouldUpdate()功能,会对传入的 props 进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新

要注意 memo 是一个高阶组件,函数式组件和类组件都可以使用。

memo 接收两个参数:

function MyComponent(props) {
  
}
function areEqual(prevProps, nextProps) {
  
}
export default React.memo(MyComponent, areEqual);
  1. 第一个参数:组件本身,也就是要优化的组件
  2. 第二个参数:(pre, next) => boolean,
    • pre:之前的数据
    • next:现在的数据
    • 返回一个布尔值
      • 若为 true 则不更新
      • false 更新

memo的注意事项

React.memoPureComponent 的区别:

  • 服务对象不同
    • PureComponent 服务于类组件,
    • React.memo既可以服务于类组件,也可以服务与函数式组件,
    • useMemo 服务于函数式组件
  • 针对的对象不同:
    • PureComponent 针对的是propsstate
    • React.memo只能针对props来决定是否渲染

React.memo 的第二个参数的返回值与shouldComponentUpdate的返回值是相反的

  • React.memo:返回 true 组件不渲染 , 返回 false 组件重新渲染。
  • shouldComponentUpdate: 返回 true 组件渲染 , 返回 false 组件不渲染

forwardRef

forwardRef引用传递,是一种通过组件向子组件自动传递引用ref的技术。

React 中,React 不允许ref通过props传递,因为ref是组件中固定存在的,在组件调和的过程中,会被特殊处理,而forwardRef就是为了解决这件事而诞生的,让ref可以通过props传递。

// 子组件
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} >
    {props.children}
  </button>
));

// 父组件范围内
const ref = React.createRef();
<FancyButton ref={ref}>谁点我!</FancyButton>;


React.Fragment

16.0后,官方推出了Fragment碎片概念,能够让一个组件返回多个元素,React.Fragment 等价于<></>

Fragment 与 <></>的不同

  • Fragment 这个组件可以赋值 key,也就是索引,
  • <></>不能赋值

React.lazy

lazy:允许你定义一个动态加载组件,这样有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件,也就是懒加载组件(高阶组件)

lazy 接收一个函数,这个函数需要动态调用import(),如:

const SomeComponent = React.lazy(
    () => import('./SomeComponent')
);
  • lazy必须接受一个函数,并且需要返回一个Promise, 并且需要 resolve 一个 default 一个React组件
  • lazy 必须要配合Suspense一起使用

Suspense

Suspense:让组件"等待"某个异步组件操作,直到该异步操作结束即可渲染。

const OtherComponent = React.lazy(
    () => import('./OtherComponent')
);

function MyComponent() {
  return (
    // 在OtherComponent加载之前,显示Spinner
    <React.Suspense fallback={<Spinner />}>
      <OtherComponent />
    </React.Suspense>
  );
}

Profiler

Profiler:这个组件用于性能检测,可以检测一次react组件渲染时的性能开销

此组件有两个参数:

  1. id:标识Profiler的唯一性
  2. onRender:回调函数,组件在commit阶段被调用
render(
  <App>
    <Profiler id="Navigation" onRender={callback}>
      <Navigation {...props} />
    </Profiler>
    <Main {...props} />
  </App>
);

StrictMode

StrictMode:严格模式,是一种用于突出显示应用程序中潜在问题的工具

Fragment 一样,StrictMode 也不会出现在UI层面,只是会检查和警告。

import React from 'react';

function ExampleApplication() {
  return (
    <div>
      <Header />
      <React.StrictMode>        
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>      
      <Footer />
    </div>
  );
}

上述代码中只会对 ComponentOneComponentTwo 进行检查。

从如下方面进行检测:

  • 识别具有不安全生命周期的组件
  • 关于旧版字符串Ref API 使用的警告
  • 关于不推荐使用 findDOMNode 的警告
  • 检测意外的副作用
  • 检测遗留Context API
  • 确保可重用状态

工具类

crateElement

JSX 会被编译为React.createElement的形式,然后被babel编译

React.createElement(type, [props], [...children])
  1. type
  • 原生组件的话是标签的字符串,如“div”
  • 如果是React自定义组件,是类名或者函数名
  • ReactFragment
  1. [props]:对象,dom类中的属性,组件中的props
  2. [...children]:其他的参数,会依此排序

cloneElement

cloneElement:克隆并返回一个新的React元素,

React.createElement(type, [props], [...children])

React.cloneElement()几乎等同于:

<element.type {...element.props} {...props}>
    {children}
</element.type>

createContext

createContext 会创建一个Context对象

  • Providervalue来传递值
  • Consumer接受value

Chidren

Children: 提供处理this.props.children不透明数据结构的实用程序。

Children.map

Children.map:遍历,并返回一个数组

const Child = ({children}) => {
  const res = React.Children.map(children, (item) => item)
  console.log(res)
  return res
}

Children.forEach

Children.forEach:与Children.map类似,不同的是Children.forEach并不会返回值,而是停留在遍历阶段

Children.count

Children.count:返回Child的总个数,等于回调传递给map或forEach将被调用的次数

Children.only

Children.only:验证Child是否只有一个元素,

  • 如果是,则正常返回,
  • 如果不是,则会报错。

Children.toArray

Children.toArray:以平面数组的形式返回children不透明数据结构,每个子元素都分配有键。


createRef

createRef:创建一个ref对象,获取节点信息

isValidElement

isValidElement:用于验证是否是React元素

  • 是的话就返回true
  • 否则返回false

version

查看React的版本号


生命周期

React 的 生命周期主要有两个比较大的版本,分别是

  1. v16.0
  2. v16.4两个版本

的生命周期。

v16.0前

React实战精讲(React_TS/API)

总共分为四大阶段

  1. {初始化| Intialization}
  2. {挂载| Mounting}
  3. {更新| Update}
  4. {卸载| Unmounting}

Intialization(初始化)

在初始化阶段,会用到 constructor() 这个构造函数,如:

constructor(props) {
  super(props);
}
  • super的作用
    • 用来调用基类的构造方法( constructor() ),
    • 也将父组件的props注入给子组件,供子组件读取
  • 初始化操作,定义this.state的初始内容
  • 只会执行一次

Mounting(挂载)

  1. componentWillMount:在组件挂载到DOM前调用
    • 这里面的调用的this.setState不会引起组件的重新渲染,也可以把写在这边的内容提到constructor(),所以在项目中很少。
    • 只会调用一次
  2. render: 渲染
    • 只要propsstate发生改变(无论值是否有变化,两者的重传递和重赋值,都可以引起组件重新render),都会重新渲染render
    • return是必须的,是一个React元素,不负责组件实际渲染工作,由React自身根据此元素去渲染出DOM。
    • render纯函数,不能执行this.setState
  3. componentDidMount:组件挂载到DOM后调用
    • 调用一次

Update(更新)

  1. componentWillReceiveProps(nextProps):调用于props引起的组件更新过程中

    • nextProps:父组件传给当前组件新的props
    • 可以用nextPropsthis.props来查明重传props是否发生改变(原因:不能保证父组件重传的props有变化)
    • 只要props发生变化就会,引起调用
  2. shouldComponentUpdate(nextProps, nextState)用于性能优化

    • nextProps:当前组件的this.props
    • nextState:当前组件的this.state
    • 通过比较nextPropsnextState,来判断当前组件是否有必要继续执行更新过程。
    • 返回false:表示停止更新,用于减少组件的不必要渲染,优化性能
    • 返回true:继续执行更新
    • componentWillReceiveProps()中执行了this.setState,更新了state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及当前组件的this.state的对比就一直是true了
  3. componentWillUpdate(nextProps, nextState):组件更新前调用

    • render方法前执行
    • 由于组件更新就会调用,所以一般很少使用
  4. render:重新渲染

  5. componentDidUpdate(prevProps, prevState):组件更新后被调用

    • prevProps:组件更新前的props
    • prevState:组件更新前的state
    • 可以操作组件更新的DOM

Unmounting(卸载)

componentWillUnmount:组件被卸载前调用

可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清除componentDidMount手动创建的DOM元素等,以避免引起内存泄漏


React v16.4

React实战精讲(React_TS/API)

v16.0的生命周期相比

  • 新增了 -- (两个getXX
    1. getDerivedStateFromProps
    2. getSnapshotBeforeUpdate
  • 取消了 -- (三个componmentWillXX)
    1. componentWillMount
    2. componentWillReceiveProps
    3. componentWillUpdate

getDerivedStateFromProps

getDerivedStateFromProps(prevProps, prevState):组件创建和更新时调用的方法

  • prevProps:组件更新前的props
  • prevState:组件更新前的state

React v16.3中,在创建和更新时,只能是由父组件引发才会调用这个函数,在React v16.4改为无论是Mounting还是Updating,全部都会调用。

是一个静态函数,也就是这个函数不能通过this访问到class的属性。

如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾。

在组件创建时和更新时的render方法之前调用,它应该

  • 返回一个对象来更新状态
  • 或者返回null来不更新任何内容

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps,prevState):Updating时的函数,在render之后调用

  • prevProps:组件更新前的props
  • prevState:组件更新前的state

可以读取,但无法使用DOM的时候,在组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)

返回的任何指都将作为参数传递给componentDidUpdate()


Note

在17.0的版本,官方彻底废除

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

React-Hooks

react-hooksReact 16.8的产物,给函数式组件赋上了生命周期

React v16.8中的hooks

useState

useState:定义变量,可以理解为他是类组件中的this.state 使用:

const [state, setState] = useState(initialState);
  • state:目的是提供给 UI,作为渲染视图的数据源
  • setState:改变 state 的函数,可以理解为this.setState
  • initialState:初始默认值

useState 有点类似于PureComponent,会进行一个比较浅的比较,如果是对象的时候直接传入并不会更新。


useEffect

useEffect:副作用,你可以理解为是类组件的生命周期,也是我们最常用的钩子

副作用(Side Effect):是指 function 做了和本身运算返回值无关的事,如请求数据、修改全局变量,打印、数据获取、设置订阅以及手动更改 React 组件中的 DOM 都属于副作用操作

  1. 不断执行
    • useEffect不设立第二个参数时,无论什么情况,都会执行
  2. 根据依赖值改变
    • 设置useEffect的第二个值

useContext

useContext:上下文,类似于Context:其本意就是设置全局共享数据,使所有组件可跨层级实现数据共享

useContent的参数一般是由createContext的创建,通过 CountContext.Provider 包裹的组件,才能通过 useContext 获取对应的值


useReducer

useReducer:它类似于redux功能的api

const [state, dispatch] = useReducer(reducer, initialArg, init);
  • state:更新后的state
  • dispatch:可以理解为和useStatesetState一样的效果
  • reducer:可以理解为reduxreducer
  • initialArg:初始值
  • init:惰性初始化

useMemo

useMemo:与memo的理念上差不多,都是判断是否满足当前的限定条件来决定是否执行callback函数,而useMemo的第二个参数是一个数组,通过这个数组来判定是否执行回调函数

当一个父组件中调用了一个子组件的时候,父组件的 state 发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新。

只要父组件的状态更新,无论有没有对子组件进行操作,子组件都会进行更新useMemo就是为了防止这点而出现的。


useCallback

useCallbackuseMemo极其类似,唯一不同的是

  • useMemo返回的是函数运行的结果,
  • useCallback返回的是函数
    • 这个函数是父组件传递子组件的一个函数,防止做无关的刷新,
    • 其次,这个子组件必须配合React.memo,否则不但不会提升性能,还有可能降低性能

useRef

useRef: 可以获取当前元素的所有属性,并且返回一个可变的ref对象,并且这个对象只有current属性,可设置initialValue

  1. 通过useRef获取对应的React元素的属性值
  2. 缓存数据

useImperativeHandle

useImperativeHandle:可以让你在使用 ref 时自定义暴露给父组件的实例值

useImperativeHandle(ref, createHandle, [deps])
  • refuseRef所创建的ref
  • createHandle:处理的函数,返回值作为暴露给父组件的 ref 对象。
  • deps:依赖项,依赖项更改形成新的 ref 对象。

useImperativeHandleforwardRef配合使用

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

在父组件中,可以渲染<FancyInput ref={inputRef} />并可以通过父组件的inputRef对子组件中的input进行处理。inputRef.current.focus()


useLayoutEffect

useLayoutEffect: 与useEffect基本一致,不同的地方时,useLayoutEffect是同步

要注意的是useLayoutEffectDOM 更新之后,浏览器绘制之前,这样做的好处是可以更加方便的修改 DOM,获取 DOM 信息,这样浏览器只会绘制一次,所以useLayoutEffect在useEffect之前执行

如果是 useEffect 的话 ,useEffect 执行在浏览器绘制视图之后,如果在此时改变DOM,有可能会导致浏览器再次回流和重绘

除此之外useLayoutEffectcallback 中代码执行会阻塞浏览器绘制


useDebugValue

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


React v18中的hooks

useSyncExternalStore

useSyncExternalStore:是一个推荐用于读取和订阅外部数据源的 hook,其方式与选择性的 hydration 和时间切片等并发渲染功能兼容

const state = useSyncExternalStore(
    subscribe, 
    getSnapshot[, getServerSnapshot]
)
  • subscribe: 订阅函数,用于注册一个回调函数,当存储值发生更改时被调用。此外, useSyncExternalStore 会通过带有记忆性的 getSnapshot 来判别数据是否发生变化,如果发生变化,那么会强制更新数据
  • getSnapshot: 返回当前存储值的函数。必须返回缓存的值。如果 getSnapshot 连续多次调用,则必须返回相同的确切值,除非中间有存储值更新。
  • getServerSnapshot:返回服务端(hydration模式下)渲染期间使用的存储值的函数

useTransition

useTransition

  • 返回一个状态值表示过渡任务的等待状态,
  • 以及一个启动该过渡任务的函数

过渡任务 在一些场景中,如:输入框、tab切换、按钮等,这些任务需要视图上立刻做出响应,这些任务可以称之为立即更新的任务

但有的时候,更新任务并不是那么紧急,或者来说要去请求数据等,导致新的状态不能立马更新,需要用一个loading...的等待状态,这类任务就是过度任务

const [isPending, startTransition] = useTransition();
  • isPending过渡状态的标志,为true时是等待状态
  • startTransition:可以将里面的任务变成过渡任务

useDeferredValue

useDeferredValue:接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。

如果当前渲染是一个紧急更新的结果,比如用户输入,React返回之前的值,然后在紧急渲染完成后渲染新的值

也就是说useDeferredValue可以让状态滞后派生

const deferredValue = useDeferredValue(value);
  • value:可变的值,如useState创建的值
  • deferredValue: 延时状态

useTransition和useDeferredValue做个对比

  • 相同点:useDeferredValueuseTransition 一样,都是过渡更新任务
  • 不同点:useTransition 给的是一个状态,而useDeferredValue给的是一个

useInsertionEffect

useInsertionEffect:与 useLayoutEffect 一样,但它在所有 DOM 突变之前同步触发

在执行顺序上 useInsertionEffect > useLayoutEffect > useEffect

seInsertionEffect 应仅限于 css-in-js 库作者使用。优先考虑使用 useEffect 或 useLayoutEffect 来替代。


useId

useId : 是一个用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免hydration不匹配的 hook。


react-dom

createPortal

createPortal:在Portal中提供了一种将子节点渲染到 DOM 节点中的方式,该节点存在于 DOM 组件的层次结构之外

也就是说 createPortal 可以把当前组件或element元素的子节点,渲染到组件之外的其他地方。

来看看createPortal(child, container)的入参:

  • child:任何可渲染的子元素
  • container:是一个DOM元素

flushSync

flushSync:可以将回调函数中的更新任务,放到一个较高级的优先级中,适用于强制刷新,同时确保了DOM会被立即更新

 onClick={() => {
          this.setState({ number: 1  })
          this.setState({ number: 2  })
          this.setState({ number: 3  })
        }}

因为this.setState会进行批量更新,所以打印出的是3

onClick={() => {
      this.setState({ number: 1  })
      ReactDOM.flushSync(()=>{
        this.setState({ number: 2  })
      })
      this.setState({ number: 3  })
    }}

发现flushSync会优先执行,并且强制刷新,所以会改变number值为2,然后1和3在被批量刷新,更新为3


render

render:这个是我们在React-Dom中最常用的Api,用于渲染一个React元素

ReactDOM.render( 
    < App / >, 
    document.getElementById('app')
)

createRoot

React v18中,render函数已经被createRoot所替代。

createRoot会控制你传入的容器节点的内容。当调用 render 时,里面的任何现有 DOM 元素都会被替换。后面的调用使用 ReactDOM diffing 算法进行有效更新。

并且 createRoot 不修改容器节点(只修改容器的子节点)。可以在不覆盖现有子节点的情况下将组件插入现有 DOM 节点。

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <Main />
  </StrictMode>
);


unmountComponentAtNode

unmountComponentAtNode:从 DOM 中卸载组件,会将其事件处理器(event handlers)和 state 一并清除。如果指定容器上没有对应已挂载的组件,这个函数什么也不会做。

  • 如果组件被移除将会返回 true
  • 如果没有组件可被移除将会返回 false

root.unmount()

unmountComponentAtNode 同样在React 18中被替代了,替换成了createRoot中的unmount()方法

const root = createRoot(container);
root.render(element);

root.unmount()

findDOMNode

findDOMNode:用于访问组件DOM元素节点(应急方案),官方推荐使用ref


unstable_batchedUpdates

unstable_batchedUpdates :可用于手动批量更新state,可以指定多个setState合并为一个更新请求


后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。

React实战精讲(React_TS/API)