在使用 State Hook 开发我们自己的博客应用程序之后,我们现在将了解 React 提供的另外两个非常重要的 Hook:Reducer 和 Effect Hook。我们首先要学习何时应该使用 Reducer Hook 而不是 State Hook。然后,我们将学习如何将现有的 State Hook 转换为 Reducer Hook,以便在实践中掌握该概念。接下来,我们将了解 Effect Hook 及其用途。最后,我们将在我们的博客应用程序中实现它们。
本章将介绍以下主题:
- 了解 Reducer Hook 和 State Hook 之间的区别
- 在我们的博客应用程序中实现 Reducer Hook
- 在我们的博客应用程序中使用 Effect Hook
技术要求
应该已经安装了相当新版本的 Node.js(vll.l2.0 或更高版本)。还需要安装 Node.js 的 npm 包管理器。
本章的代码可以在 GitHub 存储库中找到:https://github.com/PacktPublishing/Learn-React-Hooks/tree/master/Chapter04。。
观看以下视频,了解代码的实际应用:
请注意,强烈建议您自己编写代码。不要简单地运行以前提供的代码示例。重要的是你自己编写代码,以便能够正确学习和理解。但是,如果遇到任何问题,始终可以参考代码示例。
现在,让我们从本章开始。
Reducer Hook 与 State Hook
在上一章中,我们学习了如何处理本地状态和全局状态。我们在这两种情况下都使用了 State Hook,这对于简单的状态更改来说很好。但是,当我们的状态逻辑变得更加复杂时,我们将需要确保保持状态一致。为此,我们应该使用 Reducer Hook 而不是多个 State Hook,因为很难在相互依赖的多个 State Hook 之间保持同步。作为替代方案,我们可以将所有状态保留在一个 State Hook 中,但随后我们必须确保我们不会意外覆盖状态的某些部分。
State Hook 的问题
State Hook 已经支持将复杂的对象和数组传递给它,并且可以很好地处理它们的状态变化。但是,我们总是必须直接更改状态,这意味着我们需要使用大量的扩展运算符语法,以确保我们不会覆盖状态的其他部分。例如,假设我们有一个这样的状态对象:
const [config, setConfig] = usestate({
filter: 'all',
expandPosts: true
})
现在,我们要更改 filter
:
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
?这是因为扩展运算符语法也适用于字符串,字符串的扩展的方式是每个字母根据其在字符串中的索引获得一个键。
可以想象,对于较大的状态对象,使用扩展语法并直接更改状态对象可能会变得非常乏味。此外,我们始终需要确保我们不会引入任何错误,并且我们需要检查整个应用程序中多个位置的错误。
Action
我们可以创建一个处理状态更改的函数,而不是直接更改状态。此类函数仅允许通过某些操作(例如 CHANGE_FILTER
或 TOGGLE_EXPAND
action)更改状态。
Action 只是具有类型键的对象,告诉我们正在处理哪个 action,以及更密切地描述 action 的其他键。
TOGGLE_EXPAND
action 非常简单。它只是一个定义了操作 type
的对象:
{ type: 'TOGGLE_EXPAND' }
CHANGE_FILTER
action 可以处理我们之前遇到的复杂状态更改,如下所示:
{ 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' }
第二个、第三个和第四个 action 会将 filter
状态从字符串更改为对象,然后设置相应的键。如果对象已经存在,我们只需调整 action 中定义的键。每次调度后,状态将更改如下:
- { 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 }
如前面的代码所示,如果我们调度另一个 action ,则状态将恢复为 all
字符串,就像它处于初始状态一样。
Reducer
现在,我们仍然需要定义处理这些状态更改的函数。这样的函数称为 reducer
函数。它将 state
和 action
作为参数,并返回新状态。
如果您了解 Redux 库,那么您已经非常熟悉 state、action 和 reducer 的概念。
现在,我们将定义我们的 reducer
函数:
- 我们从
reducer
的函数定义开始:
function reducer(state, action){
- 然后,我们使用
switch
语句检查action.type
:
switch (action.type) {
- 现在,我们将处理
TOGGLE_EXPAND
action,我们只需切换当前的 expandPosts 状态:
case 'TOGGLE_EXPAND':
return { ...state, expandPosts: !state.expandPosts }
- 接下来,我们将处理
CHANGE_FILTER
action。在这里,我们首先需要检查all
是否设置为true
,在这种情况下,只需将我们的过滤器设置为 '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
情况,我们抛出错误,因为这是一个未知的 action:
default:
throw new Error()
}
}
在默认情况下抛出错误与 Redux reducer 的最佳实践不同,在默认情况下,我们只是返回 state
。因为 React Reducer Hook 不会将所有状态存储在一个对象中,所以我们只处理某些状态对象的某些 action,因此我们可以为未知 action 抛出错误。
现在,我们的已定义 reducer
函数,我们可以继续定义 Reducer Hook。
Reducer Hook
现在我们已经定义了 action 和 reducer
函数,我们可以从 reducer
创建一个 Reducer Hook。useReducer
的签名如下:
const [ state, dispatch ] = useReducer(reducer, initialstate)
我们唯一需要定义的是 initialstate
;然后我们可以定义一个 Reducer Hook:
const initialstate = { all: true }
现在,我们可以通过使用从 Reducer Hook 返回的 state
对象来访问状态,并通过 dispatch
函数调度 action,如下所示:
dispatch({ type: 'TOGGLE_EXPAND' })
如果我们想给 action 添加其他选项,我们只需将它们添加到 action 对象:
dispatch({ type: 'CHANGE_FILTER', fromDate: '2019-04-30' })
正如我们所看到的,使用 action 和 reducer 处理状态变化比直接调整状态对象要容易得多。
实现 Reducer Hook
了解 action、reducer 和 Reducer Hook 之后,我们将在博客应用程序中实现它们。当状态对象或状态更改变得过于复杂时,任何现有的 State Hook 都可以转换为 Reducer Hook。
如果有多个 setState
函数总是同时调用,则最好将它们组合在一个 Reducer Hook 中。
全局状态通常是使用 Reducer Hook 而不是 State Hook,因为全局状态更改可能发生在应用程序中的任何位置。这样,处理 action 和更新状态逻辑就更容易了——只需要在一个地方修改。将所有状态更改逻辑放在一个地方可以更轻松地维护和修复错误,而不会因为忘记更新每处逻辑而引入新错误。
我们现在将把博客应用程序中的一些现有 State Hook 变成 Reducer Hook。
将 State Hook 变成 Reducer Hook
在我们的博客应用程序中,有两个全局 State Hook,我们将用 Reducer Hook 替换它们:
user
状态posts
状态
我们首先替换 user
State Hook。
替换用户 State Hook
我们将从 user
State Hook 开始,因为它比文章 State Hook 更简单。稍后,user
状态将包含复杂的状态更改,因此在这里使用 Reducer Hook 是有意义的。
我们将先定义 action,然后再定义 reducer
函数。最后,我们将用 Reducer Hook 替换 State Hook。
定义 Action
我们从定义的 action 开始,因为它们在定义 reducer
函数时很重要。
现在让我们定义 action:
- 首先,我们需要一个操作来允许用户通过提供
username
和password
值进行登录:
{ type: 'LOGIN', username: 'Daniel Bugl', password: 'notsosecure' }
- 然后,我们还需要一个
REGIATER
action,在这个例子中,它将类似于LOGIN
action,因为我们还没有实现任何注册逻辑:
{
type: 'REGISTER'',
username: 'Daniel Bugl',
password: 'notsosecure',
passwordRepeat: 'notsosecure'
}
- 最后,我们将需要一个
LOGOUT
action,它只是用来注销当前登录的用户:
{ type: 'LOGOUT' }
现在,我们已经定义了所有必需的和用户相关 action,我们可以继续定义 reducer
函数。
定义 Reducer
接下来,我们为 user
状态定义一个 reducer 函数。现在,我们将把我们的 Reducer 放在 src/App.js
文件中。
稍后,创建一个单独的 src/reducers.js
文件,甚至创建一个单独的 src/reducers/ 目录,每个 reducer 函数都有单独的文件,这可能是有意义的。
让我们开始定义 userReducer
函数:
- 在
src/App.js
文件中,在App
函数定义之前,为user
状态创建一个userReducer
函数:
function userReducer (state, action) {
- 同样,我们对
action
类型使用switch
语句:
switch (action.type) {
- 然后,我们处理 LOGIN 和
REGIATER
action,我们将user
状态设置为给定的username
值。在我们的例子中,现在只是从 action 对象返回username
值:
case 'LOGIN':
case 'REGIsTER':
return action.username
- 接下来,我们处理
LOGOUT
action,将状态设置为空字符串:
case 'LOGOUT': return ''
- 最后,当遇到未处理的操作时,我们会抛出一个错误:
default:
throw new Error()
}
}
现在,定义了 userReducer
函数,我们可以继续定义 Reducer Hook。
定义 Reducer Hook
在定义了 action 和 reducer
函数之后,我们将定义 Reducer Hook,并将其状态和 dispatch 函数传递给需要它的组件。
让我们开始实现 Reducer Hook:
- 首先,我们必须通过调整
src/App.js
中以下语句来导入useReducer
Hook:
import React, { usestate, useReducer } from 'react'
- 编辑
src/App.js
,并删除以下 State Hook:
const [ user, setUser ] = usestate('')
将前面的 State Hook 替换为 Reducer Hook - 初始状态是一个空字符串,就像之前一样:
const [ user, dispatchUser ] = useReducer(userReducer, '')
- 现在,将
user
状态和dispatchUser
函数(作为 dispatch prop) 传递给UserBar
组件:
<UserBar user={user} dispatch={dispatchUser} />
- 我们不需要修改
CreatePost
组件,因为我们只是将user
状态传递给它,而该部分没有更改。 - 接下来,我们在
src/user/UserBar.js
中编辑UserBar
组件,并将setUser
prop 替换为dispatch
函数:
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
action:
<form onSubmit={e => {
e.preventDefault();
dispatch({
type:'LOGIN',
username
})
}}>
我们还可以创建返回 action 的函数,即所谓的 action creators。与其每次都手动创建 action 对象,我们可以简单地调用 loginAction('username'
),它会返回相应的 LOGIN Action 对象。
- 我们对
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 Hook 而不是简单的 State Hook!
替换文章 State Hook
对 posts
状态使用 Reducer Hook 也很有意义,因为稍后我们将拥有可用于删除和编辑文章的功能,因此保留这些复杂的状态更改非常有意义。
现在让我们开始用 Reducer Hook 替换文章 State Hook。
定义 Action
同样,我们从定义 action 开始。目前,我们只考虑一个 CREATE_POST
action:
{
type: 'CREATE_POST',
title: 'React Hooks',
content: 'The greatest thing since sliced bread!',
author: 'Daniel Bugl'
}
这是我们目前需要对文章使用的唯一 action。
定义 Reducer
接下来,我们将以和 user
状态类似的方式定义 reducer 函数:
- 首先我们编辑
src/App.js
,并在那里定义 reducer 函数。以下代码定义了postReducer
函数:
function postsReducer (state, action) {
switch (action.type) {
- 在此函数中,我们将处理
CREATE_POST
action。首先创建一个newPost
对象,然后使用展开运算符语法将其插入当前posts
状态的开头,方法与之前在src/post/CreatePost.js
组件中所使用的类似:
case 'CREATE_POST':
const newPost = {
title: action.title,
content: action.content,
author: action.author
}
return [ newPost, ...state ]
- 这将是我们在此 Reducer 中处理的唯一 action,因此我们现在可以定义
default
语句:
default:
throw new Error()
}
}
现在,定义了 posts reducer
函数,我们可以继续创建 Reducer Hook。
定义 Reducer Hook
最后,我们将定义并使用 Reducer Hook 作为 posts
状态:
- 我们首先在
src/App.js
中删除以下 State Hook:
const [ posts, setPosts ] = usestate(defaultPosts)
我们用以下 Reducer Hook 替换它:
const [ posts, dispatchPosts ] = useReducer(postsReducer, defaultPosts)
- 然后,我们将
dispatchPosts
函数作为 dispatch prop 传递给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 Hook 而不是 State Hook,它的工作方式与以前相同!但是,如果我们想稍后添加更多用于管理文章的逻辑,例如搜索、过滤、删除和编辑,这样做会容易得多。
示例代码
在我们的博客应用程序中使用 Reducer Hook 的示例代码可以在 Chapter04/chapter4_1
文件夹中找到。
只需运行 npm install
即可安装所有依赖项,运行 npm start
启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
合并 Reducer Hook
目前,我们有两个不同的 dispatch 函数:一个用于 user
状态,一个用于 posts
状态。在我们的例子中,将两个 Reducer 合二为一,然后调用进一步的 Reducer,以处理子状态是有意义的。
这种模式类似于 Reducer 在 Redux 中的工作方式,其中我们只有一个对象包含应用程序的整个状态树,这在全局状态的情况下是有意义的。但是,对于复杂的本地状态更改,将 Reducer 分开可能更有意义。
让我们开始将所有 reducer 函数合并到一个 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
,并在现有 reducer 函数下面定义一个新的 reducer 函数,称为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'
- 然后,我们删除以下两个 Reducer Hook 定义:
const [ user, dispatchUser ] = useReducer(userReducer, '')
const [ posts, dispatchPosts = useReducer(postsReducer, defaultPosts)
我们将前面的 Reducer Hook 定义替换为 appReducer
的单个 Reducer Hook 定义:
const [ state, dispatch ] = useReducer(appReducer, { user: '', posts: defaultPosts })
- 接下来,我们使用解构从
state
对象中提取user
和posts
的值:
const { user, posts } = state
- 现在,我们仍然需要用
dispatch
函数替换我们传递给其他组件的dispatchUser
和dispatchPosts
函数:
<UserBar user={user} dispatch={dispatch} />
{user && <CreatePost
user={user}
posts={posts}
dispatch={dispatch}
/>}
如我们所见,现在只有一个 dispatch
函数和一个状态对象。
忽略未处理的 Action
但是,如果我们现在尝试登录,将看到来自 postsReducer
的错误。这是因为我们仍然在未处理的操作上抛出错误。为了避免这种情况,我们必须忽略未处理的操作,并简单地返回 state
:
在 src/reducers.js
中编辑 userReducer
和 postReducer
函数,并删除以下代码:
default:
throw new Error()
将前面的代码替换为返回 state
的 return
语句:
default:
return state
正如我们所看到的,现在我们的应用程序仍然以与以前完全相同的方式工作,但我们在整个应用程序状态中使用了一个单一的 Reducer!
示例代码
在我们的博客应用程序中使用单个 Reducer Hook 的示例代码可以在 Chapter04/chapter4_2
文件夹中找到。
只需运行 npm install
即可安装所有依赖项,运行 npm start
启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
使用 Effect Hook
我们将经常使用的最后一个基本 Hook 是 Effect Hook。使用 Effect Hook,我们可以从组件中执行副作用,例如在组件挂载或更新时获取数据。
以我们的博客为例,我们将实现一项功能,该功能可在登录时更新网页标题,使其包含当前登录用户的用户名。
还记得 componentDidMount 和 componentDidUpdate 吗?
如果你以前使用过 React,你可能已经使用过 componentDidMount
和 componentDidUpdate
生命周期方法。例如,我们可以使用 React 类组件将文档标题设置为给定的 prop,如下所示。在下面的代码片段中,第 4 ~ 7
行显示为生命周期方法:
import React from 'react'
class App extends React.Component {
componentDidMount() {
const { title } = this.props
document.title = title
}
render() {
return (
<div>Test App</div>
)
}
}
这很好。但是,当 title
prop 更新时,更改不会反映在我们网页的标题中。为了解决这个问题,我们需要定义 componentDidUpdate
生命周期方法(第 9 ~ 14
行),如下所示:
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
的更新,然后在两个生命周期方法中调用它。在下面的代码片段中,第 4 ~ 7
行、第 9 ~ 11
行、第 13 ~ 17
为更新代码:
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()
,我们始终需要记住在对该方法的两次调用中都将其传递给它。如果我们忘记更新其中一个生命周期方法,都可能会引入错误。此外,我们需要在 componentDidUpdate
中添加一个 if
条件,以避免在 title
prop 没有更改时调用 this.updateTitle()
。
使用 Effect Hook
在 Hook 的世界里,componentDidMount
和 componentDidUpdate
生命周期方法组合在 useEffect
Hook 中,当不指定依赖数组时,只要组件中的任何 props 发生变化,就会触发。
因此,我们现在可以使用 Effect Hook 定义一个函数组件,而不是使用类组件,它执行与以前相同的操作。传递给 Effect Hook 的函数称为“副作用函数”:
import React, { useEffect } from 'react'
function App({ title }) {
useEffect(() => {
document.title = title
})
return (
<div>Test App</div>
)
}
这就是我们需要做的!我们定义的 Hook 将在每次任何 props 更改时调用我们的 Effect 函数。
仅在某些 Props 发生变化时触发 Effect
如果我们想确保我们的 effect 函数仅在标题 props 更改时才被调用,例如,出于性能原因,我们可以指定哪些值应该触发更改,作为 useEffect
Hook 的第二个参数:
useEffect(() => {
document.title = title
}, [title])
这不仅限于 props,我们在这里可以使用任何值,甚至是来自其他 Hook 的值,例如 State Hook 或 Reducer Hook:
const [ title, setTitle ] = usestate('')
useEffect(() => {
document.title = title
}, [title])
如我们所见,在处理不断变化的值时,使用 Effect Hook 比使用生命周期方法简单得多。
仅在挂载时触发 Effect
如果我们想复制只添加一个 componentDidMount
生命周期方法的行为,而不在 props 更改时触发,我们可以通过向 useEffect
Hook传递一个空数组作为第二个参数来实现:
useEffect(() => {
document.title = title
}, [])
传递一个空数组意味着我们的 effect 函数只会在组件挂载时触发一次,并且在 props 更改时不会触发。但是,与其考虑组件的挂载,使用 Hook,我们应该考虑 effect 的依赖关系。在这种情况下,effect 没有任何依赖项,这意味着它只会触发一次。如果 effect 指定了依赖项,则当任何依赖项更改时,它将再次触发。
清理 Effect
有时,在卸载组件时需要清理 effect。为此,我们可以从 Effect Hook 的 effect 函数返回一个函数。此返回函数的工作方式类似于 componentWillUnmount
生命周期方法:
useEffect(() => {
const updateInterval = setInterval(() => console.log('fetching update'), updateTime)
return () => clearInterval(updateInterval)
}, [updateTime])
上面代码第 3
行称为清理函数。清理函数将在组件卸载之后再次运行 effect 之前调用。例如,这避免了 updateTime
props 更改时的错误。在这种情况下,将清除以前的 effect,并使用更新后的 updateTime
值定义一个新的间隔。
在我们的博客应用中实现 Effect Hook
现在我们已经了解了 Effect Hook 的工作原理,我们将在我们的博客应用程序中使用它,以便在我们登录/注销时(当 user
状态更改时)实现标题更改。
让我们开始在博客应用程序中实现 Effect Hook:
- 编辑
src/App.js
,并导入useEffect
Hook:
import React, { useReducer, useEffect } from 'react'
- 在定义了
useReducer
Hook 和状态解构后,定义一个useEffect
Hook,它根据username
值调整document.title
变量:
useEffect(() -> {
- 如果用户已登录,我们将
document.title
设置为<username> - React Hooks Blog
。为此,我们使用模板字符串,这允许我们通过${}
语法在字符串中包含变量或 JavaScript 表达式。模板字符串使用 \` 定义:
if (user) {
document.title = `${user} - React Hooks Blog`
- 否则,如果用户没有登录,我们只需将
document.title
设置为React Hooks Blog
:
} else {
document.title = 'React Hooks Blog'
}
- 最后,我们将
user
值作为第二个参数传递给 Effect Hook,以确保每当user
值更新时,我们的 Effect 函数都会再次触发:
}, [user])
如果我们现在启动应用程序,我们可以看到文档 document.title
被设置为 React Hooks Blog
,因为 Effect Hook 在应用程序组件挂载时触发,而 user
值还没有定义 :
Effect Hook 的 effect:网页标题更改中
使用 Test User 登录后,我们可以看到 document.title
更改为 Test User - React Hooks Blog
:
Effect Hook 会在 user 值改变后重新触发 effect
如我们所见,我们的 Effect Hook user
值更改后成功重新触发!
示例代码
在我们的博客应用程序中实现 Effect Hook 的示例代码可以在 Chapter04/chapter4_3
文件夹中找到。
只需运行 npm install
即可安装所有依赖项,运行 npm start
启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
总结
在本章中,我们首先了解了 action、reducer 和 Reducer Hook。我们还学习了何时应该使用 Reducer Hook 而不是 State Hook。然后,我们将 user
和 posts
状态的现有全局 State Hook 替换为两个 Reducer Hook。接下来,我们将两个 Reducer Hook 合并为一个 app Reducer Hook。最后,我们了解了 Effect Hook,以及如何使用它们来代替 componentDidMount
和 componentDidUpdate
。
在下一章中,我们将学习 React context,以及如何将其与 Hook 一起使用。然后,我们将向应用程序添加 Context Hook,以避免在多层组件上传递 props。
问题
为了回顾我们在本章中学到的内容,请尝试回答以下问题:
- State Hook 的常见问题是什么?
- 什么是 action?
- 什么是 reducer?
- 什么时候我们应该使用 Reducer Hook 而不是 State Hook?
- 将 State Hook 转换为 Reducer Hook 需要哪些步骤?
- 我们如何才能更轻松地创建 action?
- 我们什么时候应该合并 Reducer Hook?
- 合并 Reducer Hook 时需要注意什么?
- 类组件中的什么等效于 Effect Hook?
- 与类组件相比,使用 Effect Hook 有什么优势?
延伸阅读
如果您对我们在本章中探讨的概念的更多信息感兴趣,请查看以下阅读材料:
- 关于 Reducer Hook 的官方文档:https://reactjs.org/docs/hooks-reference.html#usereducer
- 使用 Effect Hook 的官方文档和提示:https://reactjs.org/docs/hooks-effect.html
- 学习 Redux 由 Packt 发布,以获取有关 action、reducer 和管理应用程序状态的更深入信息:https://www.packtpub.com/web-development/learning-redux
评论 (0)