什么是React Hooks
React Hooks 是 React 16.8 新增的一种特性,它可以让你在函数式组件中使用 state、生命周期、context 等 React 特性。Hooks 可以让你将复杂的逻辑拆分成多个独立可重用的函数,并且使得这些函数更加易于理解和测试。
Class组件存在的问题
-
复杂度高:类组件的定义和使用更加复杂繁琐。在类组件中,需要继承 React.Component 类,并且还要管理 this 指针、生命周期等,这使得代码变得冗长且难以理解。
-
嵌套层数过多:在传统 class 组件编写方式中,为了实现一些复杂逻辑或者 UI 界面,在不同生命周期函数中进行数据更新等操作容易导致代码嵌套层数过多、阅读性差,并且增加额外的复杂度和深度嵌套。
-
对功能模块封装比较困难:对于某个特定的功能模块或业务逻辑而言,在类组件内部如果想要将其封装成一个独立的方法或者函数,则往往需要定义私有属性或方法来实现封装。然而这样增加了代码耦合性并降低了可重用性。
-
不利于优化:由于 React 生命周期方法是按顺序执行的,因此在 componentDidMount 中做出昂贵计算可能会影响首次渲染时间。即使我们可以使用 shouldComponentUpdate 和 PureComponent 来减少重新渲染次数, 但仍然无法避免当 props 和 state 变化时带来的重新渲染。
React Hooks的优势?
React Hooks 的出现主要是为了解决 React Class组件的缺陷,并且让我们的函数组件也可以拥有类似于类组件的状态管理和副作用处理能力。
-
更简单的实现代码复用:在React Hooks出现之前,我们想要实现代码逻辑的复用需要使用到高阶组件、render props 等技术来帮助我们完成,但这样会增加额外的复杂度和深度嵌套,而 Hook 可以更轻松地实现代码逻辑的重用。
-
使代码逻辑更清晰:在传统的 class 组件编写方式中,在不同生命周期函数中进行数据更新时容易导致代码难以理解和维护。Hook 可以让你将相关业务逻辑放到同一个函数内部,从而提高可读性并减少了模块之间相互影响带来风险。
-
更便于大规模开发与测试:通过使用 Hook ,React 组件变成纯粹声明式渲染效果, 并且对应该渲染结果只由 Props 和 State 决定, 这种特点使得 React 开发者倾向于采取函数式编程(Functional Programming)范例去建立 UI.
-
更加灵活和自由 :Hooks 提供了很多新特性例如 state、useEffect、useContext 等,使得函数组件拥有了更多的功能和使用场景,并且可以自由地将这些 Hook 组合在一起以实现各种需求。
Hook的出现,可以解决我们Class组件存在的这些问题,它可以让我们在不编写class的情况下使用state以及其他的React特性。
注意:Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用
Class组件和Functional组件对比
State Hook的API
useState
作用
useState
,它可以帮助我们在函数式组件中管理状态。
语法
const [state, setState] = useState(initialState);
state
: 表示当前的状态值。setState
: 是一个更新 state 值的函数,调用该函数会重新渲染组件并更新状态。
参数
- initialState: 表示 state 的初始值。可以是任何的类型,例如数字、字符串、数组或对象等。
返回值
- 数组:包含两个元素 [state, setState]
使用方法
定义了一个计数器组件,使用了 useState
来初始化 count 状态,并且通过 setCount 函数来更新 count 状态。当用户点击 Increment 按钮时,就会触发 increment 函数并将 count 增加一次。在此过程中,React 自动检测到状态变化,并重新渲染页面以显示最新结果。
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
}
export default Counter;
Effect
作用
useEffect
它可以让我们在函数式组件中执行副作用操作(例如:从服务器获取数据、手动更新 DOM 等)。
语法
useEffect(effect, dependencies);
- effect:表示要执行的副作用函数。
- dependencies:表示 useEffect 需要监测变化的依赖项数组。
参数
- effect: 副作用函数。该函数会在每次渲染时都被调用,并且可以根据需要返回清理函数。如果不需要清理,则应该返回 undefined 或者空函数。
- dependencies: 可选参数,传入一个数组来指定监测哪些状态/属性变化而触发副作用操作。
返回值
无返回值。
使用方法
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
const result = await axios("https://jsonplaceholder.typicode.com/posts");
setData(result.data);
}
fetchData();
}, []);
return (
<div>
<h1>Posts</h1>
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
export default App;
在上面的代码示例中我们定义了一个简单的应用程序,我们使用了 useState
来初始化 data 状态,并且在 useEffect
中使用 axios 库从服务器获取数据。当数据加载完成时,我们使用 setData 函数更新 data 状态,并且通过 map 方法将每个帖子的标题显示为列表项。在此过程中,React 自动检测到状态变化,并重新渲染页面以显示最新结果。
useContext
作用
useContext
它可以让我们在组件树中跨层级传递数据,避免了繁琐的 props 传递。
用法
首先,我们需要创建一个 Context 对象来存储要共享的数据:
// MyContext.js
import React from "react";
const MyContext = React.createContext();
export default MyContext;
然后,在父组件中设置共享数据,并将其作为 value
属性传递给 Context.Provider 组件:
// App.js
import React, { useState } from "react";
import MyContext from "./MyContext";
import ChildComponent from "./ChildComponent";
function App() {
const [name, setName] = useState("John");
return (
<div>
<h1>Hello, {name}!</h1>
<MyContext.Provider value={name}>
<ChildComponent />
</MyContext.Provider>
</div>
);
}
export default App;
这里我们使用了 useState
来定义状态变量 name
和用于更新该变量的函数 setName
。然后将其作为值传递给 Context。
接下来,在子组件中使用 useContext Hook 来获取共享数据并进行展示:
// ChildComponent.js
import React, { useContext } from "react";
import MyContexct from "./MyContexct";
function ChildComponent() {
const name = useContext(MyContexct);
return (
<div>
<h2>Child Component</h2>
<p>{name}, how are you doing?</p>
</div>
);
}
export default ChildComponent;
如上述代码所示,我们可以通过 useContext
Hook 来获取 Context 中的共享数据,并将其命名为 name
。然后在组件中展示该数据。
最终,当我们运行这个应用程序时,它将显示一个包含 "Hello, John!"
和 "John, how are you doing?"
的页面。
注意:到子组件无需通过 props 获取父组件的状态变量值即可访问该值。
useReducer
作用
useReducer
它可以帮助我们使用 reducer 函数管理复杂的组件状态。
用法
首先,我们需要定义一个 reducer 函数来管理组件状态。该函数接收两个参数:当前状态(state)和更新该状态所需的操作(action)。然后根据 action 的类型进行相应的处理并返回新的 state。
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
throw new Error();
}
}
在上面的代码中,我们创建了一个名为 reducer
的函数,并根据传入的不同类型执行相应操作来更新组件状态。
接下来,在父组件中使用 useReducer
Hook 来初始化和获取共享数据以及 dispatch 方法:
import React, { useReducer } from "react";
import reducer from "./reducer";
function App() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
function increment() {
dispatch({ type: "INCREMENT" });
}
function decrement() {
dispatch({ type: "DECREMENT" });
}
return (
<div>
<h2>Count:{state.count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default App;
如上述代码所示,通过调用 useReducer
Hook,我们可以获取到当前的状态(state)和 dispatch 方法。其中第一个参数是 reducer 函数,第二个参数是初始状态值。在这里我们使用了 { count: 0 }
作为初始 state 值。
然后在父组件中定义两个函数:increment 和 decrement,它们分别调用 dispatch({ type: "INCREMENT" })
和 dispatch({ type: "DECREMENT" })
来更新共享数据。
最终,在返回的 JSX 中展示当前计数器的值并添加增加/减少按钮来触发对应操作即可。
useCallback
作用
useCallback
它可以帮助我们优化性能并避免不必要的重新渲染。
用法
首先,我们需要理解什么是函数记忆。在 JavaScript 中,如果两个相同参数值之间存在对应关系,则称这些函数有“记忆”。因此,在进行计算时使用已经计算过的结果而不是再次运行该函数以提高效率。
接下来,请看如下代码示例:
import React, { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function handleClick() {
console.log("Clicked!");
setCount(count + 1);
}
return (
<div>
<h2>Count: {count}</h2>
<button onClick={handleClick}>Increment</button>
</div>
);
}
export default App;
上面的代码中展示了一个简单的计数器组件,并且定义了一个名为 handleClick
的事件处理程序方法。当点击按钮时,会触发该方法并更新内部状态以反映当前值。
然而,在每次渲染组件时都会创建新的 handleClick
方法实例可能会导致一定程度上的性能问题。这就是为什么我们需要使用 useCallback
来进行优化。
使用 useCallback
来改写后:
import React, { useState, useCallback } from "react";
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Clicked!");
setCount(count + 1);
}, [count]);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={handleClick}>Increment</button>
</div>
);
}
export default App;
在上面的代码中,我们使用了 useCallback
来缓存 handleClick
方法并根据需要更新它。这样,在组件重新渲染时就不会创建新的实例,从而避免了性能问题。
注意:第二个参数 [count]
是依赖项数组。如果该值发生变化,则会触发回调函数的重新计算和更新处理程序方法。
useMemo
作用
useMemo
,它可以帮助我们优化性能并避免不必要的重新计算。
用法
首先,我们需要理解什么是记忆。在 JavaScript 中,如果两个相同参数值之间存在对应关系,则称这些函数有“记忆”。因此,在进行计算时使用已经计算过的结果而不是再次运行该函数以提高效率。
import React, { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const result = fibonacci(count);
return (
<div>
<h2>Result: {result}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App;
上面的代码中展示了一个简单的斐波那契数列组件,并且定义了一个名为 fibonacci
的递归方法。每当用户点击按钮时都会调用该方法以更新内部状态以反映当前值。
然而,在每次渲染组件时都会重新计算斐波那契数列可能会导致一定程度上的性能问题。这就是为什么我们需要使用 useMemo
来进行优化。
改写后如下所示:
import React, { useState, useMemo } from "react";
function App() {
const [count, setCount] = useState(0);
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const result = useMemo(() => fibonacci(count), [count]);
return (
<div>
<h2>Result: {result}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App;
在上面的代码中,我们使用了 useMemo
来缓存斐波那契数列计算结果并根据需要更新它。这样,在组件重新渲染时就不会重新计算该值,从而避免了性能问题。
注意:第二个参数 [count]
是依赖项数组。如果该值发生变化,则会触发回调函数的重新计算和更新处理程序方法。
useRef
作用
useRef
它可以帮助我们在函数组件中保存可变值,并且不会导致组件重新渲染。
用法
import React, { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert(`You clicked ${count} times`);
}, 3000);
}
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={handleAlertClick}>Show Alert</button>
</div>
);
}
export default App;
上面的代码中展示了一个简单的计数器组件,并且定义了一个名为 handleAlertClick
的事件处理程序方法。当用户点击“Show Alert”按钮时,该方法会等待三秒钟然后显示警告框以反映当前值。
然而,在该三秒钟内如果用户再次点击“Increment”按钮,则计数器将增加并且警告框仍将显示旧的值。这就是因为每次重新渲染组件时都创建了新的 count
变量实例。
改写后如下所示:
import React, { useState, useRef } from "react";
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
function handleAlertClick() {
setTimeout(() => {
alert(`You clicked ${countRef.current} times`);
}, 3000);
}
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={handleAlertClick}>Show Alert</button>
</div>
);
}
export default App;
在上面的代码中,我们使用了 useRef
来保存可变值,并且不会导致组件重新渲染。这样,在每次重新渲染时都可以访问到最新的计数器值。
注意:需要将初始值传递给 useRef
,并通过 .current
属性来读写该值。