react ref转发

ref转发是一个非常实用的场景,父组件可以操作子组件的某些react ref实例

  1. const FancyButton = React.forwardRef((props, ref) => (
  2. <button ref={ref} className="FancyButton">
  3. {props.children}
  4. </button>
  5. ));
  6. // 你可以直接获取 DOM button ref
  7. const ref = React.createRef();
  8. <FancyButton ref={ref}>Click me!</FancyButton>;

以下是对上述示例发生情况的逐步解释:

  1. 我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
  2. 我们通过指定 ref 为 JSX 属性,将其向下传递给 <FancyButton ref={ref}>
  3. React 传递 reffowardRef 内函数 (props, ref) => ...,作为其第二个参数。
  4. 我们向下转发该 ref 参数到 <button ref={ref}>,将其指定为 JSX 属性。
  5. 当 ref 挂载完成,ref.current 将指向 <button> DOM 节点。

    注意 第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。 Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。

高阶组件的ref转发

  1. function logProps(WrappedComponent) {
  2. class LogProps extends React.Component {
  3. componentDidUpdate(prevProps) {
  4. console.log('old props:', prevProps);
  5. console.log('new props:', this.props);
  6. }
  7. render() {
  8. return <WrappedComponent {...this.props} />;
  9. }
  10. }
  11. return LogProps;
  12. }
  13. class FancyButton extends React.Component {
  14. focus() {
  15. // ...
  16. }
  17. // ...
  18. }
  19. // 我们导出 LogProps,而不是 FancyButton
  20. // 虽然它也会渲染一个 FancyButton
  21. export default logProps(FancyButton);
  22. /*
  23. 上面的示例有一点需要注意:refs 将不会透传下去。这是因为 ref 不是 prop 属性。
  24. 就像 key 一样,其被 React 进行了特殊处理。
  25. 如果你对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件。
  26. */
  27. 这意味着用于我们 FancyButton 组件的 refs 实际上将被挂载到 LogProps 组件:
  28. import FancyButton from './FancyButton';
  29. const ref = React.createRef();
  30. // 我们导入的 FancyButton 组件是高阶组件(HOCLogProps
  31. // 尽管渲染结果将是一样的,
  32. // 但我们的 ref 将指向 LogProps 而不是内部的 FancyButton 组件!
  33. // 这意味着我们不能调用例如 ref.current.focus() 这样的方法
  34. <FancyButton
  35. label="Click Me"
  36. handleClick={handleClick}
  37. ref={ref}
  38. />;

正确的使用方式

  1. function logProps(Component) {
  2. class LogProps extends React.Component {
  3. componentDidUpdate(prevProps) {
  4. console.log('old props:', prevProps);
  5. console.log('new props:', this.props);
  6. }
  7. render() {
  8. const {forwardedRef, ...rest} = this.props;
  9. // 将自定义的 prop 属性 forwardedRef 定义为 ref
  10. return <Component ref={forwardedRef} {...rest} />;
  11. }
  12. }
  13. // 注意 React.forwardRef 回调的第二个参数 ref”。
  14. // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 forwardedRef
  15. // 然后它就可以被挂载到被 LogProps 包裹的子组件上。
  16. return React.forwardRef((props, ref) => {
  17. return <LogProps {...props} forwardedRef={ref} />;
  18. });
  19. }

定义别名,其实没声明卵用 在 DevTools 中显示自定义名称

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

  1. const WrappedComponent = React.forwardRef((props, ref) => {
  2. return <LogProps {...props} forwardedRef={ref} />;
  3. });

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

  1. const WrappedComponent = React.forwardRef(
  2. function myFunction(props, ref) {
  3. return <LogProps {...props} forwardedRef={ref} />;
  4. }
  5. );

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

  1. function logProps(Component) {
  2. class LogProps extends React.Component {
  3. // ...
  4. }
  5. function forwardRef(props, ref) {
  6. return <LogProps {...props} forwardedRef={ref} />;
  7. }
  8. // 在 DevTools 中为该组件提供一个更有用的显示名。
  9. // 例如 “ForwardRef(logProps(MyComponent))”
  10. const name = Component.displayName || Component.name;
  11. forwardRef.displayName = `logProps(${name})`;
  12. return React.forwardRef(forwardRef);
  13. }