이 기사는 React의 Ref를 이해하고 Ref에 대해 알아야 할 몇 가지 지식 포인트를 소개합니다. 모든 사람에게 도움이 되기를 바랍니다!
React 프로젝트에는 Ref
가 필요한 시나리오가 많이 있습니다. 예를 들어, ref
속성을 사용하여 DOM 노드를 얻고 ClassComponent 객체 인스턴스를 얻습니다. useRef
후크를 사용하여 setInterval과 같은 문제를 해결하는 Ref 객체를 만듭니다.
최신 상태 문제를 얻을 수 없으면 React.createRef
메서드를 호출하여 Ref
객체를 수동으로 생성할 수도 있습니다. [관련 권장사항: Redis 동영상 튜토리얼]Ref
。例如使用 ref
属性获取 DOM 节点,获取 ClassComponent 对象实例;用 useRef
Hook 创建一个 Ref 对象,以便解决像 setInterval
获取不到最新的 state 的问题;你也可以调用 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
Hook 的时候返回的就是 RefObject/MutableRefObejct,这两个类型都是定义了一个 { current: T }
的对象结构,区别是 RefObject
的 current 属性是只读的,如果修改 refObject.current
,Typescript 会警告⚠️。
const ref = useRef<string>(null) ref.current = '' // Error
TS 报错:无法分配到 "current" ,因为它是只读属性。
查看 useRef
方法的定义,这里用了函数重载,当传入的泛型参数 T
不包含 null
时返回RefObject<T>
,当包含 null
时将返回 MutableRefObject<T>
。
function useRef<T>(initialValue: T): MutableRefObject<T>; function useRef<T>(initialValue: T | null): RefObject<T>;
所以如果你希望创建的 ref 对象 current 属性是可修改的,需要加上 | null
。
const ref = useRef<string | null>(null) ref.current = '' // OK
调用 React.createRef()
方法时返回的也是一个 RefObject
。
export function createRef(): RefObject { const refObject = { current: null, }; if (__DEV__) { Object.seal(refObject); } return refObject; }
RefObject/MutableRefObject
是在 16.3
版本才新增的,如果使用更早的版本,需要使用 Ref Callback
。
使用 Ref Callback
就是传递一个回调函数,react 回调时会将对应的实例回传过来,可以自行保存以便调用。这个回调函数的类型就是 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/LegacyRef 类型,它们用于泛指 Ref 类型。 LegacyRef
是兼容版本,在之前的老版本 ref
还可以是 字符串。
type Ref<T> = RefCallback<T> | RefObject<T> | null; type LegacyRef<T> = string | Ref<T>;
理解了和 Ref 相关的类型,写起 Typescript 来才能更得心应手。
在 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
和其他 prop 没啥区别,不过如果你尝试在组件内部打印 props.ref 却是 undefined
。并且 dev
环境控制台会给出提示。
Trying to access it will result in
undefined
참조</a> code>도 사용하기 매우 간단하지만, 실제 프로젝트에서는 필연적으로 문제가 발생할 수 있습니다. 이 글에서는 <strong>소스 코드 관점</strong>에서 <code>Ref
와 관련된 다양한 문제를 정리하겠습니다. ,ref
와 관련된 API 뒤에서 수행되는 작업을 명확히 합니다. 이 글을 읽고 나면Ref
에 대해 더 깊이 이해할 수 있을 것입니다. 🎜Ref 관련 타입 선언🎜🎜먼저
ref
는 참조(Reference)를 의미하는reference
의 약어입니다.react
의 타입 선언 파일에는 Ref와 관련된 여러 타입이 나열되어 있습니다. 🎜RefObject/MutableRefObject
🎜const RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true, };로그인 후 복사로그인 후 복사useRef
Hook을 사용하면 RefObject/MutableRefObejct가 반환됩니다. <의 개체 구조가 정의됩니다. code>{ current: T },refObject.current<를 수정하면 <code>RefObject
의 현재 속성이 읽기 전용이라는 점이 다릅니다. /code>, Typescript가 경고합니다 ⚠️. 🎜🎜TS 오류: "현재"는 읽기 전용 속성이므로 할당할 수 없습니다. 🎜🎜🎜🎜function Input () { return <input /> } const ref = useRef() <Input ref={ref} />로그인 후 복사로그인 후 복사useRef
메소드의 정의를 확인하세요. 함수 오버로딩은 수신되는 일반 매개변수T
에가 포함되지 않은 경우에 사용됩니다. null
이 포함되면RefObject<T>
가 반환되고,null
이 포함되면MutableRefObject<T>
가 반환됩니다. 🎜🎜따라서 생성된 ref 객체의 현재 속성을 수정 가능하게 하려면case ForwardRef: { child = updateForwardRef( null, workInProgress, Component, resolvedProps, renderLanes, ); return child; }로그인 후 복사로그인 후 복사| null
을 추가해야 합니다. 🎜🎜nextChildren = renderWithHooks( current, workInProgress, render, nextProps, ref, // 这里 renderLanes, );로그인 후 복사로그인 후 복사React.createRef()
메서드를 호출하면RefObject
도 반환됩니다. 🎜🎜createRef🎜🎜function commitAttachRef(finishedWork) { var ref = finishedWork.ref; if (ref !== null) { var instanceToUse = finishedWork.stateNode; if (typeof ref === 'function') { ref(instanceToUse); } else { ref.current = instanceToUse; } } }로그인 후 복사로그인 후 복사RefObject/MutableRefObject
가16.3
버전에 추가되었습니다. 이전 버전을 사용하는 경우를 사용해야 합니다. 참조 콜백
. 🎜RefCallback
🎜Ref Callback
을 사용하는 것은 콜백 함수를 전달하는 것이며, 반응은 해당 인스턴스를 호출합니다. 다시 전화할 때 다시 전달되었을 때 저장하여 쉽게 호출할 수 있습니다. 이 콜백 함수의 유형은RefCallback
입니다. 🎜🎜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; }로그인 후 복사로그인 후 복사RefCallback
사용 예: 🎜const Input = forwardRef((props, ref) => { const innerRef = useRef<HTMLInputElement>(null) return ( <input {...props} ref={???} /> ) })로그인 후 복사로그인 후 복사Ref/LegacyRef
🎜유형 선언에서 Ref /LegacyRef 유형은 일반적으로 Ref 유형을 참조하는 데 사용됩니다.LegacyRef
는 호환되는 버전입니다. 이전 버전에서는ref
가 문자열. 🎜🎜Ref와 관련된 유형을 이해해야 Typescript 작성이 더 편해집니다. 🎜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)} /> ) })로그인 후 복사로그인 후 복사Ref 전달 🎜
특수 소품
🎜JSX 구성 요소에 사용됨< code> ref에서는ref
속성을 ref
속성으로 설정합니다. 우리 모두는jsx
의 구문이 Babel과 같은 도구에 의해createElement
형식으로 컴파일된다는 것을 알고 있습니다. 🎜rrreee🎜ref
는 다른 props와 다를 바 없는 것 같지만, 컴포넌트 내부에서 props.ref를 출력하려고 하면undefine
이 됩니다. 그리고dev
환경 콘솔에 프롬프트가 표시됩니다. 🎜🎜이 항목에 액세스하려고 하면정의되지 않은
이 반환됩니다. 하위 구성 요소 내에서 동일한 값에 액세스해야 하는 경우 해당 값을 다른 prop으로 전달해야 합니다.🎜React 对 ref 做了啥?在 ReactElement 源码中可以看到,
ref
是RESERVED_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 之后不仅可以有内部状态,还可以暴露方法供外部调用(需要借助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 的赋值
打断点(给 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 的内部实现
通过跟踪代码,定位到 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 对象,就这么简单。应用:合并 ref
至此,我们了解了在 React 中
ref
的传递和赋值逻辑,以及useRef
相关的源码。用一个应用题来巩固以上知识点:有一个 Input 组件,在组件内部需要通过 innerRefHTMLInputElement
来访问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 중국어 웹사이트의 기타 관련 기사를 참조하세요!