React - 转发多个参考

IT技术 reactjs
2021-05-09 07:42:32

我有一个 SideNav 组件,它包含动态创建的链接,这些链接需要滚动到相邻 html 表 (InfoTable) 中的相应标题。我尝试了多种不同的方法来实现这一点,但无济于事。

export default class Parent extends Component {
  state = {
    categories: [],
  }

  scrollToHeader = (tableRefString) => {
    // Function to pass to SideNav to access refs in InfoTable
    this[tableRefString].scrollIntoView({ block: 'start' });
  }

  render() {
    return (
      <div>
        <SideNav
          categories={this.state.categories}
          scrollToHeader={this.scrollToHeader} />
        <InfoTable
          categories={this.state.categories} />
      </div>
    );
  }
}

export default class InfoTable extends Component {
  render() {
    return (
      <div>
        <table>
          <tbody>
            {this.props.categories.map(category => (
              <>
                // Forward the ref through InfoTableHeader to be set on the parent DOM node of each InfoTableHeader
                <InfoTableHeader />
                {category.inputs.map(input => <InfoTableRow />)}
              </>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
}

为了单击 SideNav 上的链接并滚动到 InfoTable 上的相应标题,我相信我需要转发基于我的类别数组中的名称在 Parent 上动态创建的引用,并将这些引用设置为每个标题的 DOM 节点信息表。从那里我将一个函数传递给 SideNav,该函数可以访问 Parent 中的 refs,以便滚动到标题。

  • 如何一次将多个引用转发到我的 InfoTable 组件?
  • 有没有更干净的方法来完成我想要做的事情?我已经研究过 React.findDOMNode() 但 refs 似乎是更好的选择。
4个回答

我知道有一个已经被接受的答案,虽然我发现@nicholas-haley 的解决方案是可以接受的。我认为更好的方法是使用内置useImperativeHandle钩子。

重要提示:React Hooks Api 可用

  • react@16.8.0 及更高版本
  • react-native@0.59.0 及更高版本

React hooks API Docs状态:

useImperativeHandle自定义使用 ref 时暴露给父组件的实例值。与往常一样,在大多数情况下应该避免使用 refs 的命令式代码。useImperativeHandle 应该与 `forwardRef 一起使用

此注释之后是以下示例:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

因此,在我看来,一个更简洁的解决方案是通过useImperativeHandle钩子委托所需的引用

这样就不需要特殊的 ref 语法,组件可以简单地返回一个特定类型的 FatherRef;例子:

// LabelInput.js
function LabelInput(props, ref) {
    const labelRef = useRef();
    const inputRef = useRef();

    useImperativeHandle(ref, () => ({
      focus: () => {
       inputRef.current.focus();
      },
      get input() {
          return inputRef.current;
      },
      get label() {
          return labelRef.current;
      },
      // ... whatever else one may need
    }));
    return (
      <div>
        <label ref={labelRef} ... />
        <input ref={inputRef} ... />;
      </div>
    )
}
LabelInput = forwardRef(LabelInput);

function MyScreen() {
   const labelInputRef = useRef();

   const onClick = useCallback(
    () => {
//       labelInputRef.current.focus(); // works
//       labelInputRef.current.input.focus(); // works
// ... etc
    },
    []
   );

   return (
     ...
      <LabelInput ref={labelInputRef} ... />
     ....
   )
}

我有一个类似的情况,我需要将多个引用转发到我的父组件的子组件。

我仍然没有找到一个优雅的解决方案,但是你可以尝试将你的 refs 作为一个对象传递,并在forwardRef回调中解构

// Parent
ref={{
    ref1: this.ref1,
    ref2: this.ref2
}}

// Child
export default React.forwardRef((props, ref) => {
  const { ref1, ref2 } = ref;

  return (
    <Child1
      {...props}
      ref={ref1}
    />
    <Child2
      {...props}
      ref={ref2}
    />
  );
});

我不是这里命名的忠实粉丝(我更喜欢ref被称为refs),但是如果您处于紧要关头,这会起作用。

编辑:

2020 年,我相信@samer-murad 的答案是解决此问题的最佳方法。

我实际上只是从react-hook-form 中选择了这个,但是他们提供了一种很好的方式来共享 ref 并一次分配多个 ref:

<input name="firstName" ref={(e) => {
  register(e) // use ref for register function
  firstNameRef.current = e // and you can still assign to ref
}} />

我仍然不明白该ref属性中发生了什么,但是以下React源代码typeof type.render === 'function' + 其他答案使我尝试传递functionof hoisted refsfrom parent,并且它有效!

class Child extends React.Component {
  render() {
    return (
      <div>
        <div
          ref={this.props.pa}
          style={this.props.style}
          onClick={async () => {
            this.storeAuth(...this.props.storableAuth);
            this.props.clearStore();
          }}
        />
        <div
          ref={this.props.fwd}
          style={this.props.style}
          onClick={async () => {
            this.props.onStart();
            const res = await this.getUserInfo(verbose, auth, storedAuth);
            if (res === "login?") this.props.onPromptToLogin();
            if (res) this.props.onFinish(); //res.isAnonymous
          }}
        />
      </div>
    );
  }
}

export default React.forwardRef((props, getRefs) => {
  const { pa, fwd } = props.getRefs();
  return <Child fwd={fwd} pa={pa} {...props} />;
});


class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.pa = React.createRef();
    this.fwd = React.createRef();
  }
  render() {
    return (
      <Child
        getRefs={() => {
          return {
            pa: this.pa,
            fwd: this.fwd
          };
        }}
        storableAuth={this.state.storableAuth}//[]
        clearAuth={() => this.setState({ storableAuth: null })}
      />
    );
  }
}