React之ref的高阶用法

简介: 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>
}

如上所示:


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


向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组件实例。


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


使用forwardRef 进行ref转发。


在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 对象。

2f5cf3fe0dcc4a7e93f11f2dc60a7d08.png


流程分析:


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


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


父组件可以通过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>
}


目录
相关文章
|
3月前
|
前端开发 JavaScript
学习react基础(3)_setState、state、jsx、使用ref的几种形式
本文探讨了React中this.setState和this.state的区别,以及React的核心概念,包括核心库的使用、JSX语法、类与函数组件的区别、事件处理和ref的使用。
91 3
学习react基础(3)_setState、state、jsx、使用ref的几种形式
|
3月前
|
JavaScript 前端开发 开发者
React 的正确使用方法:ref 篇
你真的用对了 useRef 吗?在与 TypeScript 一起使用、以及撰写组件库的情况下,你的写法能够避开以下所有场景的坑吗?
|
3月前
|
前端开发
react学习(17)回调形式的ref
react学习(17)回调形式的ref
|
3月前
|
存储 前端开发 容器
react学习(18)createRef形式的ref
react学习(18)createRef形式的ref
|
4月前
|
缓存 JavaScript 前端开发
react.js高级用法
【8月更文挑战第27天】react.js高级用法
48 2
|
4月前
|
Web App开发 监控 前端开发
React 性能监测工具大揭秘!Chrome DevTools 高级用法来袭,让你的 React 应用性能飙升!
【8月更文挑战第31天】在前端开发中,React 框架虽简化了高效、交互性强的用户界面构建,但应用复杂性增加亦可能引发性能问题。此时,Chrome DevTools 凭其性能面板成为了优化应用性能的重要工具,能帮助开发者记录与分析加载时间、渲染及脚本执行等性能指标,定位并解决性能瓶颈。同时,其 React 开发者扩展工具允许实时监控组件状态变化,进一步提升性能。结合运用这些功能,将有助于打造流畅的用户体验。
120 0
|
4月前
|
前端开发 知识图谱
[译] React 中的 "最新 Ref 模式"
[译] React 中的 "最新 Ref 模式"
|
5月前
|
JavaScript
react18【系列实用教程】useRef —— 创建 ref 对象,获取 DOM (2024最新版)
react18【系列实用教程】useRef —— 创建 ref 对象,获取 DOM (2024最新版)
68 0
|
7月前
|
设计模式 前端开发 API
React的高阶组件(HOC):使用与设计模式探讨
【4月更文挑战第25天】React的高阶组件(HOC)是一种复用和增强组件的高级模式,它接受组件并返回新组件。非侵入式增强使得HOC能在不修改原有组件代码的情况下添加功能。定义HOC后,将其应用于目标组件并渲染增强后的组件。常见设计模式包括属性代理、控制反转和装饰器。然而,使用时要注意避免滥用,保持命名清晰,关注性能优化。理解并恰当使用HOC能提升React应用的构建效率。
|
7月前
|
前端开发 JavaScript
深入了解 React 中的 Ref:不是魔法,是超级工具
深入了解 React 中的 Ref:不是魔法,是超级工具
207 0