关于前端:React-Ref-其实是这样的

5次阅读

共计 4324 个字符,预计需要花费 11 分钟才能阅读完成。

大家好,我是 Mokou,良久没有冒泡了,最近始终在看钻研算法和数据结构方面的货色,然而仿佛很多前端不喜爱看这种货色,而且目前自己算法方面也很挫,就不献丑了。

当然了,最近也开始钻研 React 了,这篇文章次要是讲述 Ref 相干的内容,如有谬误请斧正。

ref 的由来

在典型的 React 数据流中,props 是父组件与子组件交互的惟一形式。要批改一个子组件,你须要应用新的 props 来从新渲染它。然而,在某些状况下,你须要在典型数据流之外强制批改子组件 / 元素。

适宜应用 refs 的状况:

  • 治理焦点,文本抉择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

ref 的三种形式

在 React v16.3 之前,ref 通过字符串(string ref)或者回调函数(callback ref)的模式进行获取。

ref 通过字符获取:

// string ref
class MyComponent extends React.Component {componentDidMount() {this.refs.myRef.focus();
  }

  render() {return <input ref="myRef" />;}
}

ref 通过回调函数获取:

// callback ref
class MyComponent extends React.Component {componentDidMount() {this.myRef.focus();
  }

  render() {return <input ref={(ele) => {this.myRef = ele;}} />;
  }
}

在 v16.3 中,经 0017-new-create-ref 提案引入了新的 API:React.createRef

ref 通过 React.createRef 获取:

// React.createRef
class MyComponent extends React.Component {constructor(props) {super(props);
    this.myRef = React.createRef();}

  componentDidMount() {this.myRef.current.focus();
  }
  
  render() {return <input ref={this.myRef} />;
  }
}

将被移除的 string ref

首先来具体说说 string ref,string ref 就已被诟病已久,React 官网文档中如此申明:"如果你目前还在应用 this.refs.textInput 这种形式拜访 refs,咱们倡议用回调函数或 createRef API 的形式代替。",为何如此蹩脚?

最后由 React 作者之一的 dan abramov。公布于 https://news.ycombinator.com/edit?id=12093234,(该网站须要梯子)。吐槽内容次要有以下几点:

  1. string ref 不可组合。例如一个第三方库的父组件曾经给子组件传递了 ref,那么咱们就无奈在在子组件上增加 ref 了。另一方面,回调援用没有一个所有者,因而您能够随时编写它们。例如:
/** string ref **/
class Parent extends React.Component {componentDidMount() {
    // 可获取到 this.refs.childRef
    console.log(this.refs);
  }
  render() {const { children} = this.props;
    return React.cloneElement(children, {ref: 'childRef',});
  }
}

class App extends React.Component {componentDidMount() {
    // this.refs.child 无奈获取到
    console.log(this.refs);
  }
  render() {
    return (
      <Parent>
        <Child ref="child" />
      </Parent>
    );
  }
}
  1. string ref 的所有者由以后执行的组件确定。这意味着应用通用的“渲染回调”模式(例如 react),谬误的组件将领有援用(它将最终在 react 上而不是您的组件定义 renderRow)。
class MyComponent extends Component {renderRow = (index) => {
    // string ref 会挂载在 DataTable this 上
    return <input ref={'input-' + index} />;

    // callback ref 会挂载在 MyComponent this 上
    return <input ref={input => this['input-' + index] = input} />;
  }

  render() {return <DataTable data={this.props.data} renderRow={this.renderRow} />
  }
}
  1. string ref 不适用于 Flow 之类的动态剖析。Flow 不能猜想框架能够使字符串 ref“呈现”在 react 上的神奇成果,以及它的类型(可能有所不同)。回调援用比动态剖析更敌对。
  2. string ref 强制 React 跟踪以后正在执行的组件。这是有问题的,因为它使 react 模块处于有状态,并在捆绑中复制 react 模块时导致奇怪的谬误。在 reconciliation 阶段,React Element 创立和更新的过程中,ref 会被封装为一个闭包函数,期待 commit 阶段被执行,这会对 React 的性能产生一些影响。

对于这点能够参考 React 源码 coerceRef 的实现:

在和谐子节点得过程中,会对 string ref 进行解决,把他转换成一个办法,这个办法次要做的事件就是设置 instance.refs[stringRef] = element,相当于把他转换成了 function ref

对于更新得过程中 string ref 是否变动须要比照得是 current.ref._stringRef,这里记录了上一次渲染得时候如果应用得是 string ref 他的值是什么

owner 是在调用 createElement 的时候获取的,通过 ReactCurrentOwner.current 获取,这个值在更新一个组件前会被设置,比方更新 ClassComponent 的时候,调用 render 办法之前会设置,而后调用 render 的时候就能够获取对应的 owner 了。

坚挺的 callback ref

React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保障 refs 肯定是最新的。

如果 ref 回调函数是以内联函数的形式定义的,在更新过程中它会被执行两次,第一次传入参数 null,而后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创立一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的形式能够防止上述问题,然而大多数状况下它是无关紧要的。

最新的 React.createRef

React.createRef 的长处:

  • 绝对于 callback ref 而言 React.createRef 显得更加直观,防止了 callback ref 的一些了解问题。

React.createRef 的毛病:

  1. 性能略低于 callback ref
  2. 能力上仍逊色于 callback ref,例如上一节提到的组合问题,createRef 也是无能为力的。

ref 的值依据节点的类型而有所不同:

  • 当 ref 属性用于 HTML 元素时,构造函数中应用 React.createRef() 创立的 ref 接管底层 DOM 元素作为其 current 属性。
  • 当 ref 属性用于自定义 class 组件时,ref 对象接管组件的挂载实例作为其 current 属性。
  • 默认状况下,你不能在函数组件上应用 ref 属性(能够在函数组件外部应用),因为它们没有实例:

    • 如果要在函数组件中应用 ref,你能够应用 forwardRef(可与 useImperativeHandle 联合应用)
    • 或者能够将该组件转化为 class 组件。

Refs 转发

是否须要将 DOM Refs 裸露给父组件?

在极少数状况下,你可能心愿在父组件中援用子节点的 DOM 节点。通常不倡议这样做,因为它会突破组件的封装,但它偶然可用于触发焦点或测量子 DOM 节点的大小或地位。

如何将 ref 裸露给父组件?

如果你应用 16.3 或更高版本的 React, 这种状况下咱们举荐应用 ref 转发。Ref 转发使组件能够像裸露本人的 ref 一样裸露子组件的 ref。

什么是 ref 转发?

const FancyButton = React.forwardRef((props, ref) => (<button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你能够间接获取 DOM button 的 ref:const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

如果在低版本中如何转发?

如果你应用 16.2 或更低版本的 React,或者你须要比 ref 转发更高的灵活性,你能够应用 ref 作为非凡名字的 prop 间接传递。

比方上面这样:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {constructor(props) {super(props);
    this.inputElement = React.createRef();}
  render() {
    return (<CustomTextInput inputRef={this.inputElement} />
    );
  }
}

以下是对上述示例产生状况的逐渐解释:

  1. 咱们通过调用 React.createRef 创立了一个 React ref 并将其赋值给 ref 变量。
  2. 咱们通过指定 ref 为 JSX 属性,将其向下传递给 <FancyButton ref={ref}>
  3. React 传递 ref 给 forwardRef 内函数 (props, ref) => …,作为其第二个参数。
  4. 咱们向下转发该 ref 参数到 <button ref={ref}>,将其指定为 JSX 属性。
  5. 当 ref 挂载实现,ref.current 将指向 <button> DOM 节点。

最初

欢送关注公众号「前端进阶课」认真学前端,一起进阶。回复 全栈 Vue 有好礼相送哦

正文完
 0