前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅谈 React Refs

浅谈 React Refs

作者头像
IMWeb前端团队
发布2019-12-03 17:17:12
9870
发布2019-12-03 17:17:12
举报
文章被收录于专栏:IMWeb前端团队

本文作者:IMWeb eden 原文出处:IMWeb社区 未经同意,禁止转载

React Refs

在React组件中,props是父组件与子组件的唯一通信方式,但是在某些情况下我们需要在props之外强制修改子组件或DOM元素,这种情况下React提供了Refs解决

哪些场景会用到refs

下面列举几个场景:

  • 对input/video/audio需要控制时,例如输入框焦点、媒体播放状态
  • 直接动画控制
  • 集成第三方库

注意:如果能使用props实现,应该尽量避免使用refs实现

Refs三种方式

  • 字符串模式 :废弃不建议使用
  • 回调函数
  • React.createRef() :React16.3提供

下面分别介绍每一种方式

字符串模式

代码语言:javascript
复制
class List extends React.Component {
  constructor(props, context) {
    super(props, context);
  }
  componentDidMount(){
    this.refs.inputEl.focus();
  }
  render() {
    const { list } = this.props;
    return (
        <input ref="inputEl"/>
    );
  }
}

存在的问题

  • 针对静态类型检测不支持
  • 对复杂用例难以实现:需要向父组件暴露dom;单个实例绑定多个dom
  • 绑定到的实例,是执行render方法的实例,结果会让人很意外,例如:
代码语言:javascript
复制
class Child extends React.Component {
  render() {
    const { renderer } = this.props;
    return <div>{renderer(1)}</div>;
  }
}
class App extends React.Component {
  render() {
    return (
      <div className="App">
        <Child renderer={index => <div ref="test">{index}</div>} />
      </div>
    );
  }
}

上面这种情况,会导致test绑定的实例时Child上面,并不是App上

回调函数模式

相比 字符串模式 更加灵活,也避免了诸多问题

  • 可以优雅在组件销毁时回收变量, ref中的回调函数会在对应的普通组件componentDidMount,ComponentDidUpdate之前; 或者componentWillUnmount之后执行,componentWillUnmount之后执行时,callback接收到的参数是null
  • 很好的支持静态类型检测
  • 针对数组遍历时可以直接转换为对应的数组,看下面的例子
代码语言:javascript
复制
class List extends React.Component {
  constructor(props, context) {
    super(props, context);
  }
  _ref = el => {
    if (el) {
      if (!this.els) {
        this.els = [];
      }
      this.els.push(el);
    } else {
      this.els = [];
    }
  };
  render() {
    const { list } = this.props;
    return (
      <ul>
        {list.map((item, index) => {
          return (
            <li ref={this._ref} key={index}>
              {item}
            </li>
          );
        })}
      </ul>
    );
  }
}
class App extends React.Component {
  state = {
    value: "",
    list: []
  };
  onChange = ({ target: { value } }) => {
    this.setState({ value });
  };
  add = () => {
    const { list, value } = this.state;
    list.push(value);
    this.setState({
      value: "",
      list
    });
  };
  render() {
    const { value, list } = this.state;
    return (
      <div className="App">
        <input value={value} onChange={this.onChange} />
        <button onClick={this.add}>add</button>
        <List list={list} />
      </div>
    );
  }
}
  • 可以将父组件的ref 传入 孙组件,虽然不建议这么使用(破坏组件封装)
代码语言:javascript
复制
function Input(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class InputBox extends React.Component {
 _ref = (el)=>{
     this.inputElement = el
 }
  render() {
    return (
      <Input
        inputRef={this._ref}
      />
    );
  }
}

同样存在弊端

通常为了绑定一个组件(元素)实例到当前实例上需要写一个函数,代码结构上看起来很冗余,为了一个变量,使用一个函数去绑定,每一个绑定组件(元素)都需要一个方法处理,大材小用

因此React 提供了更简单有效的解决方案React.createRef()

React.createRef()

使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素。当构造组件时,refs 通常被赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.

代码语言:javascript
复制
class Test extends React.Component{
    myRef = React.createRef();
    componentDidMount(){
      // 访问ref
      const dom = this.myRef.current
    }
    render(){
        return (
            <div ref={this.myRef}/>
        )
    }
}

试试不是比创建一个回调函数更加简洁了?

ref的值取决于节点的类型:

  • 当 ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为它的 current 属性以创建 ref 。
  • 当 ref 属性被用于一个自定义类组件时,ref 对象将接收该组件已挂载的实例作为它的 current 。
  • 你不能在函数式组件上使用 ref 属性,因为它们没有实例。

Refs传递

  • 额外参数传递
代码语言:javascript
复制
class Sub extends Component{
    render(){
        const {forwardRef} = this.props;
        return <div ref={forwardRef}/>
    }
}
class Sup extends Component{
    subRef = React.createRef();
    render(){
        return <Sub forwardRef={this.subRef}/>
    }
}

将父组件ref作为一个props传入,在子组件显示调用

  • React.forwardRef
代码语言:javascript
复制
class Sub extends Component{
    render(){
        const {forwardRef} = this.props;
        return <div ref={forwardRef}/>
    }
}

function forwardRef(props, ref){
    return <Sup {...props} forwardRef={ref}/>
}
// 为了devtool中展示有意义的组件名称
forwardRef.displayName=`forwardRef-${Component.displayName||Component.name}`

const XSub = React.forwardRef(forwardRef);

class Sup extends Component{
    _ref=(el)=>{this.subEl =el};
    render(){
        return <XSub ref={this._ref}/>
    }
}

React.forwardRef方式,对于使用组件者来说,ref是透明的,不需要额外定一个props传入,直接传递到了下级组件,作为高阶组件封装时,这样做更加友好.

实现原理

首先我们看看String Refs的实现 String refs 在组件挂载、更新之前会被替换为一个函数如下图,因此实际上,String refs只是 函数是Refs的一种特殊场景

Ref在何时被赋值?

在源码中有两个方法commitAttachRef 挂载实例,commitDetachRef 卸载实例。commitAttachRef当组件渲染完毕(componentDidMount/comonentDidUpdate)后被执行; commitDetachRef 则在组件或元素被销毁前执行(componentWillUnmount之前),清理引用

挂载: 如果是方法直接执行并传入实例,否则就是采用createRef创建的对象,作为挂在点

卸载:方法被传入null值,createRef方式就将current赋值null,因此我们在使用函数模式时要注意传入null时需要清理引用,有的场景我们会将多个实例绑定到一个同一个对象或数组上。

总结

Refs 字符串模式已经废弃,React 不建议使用并且会提示警告,开发中推荐使用React.forwardRef方式,简单优雅,回调函数模式应用在复杂场景中。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-08-01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • React Refs
    • 哪些场景会用到refs
      • Refs三种方式
        • 字符串模式
          • 存在的问题
        • 回调函数模式
          • 同样存在弊端
        • React.createRef()
          • Refs传递
          • 实现原理
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档