跳到内容

b

表单

让我们继续扩展我们的应用,允许用户添加新的笔记。你可以找到我们当前应用的代码这里

为了让我们的页面在添加新的笔记时得到更新,最好将笔记存储在App组件的状态中。让我们导入useState函数,用它来定义一块状态,用prop中传递的初始笔记数组进行初始化。

import { useState } from 'react'import Note from './components/Note'

const App = (props) => {  const [notes, setNotes] = useState(props.notes)
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note =>
          <Note key={note.id} note={note} />
        )}
      </ul>
    </div>
  )
}

export default App

该组件使用useState函数来初始化存储在notes中的状态片断,并使用prop中传递的笔记阵列。

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)

  // ...
}

如果我们想从一个空的笔记列表开始,我们会把初始值设置为一个空的数组,而且由于prop不会被使用,我们可以从函数定义中省略props参数。

const App = () => {
  const [notes, setNotes] = useState([])

  // ...
}

让我们暂时坚持使用props中传递的初始值。

接下来,让我们在组件中添加一个HTML表单,用来添加新的笔记。

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)

  const addNote = (event) => {    event.preventDefault()    console.log('button clicked', event.target)  }
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note =>
          <Note key={note.id} note={note} />
        )}
      </ul>
      <form onSubmit={addNote}>        <input />        <button type="submit">save</button>      </form>    </div>
  )
}

我们将addNote函数作为事件处理程序添加到表单元素中,当表单被提交时,点击提交按钮,该函数将被调用。

我们使用在第一章节中讨论的方法来定义我们的事件处理器。

const addNote = (event) => {
  event.preventDefault()
  console.log('button clicked', event.target)
}

参数event是触发调用事件处理函数的事件

事件处理程序立即调用event.preventDefault()方法,防止提交表单的默认动作。默认动作会,忽略其他操作,导致页面重新加载。

存储在event.target中的事件的目标被记录到控制台。

fullstack content

本例中的目标是我们在组件中定义的表单。

我们如何访问表单的input元素中包含的数据?

Controlled component

有很多方法可以完成这个需求;我们要看的第一个方法是通过使用所谓的受控组件

让我们添加一个新的状态,叫做newNote,用来存储用户提交的输入,让我们把它设置为input元素的value属性。

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState(    'a new note...'  )
  const addNote = (event) => {
    event.preventDefault()
    console.log('button clicked', event.target)
  }

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note =>
          <Note key={note.id} note={note} />
        )}
      </ul>
      <form onSubmit={addNote}>
        <input value={newNote} />        <button type="submit">save</button>
      </form>
    </div>
  )
}

存储为newNote状态初始值的占位符文本出现在input元素中,但输入文本不能被编辑。控制台显示了一个警告,给了我们一个可能出错的提示。

fullstack content

由于我们将App组件的一部分状态指定为输入元素的value属性,App组件现在控制了输入元素的行为。

为了实现对输入元素的编辑,我们必须注册一个事件处理程序,使输入元素的变化与组件的状态同步。

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState(
    'a new note...'
  )

  // ...

  const handleNoteChange = (event) => {    console.log(event.target.value)    setNewNote(event.target.value)  }
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note =>
          <Note key={note.id} note={note} />
        )}
      </ul>
      <form onSubmit={addNote}>
        <input
          value={newNote}
          onChange={handleNoteChange}        />
        <button type="submit">save</button>
      </form>
    </div>
  )
}

我们现在已经为表单的输入元素的onChange属性注册了一个事件处理器。

<input
  value={newNote}
  onChange={handleNoteChange}
/>

每次输入元素发生变化时都会调用事件处理程序。事件处理函数接收事件对象作为其event参数。

const handleNoteChange = (event) => {
  console.log(event.target.value)
  setNewNote(event.target.value)
}

事件对象的target属性现在对应于被控制的input元素,而event.target.value指的是该元素的输入值。

注意,我们不需要像在onSubmit事件处理程序中那样调用event.preventDefault()方法。这是因为在输入变化时没有默认动作,这与表单提交时不同。

你可以在控制台中跟随,看看事件处理程序是如何被调用的。

fullstack content

你记得自己安装了React devtools,对吗?很好。你可以直接从React Devtools标签中查看状态如何变化。

fullstack content

现在App组件的newNote状态反映了输入的当前值,这意味着我们可以完成创建新笔记的addNote功能。

const addNote = (event) => {
  event.preventDefault()
  const noteObject = {
    content: newNote,
    date: new Date().toISOString(),
    important: Math.random() < 0.5,
    id: notes.length + 1,
  }

  setNotes(notes.concat(noteObject))
  setNewNote('')
}

首先,我们为笔记创建一个新的对象,叫做noteObject,它将从组件的newNote状态接收其内容。唯一的标识符id是根据笔记的总数生成的。这种方法适用于我们的应用,因为笔记永远不会被删除。在Math.random()函数的帮助下,我们的笔记有50%的机会被标记为重要。

使用concat数组方法将新笔记添加到笔记列表中,该方法在第一章节中介绍过。

setNotes(notes.concat(noteObject))

该方法并不改变原始的notes数组,而是创建一个新的数组副本,将新的项目添加到最后。这很重要,因为在React中我们必须永远不要直接改变状态!

事件处理程序也通过调用setNewNote状态的newNote函数来重设受控输入元素的值。

setNewNote('')

你可以在这个GitHub仓库part2-2分支中找到我们当前应用的全部代码。

Filtering Displayed Elements

让我们为我们的应用添加一些新功能,我们只查看重要的笔记。

让我们在App组件中添加一个状态,跟踪哪些笔记应该被显示。

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState('')
  const [showAll, setShowAll] = useState(true)
  // ...
}

让我们改变这个组件,让它在notesToShow变量中存储一个所有要显示的笔记的列表。列表中的项目取决于该组件的状态:

import { useState } from 'react'
import Note from './components/Note'

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState('')
  const [showAll, setShowAll] = useState(true)

  // ...

  const notesToShow = showAll    ? notes    : notes.filter(note => note.important === true)
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notesToShow.map(note =>          <Note key={note.id} note={note} />
        )}
      </ul>
      // ...
    </div>
  )
}

notesToShow变量的定义相当紧凑。

const notesToShow = showAll
  ? notes
  : notes.filter(note => note.important === true)

该定义使用了条件运算符,在许多其他编程语言中也有。

该操作符的功能如下。如果我们有:

const result = condition ? val1 : val2

如果condition为真,result变量将被设置为val1的值。如果condition是假的,result变量将被设置为val2的值。

如果showAll的值为false,notesToShow变量将被分配到一个列表,该列表只包含important属性设置为true的笔记。过滤是在数组filter方法的帮助下完成的。

notes.filter(note => note.important === true)

比较运算符实际上是多余的,因为note.important的值要么是true,要么是false,这意味着我们可以简单地写成:

notes.filter(note => note.important)

我们之所以先展示比较运算符,是为了强调一个重要的细节:在JavaScript中,val1 == val2并不是在所有情况下都能如愿以偿,在比较中完全使用val1 === val2会更安全。你可以阅读更多关于这个主题的内容,点击这里

你可以通过改变showAll状态的初始值来测试过滤的功能。

接下来,让我们添加功能,使用户能够从用户界面上切换应用的showAll状态。

相关的改变如下:

import { useState } from 'react'
import Note from './components/Note'

const App = (props) => {
  const [notes, setNotes] = useState(props.notes)
  const [newNote, setNewNote] = useState('')
  const [showAll, setShowAll] = useState(true)

  // ...

  return (
    <div>
      <h1>Notes</h1>
      <div>        <button onClick={() => setShowAll(!showAll)}>          show {showAll ? 'important' : 'all' }        </button>      </div>      <ul>
        {notesToShow.map(note =>
          <Note key={note.id} note={note} />
        )}
      </ul>
      // ...
    </div>
  )
}

显示的笔记(所有与重要的)是用一个按钮控制的。这个按钮的事件处理程序非常简单,它被直接定义在按钮元素的属性中。该事件处理程序将showAll的值从真切换到假,反之亦然。

() => setShowAll(!showAll)

按钮的文本取决于showAll状态的值:

show {showAll ? 'important' : 'all'}

你可以在这个GitHub仓库part2-3分支中找到我们当前应用的全部代码。