Refs和Refs 转发

1、定义

在React 数据流中,props是父组件与子组件交互的唯一方式。需要修改子组件,要使用新的props来重新渲染。
但有些情况下,需要我们强制修改子组件(被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素),这时就可以使用Refs。
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

2、使用场景

下面是几个适合使用 refs 的情况:
1.管理焦点,文本选择或媒体播放。
2.触发强制动画。
3.集成第三方 DOM 库。
4.避免使用 refs 来做任何可以通过声明式实现来完成的事情。

3、Refs API使用

3.1 创建Refs

Refs 使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。(在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们)

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

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。

console.log(this.myRef.current);//打印出设置了refs 的节点元素

ref 的值根据节点的类型而有所不同:
一、当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。

 //使用React.createRef()创建refs
      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.value = "";
          this.textInput.current.focus();
        }

        render() {
          // 告诉 React 我们想把 <input> ref 关联到
          // 构造器里创建的 `textInput` 上
          return (
            <div>
              <input type="text" ref={this.textInput} />
              <input
                type="button"
                value="Focus the text input"
                onClick={this.focusTextInput}
              />
            </div>
          );
        }
      }

二、当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。

 //为 class 组件添加 Ref
      class AutoFocusTextInput extends React.Component {
        constructor(props) {
          super(props);
          this.textInput = React.createRef();
        }

        componentDidMount() {
          //调用class组件的方法
          this.textInput.current.focusTextInput();
        }

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

你不能在函数组件上使用 ref 属性,因为他们没有实例,可但以在函数组件内部使用 ref 属性。

 //使用React.createRef()创建refs
      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.value = "";
          this.textInput.current.focus();
        }

        render() {
          // 告诉 React 我们想把 <input> ref 关联到
          // 构造器里创建的 `textInput` 上
          return (
            <div>
              <input type="text" ref={this.textInput} />
              <input
                type="button"
                value="Focus the text input"
                onClick={this.focusTextInput}
              />
            </div>
          );
        }
      }

4、回调 Refs

React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。
不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。

    //回调Refs
      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          this.myRef = null;
        }
        componentDidMount() {
          this.myRef.innerHTML = "我是回调Refs";
          console.log(this.myRef);
        }
        render() {
          return (
            <div
              ref={element => {
                this.myRef = element;
              }}
            />
          );
        }
      }

可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。

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

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

5、String 类型的 Refs(老版本React中的API,已经过时,不建议使用)

//过时的Refs
      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
        }
        componentDidMount() {
          this.refs.myRef.innerHTML = "我是过时的String 类型的Refs";//通过 this.refs.XXXX来访问 DOM 节点
          console.log(this.refs.myRef);
        }
        render() {
          return <div ref="myRef" />;
        }
      }

6、将 DOM Refs 暴露给父组件

在极少数情况下,你可能希望在父组件中引用子节点的 DOM 节点。通常不建议这样做,因为它会打破组件的封装。在新版本中可以使用Refs 转发,老版本可以将refs作为一个参数,放在props中直接传递(如下例)

 function MyComponent(props) {//作为props的一员直接传递
        return (
          <div>
            <input
              ref={props.inputRef}
              onChange={null}
            />
          </div>
        );
      }
6.1 Refs 转发

Ref 转发是一项将 refs自动地通过组件传递到其一子组件的技巧。对于可重用的组件库是很有用的。
我们需要使用API React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM 。

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

这样,使用 MyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。
注意:
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。

   class App extends React.Component {
        constructor(props) {
          super(props);
          // 你可以直接获取 DOM button 的 ref:
          this.ref = React.createRef();
        }

        componentDidMount() {
          setTimeout(() => {
            this.ref.current.innerText = "Refs 转发";
          }, 2000);
          console.log(this.ref.current);

        }
        render() {
          return (
            <div>
              <MyButton ref={this.ref}>Click me!</MyButton>
            </div>
          );
        }
      }
  ReactDOM.render(<App />, document.getElementById("root"));

如图,在父组件打印出了refs内容,并且2秒改了了button上的内容。

6.2 在高阶组件中转发 refs

利用高阶组件,就可以转发 refs 到 class 组件实例中。什么是高阶组件呢?
高阶组件是一个函数,它接受一个组件并返回一个新组件。

     class TextInput extends React.Component {
        constructor(props) {
          super(props);
          this.textInput = React.createRef();
        }
        setInputValue(value){
          this.textInput.current.value=value;
        }
        render() {
          return (<div><input ref={this.textInput} /></div>);
        }
      }

      class App extends React.Component {
        constructor(props) {
          super(props);
          // 你可以直接获取 DOM button 的 ref:
          this.ref = React.createRef();
          this.HOCref = React.createRef();
        }

        componentDidMount() {
          setTimeout(() => {
            this.ref.current.innerText = "Refs 转发";
          }, 2000);
          console.log(this.ref.current);
          console.log(this.HOCref.current);
          this.HOCref.current.setInputValue('我是父组件设置的值');
        }
        render() {
          return (
            <div>
              <MyButton ref={this.ref}>Click me!</MyButton>
              <NewFancyButton ref={this.HOCref}>高阶函数</NewFancyButton>
            </div>
          );
        }
      }
      const NewFancyButton = HOCFunc(TextInput);
     function HOCFunc(Component) {
        class HOCComponent extends React.Component {
          componentDidUpdate(prevProps) {
            console.log("old props:", prevProps);
            console.log("new props:", this.props);
          }

          render() {
            const { forwardedRef, ...rest } = this.props;

            // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
            return <Component ref={forwardedRef} {...rest} />;
          }
        }

        // 注意 React.forwardRef 回调的第二个参数 “ref”。
        // 我们可以将其作为常规 prop 属性传递给 HOCComponent,例如 “forwardedRef”
        // 然后它就可以被挂载到被 HOCComponent 包裹的子组件上。
        return React.forwardRef((props, ref) => {
          return <HOCComponent {...props} forwardedRef={ref} />;
        });
      }

上面的示例有一点需要注意:refs将不会透传下去。这是因为ref不是prop属性。就像key一样,其被React进行了特殊处理。如果你对HOC添加ref,该ref将引用最外层的容器组件,而不是被包裹的组件。
幸运的是,我们可以使用React.forwardRefAPI明确地将refs转发到内部的TextInput组件。React.forwardRef接受一个渲染函数,其接收props和ref参数并返回一个React节点。



上图中打印出了转发后得到refs的Class组件TextInput,并且在父组件中操作了子组件中的方法。

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

推荐阅读更多精彩内容

  • It's a common pattern in React to wrap a component in an ...
    jplyue阅读 3,251评论 0 2
  • 40、React 什么是React?React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIE...
    萌妹撒阅读 1,005评论 0 1
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,046评论 2 35
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,804评论 0 24
  • u/U:表示unicode字符串不是仅仅是针对中文, 可以针对任何的字符串,代表是对字符串进行unicode编码。...
    年画儿阅读 489评论 0 0