一步一步搞懂react的createRef和forwardRef

栏目: IT技术 · 发布时间: 5年前

内容简介:最近在使用react过程中发现在使用ref时的一些场景,自己初步感觉react的ref没有vue那么强大。现在我就简单看下怎么使用ref?我们直接看源码

最近在使用react过程中发现在使用ref时的一些场景,自己初步感觉react的ref没有vue那么强大。

现在我就简单看下怎么使用ref?

createRef

我们直接看源码

// node_modules/react/umd/react.development.js

// an immutable object with a single mutable value
  function createRef() {
    var refObject = {
      current: null
    };

    {
      Object.seal(refObject);
    }

    return refObject;
  }

其实 createRef 也没做什么,就是返回了一个对象 { current: null} ,但是在返回值之前进行了 Object.seal 操作

Object.seal() 方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。

我们简单测试下

一步一步搞懂react的createRef和forwardRef

createRef使用场景

我们一般是在构造函数里面先新建个空的ref,为了方便在调试 工具 中查看,我们把ref放到state里面。

比如

import AsideMenu from './Menu.jsx';

export default class Layout extends React.Component {
    constructor() {
        this.state = {
            headerRef: createRef(),
            menuRef: createRef(),
            classRef: createRef(),
        }
    }
    render() {
        return (
            <>
                <header ref={this.state.headerRef} />
                <ClassCom ref={this.state.classRef} />
                <AsideMenu ref={this.state.menuRef} />
            </>
        )
    }
}

其中 header 是原生的node节点 headerClassCom 是个class类组件, AsideMenu 是函数式组件。

const Menu = (props) => (
  <aside id="menu" className={props.show ? 'show' : ''}>
    <div className="inner flex-row-vertical">
      <Profile avatarUrl="/owner.jpg"/>
      <div className="scroll-wrap flex-col">
        <MenuList asides={props.asides}/>
        <ArchiveList />
      </div>
    </div>
  </aside>
);
export default Menu;

一步一步搞懂react的createRef和forwardRef

我们可以得出下面的结论

  1. 原生node节点,current指向该节点
  2. class类react组件,current指向该组件实例
  3. 函数式react组件,current仍然指向null。没错,就是新建时的null,未曾变更过。因为函数式组件没有实例嘛

同时我们可以在控制台看到这样的警告 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
一步一步搞懂react的createRef和forwardRef 看来函数式组件也是有办法使用像class组件那样使用ref的,我们需要 forwardRef 的帮助

forwardRef

function forwardRef(render) {
    {
      if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
        error('forwardRef requires a render function but received a `memo` ' + 'component. Instead of forwardRef(memo(...)), use ' + 'memo(forwardRef(...)).');
      } else if (typeof render !== 'function') {
        error('forwardRef requires a render function but was given %s.', render === null ? 'null' : typeof render);
      } else {
        if (render.length !== 0 && render.length !== 2) {
          error('forwardRef render functions accept exactly two parameters: props and ref. %s', render.length === 1 ? 'Did you forget to use the ref parameter?' : 'Any additional parameter will be undefined.');
        }
      }

      if (render != null) {
        if (render.defaultProps != null || render.propTypes != null) {
          error('forwardRef render functions do not support propTypes or defaultProps. ' + 'Did you accidentally pass a React component?');
        }
      }
    }

    return {
      $$typeof: REACT_FORWARD_REF_TYPE,
      render: render
    };
  }

根据源码来看 forwardRef 也没有什么神操作,从返回值来看就是将 render 作为返回值的render属性,同时还会再加一个 $$typeof 属性,它的值是 REACT_FORWARD_REF_TYPE ,这显然是一个枚举值, $$typeof 属性应该是就是供其它地方检测用的。

当然在 forwardRef 方法返回值之前会做几个判断,里面其实也用到了 $$typeof 属性。

那我们参照官网的示例简单改造下我们AsideMenu组件。

const Menu = forwardRef((props, ref) => (
  <aside ref={ref} id="menu" className={props.show ? 'show' : ''}>
    <div className="inner flex-row-vertical">
      <Profile avatarUrl="/owner.jpg"/>
      <div className="scroll-wrap flex-col">
        <MenuList asides={props.asides}/>
        <ArchiveList />
      </div>
    </div>
  </aside>
));

export default Menu;

一步一步搞懂react的createRef和forwardRef 现在我们就看不到警告,同时 this.state.menuRef.current 不再是null了,而是指向了组件对应的node节点 aside 了。跟直接使用在原生node上没什么区别。

仔细查看开发者我们会发现这个改造后的AsideMenu组件实际返回的是个匿名组件。

一步一步搞懂react的createRef和forwardRef

这样的坏处是在调试工具中不好识别,有些团队的eslint也会不允许使用匿名组件。

那我们再微调下。

const Menu = (props, ref) => (
  <aside ref={ref} id="menu" className={props.show ? 'show' : ''}>
    <div className="inner flex-row-vertical">
      <Profile avatarUrl="/owner.jpg"/>
      <div className="scroll-wrap flex-col">
        <MenuList asides={props.asides}/>
        <ArchiveList />
      </div>
    </div>
  </aside>
);
export default forwardRef(Menu);
一步一步搞懂react的createRef和forwardRef

再看一下,现在好了,继续用回Menu这个名字了。

完美!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTTP Essentials

HTTP Essentials

Stephen A. Thomas、Stephen Thomas / Wiley / 2001-03-08 / USD 34.99

The first complete reference guide to the essential Web protocol As applications and services converge and Web technologies not only assume HTTP but require developers to manipulate it, it is be......一起来看看 《HTTP Essentials》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具