The use of ref in React (class components and function components) and a detailed explanation of forwardRef and useImperativeHandle

foreword

In a parent component, we want to get the node element or child component instance in it, so as to directly call the method on it. Class class components and function components are two different ways of writing.

1. Use ref in the Class component

In React's Class component, we createRefcreate ref by

class Parent extends React.Component {
    
    
    constructor(props) {
    
    
        super(props)
        this.inputRef = React.createRef();
    }
    
    componentDidMount() {
    
    
        console.log(this.inputRef)
        this.inputRef.current.focus();
    }
    render() {
    
    
        return <input ref={
    
    this.inputRef} />
    }
}

In the above code example, we used to createRefcreate one ref, and hung it on the native DOM element input. At this time, we can ref.currentget this DOM element through and call the above method.

If ref is mounted on a Class component, ref.current gets the instance of the Class component, and can also access the methods of the Class component.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-mhBeu0cR-1669790444660) (C:\Users\Two Three\AppData\Roaming\Typora\typora-user-images \image-20221130102359179.png)]

Take the above page as an example, the following [Drawing Plan] is a sub-component, when we mount refto the sub-component, the corresponding parent component can this.ref.currentcall the method on the sub-component, such as the drop-down box to expand Callbacks, callbacks for drop-down box value changes, etc.

2. Functional components

ref 回调函数After the component is mounted, the component instance will be passed to the function. The function component is different from the Class component, and the function component has no instance . So under normal circumstances, ref cannot be mounted on function components.

However, function components can create refs. Unlike class components, function components use useRef.

import React, {
    
     memo, useEffect,  useRef, useState } from 'react';
const Parent = () => {
    
    
    const inputRef = useRef(null)
    input childRef = useRef(null)
    
    return (
        <>
        	<input ref={
    
    inputRef} onChange={
    
    onChange} />
        	<Child ref={
    
    childRef} handSave={
    
    handSave} />
        </>
    )
}

export default memo(Parent)

Note : This way of writing is grammatically correct, but if you want to ref.currentget an instance of a DOM element or subcomponent in a function component, you can't get it, and you need another way of writing.

3. The difference between createRef and useRef

  • createRef can only be used in Class class components, and useRef can only be used in functional components.
  • createRef returns a new ref every render, useRef returns the same ref every time.
  • If the ref created by createRef is used in a functional component, its value will be continuously initialized as the functional component is re-executed .

4. Partial conclusions

In summary, the following points can be summarized:

  • Native DOM element: the current of ref points to this node
  • class class component: the current of ref points to the component instance
  • Functional component: the current of ref points to null, because the functional component has no instance.

So for functional components, is there really no way for us to get and call the methods on the subcomponents?

Answer: How is it possible, we can forwardRefsolve this problem by using .

5. forwardRef

forwardRef (passing by reference) is a technique for automatically passing a reference ref from a component to a child component.

In one sentence: React uses forwardRef to complete the transparent transmission of ref, so that functional components can normally obtain the methods on subcomponents.

5.1 Code writing

import React, {
    
     memo, forwardRef,  useRef, useImperativeHandle } from 'react';

const App = () => {
    
    
    const childRef = useRef(null)
    
    const getFocus = () => {
    
    
         // 调用子组件的方法
        childRef.current.inputFocus()
        // 也可以调用暴露出来的其他值
        childRef.current.setData(20)
    }
    
    return (
        <>
        	<Child ref={
    
    childRef}/>
        	<button onClick ={
    
    getFocus}>点击获取焦点<button/>
        </>
    )
}

// 子组件

const Child = forwardRef((props, ref) => {
    
    
    const inputRef = useRef(null)
    const [data, setData] = useState('10')
    
    // 使输入框获取焦点的方法
    const inputFocus = () => {
    
    
        inputRef.current.ocus()
    }
    // 输入框内容改变回调
    const changeValue = () => {
    
    
    	console.log('哈哈哈')
    }
    
    // 将该方法暴露给父组件
    useImperativeHandle(ref, () => ({
    
    
        inputFocus,
        changeValue,
        data,
        setData
    }))
    
    return <input  ref={
    
    inputRef} onChange={
    
    changeValue}>
});

5.2 forwardRef parsing

forwardRef You can directly wrap a functional component , and the wrapped functional component will get the ref assigned to itself (as the second parameter). If you directly assign ref to a functional component that is not wrapped by forwardRef, React will report an error in the console.

forwardRefA React component will be created, which can forward the ref attribute it accepts to another component under its component tree, that is, transparent transmission.

Note: the parameter of forWardRef must be function

6. useImperativeHandle parsing

When used directly forwardRef, we cannot control the value to be exposed to the parent component, so we use to useImperativeHandlecontrol what to expose to the parent component.

Note: useImperativeHandle should be used together with forwardRef.

The calling method is shown in the above code example.

6.1 Parameter passing

  • The first parameter: ref : Receive the ref passed from forwardRef.
  • The second parameter: createHandle : Handling function, the return value is exposed as a ref object to the parent component.
  • The third parameter: deps : dependency deps, dependency changes form a new ref object, can not be passed

useImperativeHandle provides us with an instance-like thing that helps us mount the content of the returned object to the ref.current of the attachment through the second parameter of useImperativeHandle

7. Summary

The steps when the parent and child components use the hooks are as follows:

  1. The parent component uses to useRefcreate a ref object and assign this ref object to the ref property of the child component.
  2. The child component wraps forwardRefitself with a , allowing the ref to be used as a function component itself. Then use useImperativeHandlethe hook function to return some state or method in the second function parameter of the hook function, and the returned state or method can be accessed by the parent component.
  3. The parent component uses the property of the created ref object currentto obtain the state or method exposed by the child component.

8. Special attention points:

forwardRef When using and in functional components useImperativeHandle, there is a special point that needs attention: the method of passing parameters in subcomponents . As follows:

// 可以使用这样的写法
    <Child ref={
    
    childRef} list={
    
    drawingPlanList} editable={
    
    !isDisabled}/>

// 但不用采用这样的写法:这样的 写法有问题,具体原因待排查

	const childProps = {
    
    
		ref: {
    
    childRef},
		list: {
    
    drawingPlanList},
		editable: {
    
    !isDisabled}
	}
    < Child {
    
    ...childProps}/>

Guess you like

Origin blog.csdn.net/qq_41131745/article/details/128114965