【问题标题】:React - Forwarding multiple refsReact - 转发多个引用
【发布时间】:2018-11-30 17:04:22
【问题描述】:

我有一个 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 节点InfoTable 中的每个标题。从那里我会将一个函数传递给 SideNav,该函数可以访问 Parent 中的 refs 以滚动到标题。

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

【问题讨论】:

标签: reactjs


【解决方案1】:

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

重要提示:React Hooks Api 可用于

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

The 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 挂钩委派所需的 refs。

这种方式不需要特殊的 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} ... />
     ....
   )
}

【讨论】:

  • 我只是对这个很好奇,因为我认为我遗漏了一些东西,但是你将如何使用 React Class 组件来做到这一点?
  • 很遗憾,你不能。在 react-hooks 之前,您将不得不破解像@nicholas-haley's answer这样的解决方案。从技术上讲,您可以使用React.createRef() 在组件的构造函数中创建一个引用,然后在this.ref.current 上手动添加方法,但这将是非常糟糕的,灾难性的糟糕类型;总的来说,使用 refs 不是做事的“反应方式”,文档喜欢在每次有机会时提及它reactjs.org/docs/refs-and-the-dom.html。但老实说,从长远来看,重构类组件还是值得的
  • hook API 真的很强大,在我看来,组合方式总是比继承方式好。结合基于钩子的组件是(至少现在)反应方向的事实,我什至不会花时间尝试用类组件解决这个 ref 问题,实际上只是重构它
  • 对于不同意 hooks 的人,在构造函数中 this.childRefs = {current:{ref1:this.ref1}} 和 export default React.forwardRef((props, ref) =&gt; &lt;Child /*ref={refs}*/ {...props} {...ref.current}/&gt;); &lt;ExportedChild ref={this.childRefs}/&gt;
【解决方案2】:

我遇到过类似的情况,我需要将多个 ref 转发给我的 Parent 组件的子组件。

我仍然没有找到一个优雅的解决方案,但是您可以尝试将您的 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.forwardRef。这样做一切正常,不使用 React.forwardRef 有什么我遗漏的吗?
  • @EJ Morgan 如何将 ref 作为对象传递并手动附加它们?
  • @EJMorgan 我刚刚注意到,我什至可以通过将 ref 传递给除“ref”之外的任何其他道具名称来将 ref 传递给功能组件......我也觉得我可能在这里遗漏了一些东西,但它有效
  • 这种方法不正确,因为ref 应该是callback 或具有current 键的对象。尝试将该代码与 TypeScript 一起使用,该代码将无法编译。尝试传递两个自定义 ref 属性,例如 &lt;Child ref1={this.ref1} ref2={this.ref2} /&gt;
【解决方案3】:

我实际上是从 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
}} />
【解决方案4】:

我仍然不明白ref 属性中发生了什么,但以下React 源代码typeof type.render === 'function' + 其他答案使我尝试从@987654326 传递hoisted refs 的function @,它的工作原理!

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 })}
      />
    );
  }
}

【讨论】:

    猜你喜欢
    • 2021-01-22
    • 2018-10-13
    • 2018-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-28
    相关资源
    最近更新 更多