React 高阶组件

发布于 2025-02-17 04:56:54 字数 15169 浏览 8 评论 0

一、组件

  • React.Component
  • React.PureComponent
  • React.memo :把组件定义为可被包装的函数
  • React.Fragment :是一个用于减少不必要嵌套的组件

1. React.Component

2. React.PureComponent

React.PureComponentReact.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate() ,而 React.PureComponent 中以浅层对比 propstate 的方式来实现了该函数。

使用场景

如果赋予 React 组件相同的 propsstaterender() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。

注意点

React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较。

如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。仅在你的 propsstate 较为简单时,才使用 React.PureComponent ,或者在深层数据结构发生变化时调用 forceUpdate() 来确保组件被正确地更新。你也可以考虑使用 immutable 对象 加速嵌套数据的比较。

此外, React.PureComponent 中的 shouldComponentUpdate() 将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是“纯”的组件。

3. React.memo

React 组件会在两种情况下发生渲染:

  1. 当组件自身 state 发生变化时
  2. 当父组件发生重渲染时(使用 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 包裹,且其实现中拥有 useStateuseReduceruseContext 的 Hook,当 statecontext 发生变化时,它仍会重新渲染。

默认情况下 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, keyref 。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。

新的子元素将取代现有的子元素,如果在 config 中未出现 keyref ,那么原始元素的 keyref 将被保留。

React.cloneElement() 几乎等同于:

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

但是,这也保留了组件的 ref 。这意味着当通过 ref 获取子节点时,你将不会意外地从你祖先节点上窃取它。相同的 ref 将添加到克隆后的新元素中。如果存在新的 refkey 将覆盖之前的。

2 .isValidElement()

验证对象是否为 React 元素,返回值为 truefalse

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 中的组件总数量,等同于通过 mapforEach 调用回调函数的次数

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 将使用 propsref 作为参数来调用此函数。此函数应返回 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 startTransitionuseTransition 来将标记更新为 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

三寸金莲

暂无简介

文章
评论
28 人气
更多

推荐作者

Mr.HU

文章 0 评论 0

疯到世界奔溃

文章 0 评论 0

隔纱相望

文章 0 评论 0

萌无敌

文章 0 评论 0

梦幻的味道

文章 0 评论 0

自在安然

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文