前瞻
好家伙,在重温 React,了解其的历史版本迭代的过程中,发现了一个令人震惊的事情。
就是以前函数组件那般默默无闻,说是类组件的陪衬品也不为过,而如今,ta 的威名已然盖过了类组件。
这期间的转变历程,任由脑海遨游畅想,越发觉得在上演一场宫心大计,想想都觉得恐怖~
那么是什么给了函数组件的勇气?梁静茹吗? NoNoNo,是Hooks
接下来,我们就了解一下什么是 Hooks,以及看白莲花函数组件如何走上神坛!!!
介绍
Hooks,英文翻译为钩子。
ta的意思是组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。
React默认内置了一些常用的钩子 ,不过你也可以自定义封装自己的钩子。
所有的钩子都是为函数引入外部的功能,所以 React 约定,Hooks 统一规定使用use前缀,提升可读性(usexxx)。
由来
在16.8版本之前,我们知道 React 组件分为类组件和函数组件,类组件,受函数组件辅佐,当王称道。
一开始呢,React 是想要一个仅接受属性并可以返回 JSX 的组件(不需帝王之心的能人),类组件以防万一,函数组件便被净了身召入宫中,被封无状态函数组件。
但不久之后,函数组件就告诉我们,不要称呼他的全称,我们应该称之为函数组件,因为 ta 有自己的打算~
没曾想到,在 React16.8 版本后,React 引入了 Hooks,助函数组件修炼,在那时 React 内忧外患,函数组件就凭借着 Hooks 这一手完美解决了当时令类组件也头疼的问题。
这一项举动,极大地奠定了 React 写法的转变,逐渐释放了函数组件 ta 隐忍的帝王之心,从此新王登基。
那么,Hooks到底解决了什么痛点呢?
解决了什么
当时有这么几个痛点,官方当时的说法是这样的:
- 在组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的 class
这显然是 class(类组件)的痛点所在,因为其在治理 React 国度时,就存在以下问题:
- 难以琢磨的 this 指向(治理国度方向迷糊)
- 关联的逻辑被拆分(大臣们不团结)
- 众多生命周期,每一个都得适当使用(后宫众多,伤身)
- 代码量相对更多,尤其简单组件甚是(批奏折需过层层关卡,效率低下)
针对上述问题,React 决定给函数组件带来新的能力 -- Hooks
所谓 React Hooks,就是这些增强函数组件能力特性的钩子,把这些特性「钩」进纯函数。
常用Hooks
React 提供了许多常用的 Hooks,用于在函数组件中添加状态管理、副作用处理和其他功能,下面介绍其中几个常用的,React 还有很多其他 Hooks。
下面是本章主要内容的总览图:
基础Hooks
最基础的Hooks,公认为三个:useState
、useEffect
和 useContext
ta 们的作用,分别是:
- useState :向组件引入新的
状态变量
,这个状态会被保留在 React 中 - useEffect :向组件提供
副作用
的处理区域,例如数据获取、订阅、事件处理等 - useContext :向组件提供跨越组件层级直接
传递变量
,实现数据共享
useState
useState
用于在函数组件中添加局部状态。通过 useState
,函数组件可以像类组件一样拥有状态,并且能够响应状态的变化而重新渲染。
顶级理解:
Hooks之拉拢人心
基本用法:
js
import React, { useState } from 'react';
function Example() {
// 声明一个新的状态变量,我们称之为 "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useState
返回一个数组,数组的第一个元素是当前状态的值,第二个元素是一个用于更新状态的函数。通过解构赋值,我们可以很方便地获取这两个值。
更新状态
setCount
函数接受一个新的状态值作为参数,并更新 count
的值。当 count
的值改变时,组件会重新渲染。
注意事项
- 只在函数组件的顶层调用 Hooks :确保在组件的顶层调用
useState
,不要在循环、条件或嵌套函数中调用它。 - Hook 的调用顺序:React 依靠 Hook 的调用顺序来正确更新状态。因此,每次渲染时,Hook 的调用顺序都应该是一致的。
- 从 Hook 返回的值 :每次调用
useState
都会返回一个新的状态变量和更新函数,即使状态变量的初始值相同。
useEffect
useEffect
主要用于处理组件中的副作用操作。副作用是指在组件渲染过程中,可能会对外部环境产生影响的操作,比如数据获取、订阅事件、操作 DOM 等。
顶级理解:
Hooks之培养暗卫
基本用法: useEffect
接受两个参数:一个副作用函数和一个依赖数组。
js
import React, { useEffect } from 'react';
function Example() {
useEffect(() => {
// 副作用函数,在组件渲染时执行
// 可以在这里进行副作用操作,比如数据获取、事件订阅等
// 如果返回一个函数,这个函数会在组件卸载时执行,用于清理副作用
return () => {
// 清理函数
};
}, [依赖数组]); // 依赖数组,用于指定副作用函数的执行时机
return <div>Example Component</div>;
}
执行时机
- 无依赖数组 :如果
useEffect
的第二个参数(依赖数组)不传或者为空,那么副作用函数会在组件首次渲染后执行,并且在每次组件更新后也会执行。 - 有依赖数组:如果依赖数组不为空,并且依赖数组中的值发生变化时,副作用函数会被重新执行。如果依赖数组中的值没有变化,那么副作用函数不会执行。
使用场景
- 替代生命周期方法 :
useEffect
可以看作是componentDidMount
、componentDidUpdate
和componentWillUnmount
这三个生命周期方法的组合。当组件挂载后,副作用函数会执行(类似于componentDidMount
);当组件的 props 或 state 更新导致重新渲染后,如果依赖数组中的值有变化,副作用函数也会执行(类似于componentDidUpdate
);当组件卸载时,如果副作用函数返回了一个清理函数,那么这个清理函数会执行(类似于componentWillUnmount
)。 - 数据获取:在副作用函数中可以进行数据获取操作,比如从 API 获取数据并更新组件的状态。
- 事件订阅与取消订阅:在副作用函数中可以进行事件订阅,并在清理函数中取消订阅,以避免内存泄漏。
注意事项
- 不要在副作用函数中直接修改 state :如果需要更新 state,应该使用
setState
函数或通过其他方式触发组件的重新渲染。 - 注意副作用函数的执行次数:由于副作用函数可能会在每次组件更新后执行,因此需要注意其执行次数和性能影响。如果可能的话,尽量将副作用函数的执行限制在必要的时机。
useContext
useContext
它允许你在组件树中跨多层级访问 context
的值,而无需通过每层手动传递 props
。在 React 类组件中,父组件向子组件传递参数可以通过 props
和 context
,但在函数式组件中,当需要向多层组件传递数据时,useContext
就变得非常有用。
顶级理解:
Hooks之飞鸽传书
基本用法
要使用 useContext
,首先需要创建一个 context
对象,这可以通过 React.createContext()
方法实现。然后,你可以使用 <Context.Provider>
组件来包裹需要访问该 context
的组件树,并为其提供值。最后,在组件内部,你可以使用 useContext(Context)
来获取该 context
的值。
js
import React, { createContext, useContext } from 'react';
// 创建一个 context 对象
const MyContext = createContext();
function MyComponent() {
// 使用 useContext 获取 context 的值
const value = useContext(MyContext);
// 使用 value 做一些事情...
return <div>{value}</div>;
}
function App() {
// 定义 context 的值
const myValue = 'Hello, World!';
// 使用 Provider 包裹组件树,并提供值
return (
<MyContext.Provider value={myValue}>
<MyComponent />
</MyContext.Provider>
);
}
在这个例子中,MyComponent
能够通过 useContext(MyContext)
访问到 MyContext
中定义的值 myValue
。
使用场景
useContext
非常适合用于在组件树中共享全局状态或配置,例如用户认证信息、主题设置、语言偏好等。这些全局状态或配置通常不需要在每个组件中手动传递,使用 useContext
可以简化组件的逻辑,使代码更加清晰和易于维护。
注意事项
- 不要过度使用 :虽然
useContext
提供了方便的跨层级数据共享方式,但过度使用可能会导致组件间的耦合度增加,使得代码难以理解和维护。 - 性能考虑 :当
context
的值发生变化时,所有使用useContext
的组件都会重新渲染。因此,如果context
的值经常变化,或者组件树很大,那么这可能会导致性能问题。 - 后备值 :如果组件树中没有匹配的
Provider
,useContext
会返回其默认值。在调用createContext
时,可以提供一个默认值作为后备,这样即使在没有Provider
的情况下,useContext
也不会返回undefined
。
进阶Hooks
除了基础Hooks,还有很多进阶的Hooks,这里就详细介绍一下这四个:useReducer
、useCallBack
、useMemo
和 useRef
ta 们的作用,分别是:
- useReducer :用于管理组件中
复杂的状态逻辑
- useCallBack :用于返回一个记忆的
回调函数
,防止不必要的重新渲染 - useMemo :向组件提供跨越组件层级直接
传递变量
,实现数据共享 - useRef :用于保存组件内部的
可变数据
,这些数据可在组件重新渲染保持不变
useReducer
useReducer
用于管理组件中复杂的状态逻辑
。它类似于 Redux 中的 reducer,允许你将状态更新逻辑集中到一个地方,使代码更加清晰和可维护。useReducer
非常适合处理包含多个子状态的状态对象,或者当状态更新逻辑较复杂时。
顶级理解:
Hooks之集权管理
基本用法
useReducer
接受两个参数:一个 reducer 函数和一个初始状态。reducer 函数接受当前状态和一个动作(action)对象,并返回一个新的状态。useReducer
返回一个包含当前状态和 dispatch 函数的数组。
下面是一个简单的示例:
js
import React, { useReducer } from 'react';
// 定义 reducer 函数
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
// 初始化状态为 { count: 0 }
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
在上面的例子中,我们定义了一个 counterReducer
函数来处理状态的更新。然后,在 Counter
组件中,我们使用 useReducer
初始化状态,并获取到当前状态和 dispatch 函数。通过调用 dispatch
并传入一个动作对象,我们可以触发状态的更新。
使用场景
- 当多个状态需要一起更新时 :使用
useReducer
可以方便地管理多个相关的状态字段,确保它们以一致的方式更新。 - 当状态更新逻辑较复杂时 :对于复杂的状态更新逻辑,使用
useReducer
可以将逻辑集中到一个 reducer 函数中,使代码更加清晰和可维护。 - 当下一个状态依赖于之前的状态时 :在某些情况下,新的状态可能依赖于之前的状态。使用
useReducer
可以方便地处理这种情况,因为 reducer 函数会接收当前状态作为参数。
相对于 useState 的优势
与 useState
相比,useReducer
在处理复杂状态逻辑时具有以下优势:
- 更好的描述"如何更新状态" :
useReducer
通过将状态更新逻辑集中到一个 reducer 函数中,使得代码更加清晰和易于理解。相比之下,useState
的状态更新逻辑通常分散在组件的各个部分。 - 代码结构更清晰 :使用
useReducer
可以将状态更新逻辑与组件的渲染逻辑分离,使得代码结构更加清晰。 - 更容易进行状态管理扩展 :如果你发现
useReducer
不够用,可以将 reducer 逻辑与 Redux 这样的状态管理库结合使用,从而更容易地扩展到大型应用程序中。
useCallBack
useCallback
它用于返回一个记忆的回调函数
。当你把函数传递给经过优化的子组件时,可以防止不必要的重新渲染
,从而提高性能。
顶级理解:
Hooks之审慎持重
基本用法
useCallback
接收一个函数作为参数,并返回一个记忆版本的该函数。只有当它的依赖项数组中的值发生变化时,这个记忆版本的函数才会更新。
js
import React, { useCallback } from 'react';
function MyComponent({ onClick }) {
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []); // 依赖项数组
return <button onClick={handleClick}>Click me</button>;
}
在上面的例子中,handleClick
函数是记忆化的,这意味着只要依赖项数组(在这个例子中是空的)中的值不发生变化,每次渲染 MyComponent
时都会返回相同的 handleClick
函数实例。
使用场景
- 优化子组件渲染 :当你有一个经过优化的子组件(例如,使用
React.memo
或useMemo
),并且你传递一个新的回调函数给它作为 prop 时,每次父组件渲染都会导致子组件重新渲染,即使回调函数的内容没有变化。使用useCallback
可以防止这种情况。 - 避免不必要的渲染和计算 :在复杂的应用中,你可能需要避免不必要的计算和渲染。使用
useCallback
可以确保只有当依赖项发生变化时,相关的计算和渲染才会发生。
注意事项
- 不要过度使用
useCallback
。在大多数情况下,React 的性能已经足够好,不需要额外的优化。 - 只有在确定回调函数的变化会导致性能问题时,才使用
useCallback
。 - 不要仅仅为了使用
useCallback
而增加不必要的依赖项。这可能会导致回调函数比预期更频繁地更新,从而抵消了其性能优势。
useMemo
useMemo
它允许你"记住"一些计算结果,并在依赖项没有变化时避免重新进行计算。这可以帮助优化应用性能,特别是在处理复杂或计算密集型的任务时。
顶级理解:
Hooks之方案化治国
基本用法
useMemo
接受两个参数:一个函数(用于执行计算)和一个依赖项数组。如果依赖项数组中的任何值发生变化,该函数将重新执行,并返回新的计算结果。
js
import React, { useMemo } from 'react';
function ExampleComponent({ a, b }) {
// 使用 useMemo 来"记住"计算结果
const computedValue = useMemo(() => {
// 这里执行一些计算密集型的任务
const result = a * b;
return result;
}, [a, b]); // 依赖项数组
return <div>{computedValue}</div>;
}
使用场景
- 避免不必要的渲染 :当组件的 props 或 state 发生变化时,如果某些计算不是基于这些变化的,那么使用
useMemo
可以避免这些不必要的计算。 - 优化性能 :对于复杂或计算密集型的任务,使用
useMemo
可以提高应用的响应速度和性能。
注意事项
- 不要过度使用 :
useMemo
并不是在所有情况下都会带来性能提升。在某些情况下,它甚至可能导致性能下降,因为 React 需要跟踪额外的依赖项和计算。因此,在使用之前,最好先确定是否真的需要它。 - 正确设置依赖项 :如果遗漏了某个依赖项,那么当该依赖项发生变化时,
useMemo
可能不会重新执行计算,从而导致错误的结果。另一方面,如果包含了不必要的依赖项,那么可能会导致不必要的重新计算。 - 与其他 Hooks 配合使用 :
useMemo
通常与其他 React Hooks(如useState
、useEffect
等)一起使用,以构建更复杂和高效的组件。
useRef
useRef
它返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
顶级理解:
Hooks之坚守本心
useRef
主要有以下几个用途:
- 访问 DOM 元素或 React 元素 :
当你需要直接操作 DOM 元素,或者获取 React 元素(如子组件的实例)时,可以使用useRef
。例如,你可能需要手动触发某个输入框的 focus,或者获取某个子组件的状态。
js
import React, { useRef, useEffect } from 'react';
function Example() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
- 保持可变的值 :
useRef
也可以用来保存一个可以在渲染之间保持不变的值。这与useState
不同,因为useState
会触发组件的重新渲染,而useRef
不会。
js
function Timer() {
const countRef = useRef(0);
function tick() {
countRef.current += 1;
console.log(countRef.current);
}
const intervalId = setInterval(tick, 1000);
return (
<div>
<h1>This is a Timer</h1>
</div>
);
// 清理函数,在组件卸载时执行
return () => clearInterval(intervalId);
}
- 传递 ref 到子组件 :
当父组件需要将 ref 传递给子组件时,可以使用forwardRef
与useRef
结合。
js
import React, { forwardRef, useRef, useImperativeHandle } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} {...props} />;
});
function ParentComponent() {
const fancyInputRef = useRef();
const handleClick = () => {
fancyInputRef.current.focus();
};
return (
<>
<FancyInput ref={fancyInputRef} />
<button onClick={handleClick}>Focus the input</button>
</>
);
}
自定义Hooks
React 的自定义 Hooks 是一种允许你在组件之间重用状态逻辑的技术。通过自定义 Hooks,你可以提取组件中共享的逻辑,并在其他组件中重复使用,从而提高代码的可读性和可维护性。
顶级理解:
Hooks之心有万象
基本规则:
- 以
use
开头:自定义 Hooks 必须以 "use" 开头。这个约定有助于区分是否为 Hook,并提醒开发者这个特定的函数遵循 Hook 的规则。 - 函数内部调用其他 Hooks :自定义 Hooks 本质上就是函数,但是在这些函数内部,你需要调用其他的 React Hooks(如
useState
、useEffect
等)。 - 自定义 Hook 的名字应该是描述性的 :Hook 应该根据其功能命名,例如
useFetch
、useInputValidation
等。
下面是一个简单的自定义 Hook 示例,它使用 useState
和 useEffect
来处理异步数据获取:
js
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const json = await response.json();
if (!didCancel) {
setData(json);
}
} catch (error) {
if (!didCancel) {
setError(error);
}
} finally {
setIsLoading(false);
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
return { data, error, isLoading };
}
// 使用自定义 Hook
function MyComponent() {
const { data, error, isLoading } = useFetch('https://api.example.com/data');
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return (
<div>
{/* 显示数据 */}
</div>
);
}
在这个示例中,useFetch
是一个自定义 Hook,它封装了异步数据获取的逻辑。你可以在多个组件中重复使用 useFetch
,而无需在每个组件中重复编写相同的逻辑。
结语
至此,所有常用的Hooks君王秘术已展现完毕。这些秘术不仅展示了技术的深度和广度
,更为我们在实际开发中提供了无穷的灵感和可能。
无论是数据处理
的巧妙,还是状态管理
的智慧,亦或是组件复用
的创新,它们都以其独特的方式,提升了我们的开发效率
和代码质量。
然而,秘术虽好,也需善加运用。在未来的开发中,我们应根据具体场景,结合现有秘术方案
,灵活运用Hooks君王秘术,创造出更加优雅、高效、可维护
的代码。同时,我们也应保持对新技术的探索
和热情
,不断拓宽视野,提升自我,以应对日新月异的技术挑战。
在此,感谢各位
对我的关注和支持。愿我们在技术的海洋中,乘风破浪,勇往直前,共同书写更加辉煌的篇章!