一次性搞定React的Ref功能

1.String Ref

String Ref是个过时的API。因为String类型的Ref存在一些问题,将在未来的某个版本中被遗弃,不建议使用。

使用方式:this.refs.XXX获取DOM元素节点:

获取普通标签:

import React, { Component } from 'react';
class App extends Component {
    componentDidMount() {
        console.log('this.refs.XXX');
        console.log(this.refs.h1Ref);
    }
    render() {
        return <h1 ref='h1Ref'>Hello World!</h1>
    }
}
export default App;

复制代码打印结果:

 this.refs.xxx
 <h1>Hello World!</h1>

获取react组件:此时可以调用组件上的方法

import React, { Component } from 'react';
class App extends Component {
    componentDidMount() {
        console.log(this.refs.childRef);
        this.refs.childRef.handleLog(); // Child Component    }
    render() {
        return (
            <div>
                <h1>Hello World!</h1>
                <Child ref='childRef' count='1' />
            </div>
        )
    }
}
class Child extends Component {
    handleLog = () => {
        console.log('Child Component');
    }
    render() {
        const { count } = this.props;
        return <h2>count: { count }</h2>
    }
}
export default App;

// 结果
组件的引用,包括上面挂载的函数和组件props等

2.Callback Ref

Callback Ref能助你更精细地控制何时 refs

被设置和解除,传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数。

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

使用方式:ref={element => (this.eleRef = element)}

获取DOM元素节点:

import React, { Component } from "react";
class App extends Component {
  componentDidMount() {
    console.log("Callback Ref");
    console.log(this.h1Ref);
  }
  render() {
    return (
      <div>
        <h1 ref={element => (this.h1Ref = element)}>Hello World!</h1>
      </div>
    );
  }
}
export default App;

// 打印结果
 Callback Ref
 <h1>Hello World!</h1>

获取子组件实例:【同上可调用方法】

import React, { Component } from "react";
class App extends Component {
  componentDidMount() {
    console.log("Callback Ref");
    console.log(this.childRef);
    this.childRef.handleLog();
  }
  render() {
    return (
      <div>
        <h1>Hello World!</h1>
        <Child ref={component => (this.childRef = component)} count="1" />
      </div>
    );
  }
}
class Child extends Component {
  handleLog = () => {
    console.log("Child Component");
  };
  render() {
    const { props } = this;
    return <h1>count: {props.count}</h1>;
  }
}
export default App;

3.Create Ref

该功能是React16.3中发布的,并且在类组件中推荐使用

使用React.createRef()创建Refs,通过ref附加到组件中,对该节点的引用通过ref的current属性访问

React在组件挂载时给current传入DOM元素,并在组件卸载时传入null,ref会在生命周期函数前更新完成

使用方法和前两种类似,这里就不一一列举了

import React, { Component, createRef} from "react";
class App extends Component {
  constructor(props) {
    super(props);
    this.h1Ref = createRef();
  }
  componentDidMount() {
    console.log("React.createRef()");
    console.log(this.h1Ref.current);
  }
  render() {
    return <h1 ref={this.h1Ref}>Hello World!</h1>;
  }
}
export default App;

4.useRef

当在函数组件中使用前三种ref,会抛出以下错误:Uncaught Invariant Violation: Function components cannot have refs. Did you mean to use React.forwardRef()?

原因:函数组件和class组件根本区别是,函数组件没有实例,所以无法使用实例对象,取而代之的为useRef,或使用forwardRef

作用:

  • 获取DOM节点
  • 获取组件实例
  • 渲染周期之间共享数据存储(state修改会触发重新渲染,所以不能跨周期共享

使用方法:与createRef类似,挂在current上

import React, { useEffect, useRef } from 'react';
function App() {
  const h1Ref = useRef();
  useEffect(() => {
    console.log('useRef')
    console.log(h1Ref.current)
  }, [])
  return <h1 ref={h1Ref}>Hello World!</h1>
}
export default App;

5 不同渲染周期之间的数据共享

该情况主要解决一类问题:类组件中,函数组件中的属性无法跨渲染周期进行数据共享,因为每次保存都会刷新数据重新渲染

示例程序:(其中的timer无法跨渲染周期,每次执行App函数,其都会被重置)

import React, { useState, useEffect, useRef } from "react";
function App() {
  const [count, setCount] = useState(0);
  // 把定时器设置成全局变量使用useRef挂载到current上
  const timer = useRef();
  // const timer; // 这种会导致每次timer都是新的
  // 首次加载useEffect方法执行一次设置定时器
  useEffect(() => {
    timer.current = setInterval(() => {
      setCount(count => count + 1); // 此处必须传入函数,否则拿到的都是组将挂载时的初值!!
    }, 1000);
  }, []);
  // count每次更新都会执行这个副作用,当count > 5时,清除定时器
  useEffect(() => {
    if (count > 5) {
      clearInterval(timer.current);
    }
  });
  return <h1>count: {count}</h1>;
}
export default App;

6 各方式使用总结

ref方式定义获取元素引用执行挂载在元素内的函数备注
string ref标签内ref=‘xxx’this.refs.xxxthis.refs.xxx.handle()已过时
callback ref标签内ref={(element)=>{this.xxx=element}}this.xxxthis.xxx.handle()精准控制ref
create ref构造中this.xxx=createRef() 标签中ref={this.xxx}this.xxx.currentthis.xxx.current.handle()推荐使用
useRef()函数组件中const 函数标签中xxx=useRef() ref={xxx}xxx.currentxxx.current.handle()函数组件中的ref替代方法

【参考】
https://juejin.im/post/5d67d41e5188254628237485

7 Ref转发

有时候需要传递ref给子组件,需要用到React.forwardRef函数,因为常规函数组件和 class 组件不接收 ref 参数,且 props 中也不存在 ref。

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

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
// 此时的ref即React.forwardRef的参数之一
<FancyButton ref={ref}>Click me!</FancyButton>;

这个例子展示了,将ref转发到了最终的button标签上,其实Ref 转发不仅限于 DOM 组件,你也可以转发 refs class 组件实例中。

HOC Ref转发

function logProps(Component) {
  class LogProps 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 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogProps 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

这时候logProps如何调用,目前没搞懂,怎么同时把Component和ref传进去
猜测:

const Test = logProps(<button>123</button>)
const ref = React.createRef()
<Test ref={ref}/>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值