React 高阶组件
一、组件
React.Component
React.PureComponent
React.memo
:把组件定义为可被包装的函数React.Fragment
:是一个用于减少不必要嵌套的组件
1. React.Component
2. React.PureComponent
React.PureComponent
与 React.Component
很相似。两者的区别在于 React.Component
并未实现 shouldComponentUpdate()
,而 React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了该函数。
使用场景
如果赋予 React 组件相同的 props 和 state , render()
函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent
可提高性能。
注意点
React.PureComponent
中的 shouldComponentUpdate()
仅作对象的浅层比较。
如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。仅在你的 props 和 state 较为简单时,才使用 React.PureComponent
,或者在深层数据结构发生变化时调用 forceUpdate()
来确保组件被正确地更新。你也可以考虑使用 immutable 对象 加速嵌套数据的比较。
此外, React.PureComponent
中的 shouldComponentUpdate()
将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是“纯”的组件。
3. React.memo
React 组件会在两种情况下发生渲染:
- 当组件自身 state 发生变化时
- 当父组件发生重渲染时(使用
React.memo
主要针对就是优化这点)
使用场景
如果你的组件在相同 props
的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo
中调用,以此缓存该组件来提高组件的性能表现。当 props
发生变化时,重新渲染,否则返回之前的渲染结果。
这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
import React from 'react';
const MyComponent = function (props) {
/* 使用 props 渲染 */
}
export default React.memo(MyComponent);
特点
React.memo
仅检查 props 变更 。如果函数组件被 React.memo
包裹,且其实现中拥有 useState
, useReducer
或 useContext
的 Hook,当 state
或 context
发生变化时,它仍会重新渲染。
默认情况下 React.memo
只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现(此方法仅作为 性能优化 的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug ):
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
注意:与 class 组件中 shouldComponentUpdate()
方法不同的是,如果 props 相等, areEqual
会返回 true
;如果 props 不相等,则返回 false
。这与 shouldComponentUpdate
方法的返回值相反。
4. React.Fragment
片段
React 中的一个常见模式是一个组件返回多个元素(因为 React 组件的定义只能返回单根元素 )。 Fragments
允许你将子列表分组,而无需向 DOM 添加额外节点。
const App = () => {
return (
<React.Fragment>
Some text.
<h2>A heading</h2>
</React.Fragment>
);
}
// 简写方式
const App = () => {
return (
<>
Some text.
<h2>A heading</h2>
</>
);
}
React.Fragment
组件相当于如下代码:
import React from 'react';
const Fragment = (props) => {
return props.children;
}
export default Fragment;
但是使用 <> </>
,不支持 key 或 属性
带 key 的 Fragments
使用显式 <React.Fragment>
语法声明的片段可能具有 key
。一个使用场景是将一个集合映射到一个 Fragments 数组。
举个例子,创建一个描述列表:
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// 没有`key`,React 会发出一个关键警告
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
key
是唯一可以传递给 Fragment
的属性。未来我们可能会添加对其他属性的支持,例如事件。
二、创建 React 元素
每个 JSX 元素都是调用 React.createElement()
的语法糖。一般来说,如果你使用了 JSX,就不再需要调用以下方法:
createElement()
createFactory()
1. createElement()
格式: React.createElement(type /* type 必须是字母小写的标签名 */, [props], [...children])
const button = React.createElement('button', {
id: 'btn',
className: 'btn',
onClick: () => { console.log('btn.'); } // 事件名驼峰形式
}, '按钮');
const div = React.createElement('div', null, ['div:', button])
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(div)
// 老版本
// ReactDOM.render(div, document.getElementById('root'))
2. createFactory()
- 已废弃
返回用于生成指定类型 React 元素的函数。与 React.createElement()
相似的是,类型参数既可以是标签名字符串(像是 'div'
或 'span'
),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型。
React.createFactory(type)
三、转换元素
React
提供了几个用于操作元素的 API:
cloneElement()
isValidElement()
React.Children
1. cloneElement()
React.cloneElement(
element,
[config],
[...children]
)
以 element
元素为样板克隆并返回新的 React 元素。 config
中应包含新的 props, key
或 ref
。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。
新的子元素将取代现有的子元素,如果在 config
中未出现 key
或 ref
,那么原始元素的 key
和 ref
将被保留。
React.cloneElement()
几乎等同于:
<element.type {...element.props} {...props}>{children}</element.type>
但是,这也保留了组件的 ref
。这意味着当通过 ref
获取子节点时,你将不会意外地从你祖先节点上窃取它。相同的 ref
将添加到克隆后的新元素中。如果存在新的 ref
或 key
将覆盖之前的。
2 .isValidElement()
验证对象是否为 React 元素,返回值为 true
或 false
React.isValidElement(object)
3. React.Children
提供了用于处理 this.props.children
不透明数据结构的实用方法
React.Children.map
React.Children.map(children, function[(thisArg)])
在 children
里的每个直接子节点上调用一个函数,并将 this
设置为 thisArg
。如果子节点为 null
或是 undefined
,则此方法将返回 null
或是 undefined
,而不会返回数组。
注意:如果 children
是一个 Fragment
对象,它将被视为单一子节点的情况处理,而不会被遍历。
React.Children.forEach
React.Children.forEach(children, function[(thisArg)])
与 React.Children.map
类似,但它不会返回一个数组
React.Children.count
React.Children.count(children)
返回 children
中的组件总数量,等同于通过 map
或 forEach
调用回调函数的次数
React.Children.only
React.Children.only(children)
验证 children
是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。
注意: React.Children.only()
不接受 React.Children.map()
的返回值,因为它是一个数组而并不是 React 元素。
React.Children.toArray
React.Children.toArray(children)
将 children
这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。当你想要在渲染函数中操作子节点的集合时,它会非常实用, 特别是当你想要在向下传递 this.props.children
之前对内容重新排序或获取子集时 。
注意: React.Children.toArray()
在拉平展开子节点列表时,更改 key 值以保留嵌套数组的语义。也就是说, toArray
会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。
四、Refs
React.createRef
React.forwardRef
1. React.createRef
React.createRef
创建一个能够通过 ref 属性附加到 React 元素的 ref 。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
componentDidMount() {
this.inputRef.current.focus();
}
}
2. React.forwardRef
React.forwardRef
会创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。它在以下两种场景中特别有用:
React.forwardRef
接受渲染函数作为参数。React 将使用 props
和 ref
作为参数来调用此函数。此函数应返回 React 节点。
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
在上述的示例中,React 会将 <FancyButton ref={ref}>
元素的 ref
作为第二个参数传递给 React.forwardRef
函数中的渲染函数。该渲染函数会将 ref
传递给 <button ref={ref}>
元素。
因此,当 React 附加了 ref 属性之后, ref.current
将直接指向 <button>
DOM 元素实例。
五、Suspense
Suspense 使得组件可以 等待 某些操作结束后,再进行渲染。目前,Suspense 仅支持的使用场景是: 通过 React.lazy
动态加载组件 。它将在未来支持其它使用场景,如数据获取等。
React.lazy
React.Suspense
1. React.lazy
React.lazy()
允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。
// 这个组件是动态加载的
const SomeComponent = React.lazy(() => import('./SomeComponent'));
注意:渲染 lazy
组件依赖该组件渲染树上层的 <React.Suspense>
组件。
这是指定加载指示器(loading indicator)的方式。
2. React.Suspense
React.Suspense
可以指定加载指示器,以防其组件树中的某些子组件尚未具备渲染条件。
如今,懒加载组件是 <React.Suspense>
支持的唯一用例:
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
请注意, lazy
组件可以位于 Suspense
组件树的深处——它不必包装树中的每一个延迟加载组件。最佳实践是将 <Suspense>
置于你想展示加载指示器(loading indicator)的位置,而 lazy()
则可被放置于任何你想要做代码分割的地方。
注意:对于已经展示给用户的内容来说,在切换回去时,展示加载指示器可能会让人困惑。有时,在准备新的 UI 时,展示 “旧” 的 UI 可能会更加友好。要做到这一点,你可以使用新的 transition API startTransition
和 useTransition
来将标记更新为 transitions,同时避免意外的兜底方案。
服务端渲染中的 React.Suspense
在服务端渲染过程中,Suspense 边界允许你挂起,通过较小的块来刷新应用程序。 当组件挂起时,我们会安排一个低优先级的任务来渲染最近的 Suspense 边界的 fallback。如果组件在我们刷新 fallback 之前取消挂起,那么我们会发送实际内容并丢弃 fallback。
hydrate 过程中的 React.Suspense
Suspense 边界依赖于它们的父边界,在它们可以 hydrate 前被 hydrate,但是它们可以独立于兄弟边界进行 hydrate。边界 hydrate 前发生的事件将导致边界 hydrate 的优先级高于相邻边界的优先级。具体请参阅 讨论 。
六、Transitions
Transitions
是一个全新的并发特性。它允许你将 标记 更新作为一个 transitions
,这会告诉 React 它们可以被 中断执行 ,并避免回到已经可见内容的 Suspense
降级方案。
React.startTransition
React.useTransition
1. React.startTransition
React.startTransition(callback)
React.startTransition
让你把提供的 fallback 里面的更新标记为 transitions 。这个方法是为了在 React.useTransition
不可用时使用
注意:
过渡期的更新会被更紧急的更新取代,如点击操作。
过渡期的更新不会显示重新挂起内容的 fallback ,允许用户在渲染更新时继续进行交互。
React.startTransition
不提供isPending
的标志。要跟踪过渡的待定状态,请参阅React.useTransition
2. React.useTransition
const [isPending, startTransition] = useTransition();
isPending
:一个状态值,表示过渡任务的等待状态startTransition
:一个启动该过渡任务的函数
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>
);
}
注意 :
过渡任务中触发的更新会让更紧急地更新先进行,比如点击。
过渡任务中的更新将不会展示由于再次挂起而导致降级的内容。这个机制允许用户在 React 渲染更新的时候继续与当前内容进行交互。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: React 合成事件
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论