表单

¥Forms

Preact 中的表单与 HTML 中的表单工作方式非常相似。你渲染一个控件,并向其附加一个事件监听器。

¥Forms in Preact work much the same as they do in HTML. You render a control, and attach an event listener to it.

主要区别在于,大多数情况下 value 不是由 DOM 节点控制,而是由 Preact 控制。

¥The main difference is that in most cases the value is not controlled by the DOM node, but by Preact.



受控和非受控组件

¥Controlled & Uncontrolled Components

在谈论表单控件时,你经常会遇到“"受控组件"”和“"不受控制的组件"”这两个词。该描述是指处理数据流的方式。

¥When talking about form controls you'll often encounter the words "Controlled Component" and "Uncontrolled Component". The description refers to the way data flow is handled.

DOM 具有双向数据流,因为每个表单控件都会自行管理用户输入。当用户输入时,简单的文本输入将始终更新其值。相比之下,像 Preact 这样的框架通常具有单向数据流;组件不管理那里的值本身,而是管理组件树中更高层的其他东西。

¥The DOM has a bidirectional data flow, because every form control will manage the user input themselves. A simple text input will always update its value when a user typed into it. In contrast, a framework like Preact generally has a unidirectional data flow; the component doesn't manage the value itself there, but something else higher up in the component tree.

通常,你应尽可能尝试使用不受控组件,DOM 完全能够处理 <input> 的状态:

¥Generally, you should try to use Uncontrolled Components whenever possible, the DOM is fully capable of handling <input>'s state:

// Uncontrolled, because Preact doesn't set the value
<input onInput={myEventHandler} />;

但是,在某些情况下,你可能需要对输入值施加更严格的控制,在这种情况下,可以使用受控组件。

¥However, there are situations in which you might need to exert tighter control over the input value, in which case, Controlled Components can be used.

要在 Preact 中使用受控组件,你需要使用 refs 来弥合 DOM 状态和 VDOM/Preact 状态之间的固有差距。以下是如何使用受控组件来限制输入字段中的字符数的示例:

¥To use controlled components in Preact, you will need to use refs to bridge the inherent gap between the DOM state and VDOM/Preact's state. Here's an example of how you might use a controlled component to limit the number of characters in an input field:

const Input = () => {
  const [value, setValue] = useState('')
  const inputRef = useRef()

  const onInput = (e) => {
    if (e.currentTarget.value.length <= 3) {
      setValue(e.currentTarget.value)
    } else {
      const start = inputRef.current.selectionStart
      const end = inputRef.current.selectionEnd
      const diffLength = Math.abs(e.currentTarget.value.length - value.length)
      inputRef.current.value = value
      // Restore selection
      inputRef.current.setSelectionRange(start - diffLength, end - diffLength)
    }
  }

  return (
    <>
      <label>This input is limited to 3 characters: </label>
      <input ref={inputRef} value={value} onInput={onInput} />
    </>
  );
}
Run in REPL

有关 Preact 中受控组件的更多信息,请参阅 Jovi De Croock 的 受控输入

¥For more information on controlled components in Preact, see Controlled Inputs by Jovi De Croock.

创建一个简单的表单

¥Creating A Simple Form

让我们创建一个简单的表单来提交待办事项。为此,我们创建一个 <form>-Element 并绑定一个事件处理程序,每当提交表单时都会调用该事件处理程序。我们对文本输入字段执行类似的操作,但请注意,我们自己将值存储在类中。你猜对了,我们在这里使用受控输入。在这个例子中它非常有用,因为我们需要在另一个元素中显示输入的值。

¥Let's create a simple form to submit todo items. For this we create a <form>-Element and bind an event handler that is called whenever the form is submitted. We do a similar thing for the text input field, but note that we are storing the value in our class ourselves. You guessed it, we're using a controlled input here. In this example it's very useful, because we need to display the input's value in another element.

class TodoForm extends Component {
  state = { value: '' };

  onSubmit = e => {
    alert("Submitted a todo");
    e.preventDefault();
  }

  onInput = e => {
    this.setState({ value: e.currentTarget.value })
  }

  render(_, { value }) {
    return (
      <form onSubmit={this.onSubmit}>
        <input type="text" value={value} onInput={this.onInput} />
        <p>You typed this value: {value}</p>
        <button type="submit">Submit</button>
      </form>
    );
  }
}
Run in REPL

选择输入

¥Select Input

<select>-Element 稍微复杂一些,但其工作原理与所有其他表单控件类似:

¥A <select>-Element is a little more involved, but works similar to all other form controls:

class MySelect extends Component {
  state = { value: '' };

  onChange = e => {
    this.setState({ value: e.currentTarget.value });
  }

  onSubmit = e => {
    alert("Submitted " + this.state.value);
    e.preventDefault();
  }

  render(_, { value }) {
    return (
      <form onSubmit={this.onSubmit}>
        <select value={value} onChange={this.onChange}>
          <option value="A">A</option>
          <option value="B">B</option>
          <option value="C">C</option>
        </select>
        <button type="submit">Submit</button>
      </form>
    );
  }
}
Run in REPL

复选框和单选按钮

¥Checkboxes & Radio Buttons

在构建受控表单时,复选框和单选按钮 (<input type="checkbox|radio">) 最初可能会引起混乱。这是因为在不受控制的环境中,我们通常会允许浏览器为我们 "toggle" 或 "check" 复选框或单选按钮,监听更改事件并对新值做出反应。然而,这种技术并不能很好地过渡到 UI 始终自动更新以响应状态和属性变化的世界观。

¥Checkboxes and radio buttons (<input type="checkbox|radio">) can initially cause confusion when building controlled forms. This is because in an uncontrolled environment, we would typically allow the browser to "toggle" or "check" a checkbox or radio button for us, listening for a change event and reacting to the new value. However, this technique does not transition well into a world view where the UI is always updated automatically in response to state and prop changes.

演练:假设我们监听复选框上的 "change" 事件,当用户选中或取消选中该复选框时会触发该事件。在我们的更改事件处理程序中,我们将 state 中的值设置为从复选框接收到的新值。这样做将触发组件的重新渲染,这会将复选框的值重新分配给状态中的值。这是不必要的,因为我们只是向 DOM 请求一个值,然后告诉它用我们想要的任何值再次渲染。

¥Walk-Through: Say we listen for a "change" event on a checkbox, which is fired when the checkbox is checked or unchecked by the user. In our change event handler, we set a value in state to the new value received from the checkbox. Doing so will trigger a re-render of our component, which will re-assign the value of the checkbox to the value from state. This is unnecessary, because we just asked the DOM for a value but then told it to render again with whatever value we wanted.

因此,我们不应该监听 input 事件,而应该监听 click 事件,该事件在用户单击复选框或关联的 <label> 时触发。复选框只是在布尔值 truefalse 之间切换,因此单击复选框或标签,我们将反转状态中的任何值,触发重新渲染,将复选框的显示值设置为我们想要的值。

¥So, instead of listening for a input event we should listen for a click event, which is fired any time the user clicks on the checkbox or an associated <label>. Checkboxes just toggle between Boolean true and false, so clicking the checkbox or the label, we'll just invert whatever value we have in state, triggering a re-render, setting the checkbox's displayed value to the one we want.

复选框示例

¥Checkbox Example

class MyForm extends Component {
  toggle = e => {
      let checked = !this.state.checked;
      this.setState({ checked });
  };

  render(_, { checked }) {
    return (
      <label>
        <input
          type="checkbox"
          checked={checked}
          onClick={this.toggle}
        />
        check this box
      </label>
    );
  }
}
Run in REPL
Preact 中文网 - 粤ICP备13048890号