javascript Reactjs - 在动态元素渲染中将 ref 添加到输入

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46474481/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 06:41:20  来源:igfitidea点击:

Reactjs - Adding ref to input in dynamic element render

javascriptreactjsjsxsemantic-ui-react

提问by merrilj

I'm trying to focus/highlight input text onClick in React. It works as expected, but only on the last element in the rendered array. I've tried several different methods but they all do the exact same thing. Here are two examples of what I have:

我正在尝试在 React 中聚焦/突出显示 onClick 输入文本。它按预期工作,但仅适用于渲染数组中的最后一个元素。我尝试了几种不同的方法,但它们都做完全相同的事情。以下是我所拥有的两个示例:

export default class Services extends Component {

handleFocus(event) {
    event.target.select()
}

handleClick() {
    this.textInput.focus()
}


render() {
    return (
        <div>
            {element.sources.map((el, i) => (
                <List.Item key={i}>
                <Segment style={{marginTop: '0.5em', marginBottom: '0.5em'}}>
                    <Input fluid type='text'
                        onFocus={this.handleFocus}
                        ref={(input) => { this.textInput = input }} 
                        value='text to copy'
                        action={
                            <Button inverted color='blue' icon='copy' onClick={() => this.handleClick}></Button>
                        }
                    />
                </Segment>
                </List.Item>
            ))}
        </div>
    )
}

If there's only one element being rendered, it focuses the text in the input, but if there are multiple elements, every element's button click selects only the last element's input. Here's another example:

如果只有一个元素被渲染,它会聚焦输入中的文本,但如果有多个元素,每个元素的按钮点击只会选择最后一个元素的输入。这是另一个例子:

export default class Services extends Component {

constructor(props) {
    super(props)

    this._nodes = new Map()
    this._handleClick = this.handleClick.bind(this)
}

handleFocus(event) {
    event.target.select()
}

handleClick(e, i) {
    const node = this._nodes.get(i)
    node.focus()
}


render() {
    return (
        <div>
            {element.sources.map((el, i) => (
                <List.Item key={i}>
                <Segment style={{marginTop: '0.5em', marginBottom: '0.5em'}}>
                    <Input fluid type='text'
                        onFocus={this.handleFocus}
                        ref={c => this._nodes.set(i, c)} 
                        value='text to copy'
                        action={
                            <Button inverted color='blue' icon='copy' onClick={e => this.handleClick(e, i)}></Button>
                        }
                    />
                </Segment>
                </List.Item>
            ))}
        </div>
    )
}

Both of these methods basically respond the same way. I need the handleClick input focus to work for every dynamically rendered element. Any advice is greatly appreciated. Thanks in advance!

这两种方法的响应方式基本相同。我需要 handleClick 输入焦点来处理每个动态呈现的元素。任何意见是极大的赞赏。提前致谢!

The Inputcomponent is imported from Semantic UI React with no additional implementations in my app

Input组件是从 Semantic UI React 导入的,在我的应用程序中没有额外的实现

UPDATEThanks guys for the great answers. Both methods work great in a single loop element render, but now I'm trying to implement it with multiple parent elements. For example:

更新谢谢你们的好答案。这两种方法在单个循环元素渲染中都很好用,但现在我试图用多个父元素来实现它。例如:

import React, { Component } from 'react'
import { Button, List, Card, Input, Segment } from 'semantic-ui-react'

export default class ServiceCard extends Component {

handleFocus(event) {
    event.target.select()
}

handleClick = (id) => (e) => {
    this[`textInput${id}`].focus()
}

render() {
    return (
        <List divided verticalAlign='middle'>
            {this.props.services.map((element, index) => (
                <Card fluid key={index}>
                    <Card.Content>
                        <div>
                            {element.sources.map((el, i) => (
                                <List.Item key={i}>
                                    <Segment>
                                        <Input fluid type='text'
                                            onFocus={this.handleFocus}
                                            ref={input => { this[`textInput${i}`] = input }} 
                                            value='text to copy'
                                            action={
                                                <Button onClick={this.handleClick(i)}></Button>
                                            }
                                        />
                                    </Segment>
                                </List.Item>
                            ))}
                        </div>
                    </Card.Content>
                </Card>
            ))}
        </List>
    )
}

Now, in the modified code, your methods work great for one Cardelement, but when there are multiple Cardelements, it still only works for the last one. Both InputButtonswork for their inputs respectively, but only on the last Cardelement rendered.

现在,在修改后的代码中,您的方法适用于一个Card元素,但是当有多个Card元素时,它仍然只适用于最后一个。两者都InputButtons分别适用于它们的输入,但仅适用于Card渲染的最后一个元素。

采纳答案by Sagiv b.g

You are setting a refinside a loop, as you already know, the refis set to the classvia the thiskey word. This means that you are setting multiple refsbut overriding the same one inside the class.
One solution (not the ideal solution) is to name them differently, maybe add the key to each refname:

您正在ref循环内部设置 a ,正如您已经知道的那样,ref设置为classviathis关键字。这意味着您正在设置多个,refs但在class.
一种解决方案(不是理想的解决方案)是以不同的方式命名它们,也许为每个ref名称添加键:

        ref={input => {
          this[`textInput${i}`] = input;
        }}

and when you target that onClickevent of the Buttonyou should use the same key as a parameter:

当您定位该onClick事件时,Button您应该使用相同的键作为参数:

 action={
                  <Button
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick(i)}
                  >
                    Focus
                  </Button>
                }

Now, the click event should change and accept the idas a parameter and trigger the relevant ref(i'm using currying here):

现在,点击事件应该改变并接受id作为参数并触发相关ref(我在这里使用柯里化):

  handleClick = (id) => (e) => {
      this[`textInput${id}`].focus();
  }

Note that this is and easier solution but not the ideal solution, as we create a new instance of a function on each render, hence we pass a new prop which can interrupt the diffing algorithm of react (a better and more "react'ish"way coming next).

请注意,这是一个更简单的解决方案,但不是理想的解决方案,因为我们在每次渲染时创建了一个函数的新实例,因此我们传递了一个新的 prop,它可以中断 react 的差异算法(更好,更“react'ish”)接下来的方式)。

Pros:

优点:

  • Easier to implement
  • Faster to implement
  • 更容易实施
  • 实施更快

Cons:

缺点:

  • May cause performance issues
  • Less the react components way
  • 可能会导致性能问题
  • 减少反应组件的方式

Working example

工作示例

This is the full Code:

这是完整的代码:

class Services extends React.Component {

  handleFocus(event) {
    event.target.select();
  }


  handleClick = id => e => {
    this[`textInput${id}`].focus();
  };

  render() {
    return (
      <div>
        {sources.map((el, i) => (
          <List.Item key={i}>
            <Segment style={{ marginTop: "0.5em", marginBottom: "0.5em" }}>
              <Input
                fluid
                type="text"
                onFocus={this.handleFocus}
                ref={input => {
                  this[`textInput${i}`] = input;
                }}
                value="text to copy"
                action={
                  <Button
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick(i)}
                  >
                    Focus
                  </Button>
                }
              />
            </Segment>
          </List.Item>
        ))}
      </div>
    );
  }
}

render(<Services />, document.getElementById("root"));

A better and more "react'ish"solution would be to use component composition or a HOC that wraps the Buttonand inject some simple logic, like pass the idinstead of using 2 functions in the parent.

更好和更“反应”的解决方案是使用组件组合或 HOC 来包装Button并注入一些简单的逻辑,例如传递id而不是在父级中使用 2 个函数。

Pros:

优点:

  • As mentioned, Less chances of performance issues
  • You can reuse this component and logic
  • Sometimes easier to debug
  • 如前所述,性能问题的可能性较小
  • 你可以重用这个组件和逻辑
  • 有时更容易调试

Cons:

缺点:

  • More code writing

  • Another component to maintain / test etc..

  • 更多代码编写

  • 另一个组件来维护/测试等。

A working example
The full Code:

一个工作示例
完整代码:

class MyButton extends React.Component {

  handleClick = (e) =>  {
    this.props.onClick(this.props.id)
  }

  render() {
    return (
      <Button
      {...this.props}
        onClick={this.handleClick}
      >
        {this.props.children}
      </Button>
    )
  }
}


class Services extends React.Component {

  constructor(props){
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleFocus(event) {
    event.target.select();
  }


  handleClick(id){
    this[`textInput${id}`].focus();
  };

  render() {
    return (
      <div>
        {sources.map((el, i) => (
          <List.Item key={i}>
            <Segment style={{ marginTop: "0.5em", marginBottom: "0.5em" }}>
              <Input
                fluid
                type="text"
                onFocus={this.handleFocus}
                ref={input => {
                  this[`textInput${i}`] = input;
                }}
                value="text to copy"
                action={
                  <MyButton
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick}
                    id={i}
                  >
                    Focus
                  </MyButton>
                }
              />
            </Segment>
          </List.Item>
        ))}
      </div>
    );
  }
}

render(<Services />, document.getElementById("root"));

Edit
As a followup to your edit:

编辑
作为您编辑的后续:

but when there are multiple Card elements, it still only works for the last one.

但是当有多个 Card 元素时,它仍然只对最后一个有效。

This happens for the same reason as before, you are using the same ifor both arrays.
This is an easy solution, use both indexand ifor your refnames.
Setting the refname:

发生这种情况的原因与以前相同,您i对两个数组使用相同的方法。
这是一个简单的解决方案,同时使用indexi作为您的ref名字。
设置ref名称:

ref={input => { this[`textInput${index}${i}`] = input }}

Passing the name to the handler:

将名称传递给处理程序:

<Button onClick={this.handleClick(`${index}${i}`)}></Button>

Working example

工作示例

I've modified my question and provided a second solution that is considered best practice. read my answer again and see the different approaches.

我修改了我的问题并提供了第二个被认为是最佳实践的解决方案。再次阅读我的答案,看看不同的方法。