
VUE3.0 をすぐに始める方法: 入門を入力して学習する
プロジェクトでは、 Refが必要となるシナリオが数多くあります。たとえば、 ref属性を使用して DOM ノードを取得し、ClassComponent オブジェクトのインスタンスを取得します。setInterval setInterval最新の状態を取得できない問題を解決するには、 useRefフックを使用してReact.createRefを呼び出すこともできます。 Refオブジェクトを手動で作成するメソッド。
Refは非常に簡単に使用できますが、実際のプロジェクトでは依然として問題が発生することは避けられません。この記事では、 Refに関連するさまざまな問題をソース コードの観点から整理し、 ref関連の API の背後で何が行われているかを明らかにします。この記事を読むと、 Refについての理解がさらに深まるかもしれません。
まず、 ref referenceの略称であり、参照です。 react型宣言ファイルには、いくつかの Ref 関連の型があり、それらはここにリストされています。
インターフェイス RefObject<T> { 読み取り専用 現在: T null }
違いは、
useRef RefObject { current: T }プロパティが読み取り専用であることです。 , Typescript は、 refObject.currentが変更されると、⚠️ と警告します。
const ref = useRef<string>(null) ref.current = '' // エラー
TS: 「current」は読み取り専用プロパティであるため、割り当てることができません。

useRefメソッドの定義を見てください。ここでは関数のオーバーロードが使用されています。受信ジェネリック パラメーターTにnullが含まれていない場合は、 RefObject<T>が返されます。null null含まれている場合は、 MutableRefObject<T>が返されます。
関数 useRef<T>(初期値: T): MutableRefObject<T>; function useRef<T>(initialValue: T | null): RefObject<T>;
したがって、作成された ref オブジェクトの現在のプロパティを変更可能にしたい場合は、 | null追加する必要があります。
const ref = useRef<文字列>(null) ref.current = '' // OK、
React.createRef()メソッドを呼び出すと、 RefObjectも返されます。
createRef
エクスポート関数 createRef(): RefObject {
const refObject = {
現在: null、
};
if (__DEV__) {
Object.seal(refObject);
}
refObject を返します。
RefObject RefObject/MutableRefObjectバージョン16.3で追加されました。以前のバージョンを使用する場合は、 Ref Callback使用する必要があります。
Ref Callbackを使用すると、react がコールバックするときに、対応するインスタンスが返され、呼び出し用にそれ自体を保存できます。このコールバック関数の型はRefCallbackです。
type RefCallback<T> = (instance: T | null) => void
RefCallbackの使用例:
import React from 'react'
エクスポートクラス CustomTextInput extends React.Component {
textInput: HTMLInputElement null = null;
saveInputRef = (要素: HTMLInputElement | null) => {
this.textInput = 要素;
}
与える() {
戻る (
<input type="text" ref={this.saveInputRef} />
);
}
型宣言には、Ref 型を一般に参照するために使用される Ref/LegacyRef 型もあり
。
LegacyRefは互換性のあるバージョンです。以前の古いバージョンでは、 ref文字列である可能性もありました。
型 Ref<T> = RefCallback<T> | null; type LegacyRef<T> = string | Ref<T>;
Ref に関連する型を理解することによってのみ、Typescript をより快適に作成できるようになります。
JSX コンポーネントでrefを使用する場合、 Ref ref属性に設定します。 jsxの構文が Babel などのツールによってcreateElementの形式にコンパイルされることは誰もが知っています。
//jsx
<アプリ ref={ref} id="my-app" ></アプリ>
// にコンパイルされる
React.createElement(App, {
参照: 参照、
ID:「私のアプリ」
refは
refの props と変わらないように見えますが、コンポーネント内で props.ref を出力しようとすると、 undefinedになります。そして、 dev環境コンソールにプロンプトが表示されます。
子コンポーネント内で同じ値にアクセスする必要がある場合は
undefinedそれを別の prop として渡す必要があります。React
は ref で何をしますか? ReactElement のソース コードでわかるように、 ref RESERVED_PROPSです。 keyもこの処理を行い、props から抽出され、 Elementに渡されます。
const RESERVED_PROPS = {
キー: true、
参照:本当、
__自分: 本当です、
__ソース: 本当、
したがって、
ref特別に扱われる“props“です。
バージョン16.8.0より前では、関数コンポーネントはステートレスであり、受信した props に基づいてのみレンダリングされていました。 Hook を使用すると、内部状態を保持できるだけでなく、外部呼び出しのメソッドを公開することもできます ( forwardRefとuseImperativeHandleが必要)。
Function Componentにref直接使用すると、開発環境のコンソールは、それをforwardRefでラップする必要があることを警告します。
functionInput() {
<入力> を返す
}
const ref = useRef()
<Input ref={ref} />関数コンポーネントに ref を指定できません。この ref にアクセスしようとすると失敗します。forwardRef() とは
forwardRefですか?ソース コード ReactForwardRef.js を表示します。 __DEV__関連のコードを折りたたみます。これは非常に単純な高位コンポーネントです。レンダリングされた FunctionComponent を受け取り、ラップして$$typeof REACT_FORWARD_REF_TYPEとして定義してreturn 。

コードをトレースして、resolveLazyComponentTag を見つけます。 $$typeofは、対応する WorkTag に解析されます。

REACT_FORWARD_REF_TYPEに対応する WorkTag は ForwardRef です。次に、ForwardRef は updateForwardRef のロジックに入ります。
case ForwardRef: {
子 = updateForwardRef(
ヌル、
作業中、
成分、
解決された小道具、
レンダーレーン、
);
帰国子女。
このメソッドは
renderWithHooks メソッドを呼び出し、5 番目のパラメータにrefを渡します。
nextChildren = renderWithHooks( 現在、 作業中、 与える、 次の小道具、 ref, // ここでは renderLanes, );
コードのトレースを続けて、 renderWithHooks メソッドに入ります。 ref Componentの 2 番目のパラメーターとして渡されることがわかります。この時点で、 forwardRefによってラップされたFuncitonComponentの 2 番目のパラメーターrefどこから来たのかを理解できます (Context である ClassComponent コンストラクターの 2 番目のパラメーターと比較して)。

ref を渡す方法がわかったら、次の疑問は ref がどのように割り当てられるかです。
(refにRefCallbackを代入してコールバック内でブレークする) このメソッドでは、FibreノードのrefがfunctionかRefObjectかを判定します。種類に応じて加工させていただきます。ファイバー ノードが DOM ノードである HostComponent ( tag = 5 ) である場合、instance は DOM ノードであり、ファイバー ノードが ClassComponent ( tag = 1 ) である場合、instance はオブジェクト インスタンスです。
関数 commitAttachRef(finishedWork) {
var ref = 完了した作業.ref;
if (ref !== null) {
varinstanceToUse = completedWork.stateNode;
if (typeof ref === '関数') {
ref(使用するインスタンス);
} それ以外 {
ref.current = 使用するインスタンス;
}
}
上記は、
HostComponent と ClassComponent の ref の割り当てロジックです。ForwardRef タイプのコンポーネントでは、異なるコードが使用されますが、動作は基本的に同じです。
次に、引き続き React ソース コードを調べて、useRef がどのように実装されているかを確認します。
コードを追跡することで useRef ランタイム コード ReactFiberHooks を見つけます。

ここには、 mountRefとupdateRef 2 つのメソッドがあります。名前が示すように、これらはFiberノードmountおよびupdateときのrefの操作に対応します。
関数 updateRef<T>(初期値: T): {|現在: T|} {
const フック = updateWorkInProgressHook();
フック.memoizedStateを返します。
}
関数 mountRef<T>(初期値: T): {|現在: T|} {
const フック = mountWorkInProgressHook();
const ref = {現在: 初期値};
フック.memoizedState = ref;
参照を返します。
mount時にuseRef RefObject作成し、それをhookのmemoizedStateに割り当てていることがわかります。 update時にはそれが取り出されて直接返されます
。
さまざまなフック memoizedState はさまざまstateコンテンツを保存し、 useEffect effectオブジェクトを保存し、 useRef refオブジェクトを保存しますuseState
mountWorkInProgressHookとupdateWorkInProgressHookメソッドは、フックのリンク リストに基づいています。 useRef をレンダリングするたびに同じ memoizedState オブジェクトを取得するのはとても簡単です。
この時点で、React でref渡して代入するロジックと、 useRefに関連するソース コードを理解しました。アプリケーションの質問を使用して、上記の知識ポイントを統合します。 コンポーネント内には、innerRef HTMLInputElement使用してDOMノードにアクセスする必要があります。同時に、外部コンポーネントがノードを参照することもできます。それを実装するには?
const 入力 = forwardRef((props, ref) => {
const innerRef = useRef<HTMLInputElement>(null)
戻る (
<input {...props} ref={???} />
)
})上記のコードの???どのように書くべきかを考えてください。
============ 答えの分割線 ==============
Ref に関連する内部実装を理解することで、ここでRefCallback作成できることは明らかです。複数のref割り当てるだけで処理できます。
エクスポート関数combineRefs<T = any>(
refs: 配列<MutableRefObject<T | RefCallback<T>>
): React.RefCallback<T> {
戻り値 => {
refs.forEach(ref => {
if (typeof ref === '関数') {
参照(値);
else if (ref !== null) {
参照電流 = 値;
}
});
};
}
const 入力 = forwardRef((props, ref) => {
const innerRef = useRef<HTMLInputElement>(null)
戻る (
<input {...props} ref={combineRefs(ref, innerRef)} />
)
})