英国诗人 萨松在诗歌 《与我,过去、现在以及未来》中写道:"In me the tiger sniffs the rose"
诗人余光中将其翻译为:"心有猛虎,细嗅蔷薇"
大家好,我是柒八九。
今天,我们继续前端面试的知识点。我们来谈谈关于React实战的相关知识点和具体的算法。
该系列的文章,大部分都是前面文章的知识点汇总,如果想具体了解相关内容,请移步相关系列,进行探讨。
如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。
文章list
好了,天不早了,干点正事哇。
你能所学到的知识点
- TS_React:使用泛型来改善类型
- TS_React:Hook类型化
- TS_React:类型化事件回调
- React API
TS_React:使用泛型来改善类型
TypeScript
是什么
TypeScript
是⼀种由微软开源的编程语⾔。它是JavaScript
的⼀个超集。
本质上向
JS
添加了可选的
- 静态类型
- 基于类的⾯向对象编程
TypeScript
提供最新的和不断发展的 JavaScript
特性,包括那些来⾃ 2015 年的 ECMAScript 和未来的提案中的特性,⽐如异步功能和 Decorators
,以帮助建⽴健壮的组件。
TypeScript
与 JavaScript
的区别
TypeScript |
JavaScript |
JavaScript 的超集 ⽤于解决⼤型项⽬的代码复杂性 |
⼀种脚本语⾔ ⽤于创建动态⽹⻚ |
可以在编译期间发现并纠正错误 | 作为⼀种解释型语⾔,只能在运⾏时发现错误 |
强类型,⽀持静态和动态类型 | 弱类型,没有静态类型选项 |
最终被编译成 JavaScript 代码,使浏览器可以理解 | 可以直接在浏览器中使⽤ |
⽀持模块、泛型和接⼝ | 不⽀持泛型或接⼝ |
典型 TypeScript ⼯作流程
在上图中包含 3 个 ts ⽂件:a.ts
、b.ts
和 c.ts
。这些⽂件将被 TypeScript
编译器,根据配置的编译选项编译成 3 个 js ⽂件,即 a.js
、b.js
和 c.js
。对于⼤多数使⽤ TypeScript
开发的 Web 项⽬,我们还会对编译⽣成的 js ⽂件进⾏打包处理,然后在进⾏部署。
TypeScript的特点
TypeScript 主要有 3 大特点:
- 始于JavaScript,归于JavaScript
TypeScript
可以编译出纯净、 简洁的JavaScript
代码,并且可以运行在任何浏览器上、Node.js
环境中和任何支持ECMAScript 3
(或更高版本)的JavaScript 引擎中。 - 强大的类型系统
类型系统允许JavaScript
开发者在开发JavaScript
应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构。 - 先进的 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>
语法,就像传递参数⼀样,上面代码传递了我们想要⽤于特定函数调⽤的类型。
也可以引⼊希望定义的任何数量的类型变量。⽐如我们引⼊⼀个新的类型变量 U
,⽤于扩展我们定义的 identity
函数:
function identity <T, U>(value: T, message: U) : T { console.log(message); return value; } console.log(identity<Number, string>(68, "TS真的香喷喷")); 复制代码
泛型约束
有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作⽤。下⾯我们来举⼏个例⼦,介绍⼀下如何使⽤泛型约束。
确保属性存在
有时候,我们希望类型变量对应的类型上存在某些属性。这时,除⾮我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。
例如在处理字符串或数组时,我们会假设 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>('前端柒八九'); 复制代码
类型推断错误
有时,推断的类型是错误的(或者限制性太强不是你想要的类型)。这种情况经常发生在React
的useState
默认值中。比方说,name
的初始值是null
。
const [name, setName] = useState(null); 复制代码
在这种情况下,TypeScript
会推断出name
是null类型
的(这意味着它总是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
有两样东西要类型化处理:state
和action
。
这里有一个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.type
是username
,它就会自动知道它应该是第一种情况,并且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()
上加了一个?
。这是因为对于 TypeScript
,inputRef.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
)。
有一点,需要指出:组件参数
ref
和props
的顺序与泛型的<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:类型化事件回调
- 类型化事件处理程序的参数
- 类型化事件处理程序本身
- 依靠类型推断
类型化事件处理程序的参数(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
的类型别名来定义事件处理函数的类型,更方便定义其函数类型。
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
- 组件类
- 工具类
- 生命周期
- Hook
- ReactDom
组件类
Component
在 React
中提供两种形式,
- 一种是类组件
- 另一种是函数式组件
而在类组件组件中需要继承 Component
class Welcome extends React.Component { render() { return <h1>Hello, 前端柒八九</h1>; } } 复制代码
PureComponent
PureComponent
:会对 props
和 state
进行浅比较,跳过不必要的更新,提高组件性能。
可以说 PureComponent
和 Component
基本完全一致,但 PureComponent
会浅比较,也就是较少render渲染的次数,所以PureComponent
一般用于性能优化。
class Welcome extends React.PureComponent { render() { return <h1>Hello, 前端柒八九</h1>; } } 复制代码
与shouldComponentUpdate的关系如何
在生命周期中有一个 shouldComponentUpdate()
函数,shouldComponentUpdate()
如果被定义,就会对新旧 props
、state
进行 shallowEqual
比较,新旧一旦不一致,便会触发 update。
也可以这么理解:PureComponent
通过自带的 props
和 state
的浅比较实现了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); 复制代码
- 第一个参数:组件本身,也就是要优化的组件
- 第二个参数:
(pre, next) => boolean
,
pre
:之前的数据next
:现在的数据- 返回一个布尔值
- 若为
true
则不更新 - 为
false
更新
memo的注意事项
React.memo
与 PureComponent
的区别:
- 服务对象不同:
PureComponent
服务于类组件,React.memo
既可以服务于类组件,也可以服务与函数式组件,useMemo
服务于函数式组件
- 针对的对象不同:
PureComponent
针对的是props
和state
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
组件渲染时的性能开销
此组件有两个参数:
id
:标识Profiler
的唯一性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> ); } 复制代码
上述代码中只会对 ComponentOne
和ComponentTwo
进行检查。
从如下方面进行检测:
- 识别具有不安全生命周期的组件
- 关于旧版字符串Ref API 使用的警告
- 关于不推荐使用
findDOMNode
的警告 - 检测意外的副作用
- 检测遗留Context API
- 确保可重用状态
工具类
crateElement
JSX
会被编译为React.createElement
的形式,然后被babel
编译
React.createElement(type, [props], [...children]) 复制代码
type
- 原生组件的话是标签的字符串,如“div”
- 如果是React自定义组件,是类名或者函数名
ReactFragment
[props]
:对象,dom类中的属性,组件中的props[...children]
:其他的参数,会依此排序
cloneElement
cloneElement
:克隆并返回一个新的React
元素,
React.createElement(type, [props], [...children]) 复制代码
React.cloneElement()
几乎等同于:
<element.type {...element.props} {...props}> {children} </element.type> 复制代码
createContext
createContext
会创建一个Context对象
,
- 用
Provider
的value
来传递值 - 用
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
的 生命周期主要有两个比较大的版本,分别是
v16.0
前v16.4
两个版本
的生命周期。
v16.0前
总共分为四大阶段:
- {初始化| Intialization}
- {挂载| Mounting}
- {更新| Update}
- {卸载| Unmounting}
Intialization(初始化)
在初始化阶段,会用到 constructor()
这个构造函数,如:
constructor(props) { super(props); } 复制代码
super
的作用
- 用来调用基类的构造方法(
constructor()
), - 也将父组件的
props
注入给子组件,供子组件读取
- 初始化操作,定义
this.state
的初始内容 - 只会执行一次
Mounting(挂载)
componentWillMount
:在组件挂载到DOM前调用
- 这里面的调用的
this.setState
不会引起组件的重新渲染,也可以把写在这边的内容提到constructor()
,所以在项目中很少。 - 只会调用一次
render
: 渲染
- 只要
props
和state
发生改变(无论值是否有变化,两者的重传递和重赋值,都可以引起组件重新render
),都会重新渲染render
。 return
:是必须的,是一个React元素,不负责组件实际渲染工作,由React自身根据此元素去渲染出DOM。render
是纯函数,不能执行this.setState
。
componentDidMount
:组件挂载到DOM后调用
- 调用一次
Update(更新)
componentWillReceiveProps(nextProps)
:调用于props
引起的组件更新过程中
nextProps
:父组件传给当前组件新的props
- 可以用
nextProps
和this.props
来查明重传props
是否发生改变(原因:不能保证父组件重传的props有变化) - 只要
props
发生变化就会,引起调用
shouldComponentUpdate(nextProps, nextState)
:用于性能优化
nextProps
:当前组件的this.props
nextState
:当前组件的this.state
- 通过比较
nextProps
和nextState
,来判断当前组件是否有必要继续执行更新过程。 - 返回
false
:表示停止更新,用于减少组件的不必要渲染,优化性能 - 返回
true
:继续执行更新 - 像
componentWillReceiveProps()
中执行了this.setState
,更新了state
,但在render
前(如shouldComponentUpdate,componentWillUpdate),this.state
依然指向更新前的state,不然nextState及当前组件的this.state的对比就一直是true了
componentWillUpdate(nextProps, nextState)
:组件更新前调用
- 在
render
方法前执行 - 由于组件更新就会调用,所以一般很少使用
render
:重新渲染componentDidUpdate(prevProps, prevState)
:组件更新后被调用
prevProps
:组件更新前的props
prevState
:组件更新前的state
- 可以操作组件更新的DOM
Unmounting(卸载)
componentWillUnmount
:组件被卸载前调用
可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清除componentDidMount
中手动创建的DOM元素等,以避免引起内存泄漏
React v16.4
与 v16.0
的生命周期相比
- 新增了 -- (两个
getXX
)
getDerivedStateFromProps
getSnapshotBeforeUpdate
- 取消了 -- (三个
componmentWillXX
)
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
getDerivedStateFromProps
getDerivedStateFromProps(prevProps, prevState)
:组件创建和更新时调用的方法
prevProps
:组件更新前的propsprevState
:组件更新前的state
在
React v16.3
中,在创建和更新时,只能是由父组件引发才会调用这个函数,在React v16.4
改为无论是Mounting
还是Updating
,全部都会调用。
是一个静态函数,也就是这个函数不能通过this
访问到class
的属性。
如果
props
传入的内容不需要影响到你的state
,那么就需要返回一个null
,这个返回值是必须的,所以尽量将其写到函数的末尾。
在组件创建时和更新时的render方法之前调用,它应该
- 返回一个对象来更新状态
- 或者返回
null
来不更新任何内容
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps,prevState)
:Updating
时的函数,在render之后调用
prevProps
:组件更新前的propsprevState
:组件更新前的state
可以读取,但无法使用DOM的时候,在组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)
返回的任何指都将作为参数传递给componentDidUpdate()
Note
在17.0的版本,官方彻底废除
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
React-Hooks
react-hooks
是React 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
都属于副作用操作
- 不断执行
- 当
useEffect
不设立第二个参数时,无论什么情况,都会执行
- 根据依赖值改变
- 设置
useEffect
的第二个值
useContext
useContext
:上下文,类似于Context
:其本意就是设置全局共享数据,使所有组件可跨层级实现数据共享
useContent
的参数一般是由createContext
的创建,通过 CountContext.Provider
包裹的组件,才能通过 useContext
获取对应的值
useReducer
useReducer
:它类似于redux
功能的api
const [state, dispatch] = useReducer(reducer, initialArg, init); 复制代码
state
:更新后的state
值dispatch
:可以理解为和useState
的setState
一样的效果reducer
:可以理解为redux
的reducer
initialArg
:初始值init
:惰性初始化
useMemo
useMemo
:与memo
的理念上差不多,都是判断是否满足当前的限定条件来决定是否执行callback
函数,而useMemo
的第二个参数是一个数组,通过这个数组来判定是否执行回调函数
当一个父组件中调用了一个子组件的时候,父组件的
state
发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新。
只要父组件的状态更新,无论有没有对子组件进行操作,子组件都会进行更新,useMemo
就是为了防止这点而出现的。
useCallback
useCallback
与useMemo
极其类似,唯一不同的是
useMemo
返回的是函数运行的结果,- 而
useCallback
返回的是函数
- 这个函数是父组件传递子组件的一个函数,防止做无关的刷新,
- 其次,这个子组件必须配合
React.memo
,否则不但不会提升性能,还有可能降低性能
useRef
useRef
: 可以获取当前元素的所有属性,并且返回一个可变的ref对象
,并且这个对象只有current属性
,可设置initialValue
- 通过useRef获取对应的
React元素
的属性值 - 缓存数据
useImperativeHandle
useImperativeHandle
:可以让你在使用 ref
时自定义暴露给父组件的实例值
useImperativeHandle(ref, createHandle, [deps]) 复制代码
ref
:useRef
所创建的refcreateHandle
:处理的函数,返回值作为暴露给父组件的ref
对象。deps
:依赖项,依赖项更改形成新的ref
对象。
useImperativeHandle
和forwardRef
配合使用
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
是同步
要注意的是useLayoutEffect
在 DOM 更新之后,浏览器绘制之前,这样做的好处是可以更加方便的修改 DOM,获取 DOM 信息,这样浏览器只会绘制一次,所以useLayoutEffect在useEffect之前执行
如果是 useEffect
的话 ,useEffect
执行在浏览器绘制视图之后,如果在此时改变DOM,有可能会导致浏览器再次回流和重绘。
除此之外useLayoutEffect
的 callback
中代码执行会阻塞浏览器绘制
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做个对比
- 相同点:
useDeferredValue
和useTransition
一样,都是过渡更新任务 - 不同点:
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 元素都会被替换。后面的调用使用 React
的 DOM 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合并为一个更新请求
后记
分享是一种态度。
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。