React之ref的高阶用法

  • forwardRef转发Ref

forwardRef的初衷就是解决ref不能跨层级捕获和传递的问题,forwardRef接受了父级元素标记的ref信息,并把它转发下去,使得子组件可以通过props来接受到上一层级或者更上层级的ref。

  • 场景一: 跨层级获取

比如想要通过标记子组件ref,来获取子组件下的孙组件的某一DOM元素,或者是组件实例。使用 React.forwardRef

场景:想要在GrandFather组件通过标记ref,获取到孙组件Son的组件实例.可以理解为,React.forwardRef将子组件的ref跑出去到顶层。在爷爷组件中可以进行使用

// 孙组件
function Son(Props) {
    const {grandRef} = props
    return <div>
        <div>i am alien</div>
        <span ref={grandRef}>123</span>
    </div>
}
​
// 父组件
class Father extends React.Component{
    constructor(props){
        super(props)
    }
    render () {
        return <div>
            <Son grandRef={this.props.grandRef}></Son>
        </div>
    }
}
​
const newFather = React.forwardRef((props,ref) => <Father grandRef={ref} {...props}></Father>)
                                   
// 爷组件
class GrandFather extends React.Component {
    constructor(props){
        super(props)
    }   
    node = null
    componentDidMount(){
        console.log(this.node)
    }
    render () {
        return <div>
            <NewFather ref={(node) => this.node = node}></NewFather>
        </div>
    }
}

如上所述:forwardRef把ref变成了可以通过props传递和转发。

官网解释:

React.forwardRef 会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。

React.forwardRef接受渲染函数作为参数,React将使用props和ref作为参数来调用次函数,此函数返回React节点。

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));
​
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

上述中,React会将<FancyButton ref={ref}>元素的 ref作为第二个参数传递给React.forwardRef函数中的渲染函数,该渲染函数会将ref 传递给<button ref={ref}> 元素

  • 场景二:合并转发ref

通过forwardRef转发的ref不要理解为只能用来直接获取组件实例,DOM元素,也可以用来传递合并之后的自定义ref。

场景:通过Home绑定ref,来获取子组件Index的实例Index,dom元素button,以及孙组件Form的实例。

// 表单组件
class Form extends React.Component{
    render() {
        return <div>{...}</div>
    }
}
​
// index组件
class Index extends React.Component{
    componentDidMount() {
        const {forwardRef} = this.props
        forwardRef,current = {
            form: this.form,
            index: this,
            button: this.button
        }
    }
    
    form = null
    button = null
    render() {
        return <div>
            <button ref={(button) => this.button = button}>点击</button>
            <Form ref={(form) => this.form = form}></Form>
        </div>
    }
}
​
const ForwardRefIndex = React.forwardRef((props,ref) => <Index {...props} forwardRef={ref} ></Index>)
​
// home组件
export default function Home() {
    const ref = useRef(null)
    useEffect(() =>  {
        console.log(ref.current)
    },[])
    return <ForwardRefIndex ref={ref}></ForwardRefIndex>
}

如上所示:

  1. 通过useRef创建了一个ref对象,通过forwardRef将当前ref对象传递给子组件。

  2. 向Home 组件传递的ref对象上,绑定form孙组件实例,index子组件实例,和button DOM元素。

  • 场景三:高阶组件转发

如果通过高阶组件包裹一个原始类组件,就会产生一个问题,如果高阶组件HOC没有处理ref,那么由于高阶组件本身会返回一个新的组件,所以当使用HOC包装后组件的时候,标记的ref会指向HOC返回的组件,而并不是HOC包裹的原始类组件,为了解决这个问题,forwardRef可以对HOC做一层处理。


function HOC(Component){
  class Wrap extends React.Component{
     render(){
        const { forwardedRef ,...otherprops  } = this.props
        return <Component ref={forwardedRef}  {...otherprops}  />
     }
  }
  return  React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> ) 
}
class Index extends React.Component{
  render(){
    return <div>hello,world</div>
  }
}
const HocIndex =  HOC(Index)
export default ()=>{
  const node = useRef(null)
  useEffect(()=>{
    console.log(node.current)  /* Index 组件实例  */ 
  },[])
  return <div><HocIndex ref={node}  /></div>
}

经过forwardRef 处理后的HOC,就可以正常访问到Index组件实例。

  1. 声明一个组件Wrap,接收 HOC 传入的组件Index。

  2. 使用forwardRef 进行ref转发。

  3. 在HocIndex中获取到Index实例。

ref实现组件通讯

在一些场景下不想通过往常的单数据流的方式来改变子组件的更新,拿着时候就可以通过ref模式标记子组件实例。,从而操作子组件。如antd的form表单,对外暴漏出来的 resetFields,setFieldsValue等接口。

  • 类组件

对于类组件可以通过ref直接获取组件实例。实现组件通信

/* 子组件 */
class Son extends React.PureComponent{
    state={
       fatherMes:'',
       sonMes:''
    }
    fatherSay=(fatherMes)=> this.setState({ fatherMes  }) /* 提供给父组件的API */
    render(){
        const { fatherMes, sonMes } = this.state
        return <div className="sonbox" >
            <div className="title" >子组件</div>
            <p>父组件对我说:{ fatherMes }</p>
            <div className="label" >对父组件说</div> <input  onChange={(e)=>this.setState({ sonMes:e.target.value })}   className="input"  /> 
            <button className="searchbtn" onClick={ ()=> this.props.toFather(sonMes) }  >to father</button>
        </div>
    }
}
/* 父组件 */
export default function Father(){
    const [ sonMes , setSonMes ] = React.useState('') 
    const sonInstance = React.useRef(null) /* 用来获取子组件实例 */
    const [ fatherMes , setFatherMes ] = React.useState('')
    const toSon =()=> sonInstance.current.fatherSay(fatherMes) /* 调用子组件实例方法,改变子组件state */
    return <div className="box" >
        <div className="title" >父组件</div>
        <p>子组件对我说:{ sonMes }</p>
        <div className="label" >对子组件说</div> <input onChange={ (e) => setFatherMes(e.target.value) }  className="input"  /> 
        <button className="searchbtn"  onClick={toSon}  >to son</button>
        <Son ref={sonInstance} toFather={setSonMes} />
    </div>
}

父组件使用 props传递值,将子组件给父组件要说的话的方法传递给子组件,供子组件给父组件进行传递。

子组件中写好父组件要给子传递值的方法,。并且将自己的实例通过ref暴漏给父组件,父组件通过暴漏的实例上的对应方法进行传递。

useImperativeHandle 在函数组件中获取子组件ref实例。

它有三个参数:

  • 第一个参数 ref : 接受 forWardRef 传递过来的 ref 。

  • 第二个参数 createHandle :处理函数,返回值作为暴露给父组件的 ref 对象。

  • 第三个参数 deps :依赖项 deps,依赖项更改形成新的 ref 对象。

     

流程分析:

  1. 上述代码中,父组件用ref标记了子组件ForwarSon,但是子组件Son是函数组件,没有实例返回。所以用forwardRef 转发ref生成了一个新的组件,ForwarSon。

  2. 子组件Son用useImperativeHandle接收父组件ref生成Son实例,并将input的方法 onFocus 和改变input输入值的方法 onChange 传递给了ref,对外暴漏了出去。

  3. 父组件可以通过ref调用 onFocus 以及 onChangevalue

使用useRef 做函数组件缓存

函数组件每一次render,函数都会上下文重新执行,有一种情况就是,在执行一些事件方法改变数据或者保存新数据的时候,没必要更新视图,这时候就无需放在state中。

useRef 可以创建出一个ref原始对象,只要组件没有销毁,ref对象就一直存在,那么完全可以把一些不依赖于视图更新的数据存储到ref中。

优点:

  1. 可以直接修改数据,不会造成函数组件冗余的更新作用。

  2. useRef保存数据,它始终在当前组件中指向一个内存空间,这样的好处是随时可以访问到变化后的值。

const toLearn = [ { type: 1 , mes:'let us learn React' } , { type:2,mes:'let us learn Vue3.0' }  ]
export default function Index({ id }){
    const typeInfo = React.useRef(toLearn[0])
    const changeType = (info)=>{
        typeInfo.current = info /* typeInfo 的改变,不需要视图变化 */
    }
    useEffect(()=>{
       if(typeInfo.current.type===1){
           /* ... */
       }
    },[ id ]) /* 无须将 typeInfo 添加依赖项  */
    return <div>
        {
            toLearn.map(item=> <button key={item.type}  onClick={ changeType.bind(null,item) } >{ item.mes }</button> )
        }
    </div>
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端南秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值