useRef、createRef的区别及使用,及useRef妙用

3,147 阅读3分钟

最近开始学习react,记录一下学习的过程。

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。useRef是其中一个常用的hook。

useRef最基本的用法,是在函数组件中操作DOM。在类组件中对应的api为createRef。我们来看一下两个api的基本使用、两个API的区别以及useRef的妙用。


一、DOM相关操作

我们使用createRef和useRef,分别在类组件和函数组件中去做到同一个事情:父组件获取子组件的state和方法

1、createRef

// 父组件
class ParentComponent extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef(null);
    }
    render() {
        return (
            <div>
                <ChildComponent ref={this.myRef}></ChildComponent>
                <button
                    onClick={() => {
                        alert(this.myRef.current.state.childName);
                    }}
                >
                    get child childName from parent
                </button>
                <button
                    onClick={() => {
                        this.myRef.current.sayHello();
                    }}
                >
                    get child function from parent
                </button>
            </div>
        );
    }
}

// 子组件
class ChildComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            childName: "mongo",
        };
    }
    sayHello() {
        alert("hello! " + this.state.childName);
    }
    render() {
        return (
            <div>
                <button
                    onClick={() => {
                        this.sayHello();
                    }}
                >
                    alert function by child
                </button>
            </div>
        );
    }
}

2、useRef

const ParentComponentByUseRef = () => {
    const myRef = useRef();
    return (
        <div>
            <ChildComponentByUseRef ref={myRef}></ChildComponentByUseRef>
            <button
                onClick={() => {
                    alert(myRef.current.childName);
                }}
            >
                get childName by parent
            </button>
            <button
                onClick={() => {
                    myRef.current.sayHello();
                }}
            >
                get function by parent
            </button>
        </div>
    );
};

const ChildComponentByUseRef = forwardRef((props, ref) => {
    // 暴露function、state和DOM结构给父元素
    useImperativeHandle(ref, () => ({
        sayHello,
        childName,
        ref
    }));
    const [childName, setChildName] = useState("mongo");
    const sayHello = () => {
        alert("hello! " + childName);
    };
    return (
        <div ref={ref}>
            <button
                onClick={() => {
                    sayHello();
                }}
            >
                get function by child
            </button>
            <button
                onClick={() => {
                    setChildName("mongowoo");
                }}
            >
                change childName
            </button>
        </div>
    );
});

export default forwardRef(ParentComponentByUseRef);

tips:hooks中如果要进行refs转发,要配合fowardRef和useImperativeHandle使用

fowardRef : React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:

  • 转发 refs 到 DOM 组件
  • 在高阶组件中转发 refs

useImperativeHandle:useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用


二、createRef 和 useRef的区别

我们知道useRef是hooks新增的API,在类函数中肯定无法使用。那createRef在函数组件中可以使用吗?我们试一下。写一个简单的点击button设置input focus的效果。

const createRefInFunction = () => {
    const myRef = createRef(null);
    return (
        <div>
            <input ref={myRef}></input>
            <button
                onClick={() => {
                    myRef.current.focus();
                }}
            >
                set focus
            </button>
        </div>
    );
};

export default createRefInFunction;

结果发现createRef在函数组件中也是可用的,那为什么还要新增一个useRef API呢?

看下useRef定义,我们发现官方文档已经说出了它区别于createRef的地方:

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

由此可以知道,createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。 从文字来看可能比较难理解,我们写一个简单demo就能明白了。

const useRefAndCreateRefDiffrent = () => {
    const [renderIndex, setRenderIndex] = useState(1);
    const refFromUseRef = useRef();
    const refFromCreateRef = createRef();
    if (!refFromUseRef.current) {
        refFromUseRef.current = renderIndex;
    }

    if (!refFromCreateRef.current) {
        refFromCreateRef.current = renderIndex;
    }

    return (
        <div>
            <p>current render index is: {renderIndex}</p>
            <p>refFromUseRef render index is: {refFromUseRef.current}</p>
            <p>refFromCreateRef render index is: {refFromCreateRef.current}</p>

            <button
                onClick={() => {
                    setRenderIndex((prev) => prev + 1);
                }}
            >
                增加render index
            </button>
        </div>
    );
};

根据结果可以看到,index已经增加到了7,createRef显示7,而useRef只显示1。可以知道,随着render重新渲染,createRef的值也每次都重新渲染了。而useRef只初始化了一次,所以才最后显示1。

image.png


三、useRef妙用

我们知道了useRef不仅可以获取转发DOM,还可以存放任何变量,且有只初始化一次的特性。可以使用这个特性做到一些特殊的需求。

比如,获取上一个state值。

const getPreCount = () => {
    const [count, setCount] = useState(0);
    const preCount = useRef();

    useEffect(() => {
        preCount.current = count;
    });

    return (
        <div>
            {console.log("in render")}
            <div>precount:{preCount.current}</div>
            <p>you clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>Click Me</button>
        </div>
    );
};

结果如下:

image.png


End:刚开始学习react,若文中有错误请大家指正。