Main
组件
React.Component
React 类组件的基类。类组件是
React.Component
的派生。生命周期
挂载
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
卸载
当组件从 DOM 中移除时会调用如下方法:
componentWillUnmount()
错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
static getDerivedStateFromError()
componentDidCatch()
render()
render()
方法是 class 组件中唯一必须实现的方法。
- 当
render
被调用时,它会检查this.props
和this.state
的变化并返回以下类型之一: - React 元素。通常通过 JSX 创建。例如,
<div />
会被 React 渲染为 DOM 节点,<MyComponent />
会被 React 渲染为自定义组件,无论是<div />
还是<MyComponent />
均为 React 元素。 - 数组或 fragments。 使得 render 方法可以返回多个元素。
- Portals。可以渲染子节点到不同的 DOM 子树中。
- 字符串或数值类型。它们在 DOM 中会被渲染为文本节点。
- 布尔类型或
null
。什么都不渲染。(主要用于支持返回test && <Child />
的模式,其中 test 为布尔类型。)
render()
函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在componentDidMount()
或其他生命周期方法中执行你的操作。
constructor(props)
在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用
super(props)
。否则,this.props
在构造函数中可能会出现未定义的 bug。- 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
- 通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给
this.state
赋值对象来初始化内部 state。 - 为事件处理函数绑定实例
- 在
constructor()
函数中不要调用setState()
方法。如果你的组件需要使用内部 state,请直接在构造函数中为this.state
赋值初始 state。
componentDidMount()
componentDidMount()
会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。- 这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在
componentWillUnmount()
里取消订阅
- 你可以在
componentDidMount()
里直接调用setState()
。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在render()
两次调用的情况下,用户也不会看到中间状态。请谨慎使用该模式,因为它会导致性能问题。通常,你应该在constructor()
中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理
componentDidUpdate(preProps,preState,sanpshot)
componentDidUpdate()
会在更新后会被立即调用。首次渲染不会执行此方法。- 当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。
- 你也可以在
componentDidUpdate()
中直接调用setState()
,但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。
- 如果组件实现了
getSnapshotBeforeUpdate()
生命周期(不常用),则它的返回值将作为componentDidUpdate()
的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
componentWillUnmount()
componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等。
componentWillUnmount()
中不应调用 setState()
,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。shouldComponentUpdate(nextProps,nextState)
根据
shouldComponentUpdate()
的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。
当 props 或 state 发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate()
时不会调用该方法。- 此方法仅作为性能优化的方式而存在。如果需要改写该生命周期时,应该考虑
React.PureComponet
。
shouldComponentUpdate()
返回false
,则不会调用UNSAFE_componentWillUpdate()
,render()
和componentDidUpdate()
。
static getDerivedStateFromProps()
getDerivedStateFromProps
会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null
则不更新任何内容。
此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 <Transition>
组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。getSnapshotBeforeUpdate(prevProps,preState)
getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()
。
此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。static getDerivedStateFromError(error)
此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。
componentDidCatch(error,info)
此生命周期在后代组件抛出错误后被调用。 它接收两个参数:
error
—— 抛出的错误。
info
—— 带有componentStack
key 的对象,其中包含有关组件引发错误的栈信息。componentDidCatch()
会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况。
实例属性和方法
state
组件中的 state 包含了随时可能发生变化的数据。state 由用户自定义,它是一个普通 JavaScript 对象。如果某些值未用于渲染或数据流(例如,计时器 ID),则不必将其设置为 state。此类值可以在组件实例上定义。永远不要直接改变
this.state
,因为后续调用的 setState()
可能会替换掉你的改变。请把 this.state
看作是不可变的。props
this.props
包括被该组件调用者定义的 props。需特别注意,this.props.children
是一个特殊的 prop,通常由 JSX 表达式中的子组件组成,而非组件本身定义。setState(updater[,callback])
setState()
将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式。
setState()
并不总是立即更新组件。它会批量推迟更新。在setState()
后可能无法获取到更新后的值,如果需要在值更新后立即使用,可以使用componentDidUpdate
生命周期或setState
的回调函数中使用。如果要强制 DOM 更新同步,可以使用flushSync
包装。
- 除非
shouldComponentUpdate()
返回false
,否则setState()
将始终执行重新渲染操作。
updater
可以是一个对象,也可以是一个函数(state,props)=>newState
,对象或函数的返回值将会被浅合并(类似于Object.assign()
)到state
中。
- updater 函数中接收的
state
和props
都保证为最新。
- 如果在同一周期中对多个
setState
进行批处理,后续的setState
将会覆盖之前的调用,值只会有一次改变。如果后续状态取决于当前状态,建议使用updater
函数的形式代替。
this.setState({ name: 'Simon' }) this.setState((state,props)=>{ offset: state.data.length + props.pagesize },() => { console.log(this,state.offset) })
forceUpdate(callback)
- 默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果
render()
方法依赖于其他数据,则可以调用forceUpdate()
强制让组件重新渲染。
- 调用
forceUpdate()
将致使组件调用render()
方法,此操作会跳过该组件的shouldComponentUpdate()
。但其子组件会触发正常的生命周期方法,包括shouldComponentUpdate()
方法。如果标记发生变化,React 仍将只更新 DOM。
- 通常你应该避免使用
forceUpdate()
,尽量在render()
中使用this.props
和this.state
。
派生类属性
defaultProps
defaultProps
可以为 Class 组件添加默认 props。这一般用于 props 未赋值,但又不能为 null
的情况。displayName
displayName
字符串多用于调试消息。通常,你不需要设置它,因为它可以根据函数组件或 class 组件的名称推断出来。如果调试时需要显示不同的名称或创建高阶组件,请参阅使用 displayname 轻松进行调试了解更多。contextType
挂载在 class 上的
contextType
属性可以赋值为由 React.createContext()
创建的 Context 对象。此属性可以让你使用 this.context
来获取最近 Context 上的值。你可以在任何生命周期中访问到它,包括 render 函数中。class MyClass extends React.Component { componentDidMount() { let value = this.context; /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */ } componentDidUpdate() { let value = this.context; /* ... */ } componentWillUnmount() { let value = this.context; /* ... */ } render() { let value = this.context; /* 基于 MyContext 组件的值进行渲染 */ } } // 添加组件属性 MyClass.contextType = MyContext;
使用
class MyComponent extends React.Component { timer = null; // render 之前调用 constructor(props) { super(props); this.state = { name: 'Simon' } } // render之后 组件挂载之后,可以获取 DOM componentDidMount() { const main = document.querySelector('#main'); this.timer = setInterval(()=>{},10000) } // props 或 sate 变化时,render 之前触发 返回一个布尔值决定组件是否重新渲染 shouldComponentUpdate(nextProps,nextState) { } // 组件更新后调用,首次渲染时不会更新 componentDidUpdate(preProps,preState) { } // 处理组件抛出错误,埋点或显示错误UI componentDidCatch(error,info) { } // 组件卸载前 清除监听,定时器 componentWillUnmount() { clearInterval(this.timer) } render() { reutrn <div id="main">hello world</div> } }
React.pureComponent
React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了 shouldComponentUpdate()
,如果 props 和 state 相同,render()
返回的结果相同。方法
React.memo(component[,areEqual])
React.memo()
是一个高阶组件,如果组件在相同的 props
下渲染的结果相同,那么使用 React.memo()
包裹后的组件将记忆组件的渲染结果,在 props
浅比较相同的情况下跳过渲染组件并直接复用最近一次渲染的结果。- 如果函数组件内部含有
useState
,useReducer
或useContext
等 Hook 时,仍会重新渲染。
- 默认情况下
React.memo()
仅对props
进行浅比较,如果需要进行深层比较或自行控制,需要传入一个自定义的areEqual()
函数来实现。
createElement(type[,props,...children])
创建并返回指定类型的新 React 元素。其中的类型参数既可以是标签名字符串(如
'div'
或 'span'
),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型。
使用 JSX 编写的代码将会被转换成使用 React.createElement()
的形式。如果使用了 JSX 方式,那么一般来说就不需要直接调用 React.createElement()
。cloneElement(element[,config,...children])
以
element
元素为样板克隆并返回新的 React 元素。config
中应包含新的 props,key
或 ref
。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,如果在 config
中未出现 key
或 ref
,那么原始元素的 key
和 ref
将被保留。
React.cloneElement()
几乎等同于(但 cloneElement()
没有克隆 ref
属性):<element.type {...element.props} {...props}>{children}</element.type>
isValidElement(object)
验证对象是否为 React 元素,返回值为
true
或 false
。React.Children
React.Children
提供了用于处理 this.props.children
不透明数据结构的实用方法。React.Children.map(children,fuction([thisArg]))
在
children
里的每个直接子节点上调用一个函数,并将 this
设置为 thisArg
。如果 children
是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null
或是 undefined
,则此方法将返回 null
或是 undefined
,而不会返回数组。React.Children.forEach(children,fuction([thisArg]))
与[[React API#
React.Children.map(children,fuction([thisArg]))
]] 类似,但它不会返回一个数组。React.Children.count(children)
返回
children
中的组件总数量,等同于通过 map
或 forEach
调用回调函数的次数。React.Children.only(children)
验证
children
是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。React.Children.toArray(children)
将
children
这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。当你想要在渲染函数中操作子节点的集合时,它会非常实用,特别是当你想要在向下传递 this.props.children
之前对内容重新排序或获取子集时。React.Fragment
React.Fragment
组件能够在不额外创建 DOM 元素的情况下,让 render()
方法中返回多个元素。或者使用简写<></>
。React.createRef()
React.createRef
创建一个能够通过 ref 属性附加到 React 元素的 ref。React.forwardRef((props,ref)=>ReactNode)
React.forwardRef
会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。在以下两种场景中特别有用:React.lazy()
React.lazy()
允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。
一般同 import()
函数和 React.Suspense
组件一同使用。<React.Suspense fallback={<Loading/>}>
React.Suspense
可以指定加载指示器,以防其组件树中的某些子组件尚未具备渲染条件。
如今,懒加载组件是 <React.Suspense>
支持的唯一用例。React.CreateContext()
React.createContext(default)
创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的
Provider
中读取到当前的 context 值。只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue
参数才会生效。Context.Provider
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收一个
value
属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value
值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate
函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。class.contextType
仅适用于消费单个 context 的情况,如果需要消费多个 context,则需要使用
Context.Consumer
嵌套。Context.Consumer
<MyContext.Consumer> {value => /* 基于 context 值进行渲染*/} </MyContext.Consumer>
一个 React 组件可以订阅 context 的变更,此组件可以让你在函数式组件中可以订阅 context。
这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的
value
值等价于组件树上方离这个 context 最近的 Provider 提供的 value
值。如果没有对应的 Provider,value
参数等同于传递给 createContext()
的 defaultValue
。ReactDOM
1. createPortal()
将
children
组件渲染到目标 container
上, children
在 React 中继续保持和嵌套组件的父子关系。一般用在弹窗等需要跳出 CSS 层级的场景。createPortal(children,container[,key]);
使用方法:
const Component = () => { //... return ( //... { ReactDOM.createPortal(<div>我是弹窗</div>,document.body) } //... ) }
2. flushSync()
flushSync
会对性能产生很大影响。尽量少用。强制 React 同步刷新提供的回调函数中的任何更新。这确保了 DOM 会被立即 更新。
在 React18 中,状态会批量更新。如果需要立即更新,那么可以使用
flushSync()
。使用方法:
flushSync(() => { setCount(count + 1); }); // 在这里 DOM 中的值已经更新
3. render()
已经过时的方法,请使用
createRoot()
在提供的
container
里渲染一个 React 元素,并返回对该组件的引用或者针对无状态组件返回 null
)。
如果 React 元素之前已经在 container
里渲染过,这将会对其执行更新操作,并仅会在必要时改变 DOM 以映射最新的 React 元素。
如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执行。render(element, container[, callback])
4.createRoot()
createRoot(container[, options]);
根据给定的
container
返回一个根节点,具有可以渲染 React 元素到 DOM 中的 render
方法。和可以从 DOM 中卸载的 unmount
方法。
options
包含两个选项:onRecoverableError
:当React自动从错误中恢复时调用的可选回调。
identifierPrefix
:useId()
钩子的前缀,如果同时存在多个 root 的时候可以避免冲突。
使用方法:
import * as ReactDOM from 'react-dom/client'; import App from './APP.tsx'; const root = ReactDOM.createRoot(document.querySelector('#root)); root.render(<App/>)
卸载:
root.unmount();
Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
useState([initialState])
const [state, setState] = useState(initialState);
使用
initialState
初始化一个 state
,返回一个 state
,以及更新 state
的函数。- 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给
setState
。该函数将接收先前的 state,并返回一个更新后的值。
initialState
参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用。
- 如果你更新 State Hook 后的 state 与当前的 state 相同时,React 将跳过子组件的渲染并且不会触发 effect 的执行。React 可能仍需要在跳过渲染前渲染该组件,不过由于 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必担心。如果你在渲染期间执行了高开销的计算,则可以使用
useMemo
来进行优化。
- 多个
setState
函数的更新会被合并为一次重新渲染。如果需要立即同步更新,则可以使用flushSync(callback)
在callback
中调用。
useEffect(didUpdate[,dependences])
该 Hook 接收一个包含命令式、且可能有副作用代码的函数。
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。
- 使用
useEffect
完成副作用操作。赋值给useEffect
的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
- 默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。
清除副作用
当有定时器或订阅等操作需要在组件卸载时清除,可以通过
useEffect
函数返回一个清除函数。
为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除。useEffect(()=>{ const timer = setInterval(()=>{],1000}) return () => { clearInterval(timer) } },[])
effect 的执行时机
- 与
componentDidMount
、componentDidUpdate
不同的是,传给useEffect
的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。
- 如果需要在浏览器完成布局与绘制之前执行,可以使用
useLayoutEffect
Hook 来处理。
- 而且当
state
被flushSync
包装后更新时, 传递给useEffect
的函数将在屏幕布局和绘制之前同步执行。
- 即使在
useEffect
被推迟到浏览器绘制之后的情况下,它也能保证在任何新的渲染前启动。React 在开始新的更新前,总会先刷新之前的渲染的 effect。
effect 的条件执行
默认情况下,effect 会在每轮组件渲染完成后执行。这样会使 effect 在某些不必要的场景下也会执行。以给
useEffect
传递第二个参数,它是 effect 所依赖的值数组,React 会对数组中的值进行浅比较,当数组中的值发生改变时踩会重新执行。useContext(context)
const value = useContext(MyContext);
接收一个 context 对象(
React.createContext
的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop 决定。
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext
provider 的 context value
值。即使祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件本身使用 useContext
时重新渲染。useReducer(reducer,initialArg,init)
const [state, dispatch] = useReducer(reducer, initialState, init);
惰性初始化
将
init
函数作为 useReducer
的第三个参数传入,这样初始 state 将被设置为 init(initialArg)
。这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利。跳过 dispatch
如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。
useCallback(callback[,dependences])
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
返回一个 memoized 回调函数。
只有当回调函数的某个依赖改变时才更新,当把回调函数作为 props 传递给优化后的组件时十分有用。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
。useMemo(callback[,dependences])
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值。
只有当依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
- 缓存一个高开销的计算结果。
- 缓存一个字节点。
useRef(initialValue)
const refContainer = useRef(initialValue);
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内持续存在。- 最常见的用途是命令式地访问子组件。
useRef
就像是可以在其.current
属性中保存一个可变值的“盒子”。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。
- 当 ref 对象内容发生变化时,
useRef
并_不会_通知你。变更.current
属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
useImperativeHandle(ref,createHandle[,deps])
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 forwardRef
一起使用:function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
useLayoutEffect
其函数签名与
useEffect
相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect
内部的更新计划将被同步刷新。
它会阻塞浏览器更新。useDebugValue(value)
useDebugValue
可用于在 React 开发者工具中显示自定义 hook 的标签。useDeferredValue(value)
useDeferredValue
接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。如果当前渲染是一个紧急更新的结果,比如用户输入,React 将返回之前的值,然后在紧急渲染完成后渲染新的值。useTransition
const [isPending, startTransition] = useTransition(); startTransition(() => { setCount(count + 1); })
返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。
startTransition
允许你通过标记更新将提供的回调函数作为一个过渡任务,isPending
指示过渡任务何时活跃以显示一个等待状态。useId
const id = useId();
useId
是一个用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免 hydration 不匹配的 hook。React 理念
纯函数
- 当输入相同时,返回值永远相同
- 纯函数没有副作用
高阶组件 HOC
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
render props
术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术。
render props 将 行为的抽象抽离出来,通过使用一个函数将处理好的值传递给 组件来进行共享。
具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。
// 定义 const myRenderComponent = ({children}) => { const [state,setState] = useState(); // 行为的抽象 return (<div> {children(state)} </div>) } // 使用 const renderCustomer = () => { return (<div> <myRenderComponent> {state = > <AnotherComponent data={state}/> } </myRenderComponent> </div>) }
优化和自定义 Hook
usePrevious
通过这个自定义钩子来记住上一个
state
的值。type usePrevious = <T>(state:T)=> T | undefined; const usePrevious:usePrevious = (state) => { const ref = useRef(); useEffect(()=>{ ref.current(state) },[state]) return state.current }
当
usePrevious
初次执行时,ref.current
的值为 undefined
。并设置了一个 effect :当 state
改变时更新 ref.current
的值。
当 state
值改变时,effect 触发,ref.current
的值改变。同时应用该钩子的组件重新渲染。ref.current
不重复初始化仍为上一次的值。同时设置 effect 等待下一次 state
的更新。useStateCallback
type useStateCallback = <T>(initalValue?: T) => [T,(...args:[T,(state:T) => unknown]) => never]; const useStateCallback:useStateCallback = (initalValue) => { const [state,setState] = useState(initalValue); const callback = useRef(); useEffect(()=>{ callback.current?.(state) },[state]) return [state,(state,fn)=>{ setState(state); callback.current = fn }] }
拿到更新后的 state
延迟到更新后执行
- 将和更新后的值相关的执行体抽离到
useEffect(callback,[state])
中。
- 将和更新后的值相关的执行体抽离到
compontDidUpdate()
生命周期中,并比对state
进行判断。
- 使用
useRef
缓存state
,并在useEffect
中更新state
。
- 拓展 1,使用自定义钩子,
useStateCallback()
。
避免不必要的组件更新
- 使用
shouldComponentUpdate
,在这个声明周期中比对props
和state
并返回一个布尔值,如果返回false
就会跳过componentDidUpdate
和render
。
- 使用
Reac.PureComponent
,它实现了shouldComponentUpdate
并在props
和state
未改变时跳过更新。
- 使用
React.memo
包裹组件,它会比对props
,当 props 相同时,就会跳过更新。
- 使用
React.useMemo
缓存子组件,如果决定组件是否更新的依赖项数组没有改变,那么React.useMemo
会复用上一次返回的值。
惰性初始化高开销对象
使用 useRef 和 初始化方法来改造
function Image(props) { const ref = useRef(null); // ✅ IntersectionObserver 只会被惰性创建一次 function getObserver() { if (ref.current === null) { ref.current = new IntersectionObserver(onIntersect); } return ref.current; } // 当你需要时,调用 getObserver() // ... }
从 useCallback
中读取经常变化的值
如果想要记住的函数是一个在渲染期间没有被用到的事件处理器。可以使用 Ref,并手动将最后提交的值保存在 Ref 中。
function Form() { const [text, updateText] = useState(''); const textRef = useRef(); useEffect(() => { textRef.current = text; // 把它写入 ref }); const handleSubmit = useCallback(() => { const currentText = textRef.current; // 从 ref 读取它 alert(currentText); }, [textRef]); // 不要像 [text] 那样重新创建 handleSubmit return ( <> <input value={text} onChange={e => updateText(e.target.value)} /> <ExpensiveTree onSubmit={handleSubmit} /> </> ); }
或抽离为 Hook:
function useEventCallback(fn, dependencies) { const ref = useRef(() => { throw new Error('Cannot call an event handler while rendering.'); }); useEffect(() => { ref.current = fn; }, [fn, ...dependencies]); return useCallback(() => { const fn = ref.current; return fn(); }, [ref]); }
根据 state
的变化派生新的值
- 使用
getDerivedStateFromProps
生命周期,在 render 前会执行并根据返回值来更新state。(不推荐)
- 使用
pureComponent
,并将需要派生的值放在render
函数的函数体中。
- 使用
useMemo
缓存高开销的值,或直接在函数式组件的函数体中运算。