React の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。

青灯夜游
リリース: 2022-03-22 11:21:34
転載
4383 人が閲覧しました

この記事では、React の Ref を理解し、Ref について知っておくべき知識のポイントを紹介します。皆様のお役に立てれば幸いです。

React の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。

はじめに

React プロジェクトでは、Ref が必要となるシナリオが数多くあります。たとえば、ref 属性を使用して DOM ノードを取得し、ClassComponent オブジェクト インスタンスを取得します。useRef フックを使用して Ref オブジェクトを作成し、setInterval# などの問題を解決します。 ## 最新の状態を取得できません。質問; React.createRef メソッドを呼び出して、手動で Ref オブジェクトを作成することもできます。 [関連する推奨事項: Redis ビデオ チュートリアル ]

Ref は非常に簡単に使用できますが、実際のプロジェクトでは問題が発生することは避けられません。 ソースコードの、Refに関連するさまざまな問題を整理し、refに関連するAPIの背後で何が行われているかを明確にします。この記事を読むと、Ref についての理解がさらに深まるかもしれません。

Ref関連の型宣言

まず、

refは、参照であるreferenceの略称です。 react の型宣言ファイルには、Ref に関連するいくつかの型があり、それらをここにリストします。

RefObject/MutableRefObject

interface RefObject<T> { readonly current: T | null; }
interface MutableRefObject<T> { current: T; }
ログイン後にコピー

useRef フックを使用すると、RefObject/MutableRefObejct が返されます。どちらのタイプも { current: T } オブジェクト構造を定義します。違いは、RefObject の現在のプロパティが read-only であることです。refObject.current が変更されると、Typescript は警告⚠️を出します。

const ref = useRef<string>(null)
ref.current = &#39;&#39; // Error
ログイン後にコピー

TS エラー: 「現在」は読み取り専用プロパティであるため、割り当てることができません。

React の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。

useRef メソッドの定義を表示します。ここでは 関数のオーバーロード が使用されています。汎用パラメーターを渡すとき T は、null が含まれていない場合は RefObject を返し、null## が含まれている場合は MutableRefObject<T>## を返します。 #. #.

function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;
ログイン後にコピー
したがって、作成された ref オブジェクトの現在のプロパティを変更可能にしたい場合は、| null

を追加する必要があります。

const ref = useRef<string | null>(null)
ref.current = &#39;&#39; // OK
ログイン後にコピー
React.createRef()

メソッドを呼び出すと、

RefObject も返されます。 createRef

export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  if (__DEV__) {
    Object.seal(refObject);
  }
  return refObject;
}
ログイン後にコピー
RefObject/MutableRefObject

はバージョン

16.3 で追加されました。以前のバージョンを使用している場合は、次のことを行う必要があります。 Ref コールバック を使用します。 RefCallback

Ref Callback

を使用すると、コールバック関数が渡されます。react がコールバックすると、対応するインスタンスが返されます。便宜上、自分で保存してください。このコールバック関数の型は

RefCallback です。

type RefCallback<T> = (instance: T | null) => void;
ログイン後にコピー
RefCallback

の使用例:

import React from &#39;react&#39;

export class CustomTextInput extends React.Component {
  textInput: HTMLInputElement | null = null;

  saveInputRef = (element: HTMLInputElement | null) => {
    this.textInput = element;
  }

  render() {
    return (
      <input type="text" ref={this.saveInputRef} />
    );
  }
}
ログイン後にコピー
Ref/LegacyRef

型宣言には、Ref/ もあります。 LegacyRef タイプ、一般的に Ref タイプを参照するために使用されます。 LegacyRef

は互換性のあるバージョンです。以前の古いバージョンでは、

refstring であることもあります。

type Ref<T> = RefCallback<T> | RefObject<T> | null;
type LegacyRef<T> = string | Ref<T>;
ログイン後にコピー
Ref に関連する型を理解して初めて、Typescript をより快適に書くことができるようになります。

Ref の受け渡し

特殊なプロパティ

JSX コンポーネントで ref

を使用する場合、

ref を渡します。 属性は Ref を設定します。 jsx の構文は、Babel などのツールによって createElement の形式にコンパイルされることは誰もが知っています。

// jsx
<App ref={ref} id="my-app" ></App>

// compiled to
React.createElement(App, {
  ref: ref,
  id: "my-app"
});
ログイン後にコピー
ref

は他の props と変わらないように見えますが、コンポーネント内で props.ref を出力しようとすると、

unknown になります。 dev 環境コンソールにプロンプ​​トが表示されます。 アクセスしようとすると、

unknown
が返されます。子コンポーネント内の同じ値にアクセスする必要がある場合は、それを別の prop として渡す必要があります。

React 对 ref 做了啥?在 ReactElement 源码中可以看到,refRESERVED_PROPS,同样有这种待遇的还有 key,它们都会被特殊处理,从 props 中提取出来传递给 Element

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};
ログイン後にコピー

所以 ref 是会被特殊处理的 “props“

forwardRef

16.8.0 版本之前,Function Component 是无状态的,只会根据传入的 props render。有了 Hook 之后不仅可以有内部状态,还可以暴露方法供外部调用(需要借助 forwardRefuseImperativeHandle)。

如果直接对一个 Function Componentref,dev 环境下控制台会告警,提示你需要用 forwardRef 进行包裹起来。

function Input () {
    return <input />
}

const ref = useRef()
<Input ref={ref} />
ログイン後にコピー

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

forwardRef 为何物?查看源码 ReactForwardRef.js__DEV__ 相关的代码折叠起来,它只是一个无比简单的高阶组件。接收一个 render 的 FunctionComponent,将它包裹一下定义 $$typeofREACT_FORWARD_REF_TYPEreturn 回去。

React の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。

跟踪代码,找到 resolveLazyComponentTag,在这里 $$typeof 会被解析成对应的 WorkTag。

React の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。

REACT_FORWARD_REF_TYPE 对应的 WorkTag 是 ForwardRef。紧接着 ForwardRef 又会进入 updateForwardRef 的逻辑。

case ForwardRef: {
  child = updateForwardRef(
    null,
    workInProgress,
    Component,
    resolvedProps,
    renderLanes,
  );
  return child;
}
ログイン後にコピー

这个方法又会调用 renderWithHooks 方法,并在第五个参数传入 ref

nextChildren = renderWithHooks(
  current,
  workInProgress,
  render,
  nextProps,
  ref, // 这里
  renderLanes,
);
ログイン後にコピー

继续跟踪代码,进入 renderWithHooks 方法,可以看到,ref 会作为 Component 的第二个参数传递。到这里我们可以理解被 forwardRef 包裹的 FuncitonComponent 第二个参数 ref 是从哪里来的(对比 ClassComponent contructor 第二个参数是 Context)。

React の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。

了解如何传递 ref,那下一个问题就是 ref 是如何被赋值的。

ref 的赋值

打断点(给 ref 赋值一个 RefCallback,在 callback 里面打断点) 跟踪到代码 commitAttachRef,在这个方法里面,会判断 Fiber 节点的 ref 是 function 还是 RefObject,依据类型处理 instance。如果这个 Fiber 节点是 HostComponent (tag = 5) 也就是 DOM 节点,instance 就是该 DOM 节点;而如果该 Fiber 节点是 ClassComponent (tag = 1),instance 就是该对象实例。

function commitAttachRef(finishedWork) {
  var ref = finishedWork.ref;

  if (ref !== null) {
    var instanceToUse = finishedWork.stateNode;

    if (typeof ref === &#39;function&#39;) {
      ref(instanceToUse);
    } else {
      ref.current = instanceToUse;
    }
  }
}
ログイン後にコピー

以上是 HostComponent 和 ClassComponent 中对 ref 的赋值逻辑,对于 ForwardRef 类型的组件走的是另外的代码,但行为基本是一致的,可以看这里 imperativeHandleEffect

接下里,我们继续挖掘 React 源码,看看 useRef 是如何实现的。

useRef 的内部实现

通过跟踪代码,定位到 useRef 运行时的代码 ReactFiberHooks

React の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。

这里有两个方法,mountRefupdateRef,顾名思义就是对应 Fiber 节点 mountupdate 时对 ref 的操作。

function updateRef<T>(initialValue: T): {|current: T|} {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

function mountRef<T>(initialValue: T): {|current: T|} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}
ログイン後にコピー

可以看到 mount 时,useRef 创建了一个 RefObject,并将它赋值给 hookmemoizedStateupdate 时直接将它取出返回。

不同的 Hook memoizedState 保存的内容不一样,useState 中保存 state 信息, useEffect 中 保存着 effect 对象,useRef 中保存的是 ref 对象...

mountWorkInProgressHookupdateWorkInProgressHook 方法背后是一条 Hooks 的链表,在不修改链表的情况下,每次 render useRef 都能取回同一个 memoizedState 对象,就这么简单。

应用:合并 ref

至此,我们了解了在 React 中 ref 的传递和赋值逻辑,以及 useRef 相关的源码。用一个应用题来巩固以上知识点:有一个 Input 组件,在组件内部需要通过 innerRef HTMLInputElement 来访问 DOM 节点,同时也允许组件外部 ref 该节点,需要怎么实现?

const Input = forwardRef((props, ref) => {
  const innerRef = useRef<HTMLInputElement>(null)
  return (
    <input {...props} ref={???} />
  )
})
ログイン後にコピー

考虑一下上面代码中的 ??? 应该怎么写。

============ 答案分割线 ==============

通过了解 Ref 相关的内部实现,很明显我们这里可以创建一个 RefCallback,在里面对多个 ref 进行赋值就可以了。

export function combineRefs<T = any>(
  refs: Array<MutableRefObject<T | null> | RefCallback<T>>
): React.RefCallback<T> {
  return value => {
    refs.forEach(ref => {
      if (typeof ref === &#39;function&#39;) {
        ref(value);
      } else if (ref !== null) {
        ref.current = value;
      }
    });
  };
}

const Input = forwardRef((props, ref) => {
  const innerRef = useRef<HTMLInputElement>(null)
  return (
    <input {...props} ref={combineRefs(ref, innerRef)} />
  )
})
ログイン後にコピー

更多编程相关知识,请访问:编程入门!!

以上がReact の Ref を理解し、知っておく価値のある知識ポイントを共有しましょう。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!