💡 如果还不了解 HTML 、 CSS和JS,可以参考本号下的 HTML21 天入门教程、 CSS 21 天入门教程和JS21天入门教程。
函数组件没有状态,但它却是状态驱动的,而且它没有类组件生命周期的概念。
那它是如何做到状态驱动,并在组件的不同阶段执行必要的逻辑处理呢?
答案是使用 Hooks。
1. 什么是 React Hooks?
React Hooks 的用意,是让组件尽量写成纯函数。
如果需要外部功能和副作用,就用钩子把外部代码 “钩” 进来。
需要什么功能,就使用对应的钩子。
React 提供了一些常用的钩子,也提供了自定义钩子的方式。
2. 为什么要使用 Hooks?
既然是希望让组件尽量写成纯函数,那要实现的必要功能如何实现呢?
Hooks + 函数组件,就是加强版函数组件。
在完全不使用 “类” 的情况下,同样能写出一个全功能的组件。
这样的组件,反而有更简洁的组件逻辑。
同时也提高了代码的复用性。Hooks 允许将逻辑提取到可重用的函数中,以减少重复代码。
而且有些 Hooks 可以更精确地控制副作用和性能消耗,也就能获得更好的性能优化。
3. React 里主要的 Hooks
在介绍具体的 Hooks 之前,先说说一下 Hooks 的使用场合。
敲黑板,一定要在顶层使用 Hooks,不要在循环、条件或嵌套函数中使用 Hooks。
这样做是为了确保 Hooks 在每次渲染时的调用顺序相同。
如果担心自己的使用有问题,可以借助于工具帮助检查。
比如,在 ESLint 插件中 React 官方就提供了 eslint-plugin-react-hooks 插件。
React 默认提供的钩子有不少,这里挑四个最常用的钩子做简要说明。
useState()
useState()
用于为函数组件引入状态,也就是 state。
纯函数没有状态,所以把状态放在钩子里面。
还记得前面的 Counter 组件吗?一起来回顾一下。
import { useState } from 'react';const Counter = () => { const [Count, setCount] = useState(0); return ( <h4> <p> 我被点击了 {Count} 次. </p> <button onClick={() => { setCount(Count + 1); }} > 点击这里 </button> </h4> );};export default Counter;
在上述例子里,每当用户执行点击操作,页面元素里 Count 内容就会发生变化。
这种变化就是状态。
在代码中,Counter 组件是一个函数组件,内部使用 useState()
钩子引入状态。
useState()
函数接受状态的初始值,这里是 0 ,作为参数。
useState()
返回一个数组。
- 数组的第一个成员是一个变量
Count
,指向状态的当前值。 - 数组的第二个成员是一个函数,用来更新状态。函数的名称约定使用固定的命名规则,使用 set 作为前缀,再加上状态的变量名。在这里就是
setCount
。
这样,每当按钮被点击时,执行的方法里通过调用 setCount
为变量 Count
加 1。
这样的过程就完成了对状态的管理。
useContext()
Context 就是上下文。
前面讲的状态管理,是组件内部的。
如果需要和别的组件共享状态,就可以使用 useContext()。
具体的代码这里就先略过了,后面讲例子的时候再实际应用。
useReducer()
useReducer() 用于更复杂的 state 管理。
它接收一个 reducer 函数和初始状态,然后返回当前的状态和派发状态的 dispatch 函数。
const [state, dispatch] = useReducer(reducer, initialState);
虽然 React 有共享上下文状态和 Reducer 钩子,但它没有办法提供 middleware,如果需要使用这个功能,需要用到外部库 Redux。
useEffect()
useEffect() 用来引入具有副作用的操作。
副作用操作是指与组件渲染无关的操作,例如数据获取、订阅事件、手动操作 DOM 等
最常见的就是向服务器请求数据。
比如,当要在页面上加载产品的信息时,通过调用 API 获取产品的信息。
const Product = ({ productId }) => { const [loading, setLoading] = useState(true); const [product, setProduct] = useState({}); useEffect(() => { setLoading(true); fetch(`https://xxx.com/product/${productId}/`) .then(response => response.json()) .then(data => { setProduct(data); setLoading(false); }); }, [productId]) if (loading === true) { return <p>加载中 ...</p> } return <div> <p>产品名称: {product.name}</p> <p>产品颜色: {product.color}</p> <p>产品重量: {product.weight}</p> </div>}
上面代码中,使用 useEffect
来调用获取产品信息的 API,并通过 loading 来管理是否加载成功。
当组件参数 productId
发生变化时,useEffect()
就会执行。
组件第一次渲染时, useEffect()
也会执行。
4. 自定义 Hooks
如果 useEffect 里的代码封装一下,则成为了一个自定义 Hook。
const useProduct = (productId) => { const [loading, setLoading] = useState(true); const [product, setProduct] = useState({}); useEffect(() => { setLoading(true); fetch(`https://xxx.com/product/${productId}/`) .then(response => response.json()) .then(data => { setProduct(data); setLoading(false); }); }, [personId]); return [loading, product];};
useProduct()
就是自定义的 Hook。
在使用的时候,就直接使用 useProduct()
钩子。
const Product = ({ productId }) => { const [loading, product] = useProduct(personId) if (loading === true) { return <p>加载中 ...</p> } return <div> <p>产品名称: {product.name}</p> <p>产品颜色: {product.color}</p> <p>产品重量: {product.weight}</p> </div>}
是不是代码看起来简洁了很多?
5. 总结
最后来总结一下今天的内容要点:
- 🍑 组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码 “钩” 进来。
- 🍑 React 提供了一些常用的钩子,比如
useState
,useContext
,useEffect
等,也提供了自定义钩子的方式。 - 🍑 一定要在顶层使用 Hooks,不要在循环、条件或嵌套函数中使用 Hooks。
该文章在 2024/12/4 15:31:50 编辑过