SolidJS:输入字段在输入时失去焦点

SolidJS: input field loses focus when typing

我有一个关于 SolidJS 的新手问题。我有一个包含对象的数组,例如待办事项列表。我将其呈现为一个列表,其中包含用于编辑这些对象中的一个属性的输入字段。在其中一个输入字段中输入时,输入直接失去焦点。

如何防止输入在输入时失去焦点?

这是一个演示问题的 CodeSandbox 示例:https://codesandbox.io/s/6s8y2x?file=/src/main.tsx

这是演示该问题的源代码:

import { render } from "solid-js/web";
import { createSignal, For } from 'solid-js'

function App() {
  const [todos, setTodos] = createSignal([
    { id: 1, text: 'cleanup' },
    { id: 2, text: 'groceries' },
  ])

  return (
    <div>
      <div>
        <h2>Todos</h2>
        <p>
          Problem: whilst typing in one of the input fields, they lose focus
        </p>
        <For each={todos()}>
          {(todo, index) => {
            console.log('render', index(), todo)
            return <div>
              <input
                value={todo.text}
                onInput={event => {
                  setTodos(todos => {
                    return replace(todos, index(), {
                      ...todo,
                      text: event.target.value
                    })
                  })
                }}
              />
            </div>
          }}
        </For>
        Data: {JSON.stringify(todos())}
      </div>
    </div>
  );
}

/*
 * Returns a cloned array where the item at the provided index is replaced
 */
function replace<T>(array: Array<T>, index: number, newItem: T) : Array<T> {
  const clone = array.slice(0)
  clone[index] = newItem
  return clone
}

render(() => <App />, document.getElementById("app")!);

更新:我已经制定了一个 CodeSandbox 示例,其中包含问题和三个建议的解决方案(基于两个答案):https://codesandbox.io/s/solidjs-input-field-loses-focus-when-typing-itttzy?file=/src/App.tsx

<For> components keys items of the input array by reference. 当您使用 replace 更新待办事项内的待办事项时,您正在创建一个全新的对象。 Solid 然后将新对象视为完全不相关的项目,并为其创建一个新的 HTML 元素。

您可以改用 createStore,只更新待办事项对象的单个 属性,而不更改对它的引用。

const [todos, setTodos] = createStore([
   { id: 1, text: 'cleanup' },
   { id: 2, text: 'groceries' },
])
const updateTodo = (id, text) => {
   setTodos(o => o.id === id, "text", text)
}

或者使用另一个控制流组件来映射输入数组,它采用显式键 属性: https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#Key

<Key each={todos()} by="id">
   ...
</Key>

虽然@thetarnav 解决方案有效,但我想提出自己的解决方案。 我会用 <Index>

来解决
import { render } from "solid-js/web";
import { createSignal, Index } from "solid-js";

/*
 * Returns a cloned array where the item at the provided index is replaced
 */
function replace<T>(array: Array<T>, index: number, newItem: T): Array<T> {
  const clone = array.slice(0);
  clone[index] = newItem;
  return clone;
}

function App() {
  const [todos, setTodos] = createSignal([
    { id: 1, text: "cleanup" },
    { id: 2, text: "groceries" }
  ]);

  return (
    <div>
      <div>
        <h2>Todos</h2>
        <p>
          Problem: whilst typing in one of the input fields, they lose focus
        </p>
        <Index each={todos()}>
          {(todo, index) => {
            console.log("render", index, todo());
            return (
              <div>
                <input
                  value={todo().text}
                  onInput={(event) => {
                    setTodos((todos) => {
                      return replace(todos, index, {
                        ...todo(),
                        text: event.target.value
                      });
                    });
                  }}
                />
              </div>
            );
          }}
        </Index>
        Dat: {JSON.stringify(todos())}
      </div>
    </div>
  );
}
render(() => <App />, document.getElementById("app")!);

如您所见,index 不再是 function/signal,现在对象是。这允许框架替换内联文本框的值。 记住它是如何工作的:为了通过引用记住你的对象。如果您的对象交换位置,则可以重复使用同一个对象。 Index 按索引记住您的值。如果某个索引处的值发生变化,则会反映在信号中。

这个解决方案与另一个提出的解决方案并没有或多或少是正确的,但我觉得这更符合并且更接近Solid的核心。

使用 For,当项目更新时整个元素将是 re-created。更新项目时失去焦点,因为具有焦点的元素 (input) 及其父元素 (li) 被销毁,并创建了一个新元素。

你有两个选择。您可以在创建新元素时手动获取焦点,也可以在 属性 更新时保持元素的更好反应性。 indexArray 提供开箱即用的后者。

indexArray 在更新项目时保留元素引用。 Index 组件在后台使用 indexArray

function App() {
  const [todos, setTodos] = createSignal([
    { id: 1, text: "cleanup" },
    { id: 2, text: "groceries" }
  ]);

  return (
    <ul>
      {indexArray(todos, (todo, index) => (
        <li>
          <input
            value={todo().text}
            onInput={(event) => {
              const text = event.target.value;
              setTodos(todos().map((v, i) => i === index ? { ...v, text } : v))
            }}
          />
        </li>
      ))}
    </ul>
  );
}

注意:For 组件在内部缓存项目以避免不必要的 re-renders。未更改的项目将为 re-used,但更新的项目将为 re-created。