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

Posted

技术标签:

【中文标题】Reactjs - 在动态元素渲染中添加 ref 到输入【英文标题】:Reactjs - Adding ref to input in dynamic element render 【发布时间】:2018-03-10 12:17:41 【问题描述】:

我正在尝试在 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 元素上工作。

【问题讨论】:

React Select mapping issue的可能重复 不同之处在于,除了 handleClick 之外,输入上的其他方法对每个元素都可以正常工作。 ref 只选择最后渲染的元素,不选择其他元素。 @MerrilJeffs 第二个代码将按预期工作。您在控制台上收到第二个代码的任何错误吗? 显示您的Input 组件的代码以了解您如何实现操作 (Button) 事件 @MerrilJeffs 第二个代码正在运行。这是工作示例codesandbox.io/s/p3y90wmp7m 【参考方案1】:

您正在循环内设置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(我在这里使用currying):

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

请注意,这是一个更简单的解决方案,但不是理想的解决方案,因为我们在每个渲染上创建一个函数的新实例,因此我们传递一个新的 prop 可以中断 react 的差异算法(更好和更多 “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并注入一些简单的逻辑,例如传递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"));

编辑 作为您编辑的后续行动:

但是当有多个 Card 元素时,它仍然只适用于 最后一个。

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

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

将名称传递给处理程序:

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

Working example

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

【讨论】:

感谢您的意见!您的示例效果很好,但现在我正在尝试使用多个父组件。我在原始问题中发布了一个示例。 谢谢.. 这是完美的! 这正是我的解决方案。谢谢!【参考方案2】:

完整的运行示例:

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;

【讨论】:

以上是关于Reactjs - 在动态元素渲染中添加 ref 到输入的主要内容,如果未能解决你的问题,请参考以下文章

ReactJs:为啥 ref.current 在渲染组件时返回 null?

调用 setState 后 ReactJS 元素未更新

ReactJS 中的动态属性

如何在 ReactJS 中使用 ref 触发动画

reactJS 和动态渲染和变量填充

使用 ReactJS 动态加载本地图像