Appearance
React
JSX.Element 与 React.ReactNode 与 React.ReactElement
jsx 文件就是实现 React.createElement()
的一个语法糖。 React.createElement
方法会根据传入 type
的类型来区分返回普通的 HTMLElement
或者是 React.ReactElement
实例
React.ReactElement
在使用上就是一个含有 type
、props
、key
的实例对象,由 React 最终渲染成真实的 DOM 元素
ts
// index.d.ts
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T
props: P
key: Key | null
}
// Playground.tsx
const element: React.ReactElement = React.createElement("div", { className: "test", id: "unique" }, "hello world")
ReactDOM.render(element, document.getElementById("render-container"))
console.log(element)
实际的 ReactElement 对象则具有以下元素:
React.ReactNode
是一种联合类型,并且继承了 React.ReactElement
ts
type ReactText = string | number
type ReactChild = ReactElement | ReactText
// ...
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined
JSX.Element
就是通过继承 React.ReactNode
实现的,没有任何区别
区分 class 组件和 function 组件
How Does React Tell a Class from a Function?
为什么要区分
箭头函数的组件没有 this,new 会报错
怎么区分
class 组件 会继承 React.Component
,MyComponent instanceof React.Component
js
// Inside React
class Component {}
Component.prototype.isReactComponent = {}
// We can check it like this
class Greeting extends Component {}
console.log(Greeting.prototype.isReactComponent)
ref
class 组件
jsx
import React from "react"
export default class Test extends React.Component {
ref = React.createRef()
componentDidUpdate() {
this.ref.current.focus()
}
render() {
return <input ref={this.ref} />
}
}
函数组件
jsx
import { useRef } from "react"
export default function Test() {
const ref = useRef()
useEffect(() => {
this.ref.current.focus()
}, [])
return <input ref={this.ref} />
}
函数组件不会暴露出自己的实例,需要使用 forwardRef
来包装组件
jsx
import { forwardRef, useImperativeHandle } from "react"
function Child(props, ref) {
useImperativeHandle(ref, () => {
return {
onXXX,
onResetXXX,
XXX,
}
})
return <>...</>
}
export default forwardRef(Child)
Fiber
由于 react 和 vue 的响应式实现原理不同,react 每次更新都需要渲染一颗更大的虚拟 DOM 树
在 fiber 出现之前,react 的虚拟 DOM 树只有指向子节点的指针,所以中断渲染,暂存当前的 DOM 节点信息就会丢失父节点和兄弟节点的信息,无法完成遍历
通过 requestIdleCallback 来控制遍历的进度条,决定是否让出线程给其他操作
React 中最多会存在两颗 Fiber 树
- currentFiber:页面中显示的内容
- workInProgressFiber:内存中正在重新构建的 Fiber 树
双缓存 Fiber 树
在 React 中最多会同时存在两棵 Fiber 树。当前屏幕上显示内容对应的 Fiber 树称为 current Fiber 树,正在内存中构建的 Fiber 树称为 workInProgress Fiber 树
React 应用的根节点通过 current 指针在不同 Fiber 树的 rootFiber 间切换来实现 Fiber 树的切换
当 workInProgress Fiber 树构建完成交给 Renderer 渲染在页面上后,应用根节点的 current 指针指向 workInProgress Fiber 树,此时 workInProgress Fiber 树就变为 current Fiber 树
Hooks 链表
hooks mount 阶段
当函数式组件初始化时,会调用 renderWithHooks
函数,初始化走 HooksDispatcherOnMount
,后续更新走 HooksDispatcherOnUpdate
初始化时,会调用 mountWorkInProgressHook
创建 hook 链表节点,挂载到 fiber 节点的 memoizedState
上(Fiber 上的 memoizedState
指向 hooks 链表,hook 身上的 memoizedState
存储他们自己的状态)
hooks update 阶段
调用 updateWorkInProgressHook
克隆 hook 节点,进行新旧比较
- 由
nextCurrentHook
中间变量 记录旧的 hooks 链表 - 由
nextWorkInProgressHook
中间变量 克隆旧的 hook 节点形成新的 hooks 链表 - currentHook 指向旧 hooks 链表;
workInProgressHook
指向新的 hooks 链表,返回workInProgressHook
合成事件
v16 之前所有事件统一绑定在 document 上
v17 绑定在 ReactDOM.render(app, container)
的 container 上
事件绑定
- diff 时判断 dom 的 props 是否有合成事件(onXxx)
- 判断事件是否有对应的原生事件
- 调用
addTrappedEventListener
将真正的事件绑定在顶层
事件触发
- 任意一个事件触发,执行
dispatchEvent
函数 - 检测到自己需要处理的事件类型时,处理该事件
- 找到对应 DOM 节点
- 反向触发合成事件链,捕获
- 正向触发合成事件链,冒泡
彻底搞懂 React 18 并发机制的原理
异步获取 state
ts
// function
function fn1() {
setCount(count + 1)
}
function fn2() {
setTimeout(() => {
alert(count)
}, 3000)
}
// class
fn1 = () => {
this.setState({ count: this.state.count + 1 })
}
fn2 = () => {
setTimeout(() => {
alert(this.state.count)
}, 3000)
}
在 function 中,每次页面更新都会重新触发 function,生成新的函数,异步的 alert 只能拿到那个时候执行的 state。
在 class 中,始终是同一个 class 实例,每次页面更新只会重新出发 render 函数,通过 this 拿到的是最新的 state。
useState 通过链表的结构存在对应的 fiber 节点上,按顺序区分哪个 state 是哪个,所以不允许 if
useEffect 和 useMemo 区别
- useEffect 是在 DOM 改变之后触发,useMemo 在 DOM 渲染之前就触发,类似 shouldComponentUpdate 生命周期
- 在 useMemo 中使用 setState 会产生死循环,并且会有警告,因为 useMemo 是在渲染中进行的,你在其中操作 DOM 后,又会导致触发 memo
key
在 diff 算法的实现过程中,如果 key 和 element type 都是相同的话,则通过 useFiber() 函数,基于旧 fiber 节点和新 element 的 props 来复用旧 fiber 节点,否则直接删除旧 fiber 节点,创建新的 fiber 节点。而复用或者创建新的 fiber 节点就意味着复用旧的 DOM 对象或者创建新的 DOM 对象(HostComponent 类型的 fiber 节点所关联的 DOM 对象存放在它的 stateNode 属性上)。
useMemoizedFn 替代 useCallback
ts
function useMemoizedFn(func) {
if (typeof func !== "function") return
// 通过 useRef 保持其引用地址不变,并且值能够保持值最新
const funcRef = useRef(func)
funcRef.current = useMemo(() => {
return func
}, [func])
const memoizedFn = useRef()
if (!memoizedFn.current) {
// 返回的持久化函数,调用该函数的时候,调用原始的函数
memoizedFn.current = function (...args) {
return funcRef.current.apply(this, args)
}
}
return memoizedFn.current
}