Hooks 使得在组件中组织逻辑成为可能,使它们变得小巧且可重用,而无需编写类。从某种意义上说,它们是 React 倾向于函数的方式,因为在它们之前,我们必须在组件中编写它们,而组件本身已被证明是强大且功能性的,但它们必须在前端渲染某些内容。在某种程度上,这一切都很好,但结果是 DOM 上充斥着 div,这使得在 DevTools 中进行挖掘和调试变得棘手。
好吧,React Hooks 改变了这一点。我们无需依赖组件的自上而下的流程或以各种方式抽象组件(例如 高阶组件),而可以在组件内部调用和管理流程。Dan Abramov 在他的 理解 React Hooks 文章中很好地解释了这一点
Hooks 将 React 的理念(显式数据流和组合)应用于组件内部,而不仅仅是组件之间。 这就是为什么我认为 Hooks 非常适合 React 组件模型。
与渲染属性或高阶组件等模式不同,Hooks 不会在您的组件树中引入不必要的嵌套。它们也不会受到混合体的缺点的影响。
Dan 的文章的其余部分提供了许多有用的上下文,说明 React 团队为何朝着这个方向发展(它们现在在 React v16.7.0-alpha 中可用)以及 Hooks 设计用来解决的各种问题。React 文档有一个 Hooks 入门,该入门反过来包含一个关于 促使团队创建 Hooks 的原因 的部分。我们更关心如何使用它们,所以让我们继续看一些例子吧!
在我们开始时需要注意的重要一点是,目前有九个可用的 Hooks,但我们将看看 React 文档中所说的三个基本 Hooks:useState()
、useEffect
和 setContext()
。我们将在本文中深入探讨每一个 Hooks,并在最后总结高级 Hooks。
useState()
定义状态
使用 如果您在任何层面上都使用过 React,那么您可能熟悉状态是如何定义的:编写一个类并使用 this.state
初始化一个类
class SomeComponent extends React.component {
constructor(props)
super(props);
this.state = {
name: Barney Stinson // Some property with the default state value
}
}
React Hooks 允许我们抛弃所有这些类的东西,而是使用 useState()
Hook。类似这样
import { useState } from 'react';
function SomeComponent() {
const [name, setName] = useState('Barney Stinson'); // Defines state variable (name) and call (setName) -- both of which can be named anything
}
怎么说?!就是这样!请注意,我们正在类之外工作。Hooks 不在类内部工作,因为它们用于代替类。我们直接在组件中使用 Hook
import { useState } from 'react';
function SomeComponent() {
const [name, setName] = useState('Barney Stinson');
return
<div>
<p>Howdy, {name}</p>
</div>
}
哦,您想更新 name 的状态?让我们在输出中添加一个输入框和提交按钮,并调用 setName
来在提交时更新默认名称。
import { useState } from 'react'
function SomeComponent() {
const [input, setValue] = useState("");
const [name, setName] = useState('Barney Stinson');
handleInput = (event) => {
setValue(event.target.value);
}
updateName = (event) => {
event.preventDefault();
setName(input);
setValue("");
}
return (
<div>
<p>Hello, {name}!</p>
<div>
<input type="text" value={input} onChange={handleInput} />
<button onClick={updateName}>Save</button>
</div>
</div>
)
}
在这个例子中注意到其他什么了吗?我们正在构建两个不同的状态(input 和 name)。这是因为 useState()
Hook 允许在同一个组件中管理多个状态!在本例中,input
是属性,setValue
持有输入元素的状态,它由 handleInput
函数调用,然后触发 updateName
函数,该函数获取输入值并将其设置为新的 name
状态。
useEffect()
创建副作用
使用 因此,定义和设置状态都很好,但是还有一个名为 useEffect()
的 Hook 可用于——您猜对了——直接在组件中定义和重用效果,而无需类或为每个方法的生命周期使用冗余代码(即 componentDidMount
、componentDidUpdate
和 componentWillUnmount
)。
当我们谈论效果时,我们指的是 API 调用、DOM 更新和事件监听器等内容。 React 文档 引用了数据获取、设置订阅和更改 DOM 等示例作为此 Hook 的可能用例。也许与 useState()
最大的区别在于 useEffect()
在渲染后运行。将其视为向 React 发出指令,让它保留传递的函数,然后在渲染发生以及之后的任何更新后对 DOM 进行调整。同样,React 文档很好地解释了这一点
默认情况下,它在第一次渲染后和每次更新后都会运行。[…]与其考虑“挂载”和“更新”,您可能会发现更容易理解效果发生在“渲染之后”。React 保证在运行效果时 DOM 已经更新。
好的,那么我们如何运行这些效果呢?好吧,我们首先像对 useState()
做的那样导入 Hook。
import { useEffect } from 'react';
事实上,我们可以在同一个导入中调用 useState()
和 useEffect()
import { useState, useEffect } from 'react';
或者,构造它们
const { useState, useEffect } = React;
因此,让我们偏离我们之前的名称示例,通过连接到包含用户数据的外部 API 使用 axios 在 useEffect()
Hook 内,然后将该数据渲染到用户列表中。
首先,让我们引入我们的 Hooks 并初始化 App。
const { useState, useEffect } = React
const App = () => {
// Hooks and render UI
}
现在,让我们使用 useState()
将 users
定义为一个变量,该变量包含一个 setUsers
状态,一旦数据被获取,我们将把用户数据传递给它,以便它准备好进行渲染。
const { useState, useEffect } = React
const App = () => {
const [users, setUsers] = useState([]);
// Our effects come next
}
这里就是 useEffect()
发挥作用的地方。我们将使用它来连接到 API 并从中获取数据,然后将该数据映射到我们在渲染时可以调用的变量。
const { useState, useEffect } = React
const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
// Connect to the Random User API using axios
axios("https://randomuser.me/api/?results=10")
// Once we get a response, fetch name, username, email and image data
// and map them to defined variables we can use later.
.then(response =>
response.data.results.map(user => ({
name: `{user.name.first} ${user.name.last}`,
username: `{user.login.username}`,
email: `{user.email}`,
image: `{user.picture.thumbnail}`
}))
)
// Finally, update the `setUsers` state with the fetched data
// so it stores it for use on render
.then(data => {
setUsers(data);
});
}, []);
// The UI to render
}
好的,现在让我们渲染我们的组件!
const { useState, useEffect } = React
const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
axios("https://randomuser.me/api/?results=10")
.then(response =>
response.data.results.map(user => ({
name: `{user.name.first} ${user.name.last}`,
username: `{user.login.username}`,
email: `{user.email}`,
image: `{user.picture.thumbnail}`
}))
)
.then(data => {
setUsers(data);
});
}, []);
return (
<div className="users">
{users.map(user => (
<div key={user.username} className="users__user">
<img src={user.image} className="users__avatar" />
<div className="users__meta">
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
</div>
))}
</div>
)
}
以下是我们得到的结果
值得注意的是,useEffect()
能够做到更多,例如链接效果并在条件下触发它们。此外,在某些情况下,我们需要在效果运行后进行清理(例如订阅外部资源)以防止内存泄漏。完全值得在 React 文档中详细了解 带有清理的效果。
useContext()
Context 和 React 中的 Context 使得能够将 props 从父组件传递到子组件。这使您免于处理 props 逐层传递的麻烦。但是,您只能在类组件中使用 Context,但现在您可以使用 useContext()
在函数组件中使用 Context。让我们创建一个计数器示例,我们将使用 useContext()
将用于增加或减少计数的状态和函数从父组件传递到子组件。首先,让我们创建我们的 Context
const CountContext = React.createContext();
我们将在 App 组件中声明计数器状态和增加/减少方法,并设置包含组件的包装器。我们将在稍后在实际的计数器组件中使用 Context Hook。
const App = () => {
// Use `useState()` to define a count variable and its state
const [count, setCount] = useState(0);
// Construct a method that increases the current `setCount` variable state by 1 with each click
const increase = () => {
setCount(count + 1);
};
// Construct a method that decreases the current `setCount` variable state by 1 with each click.
const decrease = () => {
setCount(count - 1);
};
// Create a wrapper for the counter component that contains the provider that will supply the context value.
return (
<div>
<CountContext.Provider
// The value is takes the count value and updates when either the increase or decrease methods are triggered.
value={{ count, increase, decrease }}
>
// Call the Counter component we will create next
<Counter />
</CountContext.Provider>
</div>
);
};
好了,进入 Counter 组件!useContext()
接受一个对象(我们正在传递 CountContext
提供程序)并允许我们告诉 React 我们想要什么值(`count`)以及哪些方法触发更新的值(increase
和 decrease
)。然后,当然,我们将通过渲染组件来完成所有操作,该组件由 App 调用。
const Counter = () => {
const { count, increase, decrease } = useContext(CountContext);
return (
<div className="counter">
<button onClick={decrease}>-</button>
<span className="count">{count}</span>
<button onClick={increase}>+</button>
</div>
);
};
然后瞧!看看我们强大的计数器,其计数由 Context 对象和值提供支持。
总结
我们仅仅触及了 React Hooks 能够做的事情的表面,但希望这能为您奠定坚实的基础。例如,除了我们在本文中介绍的基本 Hooks 之外,还有更多高级 Hooks 可用。以下是这些 Hooks 的列表以及文档提供的描述,以便您在掌握基础知识后能够更上一层楼
Hook | 描述 |
---|---|
userReducer() | useState 的替代方案。接受类型为 (state, action) => newState 的 reducer,并返回当前状态以及一个 dispatch 方法。 |
useCallback() | 返回一个记忆化的回调函数。传递一个内联回调函数和一个输入数组。useCallback 将返回回调函数的记忆化版本,该版本仅在输入之一发生更改时才会更改。 |
useMemo() | 返回一个记忆化的值。传递一个“创建”函数和一个输入数组。useMemo 仅当输入之一发生更改时才会重新计算记忆化的值。 |
useRef() | useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数(initialValue )。返回的对象将在组件的整个生命周期中持续存在。 |
useImperativeMethods | useImperativeMethods 自定义了在使用 ref 时公开给父组件的实例值。与往常一样,在大多数情况下应避免使用 ref 的命令式代码。useImperativeMethods 应与 forwardRef 一起使用。 |
useLayoutEffect
| useLayoutEffect 的签名与 useEffect 相同,但它会在所有 DOM 变更之后同步触发。使用它来读取 DOM 中的布局并同步重新渲染。在 useLayoutEffect 内部调度的更新将同步刷新,在浏览器有机会绘制之前。 |
第 4 个嵌入的处理程序函数缺少
const
。第一个使用 useEffect 的示例缺少(尽管不需要?)映射响应数据中括号前的
$
。感谢这篇文章,内容丰富。底部表格中有一些小错误:userReducer 应为 useReducer,useImperativeMethods 应为 useImperativeHandle
这并不完全正确。
组件不必渲染任何内容——它们可以简单地返回 null 或只委托给一个或多个(使用 Fragments)其他组件,而不需要自己的 DOM 节点。
我们在 react-dev-tools 中确实看到了来自高阶组件的所有包装组件,但并非每一层都会贡献一个包装 DOM 节点(div 或其他)。