この記事では、React の Ref を理解し、Ref について知っておくべき知識のポイントを紹介します。皆様のお役に立てれば幸いです。
React プロジェクトでは、Ref
が必要となるシナリオが数多くあります。たとえば、ref
属性を使用して DOM ノードを取得し、ClassComponent オブジェクト インスタンスを取得します。useRef
フックを使用して Ref オブジェクトを作成し、setInterval# などの問題を解決します。 ## 最新の状態を取得できません。質問;
React.createRef メソッドを呼び出して、手動で
Ref オブジェクトを作成することもできます。 [関連する推奨事項:
Redis ビデオ チュートリアル ]
Ref は非常に簡単に使用できますが、実際のプロジェクトでは問題が発生することは避けられません。
ソースコードの、Refに関連するさまざまな問題を整理し、
refに関連するAPIの背後で何が行われているかを明確にします。この記事を読むと、
Ref についての理解がさらに深まるかもしれません。
refは、参照である
referenceの略称です。
react の型宣言ファイルには、Ref に関連するいくつかの型があり、それらをここにリストします。
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 = '' // Error
TS エラー: 「現在」は読み取り専用プロパティであるため、割り当てることができません。
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 = '' // OK
React.createRef() メソッドを呼び出すと、RefObject も返されます。
createRef
export function createRef(): RefObject { const refObject = { current: null, }; if (__DEV__) { Object.seal(refObject); } return refObject; }
16.3 で追加されました。以前のバージョンを使用している場合は、次のことを行う必要があります。
Ref コールバック を使用します。
RefCallback
RefCallback です。
type RefCallback<T> = (instance: T | null) => void;
RefCallback の使用例: import React from 'react' 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/LegacyRefref は
string であることもあります。
type Ref<T> = RefCallback<T> | RefObject<T> | null; type LegacyRef<T> = string | Ref<T>;
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 環境コンソールにプロンプトが表示されます。
アクセスしようとすると、
が返されます。子コンポーネント内の同じ値にアクセスする必要がある場合は、それを別の prop として渡す必要があります。
React 对 ref 做了啥?在 ReactElement 源码中可以看到,ref
是 RESERVED_PROPS
,同样有这种待遇的还有 key
,它们都会被特殊处理,从 props 中提取出来传递给 Element
。
const RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true, };
所以 ref
是会被特殊处理的 “props“
。
在 16.8.0
版本之前,Function Component 是无状态的,只会根据传入的 props render。有了 Hook 之后不仅可以有内部状态,还可以暴露方法供外部调用(需要借助 forwardRef
和 useImperativeHandle
)。
如果直接对一个 Function Component
用 ref
,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,将它包裹一下定义 $$typeof
为 REACT_FORWARD_REF_TYPE
,return
回去。
跟踪代码,找到 resolveLazyComponentTag,在这里 $$typeof
会被解析成对应的 WorkTag。
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)。
了解如何传递 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 === 'function') { ref(instanceToUse); } else { ref.current = instanceToUse; } } }
以上是 HostComponent 和 ClassComponent 中对 ref 的赋值逻辑,对于 ForwardRef 类型的组件走的是另外的代码,但行为基本是一致的,可以看这里 imperativeHandleEffect。
接下里,我们继续挖掘 React 源码,看看 useRef 是如何实现的。
通过跟踪代码,定位到 useRef 运行时的代码 ReactFiberHooks
这里有两个方法,mountRef
和 updateRef
,顾名思义就是对应 Fiber
节点 mount
和 update
时对 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
,并将它赋值给 hook
的 memoizedState
,update
时直接将它取出返回。
不同的 Hook memoizedState 保存的内容不一样,useState
中保存 state
信息, useEffect
中 保存着 effect
对象,useRef
中保存的是 ref
对象...
mountWorkInProgressHook
,updateWorkInProgressHook
方法背后是一条 Hooks 的链表,在不修改链表的情况下,每次 render useRef 都能取回同一个 memoizedState 对象,就这么简单。
至此,我们了解了在 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 === 'function') { 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 サイトの他の関連記事を参照してください。