React 中获取 DOM 引用的方式

#react

目录

1. 总结

  1. 优先使用 useRefcreateRef
  2. 需要更细粒度控制时使用 回调refs
  3. 需要跨组件传递 ref 时使用 forwardRef
  4. 尽量避免使用字符串 refsfindDOMNode
  5. 只在必要时使用 refs(如焦点管理、动画、第三方 DOM 库集成等)
  6. 不要过度使用 refs 来操作 DOM,尽量通过 React 的声明式更新来管理 UI

2. React.createRef()

最新推荐的创建 ref 的方式:

// 1.1 在类组件中使用
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    // 访问 DOM 节点
    console.log(this.myRef.current);
  }

  render() {
    return <div ref={this.myRef}>Hello</div>;
  }
}

// 1.2 在类组件中使用 class fields
class MyComponent extends React.Component {
  myRef = React.createRef();

  handleClick = () => {
    // 访问 DOM 节点
    this.myRef.current.focus();
  };

  render() {
    return (
      <>
        <input ref={this.myRef} />
        <button onClick={this.handleClick}>Focus Input</button>
      </>
    );
  }
}

3. useRef Hook

在函数组件中使用的 Hook 方式:

// 2.1 基本用法
function TextInputWithFocusButton() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </>
  );
}

// 2.2 多个 ref 的使用
function MultipleRefs() {
  const firstRef = useRef(null);
  const secondRef = useRef(null);
  const thirdRef = useRef(null);

  const handleClick = (ref) => {
    ref.current.scrollIntoView({ behavior: 'smooth' });
  };

  return (
    <div>
      <button onClick={() => handleClick(firstRef)}>Scroll to First</button>
      <button onClick={() => handleClick(secondRef)}>Scroll to Second</button>
      <button onClick={() => handleClick(thirdRef)}>Scroll to Third</button>
      
      <div ref={firstRef}>First Element</div>
      <div ref={secondRef}>Second Element</div>
      <div ref={thirdRef}>Third Element</div>
    </div>
  );
}

4. 回调 Refs(Callback Refs)

通过回调函数的方式设置 ref:

// 3.1 基本用法
class CallbackRefComponent extends React.Component {
  setTextInputRef = (element) => {
    this.textInput = element;
  };

  focusTextInput = () => {
    // 直接使用 DOM API
    if (this.textInput) this.textInput.focus();
  };

  render() {
    return (
      <>
        <input type="text" ref={this.setTextInputRef} />
        <button onClick={this.focusTextInput}>Focus Input</button>
      </>
    );
  }
}

// 3.2 在函数组件中使用
function CallbackRefFunctional() {
  let textInput = null;

  const setTextInputRef = (element) => {
    textInput = element;
  };

  const focusTextInput = () => {
    if (textInput) textInput.focus();
  };

  return (
    <>
      <input ref={setTextInputRef} />
      <button onClick={focusTextInput}>Focus Input</button>
    </>
  );
}

5. forwardRef

用于转发 refs 到子组件

// 4.1 基本用法
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="fancy-button">
    {props.children}
  </button>
));

// 使用 FancyButton
function Parent() {
  const buttonRef = useRef(null);

  return <FancyButton ref={buttonRef}>Click me!</FancyButton>;
}

// 4.2 带有 HOC 的 forwardRef
const withLogger = (WrappedComponent) => {
  return React.forwardRef((props, ref) => {
    useEffect(() => {
      console.log('Component mounted');
    }, []);

    return <WrappedComponent {...props} ref={ref} />;
  });
};

const LoggedButton = withLogger(FancyButton);

6. 字符串 Refs(不推荐)

旧版本的 ref 方式,现已不推荐使用:

// 不推荐使用的字符串 ref
class LegacyComponent extends React.Component {
  componentDidMount() {
    // 不推荐
    this.refs.myInput.focus();
  }

  render() {
    return <input ref="myInput" />;
  }
}

7. findDOMNode(不推荐)

legacy API,在严格模式下会警告:

// 不推荐使用 findDOMNode
class LegacyFindDOMNode extends React.Component {
  componentDidMount() {
    // 不推荐
    const node = ReactDOM.findDOMNode(this);
  }

  render() {
    return <div>Legacy</div>;
  }
}

8. 实际应用示例

// 7.1 复杂表单处理
function ComplexForm() {
  const formRefs = {
    name: useRef(null),
    email: useRef(null),
    phone: useRef(null)
  };

  const validateField = (fieldName) => {
    const value = formRefs[fieldName].current.value;
    // 验证逻辑
  };

  return (
    <form>
      <input ref={formRefs.name} placeholder="Name" />
      <input ref={formRefs.email} type="email" placeholder="Email" />
      <input ref={formRefs.phone} placeholder="Phone" />
    </form>
  );
}

// 7.2 媒体播放器
function VideoPlayer({ src }) {
  const videoRef = useRef(null);

  const handlePlay = () => {
    videoRef.current.play();
  };

  const handlePause = () => {
    videoRef.current.pause();
  };

  return (
    <div>
      <video ref={videoRef} src={src} />
      <button onClick={handlePlay}>Play</button>
      <button onClick={handlePause}>Pause</button>
    </div>
  );
}