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中的事件的目标被记录到控制台。
本例中的目标是我们在组件中定义的表单。
我们如何访问表单的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元素中,但输入文本不能被编辑。控制台显示了一个警告,给了我们一个可能出错的提示。
由于我们将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()方法。这是因为在输入变化时没有默认动作,这与表单提交时不同。
你可以在控制台中跟随,看看事件处理程序是如何被调用的。
你记得自己安装了React devtools,对吗?很好。你可以直接从React Devtools标签中查看状态如何变化。
现在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分支中找到我们当前应用的全部代码。