React - Refs 和 DOM

  在常规的 React 数据六中,props 是父组件与子组件交互的唯一方法。如果需要改子元素,你需要用新的props去重新渲染子元素。 然而,在少数情况下,你需要在常规数据流外强制修改子元素。被修改的子元素可以是React组件实例,或者是一个DOM元素。React团队提供了Refs办法

# 使用Refs 场景

  在如下几种情形中,我们可以引入refs

  • 处理 focus、文本选择或者媒体播放时
  • 触发强制动画时
  • 继承第三方DOM库时

注意:如果可以通过声明式实现,就尽量避免使用refs。例如一个场景。如果我们想在Dialog组件中暴露open()close()方法,则传递isOpen状态标志即可。也就是说,我们不应该过度的使用refs,通常较高级别的state更为清晰。

# 在元素本身的DOM元素上添加 Ref

  React 支持给任何组件添加特殊的属性。 ref属性接受回调函数,并且当组件 装载(mounted) 或者 卸载(unmounted) 之后,回调函数会立即执行。

  当给HTML元素添加 ref 属性时,ref回调接受底层的DOM 元素作为参数。例如,下面的代码使用ref 回调来存储DOM节点的引用

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.focus = this.focus.bind(this);
  }

  focus() {
    // 通过原生的API,显示地聚焦text输入框
    this.textInput.focus();
  }

  render() {
    // 在实例中通过使用`ref`回调函数来存储text输入框的DOM元素引用(例如:this.textInput)
    return (
      <div>
        <input 
            type="text" 
            ref={(input) => { this.textInput = input; }} />
        <input 
            type="button" 
            value="Focus the text input" 
            onClick={this.focus} />
      </div>
    )  
  }
}

  React 组件在加载时将DOM元素传入ref 的回调函数,在写在时则会传入 null, 在 componentDidMountcomponentDidUpdate 这些生命周期回之前执行 ref回调

  使用ref 回调只是为了在类上设置一个属性,是访问DOM元素常见模式。 首选的方法是在 ref 回调中设置属性。就像上面的例子一样,他也可以写成ref={input => this.textInput = input}

# 为 类(class)组件添加 Ref

  当 ref 属性用于类声明的自定义组件时, ref回到上述收到的参数装载(mounted)的组件实例. 例如如果我们想包装 CustomTextInput 组件,实现组件在 装载(mounted) 后立即点击的效果:

class AutoFocusTextInput extends React.Component {
  componentDidMount() {
    this.textInput.focus();
  }

  render() {
    return (
      <CustomTextInput
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

 需要注意的是,这种方法仅对以类声明的 CustomTextInput 有效,函数式声明的是无效的。

# Refs 与函数式组件

你不能在函数式组件上使用 ref 属性,因为它们没有实例

function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  render() {
    // 这里 *不会* 执行!
    return (
      <MyFunctionalComponent
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

  如果你需要使用 ref ,你需要将组件转化成 类组件,就像需要 生命周期方法 或者 state 一样。然而你可以 在函数式组件内部使用ref 来引用一个 DOM 元素或者 类组件

function CustomTextInput(props) {
  // textInput必须在这里声明,所以 ref 回调可以引用它
  let textInput = null;

  function handleClick() {
    textInput.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={(input) => { textInput = input; }} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}

# 对父组件暴露 DOM 节点

  在极少数的情况下,你可能希望从父组件中访问子节点的DOM节点。这种情况不被推崇使用,因为他会破坏组件的封装,丢失子组件独立工作的效果。但偶尔也可用于触发焦点或测量子 DOM 节点的大小或位置。

  虽然你可以向子组件添加 ref,但这不是一个理想的解决方案,因为你只能获取组件实例而不是 DOM 节点。并且,它还在函数式组件上无效。

  相反,在这种情况下,我们建议在子节点上暴露一个特殊的属性。子节点将会获得一个函数属性,并将其作为 ref 属性附加到 DOM 节点。这允许父代通过中间件将 ref回调给子代的 DOM 节点. 这适用于类组件和函数式组件。

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

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

  在上面的例子中,Parent 将它的ref 回调作为一个特殊的inputRef传递给 CustomTextInput,然后 CustomTextInput 通过ref属性将其传递给 <input>。最终,Parent中的 this.inputElement 将被设置为与 CustomTextInput中的 <input> 元素相对应的 DOM 节点。

  请注意,上述示例中的 inputRef 属性没有特殊的含义,它只是一般的组件属性。然而,使用<input>本身的ref 属性很重要,因为它告诉 Reactref 附加到它的 DOM 节点。

  即使 CustomTextInput 是一个函数式组件,它也同样有效。这里在父组件定义的是自定义属性,而不是特殊的 ref 因此不会受影响

  这种模式的另一个好处是它能作用很深。假如有个 Parent 组件不需要 DOM 节点 A,但是某个渲染 Parent 的组件(我们称之为Grandparent)需要通过它访问。这时我们可以让 Grandparent 传递 inputRefParent 组件,然后让Parent组件将其转发给 CustomTextInput

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

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


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

总而言之,我们建议尽可能不暴露 DOM 节点,但这是一个有用的解决方式。请注意,此方法要求您向子组件添加一些代码,如果你无法完全控制子组件,最后的办法是使用findDOMNode(),但是不推荐这样做。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容