Skip to content

Refs and the DOM

Refs提供了一种方式,允许我们访问DOM节点或者render方法中创建React元素。 在经典的React数据流中,props是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的props来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个React组件的实例,也可能是一个DOM元素。对于这两种情况,React都提供了解决方法。

何时使用Refs

下面几个适合使用refs的情况:

  • 管理焦点,文本选择或媒体播放
  • 触发强制动画
  • 集成第三方DOM库

避免使用refs来做任何可以可以通过声明式实现来完成的事情。 举个例子,避免在dialog组件里暴露open()close方法,最好传递isOpen属性。

勿过度使用Refs

你可能首先会想到使用refs在你的app中"让事情发生"。如果是这种情况,请花一点时间,认真在考虑以下state属性应该被那排在那个组件层级中。通常让高的组件层级拥有整个state,是更加恰当的。

创建Refs

Refs是使用React.createRefs创建的,并通过ref属性附加到React元素上。在构造组件时,通常将Refs分配给实例属性,以便可以在整个组件中引用它们。

class MyComponent extends React.Component {
    constructor(props){
        super(props)
        this.myRef  = React.createRefs();
    }
    render(){
        return <div ref={this.myRef}  />
    }
}

访问Refs

当ref被传递给render中元素时,对该节点的引用可以在ref的current属性中被访问。

const node = this.myRef.current;

ref的值根据节点类型而有所不同:

  • ref属性用于HTML元素时,构造函数使用React.createRefs创建的 ref接受底层DOM元素作为其current属性。
  • ref用于自定义class组件时候,ref对象接受组件的挂载实例作为其current属性。
  • 你不用在函数组件上使用ref属性,因为它们没有实例。

React会在组件挂载时给current属性传入DOM元素,并且在组件卸载时传入null值。ref会在componentDidMountcomponentDidUpdate生命周期钩子出发前更新。

为class组件增加Ref

如果我们想包装CustomTextInput,来模拟它挂载之后立即被点击的操作。我们可以使用ref来获取整个自定义的input组件并手动调用它的focusTextInput方法:

class AutoFocusTextInput extends React.Component{
    constructor(props){
        super(props)
        this.textInput = React.createRefs()
    }
    componentDidMount(){
        this.textInput.current.focusTextInput()
    }
    render(){
        return <CustomTextInput ref={this.textInput} / >
    }
}

请注意,这尽在组件声明为class时才有效。

Refs与函数组件

默认情况下。你不能在函数组件上使用ref属性,因为它们没有实例: 如果要在函数组件中使用ref,你可以使用forwardRef(可与useImperativeHandle结合使用),或者可以将改组件转为class组件。 不管怎样,你可以在函数组件内部使用ref,只要它指向一个DOM元素或者class组件。

  • useRef:
function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  const textInput = useRef(null);

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput}  />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
       />
    </div>
  );
}

useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数(initialValue)。返回的ref对象在整个生命周期内保持不变。

  • forwardRef解决textInput不是dom元素而是组件,转发ref。
const textInput = forwardRef((props,ref)=>{
    return <input ref={ref}></input>
})
function TextInputWithFocusButton(){
    const inputEl = useRef();
    const onButtonClick= () => {
        inputEl.current.focus();
    }

    return (
        <>
        <TextInput ref={inputEl}  />
        <button onClick={onButtonClick}>按钮</button>
        < />
    )
}
  • useImperativeHandle:当我不想把整个子组件暴露给父组件,只是提供父组件需要的方法或者值得时候:
const textInput = forwardRef((props,ref)=>{
    const inputEl  = useRef();
    useImperativeHandle(ref,()=>({
        focus:()=>{
            inputEl.current.focus()
        }
    }))

    return <input  ref="inputEl"  />
})
  • 回调Ref(useCallback):回调得方式可以把ref得变化通知父组件。
function TextInputWithFocusButton(){
    const {value,setValue} = useState("");
    const inputEl = useCallback(node=>{
        console.log(node)
        // 设置state.value
    },[])

    return (
        <>
            <div>{value}</div>
            子组件:<TextInput  ref={inputEl} />
        < />
    )
}