React中的ref怎么具体使用
发布时间:2023-07-10 11:30:18 所属栏目:教程 来源:网络
导读: 这篇文章主要介绍“React中的ref怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“React中的ref怎么使用”文章
这篇文章主要介绍“React中的ref怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“React中的ref怎么使用”文章能帮助大家解决问题。 1. ref 的理解与使用 对于 Ref 的理解,要从两个角度去分析: Ref 对象的创建:使用 createRef 或 useRef 创建 Ref 对象 React 本身对 Ref 的处理:对于标签中的 ref 属性,React 是如何处理的 1.1. ref 对象的创建 1.1.1. createRef 在类组件中,我们会通过 createRef 去创建一个 Ref 对象,其会被保存在类组件实例上,它的实现很简单 packages/react/src/ReactCreateRef.js export function createRef(): RefObject { const refObject = { current: null, } return refObject } 可以看到,就是创建了一个包含 current 属性的对象,仅此而已 1.1.2. useRef 这也就意味着我们不能在函数组件中使用 createRef,因为每次函数组件渲染都是一次新的函数执行,每次执行 createRef 得到的都是一个新的对象,无法保留其原来的引用 所以在函数组件中,我们会使用 useRef 创建 Ref 对象,React 会将 useRef 和函数组件对应的 fiber 对象关联,将 useRef 创建的 ref 对象挂载到对应的 fiber 对象上 这样一来每次函数组件执行,只要函数组件不被销毁,那么对应的 fiber 对象实例也会一直存在,所以 ref 也能够被保留下来 1.2. React 对标签中 ref 属性的处理 首先要明确一个结论,在 React 中获取 DOM 元素或者组件实例并不是只能通过 ref 对象获取!!! 也就是说并不是只能通过先调用 createRef 创建 ref 对象,然后将它赋值到要获取的元素或组件实例的 ref 属性上,实际上还有别的方式 :::tip 只有类组件才有获取组件实例这一说法,函数组件没有实例,不能被 ref 标记,但是可以通过 forwardRef 结合 useImperativeHandle 给函数组件赋予 ref 标记的 ::: 1.2.1. string ref 当我们给元素或类组件标签中的 ref 属性传递字符串时,能够在组件实例的 this.refs 中访问到 class Child extends React.Component<PropsWithChildren> { render(): React.ReactNode { const { children } = this.props return ( <div> <p>Child</p> {children} </div> ) } } /** @description ref 属性传递字符串 */ class RefDemo1 extends React.Component { logger = createLoggerWithScope('RefDemo1') componentDidMount(): void { this.logger.log(this.refs) } render(): React.ReactNode { return ( <> <div ref="refDemo1DOM">ref 属性传递字符串获取 DOM 元素</div> <Child ref="refDemo1Component">ref 属性传递字符串获取类组件实例</Child> </> ) } } React中的ref怎么使用 :::warning 这种方式已经被 React 官方废弃,尽量不要使用 ::: 1.2.2. callback ref ref 属性传递函数时,会在 commit 阶段创建真实 DOM 时执行 ref 指定的函数,并将元素作为第一个参数传入,此时我们就可以利用它进行赋值以获取 DOM 元素或组件实例 /** @description ref 属性传递函数 */ class RefDemo2 extends React.Component { logger = createLoggerWithScope('RefDemo2') refDemo2DOM: HTMLElement | null = null refDemo2Component: Child | null = null componentDidMount(): void { this.logger.log(this.refDemo2DOM) this.logger.log(this.refDemo2Component) } render(): React.ReactNode { return ( <> <div ref={(el) => (this.refDemo2DOM = el)}> ref 属性传递函数获取 DOM 元素 </div> <Child ref={(child) => (this.refDemo2Component = child)}> ref 属性传递函数获取类组件实例 </Child> </> ) } } React中的ref怎么使用 1.2.3. object ref 这种方式就是我们最常用的方式了,使用 createRef 或者 useRef 创建 Ref 对象,并将其传给标签的 ref 属性即可 这种方式获取到的 ref 需要先调用 current 属性才能获取到对应的 DOM 元素或组件实例 /** @description ref 属性传递对象 */ class RefDemo3 extends React.Component { logger = createLoggerWithScope('RefDemo3') refDemo3DOM = React.createRef<HTMLDivElement>() refDemo3Component = React.createRef<Child>() componentDidMount(): void { this.logger.log(this.refDemo3DOM) this.logger.log(this.refDemo3Component) } render(): React.ReactNode { return ( <> <div ref={this.refDemo3DOM}>ref 属性传递对象获取 DOM 元素</div> <Child ref={this.refDemo3Component}> ref 属性传递对象获取类组件实例 </Child> </> ) } } 2. ref 高阶用法 2.1. forwardRef 转发 ref 2.1.1. 跨层级获取 想要在爷组件中通过在子组件中传递 ref 获取到孙组件的某个元素,也就是在爷组件中获取到了孙组件的元素,是一种跨层级获取 /** @description 孙组件 */ const Child: React.FC<{ grandRef: LegacyRef<HTMLDivElement> }> = (props) => { const { grandRef } = props return ( <> <p>Child</p> <div ref={grandRef}>要获取的目标元素</div> </> ) } /** * @description 父组件 * * 第一个泛型参数是 ref 的类型 * 第二个泛型参数是 props 的类型 */ const Father = forwardRef<HTMLDivElement, {}>((props, ref) => { return ( <div> <Child grandRef={ref} /> </div> ) }) /** @description 爷组件 */ const GrandFather: React.FC = () => { let grandChildDiv: HTMLDivElement | null = null useEffect(() => { logger.log(grandChildDiv) }, []) return ( <div> <Father ref={(el) => (grandChildDiv = el)} /> </div> ) } 2.1.2. 合并转发自定义 ref forwardRef 不仅可以转发 ref 获取 DOM 元素和组件实例,还可以转发合并后的自定义 ref 什么是“合并后的自定义 ref”呢?通过一个场景来看看就明白了 :::info{title=场景} 通过给 Foo 组件绑定 ref,获取多个内容,包括: 子组件 Bar 的组件实例 Bar 组件中的 DOM 元素 button 孙组件 Baz 的组件实例 ::: 这种在一个 ref 里能够访问多个元素和实例的就是“合并后的自定义 ref” /** @description 自定义 ref 的类型 */ interface CustomRef { bar: Bar barButton: HTMLButtonElement baz: Baz } class Baz extends React.Component { render(): React.ReactNode { return <div>Baz</div> } } class Bar extends React.Component<{ customRef: ForwardedRef<CustomRef> }> { buttonEl: HTMLButtonElement | null = null bazInstance: Baz | null = null componentDidMount(): void { const { customRef } = this.props if (customRef) { ;(customRef as MutableRefObject<CustomRef>).current = { bar: this, barButton: this.buttonEl!, baz: this.bazInstance!, } } } render() { return ( <> <button ref={(el) => (this.buttonEl = el)}>Bar button</button> <Baz ref={(instance) => (this.bazInstance = instance)} /> </> ) } } const FowardRefBar = forwardRef<CustomRef>((props, ref) => ( <Bar {...props} customRef={ref} /> )) const Foo: React.FC = () => { const customRef = useRef<CustomRef>(null) useEffect(() => { logger.log(customRef.current) }, []) return <FowardRefBar ref={customRef} /> } React中的ref怎么使用 2.1.3. 高阶组件转发 ref 如果我们在高阶组件中直接使用 ref,它会直接指向 WrapComponent class TestComponent extends React.Component { render(): React.ReactNode { return <p>TestComponent</p> } } /** @description 不使用 forwardRef 转发 HOC 中的 ref */ const HOCWithoutForwardRef = (Component: typeof React.Component) => { class WrapComponent extends React.Component { render(): React.ReactNode { return ( <div> <p>WrapComponent</p> <Component /> </div> ) } } return WrapComponent } const HOCComponent1 = HOCWithoutForwardRef(TestComponent) const RefHOCWithoutForwardRefDemo = () => { const logger = createLoggerWithScope('RefHOCWithoutForwardRefDemo') const wrapRef = useRef(null) useEffect(() => { // wrapRef 指向的是 WrapComponent 实例 而不是 HOCComponent1 实例 logger.log(wrapRef.current) }, []) return <HOCComponent1 ref={wrapRef} /> } React中的ref怎么使用 如果我们希望 ref 指向的是被包裹的 TestComponent 而不是 HOC 内部的 WrapComponent 时该怎么办呢? 这时候就可以用 forwardRef 进行转发了 /** @description HOC 中使用 forwardRef 转发 ref */ const HOCWithForwardRef = (Component: typeof React.Component) => { class WrapComponent extends React.Component<{ forwardedRef: LegacyRef<any> }> { render(): React.ReactNode { const { forwardedRef } = this.props return ( <div> <p>WrapComponent</p> <Component ref={forwardedRef} /> </div> ) } } return React.forwardRef((props, ref) => ( <WrapComponent forwardedRef={ref} {...props} /> )) } const HOCComponent2 = HOCWithForwardRef(TestComponent) const RefHOCWithForwardRefDemo = () => { const logger = createLoggerWithScope('RefHOCWithForwardRefDemo') const hocComponent2Ref = useRef(null) useEffect(() => { // hocComponent2Ref 指向的是 HOCComponent2 实例 logger.log(hocComponent2Ref.current) }, []) return <HOCComponent2 ref={hocComponent2Ref} /> } React中的ref怎么使用 2.2. ref 实现组件通信 一般我们可以通过父组件改变子组件 props 的方式触发子组件的更新渲染完成组件间通信 但如果我们不希望通过这种改变子组件 props 的方式的话还能有别的办法吗? 可以通过 ref 获取子组件实例,然后子组件暴露出通信的方法,父组件调用该方法即可触发子组件的更新渲染 对于函数组件,由于其不存在组件实例这样的说法,但我们可以通过 useImperativeHandle 这个 hook 来指定 ref 引用时得到的属性和方法,下面我们分别用类组件和函数组件都实现一遍 2.2.1. 类组件 ref 暴露组件实例 /** * 父 -> 子 使用 ref * 子 -> 父 使用 props 回调 */ class CommunicationDemoFather extends React.Component< {}, CommunicationDemoFatherState > { state: Readonly<CommunicationDemoFatherState> = { fatherToChildMessage: '', childToFatherMessage: '', } childRef = React.createRef<CommunicationDemoChild>() /** @description 提供给子组件修改父组件中的状态 */ handleChildToFather = (message: string) => { this.setState((state) => ({ ...state, childToFatherMessage: message, })) } constructor(props: {}) { super(props) this.handleChildToFather = this.handleChildToFather.bind(this) } render(): React.ReactNode { const { fatherToChildMessage, childToFatherMessage } = this.state return ( <div className={s.father}> <h4>父组件</h4> <p>子组件对我说:{childToFatherMessage}</p> <div className={s.messageInputBox}> <section> <label htmlFor="to-father">我对子组件说:</label> <input type="text" id="to-child" onChange={(e) => this.setState((state) => ({ ...state, fatherToChildMessage: e.target.value, })) } /> </section> {/* 父 -> 子 -- 使用 ref 完成组件通信 */} <button onClick={() => this.childRef.current?.setFatherToChildMessage( fatherToChildMessage, ) } > 发送 </button> </div> <CommunicationDemoChild ref={this.childRef} onChildToFather={this.handleChildToFather} /> </div> ) } } interface CommunicationDemoChildProps { onChildToFather: (message: string) => void } // 子组件自己维护状态 不依赖于父组件 props interface CommunicationDemoChildState { fatherToChildMessage: string childToFatherMessage: string } class CommunicationDemoChild extends React.Component< CommunicationDemoChildProps, CommunicationDemoChildState > { state: Readonly<CommunicationDemoChildState> = { fatherToChildMessage: '', childToFatherMessage: '', } /** @description 暴露给父组件使用的 API -- 修改父到子的消息 fatherToChildMessage */ setFatherToChildMessage(message: string) { this.setState((state) => ({ ...state, fatherToChildMessage: message })) } render(): React.ReactNode { const { onChildToFather: emitChildToFather } = this.props const { fatherToChildMessage, childToFatherMessage } = this.state return ( <div className={s.child}> <h4>子组件</h4> <p>父组件对我说:{fatherToChildMessage}</p> <div className={s.messageInputBox}> <section> <label htmlFor="to-father">我对父组件说:</label> <input type="text" id="to-father" onChange={(e) => this.setState((state) => ({ ...state, childToFatherMessage: e.target.value, })) } /> </section> {/* 子 -> 父 -- 使用 props 回调完成组件通信 */} <button onClick={() => emitChildToFather(childToFatherMessage)}> 发送 </button> </div> </div> ) } } React中的ref怎么使用 2.2.2. 函数组件 ref 暴露指定方法 使用 useImperativeHandle hook 可以让我们指定 ref 引用时能获取到的属性和方法,个人认为相比类组件的 ref,使用这种方式能够更加好的控制组件想暴露给外界的 API 而不像类组件那样直接全部暴露出去,当然,如果你想在类组件中只暴露部分 API 的话,可以用前面说的合并转发自定义 ref 的方式去完成 接下来我们就用 useImperativeHandle hook 改造上面的类组件实现的 demo 吧 interface ChildRef { setFatherToChildMessage: (message: string) => void } /** * 父 -> 子 使用 ref * 子 -> 父 使用 props 回调 */ const CommunicationDemoFunctionComponentFather: React.FC = () => { const [fatherToChildMessage, setFatherToChildMessage] = useState('') const [childToFatherMessage, setChildToFatherMessage] = useState('') const childRef = useRef<ChildRef>(null) return ( <div className={s.father}> <h4>父组件</h4> <p>子组件对我说:{childToFatherMessage}</p> <div className={s.messageInputBox}> <section> <label htmlFor="to-father">我对子组件说:</label> <input type="text" id="to-child" onChange={(e) => setFatherToChildMessage(e.target.value)} /> </section> {/* 父 -> 子 -- 使用 ref 完成组件通信 */} <button onClick={() => childRef.current?.setFatherToChildMessage(fatherToChildMessage) } > 发送 </button> </div> <CommunicationDemoFunctionCom (编辑:晋中站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
推荐文章
站长推荐