React的Refs转发用法详解!

一、什么是 ref

ref 就是DOM节点或class组件的引用,方便其他组件访问其内部方法。


1、创建 ref

创建 ref 并将其与React元素的 ref 属性进行关联。

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    render() {
        return <div ref={this.myRef} />;

2、访问 ref

const node = this.myRef.current;

二、用 ref 操作DOM元素

用button点击,让输入焦点回到input。复制下面代码到 index.html ,用浏览器打开即可看到效果。

<!DOCTYPE html>
    <meta charset="UTF-8" />
    <title>DOM元素使用ref</title>
    <!-- 第一步:加载开发版本的React -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 部署时,请用下面链接替代上面 -->
    <!-- <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> -->
    <!-- 第二步:加载开发版本的JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
    <div id="root"></div>
    <script type="text/babel">
        class CustomTextInput extends React.Component {
            constructor(props) {
                super(props);
                // 第一步:创建一个 ref 来存储 textInput 的 DOM 元素
                this.textInput = React.createRef();
                this.focusTextInput = this.focusTextInput.bind(this);
            focusTextInput() {
                // 第二步:直接使用原生 API 使 text 输入框获得焦点
                // 注意:我们通过 "current" 来访问 DOM 节点
                this.textInput.current.focus();
            render() {
                // 第三步:告诉 React 我们想把 <input> ref 关联到
                // 构造器里创建的 `textInput` 上
                return (
                        <input
                            type="text"
                            ref={this.textInput} />
                        <input
                            type="button"
                            value="Focus the text input"
                            onClick={this.focusTextInput}
        ReactDOM.render(<CustomTextInput />, document.getElementById('root'));
    </script>
</body>
</html>

三、用 ref 操作class组件

利用ref来调用子组件的方法。复制下面代码到 index.html ,用浏览器打开即可看到效果。

<!DOCTYPE html>
    <meta charset="UTF-8" />
    <title>class组件使用ref</title>
    <!-- 第一步加载开发版本的React -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 部署时请用下面链接替代上面 -->
    <!-- <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> -->
    <!-- 第二步加载开发版本的JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
    <div id="root"></div>
    <script type="text/babel">
        class CustomTextInput extends React.Component {
            constructor(props) {
                super(props);
                this.state = { text: "" };
            setTextInput(text) {
                this.setState({ text: text });
            render() {
                return (
                        <input type="text" value={this.state.text} onChange={() => { }} />
        class SetTextInput extends React.Component {
            constructor(props) {
                super(props);
                // 第一步:创建Ref
                this.textInput = React.createRef();
            componentDidMount() {
                // 第三步:渲染时,调用其他组件内的方法
                this.textInput.current.setTextInput("测试");
            render() {
                // 第二步:创建的Ref与组件关联(赋值)
                return (
                    <CustomTextInput ref={this.textInput} />
        ReactDOM.render(<SetTextInput />, document.getElementById('root'));
    </script>
</body>
</html>

四、用 ref 操作函数组件

1、错误案例

函数组件因为没有实例,默认情况下无法使用 ref ,下面代码是错误的。

function MyFunctionComponent() {
    return <input />;
class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.textInput = React.createRef();
    render() {
        // This will *not* work!
        return (
            <MyFunctionComponent ref={this.textInput} />

2、useRef

class组件内用 React.createRef() 实现 ref 功能;而函数组件内,用Hook函数 useRef 实现 ref 功能 。复制下面代码到 index.html ,用浏览器打开即可看到效果。

<!DOCTYPE html>
    <meta charset="UTF-8" />
    <title>函数组件使用ref</title>
    <!-- 第一步:加载开发版本的React -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 部署时,请用下面链接替代上面 -->
    <!-- <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> -->
    <!-- 第二步:加载开发版本的JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
    <div id="root"></div>
    <script type="text/babel">
        function CustomTextInput(props) {
            // 这里必须声明 textInput,这样 ref 才可以引用它
            const textInput = React.useRef(null);
            function handleClick() {
                textInput.current.focus();
            return (
                    <input
                        type="text"
                        ref={textInput} />
                    <input
                        type="button"
                        value="Focus the text input"
                        onClick={handleClick}
        ReactDOM.render(<CustomTextInput />, document.getElementById('root'));
    </script>
</body>
</html>

五、什么是Refs转发

Ref 转发 是将 ref 传递给子组件的技巧,特别适用于 重用组件

1、ref 默认不传递给子节点

复制下面代码到 index.html ,用浏览器打开,在控制台里看输出。

<!DOCTYPE html>
    <meta charset="UTF-8" />
    <title>组件间传递ref</title>
    <!-- 第一步:加载开发版本的React -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 部署时,请用下面链接替代上面 -->
    <!-- <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> -->
    <!-- 第二步:加载开发版本的JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
    <div id="root"></div>
    <script type="text/babel">
        class FancyButton extends React.Component {
            handleClick() {
                console.log('FancyButton')
            render() {
                return <button onClick={this.handleClick}>{this.props.label}</button>
        function logProps(WrappedComponent) {
            class LogProps extends React.Component {
                componentDidMount(prevProps) {
                    console.log('old props:', prevProps)
                    console.log('new props:', this.props)
                handleClick() {
                    console.log('LogProps')
                render() {
                    return <WrappedComponent {...this.props} />;
            return LogProps;
        let LogProps = logProps(FancyButton);
        class App extends React.Component {
            constructor(props) {
                super(props)
                // 第一步:创建ref
                this.ref = React.createRef()
            componentDidMount() {
                // 第三步:ref赋值给LogProps后,但LogProps并不能把ref传递给子组件FancyButton,所以只能调用LogProps的handleClick
                this.ref.current.handleClick();
            // 第二步:创建的Ref与组件关联(赋值)
            render() {
                return <LogProps label="Click Me" ref={this.ref} />
        ReactDOM.render(<App />, document.getElementById('root'));
    </script>
</body>
</html>

2、解决默认不转发的问题

React.forwardRef 可以利用props,把ref传递给子组件。复制下面代码到 index.html ,用浏览器打开,在控制台里看输出。

<!DOCTYPE html>
    <meta charset="UTF-8" />
    <title>组件间传递ref</title>
    <!-- 第一步加载开发版本的React -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 部署时请用下面链接替代上面 -->
    <!-- <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> -->
    <!-- 第二步加载开发版本的JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
    <div id="root"></div>
    <script type="text/babel">
        class FancyButton extends React.Component {
            handleClick() {
                console.log('FancyButton')
            render() {
                return <button onClick={this.handleClick}>{this.props.label}</button>
        function logProps(WrappedComponent) {
            class LogProps extends React.Component {
                componentDidMount(prevProps) {
                    console.log('old props:', prevProps)
                    console.log('new props:', this.props)
                handleClick() {
                    console.log('LogProps')
                render() {
                    // 第五步:读取父容器的forwardedRef属性;
                    const { forwardedRef, ...rest } = this.props;
                    // 第六步:将forwardedRef与WrappedComponent的ref属性进行关联;
                    // 也就是跟FancyButton组件的ref进行关联。
                    return <WrappedComponent ref={forwardedRef} {...rest} />;
            // 第四步:调用React.forwardRef,把ref赋值给LogProps的自定义属性“forwardedRef”进行传递;
            // forwardedRef:此属性名可随意设置;
            // {...props}:这是js的展开语法,表示把props所有值都传递给LogProps
            return React.forwardRef((props, ref) => {
                return <LogProps {...props} forwardedRef={ref} />;
        // 第三步:调用高阶组件logProps创建新组件LogProps
        let LogProps = logProps(FancyButton);
        class App extends React.Component {
            constructor(props) {
                super(props)
                // 第一步:创建ref
                this.ref = React.createRef()
            componentDidMount() {
                // 第七步:调用ref
                this.ref.current.handleClick();
            // 第二步:把创建的ref传递给组件LogProps
            render() {
                return <LogProps label="Click Me" ref={this.ref} />
        ReactDOM.render(<App />, document.getElementById('root'));
    </script>
</body>
</html>

六、回调Refs

不通过 createRef() 设置ref,而是通过回调方式设置ref。复制下面代码到 index.html ,用浏览器打开即可看到效果。

<!DOCTYPE html>
    <meta charset="UTF-8" />
    <title>回调ref</title>
    <!-- 第一步:加载开发版本的React -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 部署时,请用下面链接替代上面 -->
    <!-- <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> -->
    <!-- 第二步:加载开发版本的JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
    <div id="root"></div>
    <script type="text/babel">
        class CustomTextInput extends React.Component {
            constructor(props) {
                super(props);
                // 第一步:申请ref,但是不赋值
                this.textInput = null;
                this.setTextInputRef = element => {
                    this.textInput = element;
                this.focusTextInput = () => {
                    // 使用原生 DOM API 使 text 输入框获得焦点
                    if (this.textInput) this.textInput.focus();
            componentDidMount() {
                // 组件挂载后,让文本框自动获得焦点
                this.focusTextInput();
            render() {
                // 第二步:使用回调函数setTextInputRef,将input的ref属性关联到第一步申请的ref
                // 第三步:通过button的click事件,调用ref使得textinput输入聚焦
                return (
                        <input
                            type="text"
                            ref={this.setTextInputRef}
                        <input
                            type="button"
                            value="Focus the text input"
                            onClick={this.focusTextInput}
        ReactDOM.render(<CustomTextInput />, document.getElementById('root'));
    </script>
</body>
</html>

七、组件间传递ref

通过组件的props属性,来传递设置ref,让input启动就聚焦输入光标。复制下面代码到 index.html ,用浏览器打开即可看到效果。

<!DOCTYPE html>
    <meta charset="UTF-8" />
    <title>组件间传递ref</title>
    <!-- 第一步:加载开发版本的React -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 部署时,请用下面链接替代上面 -->
    <!-- <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> -->
    <!-- 第二步:加载开发版本的JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
    <div id="root"></div>
    <script type="text/babel">
        function CustomTextInput(props) {
            // 第三步:接收到属性inputRef后,用input的ref与inputRef属性进行关联
            return (
                    <input ref={props.inputRef} />
        class Parent extends React.Component {
            constructor(props) {
                super(props);
                // 第一步:申请ref,但不赋值;
                this.inputElement = null;
                this.focusTextInput = () => {
                    // 使用原生 DOM API 使 text 输入框获得焦点
                    if (this.inputElement) {
                        this.inputElement.focus();
            componentDidMount() {
                // 组件挂载后,让文本框自动获得焦点
                this.focusTextInput();
            // 第二步:自定义属性inputRef,把第一步申请的ref,通过props属性传递给组件CustomTextInput
            render() {
                return (
                    <CustomTextInput
                        inputRef={el => this.inputElement = el}