在条件渲染中使用引用

11

我在使用ref和条件渲染时遇到了问题。 我想在单击按钮标签时将焦点放在输入标签上。 基本上,我有这个简化的代码。

class App extends React.Component {
  textInput
  constructor(props) {
    super(props)
    this.state = {isEditing: false}
    this.textInput = React.createRef()
  }

    onClick = () => {
        this.setState({isEditing: !this.state.isEditing})
        this.textInput.current.focus();
    }
  render () {
    let edit = this.state.isEditing ?
        (<input type="text" ref={this.textInput} />)
        : ""
    return (
      <div>
            <button onClick={this.onClick}>lorem </button>
            {edit}
      </div>
    );
  }
}

当我点击按钮时,输入标签会显示出来,但参考textInput仍然设置为null。因此,我无法聚焦输入。

我找到了一些解决方法,例如:

  • 在输入标签中设置autoFocus属性
  • isEditing == false时使用css隐藏输入标签

但实际上,这是一个非常基本的模式,我想知道是否有更简洁的解决方案。

谢谢

2个回答

14

简而言之:

将此更改为:

this.setState({isEditing: !this.state.isEditing})
this.textInput.current.focus();

变成这样:

this.setState(previousState => ({isEditing: !previousState.isEditing}), () => {
    this.textInput.current.focus();    
});

更新:函数组件/钩子

评论区问到如何在使用useState和函数组件时实现这一点。Rafał Guźniczak的回答解释了这个问题,但我想提供更多的解释和可运行的示例。

仍然不建议在设置状态后立即读取它,而是需要在状态更新并且组件重新渲染后运行一些代码。那么我们该怎么做呢?

答案是useEffect。effect的目的是将外部"事物"(例如:焦点等命令式的DOM操作)与React状态同步:

const { useEffect, useRef, useState } = React;
const { render } = ReactDOM;

function App(props) {
  const [isEditing, setIsEditing] = useState(false);
  const textInputRef = useRef(null);

  const toggleEditing = () => setIsEditing(val => !val);

  // whenever isEditing gets set to true, focus the textbox
  useEffect(() => {
    if (isEditing && textInputRef.current) {
      textInputRef.current.focus();
    }
  }, [isEditing, textInputRef]);

  return (
    <div>
      <button onClick={toggleEditing}>lorem </button>
      {isEditing && <input type="text" ref={textInputRef} />}
    </div>
  );
}

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

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

您在React中遇到的一个普遍问题是假定设置状态是同步的,但实际上不是。当您调用setState时,您正在“请求”React更新状态。实际的状态更新会在稍后发生。这意味着在setState调用之后,edit元素尚未被创建或渲染,因此引用指向空值。 根据文档setState()将更改排队到组件状态,并告诉React需要使用更新后的状态重新呈现此组件及其子组件。这是您用于响应事件处理程序和服务器响应以更新用户界面的主要方法。 把setState()视为一种请求而不是立即更新组件的命令。为了获得更好的感知性能,React可能会延迟它,然后在单个传递中更新多个组件。React不能保证状态更改立即生效。setState()并不总是立即更新组件。它可能会批处理或推迟更新。这使得在调用setState()后立即读取this.state成为潜在的陷阱。相反,请使用componentDidUpdatesetState回调(setState(updater, callback)),两者都保证在更新应用后触发。

明白了,我不知道我们可以将一个函数作为setState的参数传递。 - huguesvincent
是的,很不幸大多数文档介绍setState的方式都不是这样。在我看来,API 应该 支持双函数形式,并且可能应该被称为 setStateAsync,但不幸的是现在为时已晚 :( - rossipedia
2
@rossipedia,虽然你的方法还可以,但我建议在这里使用函数式的setState(),因为新状态取决于之前的状态。这也是官方文档推荐的方法。 - Chris
如何在函数式组件中实现这个? - Faizan khan
setState() 不是一个 async 函数,不能使用 await 等待。 - rossipedia
显示剩余3条评论

4

非常感谢@rossipedia的回答。我想知道是否可以使用hooks来实现。

显然,你不能像setState一样将第二个参数传递给useState setter。但是你可以像这样使用useEffect(注意useEffect中的第二个参数):

const [isEditing, setIsEditing] = React.useState(false);
React.useEffect(() => {
    if (isEditing) {
        textInput.current.focus();
    }
}, [isEditing]);

const handleClick = () => setIsEditing(isEditing);

它起作用了!;)

来源:https://www.robinwieruch.de/react-usestate-callback/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接