首页 > 解决方案 > Reactjs:以编程方式关注以编程方式生成的输入

问题描述

我有一个呈现可编辑表格的应用程序,每行有几个输入元素。由于该表填充了用户条目,因此它可以是可变长度的。表中的某些用户条目将无效。当条目无效时,我想显示一条警报,上面写着“您输入了无效数据”,然后将用户重点放在具有无效条目的输入上。

通读文档, ref 似乎是最好的做事方式。但是,我发现的所有示例,无论是来自 React 团队还是 Stack Overflow,都显示单个输入绑定到单个 ref。某些操作(通常是单击按钮)被硬编码以引用特定的 ref,并且一切正常。

我想确定在出现错误时访问相关输入的最佳方法。做到这一点的“最佳”方法似乎是创建一个 refs 数组,然后为每个输入分配一个 ref——但文档也强调要谨慎使用 refs,这感觉像是对 refs 的严重滥用。

我更愿意做一些事情,比如创建一个 ref,然后将它重新分配给最近更新的任何输入。每次用户失去焦点时,它都会检查是否强制将焦点放在包含错误数据的输入上。这感觉它有可能成为一种反模式,特别是因为我找不到任何其他人实施它的证据。

我很想知道解决这个问题的最佳方法,因为我担心我的两个想法都不是好的前进道路。

标签: reactjsfocus

解决方案


您可以不依赖于输入的单个 ref,而是依赖于容器 ref。您始终拥有一个容器引用,并且每当用户模糊输入时,您必须使用从容器中获取所有输入.querySelectorAll('input'),找到无效的输入并将其聚焦。

const {
  useState,
  useCallback,
  useMemo,
  useRef,
  useEffect
} = React;

const defaultUsers = [
  { id: 1, name: "User #1", email: "user1@gmail.com" },
  { id: 2, name: "User #2", email: "user2@gmail.com" },
  { id: 3, name: "User #3", email: "user3@gmail.com" },
  { id: 4, name: "User #4", email: "user4@gmail.com" },
  { id: 5, name: "", email: "user5@gmail.com" },
  { id: 6, name: "User #6", email: "user6@gmail.com" },
  { id: 7, name: "User #7", email: "user7@gmail.com" },
  { id: 8, name: "User #8", email: "" },
  { id: 9, name: "User #9", email: "user9@gmail.com" },
  { id: 10, name: "User #10", email: "user10@gmail.com" }
];

const App = () => {
  const tbodyRef = useRef();

  const focusNextInvalidInput = useCallback(() => {
    if (tbodyRef.current) {
      const inputs = tbodyRef.current.querySelectorAll("td > input");
      for (const input of inputs) {
        if (input.value.length === 0) {
          input.focus();
          return;
        }
      }
    }
  }, [tbodyRef]);

  useEffect(() => {
    focusNextInvalidInput();
  }, [focusNextInvalidInput]);

  const [users, setUsers] = useState(defaultUsers);

  const updateUserField = useCallback(
    (id, field, value) =>
      setUsers(
        users.map(user => (user.id === id ? { ...user, [field]: value } : user))
      ),
    [users]
  );

  const changeUserName = useCallback(
    (id, name) => updateUserField(id, "name", name),
    [updateUserField]
  );
  const changeUserEmail = useCallback(
    (id, email) => updateUserField(id, "email", email),
    [updateUserField]
  );

  const usersRows = useMemo(() => {
    return users.map(({ id, name, email }) => (
      <tr key={id}>
        <td>{id}</td>
        <td>
          <input
            type="text"
            onChange={e => changeUserName(id, e.target.value)}
            value={name}
            onBlur={focusNextInvalidInput}
          />
        </td>
        <td>
          <input
            type="email"
            onChange={e => changeUserEmail(id, e.target.value)}
            value={email}
            onBlur={focusNextInvalidInput}
          />
        </td>
      </tr>
    ));
  }, [users, changeUserEmail, changeUserName, focusNextInvalidInput]);

  return (
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Email</th>
        </tr>
      </thead>
      <tbody ref={tbodyRef}>{usersRows}</tbody>
    </table>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

代码沙盒示例


推荐阅读