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

Reactjs - Adding ref to input in dynamic element render

我正在尝试 focus/highlight 在 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>
    )
}

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

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

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

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

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

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

现在,在修改后的代码中,您的方法对一个 Card 元素非常有效,但是当有多个 Card 元素时,它仍然只对最后一个元素有效。 Input Buttons 分别为它们的输入工作,但仅在最后呈现的 Card 元素上工作。

您正在循环中设置 ref,如您所知,ref 通过 this 关键字设置为 class。这意味着您正在设置多个 refs 但覆盖 class.
中的同一个 一种解决方案(不是理想的解决方案)是以不同的方式命名它们,也许将密钥添加到每个 ref 名称:

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

并且当您针对 ButtononClick 事件时,您应该使用相同的键作为参数:

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

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

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

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

优点:

  • 更容易实施
  • 实施速度更快

缺点:

  • 可能会导致性能问题
  • 减少反应组件方式

Working example

这是完整的代码:

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"));

更好更"react'ish"的解决方案是使用组件组合或包装Button的HOC并注入一些简单的逻辑,比如pass id 而不是在父级中使用 2 个函数。

优点:

  • 如前所述,出现性能问题的可能性较小
  • 您可以重复使用该组件和逻辑
  • 有时更容易调试

缺点:

  • 更多代码编写

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

A working example
完整代码:

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"));

编辑
作为您编辑的跟进:

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

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

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

将名称传递给处理程序:

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

Working example

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

完成运行示例:

import React,{useRef} from 'react';

function Userlist(){
    let toogleRef = useRef([])
        
    const userlist = [
        {name:'ankit',email:'a@gmail.com',phone:222},
        {name:'verma',email:'v@gmail.com',phone:33},
        {name:'sumit',email:'s@gmail.com',phone:444},
    ];
    function toogleFun(ind){
        
        console.log(toogleRef.current);
        toogleRef.current[ind].style.display="none";        
    }
    return(
    <>
        <table>
        <tbody>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Email</th>
                <th>Phone</th>
                <th>Toggle</th>
            </tr>
            {
                userlist.map((item, index)=>
                        <tr key={index}  ref={(element) => toogleRef.current.push(element)}>
                            <td>{index + 1}</td>
                            <td>{item.name}</td>
                            <td>{item.email}</td>
                            <td>{item.phone}</td>
                            <td><button onClick={()=>toogleFun(index)}>Toogle</button></td>
                        </tr>
                )   
            }
        </tbody>
        </table>
    </>
    )
}
export default Userlist;