【问题标题】:How to deal with a ref within a loop?如何处理循环内的引用?
【发布时间】:2019-02-26 02:58:57
【问题描述】:

下面是我的父组件,其中包含来自循环的多个输入。如何选择一个input 关注?在这种情况下我是否必须创建一个动态的ref

class TestRef extends React.Component {
  ref = React.createRef();
  state = {
    data: [
      {
        name: "abc"
      },
      { name: "def" }
    ]
  };
  focusInput = () => this.ref.current.focus();
  render() {
    return (
      <div>
        {this.state.data.map(o => {
          return <Hello placeholder={o.name} ref={this.ref} />;
        })}
        <button onClick={this.focusInput}>focus input 1</button>
        <button onClick={this.focusInput}>focus input 2</button>
      </div>
    );
  }
}

【问题讨论】:

标签: javascript reactjs


【解决方案1】:

您可以使用callback refs 生成每个输入的动态引用并将其存储在一个数组中。现在您可以使用 ref 的索引来引用它们:

const Hello = React.forwardRef((props,  ref) => <input ref={ref} />);

class Button extends React.Component {
  onClick = () => this.props.onClick(this.props.id);

  render() {
    return (
      <button onClick={this.onClick}>{this.props.children}</button>
    );
  }
}

class TestRef extends React.Component {
  state = {
    data: [
      {
        name: "abc"
      },
      { name: "def" }
    ]
  };
  
  inputRefs = [];
  
  setRef = (ref) => {
    this.inputRefs.push(ref);
  };
  
  focusInput = (id) => this.inputRefs[id].focus();
  
  render() {
    return (
      <div>
        {this.state.data.map(({ name }) => (
          <Hello 
            placeholder={name} 
            ref={this.setRef} 
            key={name} />
        ))}
        <Button onClick={this.focusInput} id={0}>focus input 1</Button>
        <Button onClick={this.focusInput} id={1}>focus input 2</Button>
      </div>
    );
  }
}

ReactDOM.render(<TestRef />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

如果列表不是静态的,并且项目可能被删除/替换,您可能应该使用WeakMap 来保存引用,或者通过常量id 添加引用的任何其他方法。您还应该在使用 ref 之前进行检查,因为它可能不存在:

const Hello = React.forwardRef((props,  ref) => <input ref={ref} />);

class Button extends React.Component {
  onClick = () => this.props.onClick(this.props.id);

  render() {
    return (
      <button onClick={this.onClick}>{this.props.children}</button>
    );
  }
}

class TestRef extends React.Component {
  state = {
    data: [{ name: "abc" }, { name: "def" }, { name: "ghi" }]
  };
  
  componentDidMount() {
    setTimeout(() => {
      this.setState(({ data }) => ({
        data: data.slice(0, -1)
      }))
    }, 3000);
  }
  
  inputRefs = new WeakMap;
  
  setRef = (id) => (ref) => {
    this.inputRefs.set(id, ref);
  };
  
  focusInput = (id) => {
    const input = this.inputRefs.get(id);
    
    if(input) input.focus(); // use only if the ref exists - use optional chaining ?. if possible instead
  }
  
  render() {
    const { data } = this.state;
  
    return (
      <div>
        {data.map(o => (
          <Hello 
            placeholder={o.name} 
            ref={this.setRef(o)} 
            key={o.name} />
        ))}
        
        <br />
        
        {data.map((o, i) => (
          <Button onClick={this.focusInput} id={o} key={o.name}>focus input {i + 1}</Button>
        ))}
      </div>
    );
  }
}

ReactDOM.render(<TestRef />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

【讨论】:

  • 非常有用的解决方案
  • 这不适用于功能组件...我看到您有一个函数在加载引用时生成引用。我看到了 ref 参数,但我没有看到它作为参数传入?这是一个特殊的关键词还是我错过了什么?
  • 我只是想知道 (ref) 是从哪里来的,因为没有传入任何东西......
  • this.setRef 函数作为引用传递给&lt;Hello&gt; 组件。 &lt;Hello&gt; 组件将 ref 传递给 &lt;input&gt;,后者调用该函数并通过 (ref)ref 传递给它。阅读callback refs
  • 如果第一个 Hello 组件被删除会怎样?裁判仍然一致吗?例如refs[0] 仍然指向被删除的旧 Hello 不是吗?
【解决方案2】:

如果您要回答 2020 年的这个问题,这里是您如何使用循环中的 create hooks 创建多个参考

   const MyComponent=(){
    // empty list to put our refs in
    let LiRefs = []
    
    return (
        <React.Fragment>
          <ul className="event-list">
            // Check if my data exists first otherwise load spinner 
            {newData ? (
              newData.map((D) => {
                // the cool part inside the loop 
                // create new ref 
                // push it into array 

                const newRef = createRef();
                LiRefs.push(newRef);
                return (
                  // link it to your li 
                  // now you have list of refs that points to your list items 
                  <li key={D._id} ref={newRef}>
                    title : {D.title} <br />
                    description : {D.description}
                    <br />
                    data : {D.date} <br />
                    price : {D.price}
                    <br />
                    <div>creator : {D.creator.username}</div>
                    {authData.token && (
                      <button type="button" id={D._id} onClick={handelBooking}>
                        Book
                      </button>
                    )}
                  </li>
                );
              })
            ) : (
              <Spinner />
            )}
          </ul>
        </React.Fragment>
      );
 }

【讨论】:

  • 我不认为拥有 .map 会产生这样的副作用很酷的部分......
  • 虽然对模式进行了一些重构并且是可读的,但我删除了对这个想法的反对票。
  • 我预计 2020 年的答案将使用钩子而不是 createRef。这种方法有一些注意事项,尤其是当列表是动态的时。应该使用useRef 而不是createRef 甚至更好的回调参考。
【解决方案3】:

使用一般用途的焦点挂钩

// General Focus Hook
const useFocus = (initialFocus = false, id = "") => {
    const [focus, setFocus] = useState(initialFocus)
    return ([
        (newVal=true) => setFocus(newVal), {
            autoFocus: focus,
            key: `${id}${focus}`,
            onFocus: () => setFocus(true),
            onBlur: () => setFocus(false),
        },
    ])
}

const data: [{
        name: "abc"
    },{ 
        name: "def" 
}]

const TestRef = () => {

    const focusHelper = data.map( (_,i) => {
        const [setFocus, focusProps]= useFocus(false, i)
        return {setFocus, focusProps}
    }) 

    return (
      <div>
        {data.map( (o,i) => (
          <Hello placeholder={o.name} {...focusHelper[i].focusProps} />;
        ))}
        <button onClick={() => focusHelper[0].setFocus()}>focus input 1</button>
        <button onClick={() => focusHelper[1].setFocus()}>focus input 2</button>
      </div>
    );
}

您可以在这里找到更多信息:Set focus on input after render

【讨论】:

    【解决方案4】:

    我发现了另一种解决方法:

    let dataCount = 0;
    
    class TestRef extends React.Component {
      state = {
        data: [
          {
            name: "abc"
          },
          { name: "def" }
        ]
      };
      focusInput = (thisHello) => this[`ref${thisHello}`].current.focus();
      render() {
        return (
          <div>
            {this.state.data.map(o => {
              dataCount++
              return <Hello placeholder={o.name} ref={(el) => { this[`ref${dataCount}`] = el; }} />;
            })}
            <button onClick={() => this.focusInput(1)}>focus input 1</button>
            <button onClick={() => this.focusInput(2)}>focus input 2</button>
          </div>
        );
      }
    }
    

    如果您的 Hello 元素具有可用作变量的键或唯一 ID,则不需要 dataCount

    【讨论】:

      猜你喜欢
      • 2011-04-12
      • 2015-03-02
      • 1970-01-01
      • 2016-10-25
      • 1970-01-01
      • 2014-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多