在使用 State 挂钩开发了我们自己的 blog 应用之后,我们现在将学习 React 提供的另外两个非常重要的挂钩:Reducer和Effect挂钩。我们首先要学习什么时候应该使用减速机挂钩而不是状态挂钩。然后,我们学习如何将现有的状态挂钩转换为减速挂钩,以便在实践中掌握该概念。接下来,我们将学习效果挂钩以及它们的用途。最后,我们将在我们的博客应用中实现它们。
本章将介绍以下主题:
- 了解简化器挂钩和状态挂钩之间的差异
- 在我们的博客应用中实现 Reducer 挂钩
- 在我们的博客应用中使用效果挂钩
应该已经安装了 Node.js 的最新版本(v11.12.0 或更高版本)。Node.js 的npm包管理器也需要安装。
本章的代码可以在 GitHub 存储库中找到:https://github.com/PacktPublishing/Learn-React-Hooks/tree/master/Chapter04 。
请查看以下视频以查看代码的运行情况:
Please note that it is highly recommended that you write the code on your own. Do not simply run the code examples that have been provided. It is important that you write the code yourself in order for you to be able to learn and understand properly. However, if you run into any issues, you can always refer to the code example.
现在,让我们从这一章开始。
在上一章中,我们学习了如何与地方和全球国家打交道。我们在这两种情况下都使用了状态挂钩,这对于简单的状态更改很好。然而,当我们的状态逻辑变得更加复杂时,我们需要确保保持状态的一致性。为了做到这一点,我们应该使用一个 Reducer 挂钩而不是多个状态挂钩,因为在相互依赖的多个状态挂钩之间很难保持同步。作为替代方案,我们可以将所有状态保持在一个状态挂钩中,但是我们必须确保不会意外地覆盖状态的一部分。
状态挂钩已经支持向它传递复杂的对象和数组,并且它可以很好地处理它们的状态更改。然而,我们总是必须直接改变状态,这意味着我们需要使用大量的扩展语法,以确保我们不会覆盖状态的其他部分。例如,假设我们有这样一个状态对象:
const [ config, setConfig ] = useState({ filter: 'all', expandPosts: true })现在,我们要更改过滤器:
setConfig({ filter: { byAuthor: 'Daniel Bugl', fromDate: '2019-04-29' } })如果我们只是运行前面的代码,我们将删除状态的expandPosts部分!因此,我们需要做以下工作:
setConfig({ ...config, filter: { byAuthor: 'Daniel Bugl', fromDate: '2019-04-29' } })现在,如果我们想将fromDate过滤器更改为不同的日期,我们需要使用两次扩展语法,以避免删除byAuthor过滤器:
setConfig({ ...config, filter: { ...config.filter, fromDate: '2019-04-30' } })但是,当filter状态仍然是字符串时,如果我们这样做会发生什么?我们将得到以下结果:
{ filter: { '0': 'a', '1': 'l', '2': 'l', fromDate: '2019-04-30' },
expandPosts: true }什么为什么突然出现了三个新键——0、1和2?这是因为扩展语法也适用于字符串,字符串的扩展方式是每个字母根据其在字符串中的索引获得一个键。
可以想象,对于较大的状态对象,使用扩展语法和直接更改状态对象可能会变得非常乏味。此外,我们始终需要确保不会引入任何 bug,并且我们需要检查应用中多个位置的 bug。
我们可以创建一个处理状态更改的函数,而不是直接更改状态。这样的函数只允许通过某些动作(如CHANGE_FILTER或TOGGLE_EXPAND动作)改变状态。
动作是简单的对象,它有一个type键,告诉我们正在处理的动作,还有更多的键更接近地描述动作。
TOGGLE_EXPAND动作相当简单。它只是一个定义了动作type的对象:
{ type: 'TOGGLE_EXPAND' }CHANGE_FILTER操作可以处理我们之前遇到问题的复杂状态更改,如下所示:
{ type: 'CHANGE_FILTER', all: true }
{ type: 'CHANGE_FILTER', fromDate: '2019-04-29' }
{ type: 'CHANGE_FILTER', byAuthor: 'Daniel Bugl' }
{ type: 'CHANGE_FILTER', fromDate: '2019-04-30' }第二个、第三个和第四个操作将把filter状态从字符串更改为对象,然后设置相应的键。如果对象已经存在,我们只需调整动作中定义的关键点。每次操作后,状态将改变如下:
{ expandPosts: true, filter: 'all' }{ expandPosts: true, filter: **{** fromDate: '2019-04-29' **}** }{ expandPosts: true, filter: { fromDate: '2019-04-29', byAuthor: 'Daniel Bugl' } }{ expandPosts: true, filter: { fromDate: '2019-04-30', byAuthor: 'Daniel Bugl' } }
现在,看看下面的代码:
{ type: 'CHANGE_FILTER', all: true }如果我们发送了另一个操作,如前面的代码中所示,那么状态将返回到all字符串,就像它处于初始状态一样。
现在,我们仍然需要定义处理这些状态更改的函数。这样的函数称为减缩函数。它将当前的state和action作为参数,并返回一个新状态。
If you are aware of the Redux library, you will already be very familiar with the concept of state, actions, and reducers.
现在,我们将定义我们的reducer函数:
- 我们从
reducer的函数定义开始:
function reducer (state, action) {- 然后,我们使用
switch语句检查action.type:
switch (action.type) {- 现在,我们将处理
TOGGLE_EXPAND操作,我们只需切换当前expandPosts状态:
case 'TOGGLE_EXPAND':
return { ...state, expandPosts: !state.expandPosts }- 接下来,我们将处理
CHANGE_FILTER操作。这里,我们首先需要检查all是否设置为true,在这种情况下,只需将filter设置为'all'字符串:
case 'CHANGE_FILTER':
if (action.all) {
return { ...state, filter: 'all' }
}- 现在,我们必须处理其他
filter选项。首先,我们检查filter变量是否已经是object。如果没有,我们将创建一个新的。否则,我们将使用现有对象:
let filter = typeof state.filter === 'object' ? state.filter : {}- 然后,我们为各种过滤器定义处理程序,允许通过不立即返回新的
state一次设置多个过滤器:
if (action.fromDate) {
filter = { ...filter, fromDate: action.fromDate }
}
if (action.byAuthor) {
filter = { ...filter, byAuthor: action.byAuthor }
}- 最后,我们返回新的
state:
return { ...state, filter }- 对于
default案例,我们抛出一个错误,因为这是一个未知操作:
default:
throw new Error()
}
}Throwing an error in the default case is different to what is best practice with Redux reducers, where we would simply return the current state in the default case. Because React Reducer Hooks do not store all state in one object, we are only going to handle certain actions for certain state objects, so we can throw an error for unknown actions.
现在,我们的reducer函数已经定义,我们可以继续定义简化器挂钩。
现在我们已经定义了动作和reducer函数,我们可以从reducer创建简化器挂钩。useReducer吊钩的签字如下:
const [ state, dispatch ] = useReducer(reducer, initialState)我们唯一需要定义的是initialState;然后我们可以定义简化器挂钩:
const initialState = { all: true }现在,我们可以使用 Reducer 挂钩返回的state对象访问状态,并通过dispatch函数分派动作,如下所示:
dispatch({ type: 'TOGGLE_EXPAND' })如果要向操作添加其他选项,只需将它们添加到操作对象:
dispatch({ type: 'CHANGE_FILTER', fromDate: '2019-04-30' })正如我们所见,使用动作和减缩器处理状态更改比直接调整状态对象容易得多。
在了解了操作、减缩器和减缩器挂钩之后,我们将在我们的博客应用中实现它们。当状态对象或状态更改变得过于复杂时,任何现有的状态挂钩都可以转换为简化器挂钩。
If there are multiple setState functions that are always called at the same time, it is a good hint that they should be grouped together in a single Reducer Hook.
全局状态通常是使用 Reducer 挂钩而不是状态挂钩的好选择,因为全局状态更改可以在应用中的任何位置发生。然后,处理操作和只在一个地方更新状态更改逻辑就容易多了。将所有状态更改逻辑放在一个地方可以更容易地维护和修复错误,而不会因为忘记在任何地方更新逻辑而引入新的错误。
我们现在将把博客应用中现有的一些状态挂钩转换成 Reducer 挂钩。
在我们的博客应用中,我们有两个全局状态挂钩,我们将用 Reducer 挂钩替换它们:
user状态posts状态
我们首先替换user状态挂钩。
我们将从user状态挂钩开始,因为它比posts状态挂钩简单。稍后,user状态将包含复杂的状态更改,因此在此处使用简化器挂钩是有意义的。
首先,我们要定义我们的动作,然后我们要定义 reducer 函数。最后,我们将用减速机挂钩替换状态挂钩。
我们从定义操作开始,因为在定义 reducer 函数时,这些操作非常重要。
现在让我们定义操作:
- 首先,我们需要一个允许用户登录的操作,通过提供一个
username值和一个password值:
{ type: 'LOGIN', username: 'Daniel Bugl', password: 'notsosecure' }- 然后,我们还需要一个
REGISTER动作,在我们的例子中,这个动作类似于LOGIN动作,因为我们还没有实现任何注册逻辑:
{ type: 'REGISTER', username: 'Daniel Bugl', password: 'notsosecure', passwordRepeat: 'notsosecure' }- 最后,我们需要一个
LOGOUT操作,它只是要注销当前登录的用户:
{ type: 'LOGOUT' }现在,我们已经定义了所有必需的与用户相关的操作,我们可以继续定义 reducer 函数。
接下来,我们为user状态定义一个 reducer 函数。现在,我们将把减速机放在src/App.js文件中。
Later on, it might make sense to create a separate src/reducers.js file, or even a separate src/reducers/ directory, with separate files for each reducer function.
让我们开始定义userReducer函数:
- 在
src/App.js文件中,在App函数定义之前,为user状态创建一个userReducer函数:
function userReducer (state, action) {- 同样,我们对
action类型使用switch语句:
switch (action.type) {- 然后,我们处理
LOGIN和REGISTER动作,将user状态设置为给定的username值。在本例中,我们现在只需从action对象返回username值:
case 'LOGIN':
case 'REGISTER':
return action.username- 接下来,我们处理
LOGOUT操作,将状态设置为空字符串:
case 'LOGOUT':
return ''- 最后,当遇到未处理的操作时,我们抛出一个错误:
default:
throw new Error()
}
}现在,定义了userReducer函数,我们可以继续定义简化器挂钩。
在定义了操作和 reducer 函数之后,我们将定义 reducer 挂钩,并将其状态和分派函数传递给需要它的组件。
让我们开始实施简化器挂钩:
- 首先要导入
useReducer挂钩,调整src/App.js中的import语句:
import React, { useState, useReducer } from 'react'- 编辑
src/App.js,删除以下状态挂钩:
const [ user, setUser ] = useState('')将前面的状态挂钩替换为 Reducer 挂钩初始状态为空字符串,正如我们之前所做的:
const [ user, dispatchUser ] = useReducer(userReducer, '')- 现在,将
user状态和dispatchUser功能作为dispatch道具传递给UserBar组件:
<UserBar user={user} dispatch={dispatchUser} />- 我们不需要修改
CreatePost组件,因为我们只是将user状态传递给它,而该部分没有改变。 - 接下来,我们在
src/user/UserBar.js中编辑UserBar组件,并用dispatch功能替换setUser道具:
export default function UserBar ({ user, dispatch }) {
if (user) {
return <Logout user={user} dispatch={dispatch} />
} else {
return (
<React.Fragment>
<Login dispatch={dispatch} />
<Register dispatch={dispatch} />
</React.Fragment>
)
}
}- 现在,我们可以编辑
src/user/Login.js中的Login组件,并将setUser功能替换为dispatch功能:
export default function Login ({ dispatch }) {- 然后,我们将对
setUser的调用替换为对dispatch函数的调用,调度LOGIN动作:
<form onSubmit={e => { e.preventDefault(); dispatch({ type: 'LOGIN', username }) }}>We could also make functions that return actions—so-called action creators. Instead of manually creating the action object every time, we could simply call loginAction('username') instead, which returns the corresponding LOGIN action object.
- 我们对
src/user/Register.js中的Register组件重复相同的过程:
export default function Register ({ dispatch }) {
// ...
<form onSubmit={e => { e.preventDefault(); dispatch({ type: 'REGISTER', username }) }}>- 最后,我们还对
src/user/Logout.js中的Logout组件重复相同的过程:
export default function Logout ({ user, dispatch }) {
// ...
<form onSubmit={e => { e.preventDefault(); dispatch({ type: 'LOGOUT' }) }}>现在,我们的应用应该和以前一样工作,但是它使用了 Reducer 挂钩而不是简单的状态挂钩!
在posts状态中使用 Reducer 挂钩也是有意义的,因为稍后,我们将拥有可以用来删除和编辑帖子的功能,所以保持这些复杂的状态更改是很有意义的。现在让我们开始用减速机挂钩替换 posts 状态挂钩。
同样,我们从定义动作开始。目前,我们只会考虑一个行动:
{ type: 'CREATE_POST', title: 'React Hooks', content: 'The greatest thing since sliced bread!', author: 'Daniel Bugl' }这是我们目前唯一需要采取的措施。
接下来,我们将以与user状态类似的方式定义 reducer 函数:
- 我们从编辑
src/App.js开始,并在那里定义 reducer 函数。以下代码定义了postsReducer功能:
function postsReducer (state, action) {
switch (action.type) {- 在这个函数中,我们将处理
CREATE_POST操作。我们首先创建一个newPost对象,然后使用扩展语法将其插入到当前posts状态的开头,类似于我们在前面的src/post/CreatePost.js组件中所做的方式:
case 'CREATE_POST':
const newPost = { title: action.title, content: action.content, author: action.author }
return [ newPost, ...state ]- 现在,这将是我们在这个 reducer 中处理的唯一操作,因此我们现在可以定义
default语句:
default:
throw new Error()
}
}现在,定义了postsReducer函数,我们可以继续创建简化器挂钩。
最后,我们将为posts状态定义并使用简化器挂钩:
- 我们首先移除
src/App.js中的以下状态挂钩:
const [ posts, setPosts ] = useState(defaultPosts)我们将其替换为以下简化器挂钩:
const [ posts, dispatchPosts ] = useReducer(postsReducer, defaultPosts)- 然后,我们将
dispatchPosts函数作为dispatch道具传递给CreatePost组件:
{user && <CreatePost user={user} posts={posts} dispatch={dispatchPosts} />}- 接下来,我们在
src/post/CreatePost.js中编辑CreatePost组件,将setPosts功能替换为dispatch功能:
export default function CreatePost ({ user, posts, dispatch }) {- 最后,我们在
handleCreate函数中使用dispatch函数:
function handleCreate () {
dispatch({ type: 'CREATE_POST', title, content, author: user })
}现在,posts状态也使用简化器挂钩,而不是状态挂钩,其工作方式与之前相同!然而,如果我们想为管理帖子添加更多的逻辑,比如搜索、筛选、删除和编辑,那么以后这样做会容易得多。
在我们的博客应用中使用 Reducer 挂钩的示例代码可以在Chapter04/chapter4_1文件夹中找到。
只需运行npm install即可安装所有依赖项,npm start即可启动应用;然后在浏览器中访问http://localhost:3000(如果它没有自动打开)。
目前,我们有两种不同的调度功能:一种用于user状态,另一种用于posts状态。在我们的例子中,为了处理子状态,将两个减缩器合并成一个,然后调用进一步的减缩器是有意义的。
This pattern is similar to the way in which reducers work in Redux, where we only have one object containing the whole state tree of the application, which in the case of the global state, makes sense. However, for complex local state changes, it might make more sense to keep the reducers separate.
让我们开始将 reducer 函数合并为一个 reducer 函数。在我们进行此操作时,让我们将所有还原程序重构为一个src/reducers.js文件,以使src/App.js文件更具可读性:
- 创建一个新的
src/reducers.js文件。 - 从
src/App.js文件中剪切以下代码,粘贴到src/reducers.js文件中:
function userReducer (state, action) {
switch (action.type) {
case 'LOGIN':
case 'REGISTER':
return action.username
case 'LOGOUT':
return ''
default:
throw new Error()
}
}
function postsReducer (state, action) {
switch (action.type) {
case 'CREATE_POST':
const newPost = { title: action.title, content: action.content, author: action.author }
return [ newPost, ...state ]
default:
throw new Error()
}
}- 编辑
src/reducers.js,在现有减速机函数下定义一个新的减速机函数,称为appReducer:
export default function appReducer (state, action) {- 在这个
appReducer函数中,我们将调用另外两个 reducer 函数,并返回完整的状态树:
return {
user: userReducer(state.user, action),
posts: postsReducer(state.posts, action)
}
}- 编辑
src/App.js,并在此处导入appReducer:
import appReducer from './reducers'- 然后,我们删除以下两个简化器挂钩定义:
const [ user, dispatchUser ] = useReducer(userReducer,
'')
const [ posts, dispatchPosts = useReducer(postsReducer,
defaultPosts)我们将前面的简化器吊钩定义替换为appReducer的单个简化器吊钩定义:
const [ state, dispatch ] = useReducer(appReducer, { user: '', posts: defaultPosts })- 接下来,我们使用解构从
state对象中提取user和posts值:
const { user, posts } = state- 现在,我们仍然需要将传递给其他组件的
dispatchUser和dispatchPosts函数替换为dispatch函数:
<UserBar user={user} dispatch={dispatch} />
<br />
{user && <CreatePost user={user} posts={posts} dispatch={dispatch} />}正如我们所看到的,现在只有一个dispatch函数和一个状态对象。
但是,如果我们现在尝试登录,我们将看到来自postsReducer的错误。这是因为我们仍在对未处理的操作抛出错误。为了避免这种情况,我们必须忽略未处理的操作,只返回当前状态:
编辑src/reducers.js中的userReducer和postsReducer功能,并删除以下代码:
default:
throw new Error()用返回当前state的return语句替换前面的代码:
default:
return state正如我们所看到的,现在我们的应用仍然以与以前完全相同的方式工作,但我们正在为整个应用状态使用一个减速机!
在我们的 blog 应用中使用单个 Reducer Hook 的示例代码可以在Chapter04/chapter4_2文件夹中找到。
只需运行npm install安装所有依赖项,并启动npm start应用,然后在浏览器中访问http://localhost:3000(如果它没有自动打开)。
我们将经常使用的最后一个基本挂钩是效果挂钩。使用 effecthook,我们可以执行组件的副作用,例如在组件挂载或更新时获取数据。
在我们的博客中,我们将实现一个功能,在我们登录时更新我们网页的标题,以便它包含当前登录用户的用户名。
如果您以前使用过 React,您可能使用过componentDidMount和componentDidUpdate生命周期方法。例如,我们可以使用 React 类组件将文档title设置为给定的属性,如下所示。在以下代码部分中,生命周期方法以粗体突出显示:
import React from 'react'
class App extends React.Component {
componentDidMount () {
const { title } = this.props document.title = title
}
render () {
return (
<div>Test App</div>
)
}
}这个很好用。然而,当title道具更新时,更改不会反映在我们网页的标题中。为了解决这个问题,我们需要定义componentDidUpdate生命周期方法(新的粗体代码),如下所示:
import React from 'react'
class App extends React.Component {
componentDidMount () {
const { title } = this.props
document.title = title
}
componentDidUpdate (prevProps) {
const { title } = this.props
if (title !== prevProps.title) {
document.title = title
}
}
render () {
return (
<div>Test App</div>
)
}
}您可能已经注意到,我们正在两次编写几乎相同的代码;因此,我们可以创建一个新方法来处理对title的更新,然后从两个生命周期方法调用它。在以下代码部分中,更新的代码以粗体突出显示:
import React from 'react'
class App extends React.Component {
updateTitle () {
const { title } = this.props
document.title = title
}
componentDidMount () {
this.updateTitle()
}
componentDidUpdate (prevProps) {
if (this.props.title !== prevProps.title) {
this.updateTitle()
}
}
render () {
return (
<div>Test App</div>
)
}
}然而,我们仍然需要打两次电话this.updateTitle()。当我们稍后更新代码时,例如,将一个参数传递给this.updateTitle()时,我们总是需要记住在对该方法的两个调用中都传递它。如果我们忘记更新一个生命周期方法,我们可能会引入 bug。此外,我们需要在componentDidUpdate中添加if条件,以避免在title道具未更改时调用this.updateTitle()。
在挂钩世界中,componentDidMount和componentDidUpdate生命周期方法组合在useEffect挂钩中,当不指定依赖项数组时,只要组件中的任何道具发生变化,就会触发该挂钩。
因此,我们现在可以定义一个带有效果挂钩的函数组件,而不是使用类组件,它的作用与以前相同。传递给效果挂钩的函数称为“效果函数”:
import React, { useEffect } from 'react'
function App ({ title }) {
useEffect(() => {
document.title = title
})
return (
<div>Test App</div>
)
}这就是我们需要做的!我们定义的挂钩将在每次道具更改时调用我们的效果函数。
如果我们想确保我们的 effect 函数仅在title属性更改时被调用,例如,出于性能原因,我们可以指定哪些值应该触发更改,作为useEffect挂钩的第二个参数:
useEffect(() => {
document.title = title
}, [title])这不仅仅局限于道具,我们可以在这里使用任何值,甚至是来自其他挂钩的值,比如状态挂钩或简化器挂钩:
const [ title, setTitle ] = useState('')
useEffect(() => {
document.title = title
}, [title])正如我们所见,在处理不断变化的值时,使用效果挂钩比使用生命周期方法简单得多。
如果我们想复制只添加一个componentDidMount生命周期方法的行为,而不在道具改变时触发,我们可以通过将一个空数组作为第二个参数传递给useEffect挂钩来实现:
useEffect(() => {
document.title = title
}, [])传递一个空数组意味着我们的效果函数在组件挂载时只触发一次,而在道具更改时不会触发。但是,我们不应该考虑使用挂钩安装组件,而应该考虑效果的依赖性。在这种情况下,效果没有任何依赖关系,这意味着它只会触发一次。如果效果具有指定的依赖项,则当任何依赖项发生更改时,它将再次触发。
有时,组件卸载时需要清除效果。为此,我们可以从 effect 挂钩的 effect 函数返回一个函数。此返回函数的工作方式与componentWillUnmount生命周期方法类似:
useEffect(() => {
const updateInterval = setInterval(() => console.log('fetching update'), updateTime)
return () => clearInterval(updateInterval)
}, [updateTime])上面用粗体标记的代码称为清除函数。组件卸载时以及再次运行效果之前,将调用 cleanup 函数。例如,当updateTime道具发生变化时,这可以避免出现错误。在这种情况下,将清除先前的效果,并定义一个具有更新的updateTime值的新间隔。
现在我们已经了解了 Effect Hook 是如何工作的,我们将在我们的博客应用中使用它,在我们登录/注销时(当user状态更改时)实现标题更改。
让我们开始在我们的博客应用中实现一个效果挂钩:
- 编辑
src/App.js,导入useEffect挂钩:
import React, { useReducer, useEffect } from 'react'- 在定义了我们的
useReducer挂钩和状态分解之后,定义一个useEffect挂钩,根据username值调整document.title变量:
useEffect(() => {- 如果用户登录,我们将
document.title设置为<username> - React Hooks Blog。为此,我们使用模板字符串,它允许我们通过${ }语法在字符串中包含变量或 JavaScript 表达式。模板字符串使用```jsx 定义:
if (user) {
document.title = `${user} - React Hooks Blog`
```jsx
4. 否则,如果用户未登录,我们只需将`document.title`设置为`React Hooks Blog`:
} else {
document.title = 'React Hooks Blog'
}
5. 最后,我们将`user`值作为第二个参数传递给 Effect Hook,以确保每当`user`值更新时,我们的 Effect 函数再次触发:}, [user])
如果我们现在启动我们的应用,我们可以看到`document.title`被设置为`React Hooks Blog`,因为`App`组件挂载时会触发效果挂钩,而`user`值尚未定义:

The effect of our Effect Hook: changing the web page title
使用`Test User`登录后,我们可以看到`document.title`变为`Test User - React Hooks Blog`:

The effect of our Effect Hook re-triggering, after the user value changes
如我们所见,`user`值更改后,我们的效果挂钩成功重新触发!
# 示例代码
在我们的博客应用中实现效果挂钩的示例代码可以在`Chapter04/chapter4_3`文件夹中找到。
只需运行`npm install`安装所有依赖项,并启动`npm start`应用,然后在浏览器中访问`http://localhost:3000`(如果它没有自动打开)。
# 总结
在本章中,我们首先学习了操作、减速机和减速机挂钩。我们还学习了何时应该使用 Reducer 挂钩而不是 State 挂钩。然后,我们用两个 Reducer 挂钩替换了`user`和`posts`状态的现有全局状态挂钩。接下来,我们将两个 Reducer 挂钩合并为一个 app Reducer 挂钩。最后,我们学习了效果挂钩,以及如何使用它们来代替`componentDidMount`和`componentDidUpdate`。
在下一章中,我们将学习 React 上下文,以及如何将其与挂钩一起使用。然后,我们将向我们的应用添加上下文挂钩,以避免在多层组件上传递道具。
# 问题
为了回顾我们在本章学到的知识,请尝试回答以下问题:
1. 状态挂钩的常见问题是什么?
2. 什么是行动?
3. 什么是简化器?
4. 什么时候我们应该使用减速机挂钩而不是状态挂钩?
5. 要将状态挂钩转换为简化器挂钩,需要执行哪些步骤?
6. 我们如何更容易地创建动作?
7. 我们什么时候合并?
8. 合并简化器挂钩时需要注意什么?
9. 什么是类组件中的效果挂钩的等价物?
10. 与类组件相比,使用效果挂钩的优势是什么?
# 进一步阅读
如果您对我们在本章中探讨的概念的更多信息感兴趣,请阅读以下阅读材料:
* 关于简化器吊钩的正式文件:[https://reactjs.org/docs/hooks-reference.html#usereducer](https://reactjs.org/docs/hooks-reference.html#usereducer)
* 使用效果挂钩的官方文档和提示:[https://reactjs.org/docs/hooks-effect.html](https://reactjs.org/docs/hooks-effect.html)
* *学习 Redux*由*Pa**ckt*发布,更多关于动作、还原和管理应用状态的深入信息:*[https://www.packtpub.com/web-development/learning-redux](https://www.packtpub.com/web-development/learning-redux)*