useRef() 使用详解(二)

232 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情

关于 forwardRef 和 useImperativeHandle

React 组件隐藏其实现细节,包括其渲染结果。其他使用 FancyButton 的组件通常不需要获取内部的 DOM 元素 button 的 ref。 防止组件过度依赖其他组件的 DOM 结构。

但是我们在编写一些高可复用组件的时候,是需要去获取操作其他组件的元素实例。

forwardRef 的初衷就是解决 ref 不能跨层级捕获和传递的问题。

当子组件需要引用其父组件当前节点时,使用 forwardRef 能够将 ref 传递给它的子组件。

forwardRef 接受了父级元素标记的 ref 信息,并把它转发下去,使得子组件可以通过 props 来接受到上一层级或者是更上层级的ref。

而 forwardRef 配合 React Hooks 的 useImperativeHandle 让函数组件也能流畅得使用 ref 通信。

forwardRef使用例子一:跨层级获取元素

function Child(props) {
    const { cRef } = props
    return <div>
        <div>am react</div>
        <span ref={cRef}>需要获取的元素</span>
    </div>
}

class Father extends React.Component{
    constructor(props) {
        super(props)
    }
    render() {
        return <div>
            <Child cRef={this.props.iRef}/>
        </div>
    }
}

const AFather = React.forwardRef((props, ref) => <Father iRef={ref} {...props} />)

class Index extends React.Component {
    node = null

    logRef = () => {
        console.log(this.node)
    }

    render() {
        return (
            <div>
                <AFather ref={(node) => this.node = node}></AFather>
                <button onClick={this.logRef}>logRef</button>
            </div>
        )
    }
}
  • 创建了 Child、Index、Father 组件
  • Index 组件想要通过设置 ref,来获取孙组件 Child 的组件实例
  • Index 组件中创建了一个 node = null,使用 回调Ref 设置 ref 传递给孙组件
  • AFather 使用 forwardRef 第二个参数获取 ref 传递给它的值,将 ref 向下转发到 Father 组件。
  • 而 Father 组件将 ref 通过 props 传递到 Child 组件
  • Index 组件点击 logRef 按钮获取 Child 组件指定的 ref 节点 <span ref={fatherRef}>需要获取的元素</span>

函数组件的refs

ref 不能附加到函数组件上。虽然,我们可以定义 ref 并将它们附加到 DOM 元素或类组件。但是,函数组件没有实例,所以你不能引用它们。

React Hooks 提供了 useImperativeHandle,它一共接受三个参数。

useImperativeHandle 接受三个参数:

  • 第一个参数 ref : 接受 forWardRef 传递过来的 ref 。
  • 第二个参数 createHandle :处理函数,返回值作为暴露给父组件的 ref 对象。
  • 第三个参数 deps :依赖项 deps,依赖项更改形成新的 ref 对象。
// 子组件
function Son (props,ref) {
    const inputRef = useRef(null)
    const [ inputValue , setInputValue ] = useState('')
    useImperativeHandle(ref,()=>{
       const handleRefs = {
           onFocus(){              /* 声明方法用于聚焦input框 */
              inputRef.current.focus()
           },
           onChangeValue(value){   /* 声明方法用于改变input的值 */
               setInputValue(value)
           }
       }
       return handleRefs
    },[])
    return <div>
        <input placeholder="请输入内容"  ref={inputRef}  value={inputValue} />
    </div>
}

const ForwarSon = forwardRef(Son)
// 父组件
class Index extends React.Component{
    cur = null
    handerClick(){
       const { onFocus , onChangeValue } =this.cur
       onFocus() // 让子组件的输入框获取焦点
       onChangeValue('let us learn React!') // 让子组件input  
    }
    render(){
        return <div style={{ marginTop:'50px' }} >
            <ForwarSon ref={cur => (this.cur = cur)} />
            <button onClick={this.handerClick.bind(this)} >操控子组件</button>
        </div>
    }
}
  • 创建 Son、Index 组件
  • 因为 Son 为函数组件,没有实例,所以需要使用 forwardRef 转发 ref。const ForwarSon = forwardRef(Son)
  • 子组件 Son 用 useImperativeHandle 接收父组件 ref,将让 input 聚焦的方法 onFocus 和 改变 input 输入框的值的方法 onChangeValue,通过返回值传递给 ref 。
  • 父组件调用 ref 下子组件 Son 实例的 onFocus 和 onChangeValue 方法。

总结

使用 refs 让我们的 React 代码变得更好,在组件通信方面更加方便,让我们决定组件如何进行通信。