React —— 使用Refs转发技巧

Refs转发

Ref转发是一项将ref自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但是其对某些组件,尤其是可重用的组件库是很有用的。

转发refs到DOM组件

考虑这个渲染原生DOM元素button的FancyButton组件:

function FancyButton(props){
    return (
        <button className="FancyButton">
            {props.children}
        </button>
    )
}

React组件隐藏其他实现细节,包括其渲染结果。其他使用FancyButton的组件通常不需要获取内部的DOM元素button的ref。这样可以防止组件过度依赖其他组件的DOM结构。
虽然这种封装对于类似FeedStoryComments这样的应用级组件是理想的,但其对FancyButtonMyTextInput这样的高可复用“叶”组件来说可能是不方便的。这些组件倾向于在整个应用中以一种类似常规DOMbuttoninput的方式被使用,并且访问其DOM节点对管理焦点,选中或动画来说是不可避免的。
Ref转发是一个可选特性,其允许某些组件接受ref,并将其向下传递(换句话说,转发发它)给子组件
在下面的实例中,FancyButton使用React.forwardRef来获取传递给它的ref,然后转发到它渲染的DOMbutton:

const FancyButton = React.forwardRef((props,ref)=>(
    <button ref={ref} className="FancyButton">
        {props.children}
    </button>
))
// 你可以直接获取DOM button 的ref
const ref = React.createRef();
<FancyButton ref="ref">click me!</FancyButton>

这样,使用FancyButton的组件可以获取底层DOM节点的ref,并在必要时访问,就像其直接使用DOMbuttom一样。
逐步解析:

  1. 我们通过调用React.createRef()创建了一个React Ref并将其赋值给了ref变量。
  2. 我们通过指定ref为了JSX属性,将其向下传递给<FancyButton ref={ref}/>
  3. React传递ref给forwardRef内函数(props,ref)=>...,座位其第二个参数。
  4. 我们向下转发该ref参数到<button ref={ref}>,将其指定为JSX属性。
  5. 当ref挂载完成。ref.current将指向<button>DON节点。
    注意:第二个参数ref只在使用React。forwardRef定义组件时存在。常规函数和class组件不接受ref参数,且props中也不存在ref
    Ref转发不限于DOM组件,你也可以转发refs到class组件实例中。

组件库维护者的注意事项

当你开始在组件库中使用forwardRef时,你应当将其视为一个破坏性更改,并发布库的一个新的主版本。这是因为你的库可能会有明显不同的行为(例如refs被分配给了谁,以及导出了什么类型),并且这样可能会导致依赖旧行为的应用和其他库崩溃。
出于同样的愿意,当React.forwardRef存在时有条件的使用它也是不推荐的:它改变了你的库的行为,并在升级React自身时破坏用户的应用。

在高级组件中转发refs

这个技巧对高阶组件(也被成为H0C)特别有用。让我们从一个输出组件props到控制台的HOC实例开始:

function logProps(WrappedComponent){
    class LogProps extends React.Component{
        componentDidUpdate(prevProps){
            console.log('old props:',prevProps)
            console.log('new props:',this.props)
        }
        render(){
            return <WrappedComponent {...this.props} />
        }
    }

    return LogProps
}

“logProps”HOC透传(pass through)所有的props到其包裹的组件,所以选结果将是相同的,例如,如果我们使用该HOC记录所有传递到Fancybutton组件的props:

function logProps(WrappedComponent){
    class LogProps extends React.Component{
        componentDidUpdate(prevProps){
            console.log('old props:',prevProps)
            console.log('new props:',this.props)
        }
        render(){
            return <WrappedComponent {...this.props} />
        }
    }

    return LogProps
}

class FancyButton extends React.Component{
    focus(){}
}

const MFancyButton = logProps(FancyButton);

上面示例有一点需要注意:refs将不会透传下去。这是因为ref不是prop属性。就像key一样,其被React做了特殊处理,如果你对应HOC添加ref,该ref将引用最外层的容器组件,而不是包裹组件。
这意味着我们FancyButton组件的refs实际上被挂到LogProps组件:

const ref = React.createRef();
// 我们导入的 FancyButton 组件是高阶组件(HOC)LogProps。
// 尽管渲染结果将是一样的,
// 但我们的 ref 将指向 LogProps 而不是内部的 FancyButton 组件!
// 这意味着我们不能调用例如 ref.current.focus() 这样的方法
<MFancyButton label="click me" handleClick={handleClick} ref={ref} />

幸运的是,我们可以使用React.forwardRefAPI明确地将refs转发到内部的FancyButton组件。React.forwardRef接受一个渲染函数,其接受propsref参数并返回一个React及诶点。例如:

function logProps(Component){
    class LogProps extends React.Component{
        componentDidUpdate(prevProps){
            console.log('old props:',prevProps)
            console.log('new props:',this.props)
        }
        render(){
            const {forwardedRef,...res} = this.props;
            return <Component {...res}  ref={forwardedRef} />
        }
    }

    return React.forwardRef((props,ref)=>{
            return  <LogProps {...props}  forwardRef={ref} />  
    })
}

在DevTools中显示自定义名称

React.forwardedRef接受一个渲染函数。React DevTools使用该函数来决定为ref转发组件显示的内容。
例如,以下组件将在DevTools中显示为了ForwardRef

const WrappedComponent = React.forwardRef((props,ref)=>{
    return <LogProps  ref={ref} {...props}/>
})

如果你命名了渲染函数,devTools也将包含其名称(例如’ForwardRef(myFunction)’):

const WrappedComponent = React.forwardRef(
    function myFunction(this.props,ref){
        return <LogProps {...props}  forwardRef={ref} />
    }
)

你甚至可以设置函数的displayName属性来包含被包裹组件的名称:

function logProps(Component){
    class LogProps extends React.Component{

    }
    function forwardRef(props,ref){
        return <LogProps {...props} forwardRef={ref} />
    }
    // 在DevTools中为该组件提供一个更有用的显示名
    // 例如 "ForwardRef(logProps(MyComponent))"
    const name = Component.displayName || Component.name ;

    forwardRef.displayName = `logProps(${name})`;
    return React.forwardRef(forwardRef)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值