Hooks的概念
Hooks是React 16.8引入的特性,允许在函数组件中使用状态(state)和其他React特性(如生命周期、上下文等),而无需编写class组件。Hooks的目的是解决函数组件功能不足的问题,同时简化代码逻辑,提高复用性。
在Hooks之前,函数组件被称为“无状态组件”,仅能接收props并返回UI,无法管理内部的状态、使用生命周期方法或访问React的高级功能(如ref、上下文等)。复杂逻辑必须依赖class组件实现,导致代码逻辑分散。
以下我通过一个例子让大家了解hooks出来前的class组件和函数组件
clss组件:
import { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
const { count } = this.state;
return (
<>
<div>{count}</div>
<button onClick={() => {
this.setState(prevState => ({
count: prevState.count + 1
}));
}}>点击</button>
</>
);
}
}
export default App;
函数组件:
function App() {
let count = 0;
return (
<>
<div>{count}</div>
<button type="button" onClick={() => {
count ++;
console.log(count);
}}>+1</button>
</>
)
}
export default App;
打印结果:
不管是class组件还是函数组件,它们实现的都是点击按钮,数据+1,但是为什么使用函数组件他控制台打印的数据自增了,但页面上面还是0。
这是因为声明的不是一个状态,只是普通的变量,在react中状态发生改变,组件会重新渲染,那如何在函数组件中声明一个状态,接下来到主题了。
常用Hooks及其作用
useState
允许函数组件管理内部状态。
需要传入一个初始值,它返回一个数组,数组中有两个元素,第一个是当前的值,第二个是一个函数,用来设置这个值的。
修改刚刚的例子:
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>{count}</div>
<button type="button" onClick={() => {
setCount(count+1)
}}>+1</button>
</>
)
}
这时候count就是一个状态了,然后使用setCount这个函数来改变count的值。可以看到使用函数组件+hooks使得代码更加简洁和明了。
useEffect
在官网的解释中它允许你将组件与外部系统同步,例如,你可能希望根据 React state 控制非 React 组件、建立服务器连接或当组件在页面显示时发送分析日志。Effect 允许你在渲染结束后执行一些代码,以便将组件与 React 外部的某个系统相同步。
要编写一个Effect,请遵循以下三个步骤:
1.声明Effect。通常Effect会在每次提交后运行。
2.指定Effect依赖。大多数Effect需要按需运行,而不是每次渲染后都运行。
3.必要时添加清理操作。一些Effect需要指定如何停止、撤销,或者清除它们所执行的操作。
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0)
useEffect(()=>{
// 每次渲染后都会执行此处代码
console.log('hello world');
})
return (
<>
<div>{count}</div>
<button type="button" onClick={() => {
setCount(count+1)
}}>+1</button>
</>
)
}
export default App;
每点击按钮一次,useEffect都会被调用,也就是说每当你的组件渲染时,React会先更新页面,然后再运行useEffect中的代码。换句话说,useEffect会“延迟”一段代码的运行,直到渲染结果反馈在页面上。
但是有时候不希望每次组件渲染都运行useEffect中的代码都会执行,就比如以下代码:
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0)
const [text, setText] = useState('')
useEffect(() => {
// 每次渲染后都会执行此处代码
console.log('hello world');
})
return (
<>
<input type="text" value={text} onChange={e => {setText(e.target.value)}}/>
<div>{count}</div>
<button type="button" onClick={() => {
setCount(count + 1)
}}>+1</button>
</>
)
}
export default App;
在上述代码中,不管是在输入框中输入内容还是点击按钮,组件都会渲染,useEffect代码也会执行,但有时候这不是我们期望的结果,就比如我只希望点击按钮时才会执行useEffect中的代码,则需要给useEffect添加依赖项。
通常在调用useEffect时指定一个依赖项作为第二个参数,可以让React跳过不必要地重新运行Effect
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0)
const [text, setText] = useState('')
useEffect(() => {
// 每次渲染后都会执行此处代码
console.log('hello world');
}, [count]) // 这里的代码不但会在组件挂载时运行,而且当count的值自上次渲染后发生变化后也会运行
return (
<>
<input type="text" value={text} onChange={e => { setText(e.target.value) }} />
<div>{count}</div>
<button type="button" onClick={() => {
setCount(count + 1)
}}>+1</button>
</>
)
}
export default App;
这样除了useEffect中的代码除了挂载时运行,而且在count的值发生变化后也会运行。现在在表单中输入内容后不会运行了。
当然也可以不传依赖项,只写一个空数组,这样代码只会在组件挂载时运行。
useEffect(() => {
// 这里的代码会在每次渲染后运行
});
useEffect(() => {
// 这里的代码只会在组件挂载(首次出现)时运行
}, []);
useEffect(() => {
// 这里的代码不但会在组件挂载时运行,而且当 a 或 b 的值自上次渲染后发生变化后也会运行
}, [a, b]);
到这里其实大家都有一个疑问,为什么Effect在挂载时运行了两次,这是因为在开发环境下,如果开启严格模式,React会在实际运行setup之前额外运行一次setup和cleanup。如果只需要调用一次,则关闭严格模式即可。
// import { StrictMode } from 'react' // 严格模式
import { createRoot } from 'react-dom/client'
import App from './components/text'
createRoot(document.getElementById('root')).render(
// <StrictMode>
<App></App>
// </StrictMode>,
)
useContext
在学习useContext之前先来写一个组件数据共享的例子,假如需要将父组件中的状态传给子组件,需要在父组件中的子组件标签中添加两个属性,一个是传递状态,另外一个是传递改变状态的方法,在子组件通过参数props接收传递过来的状态和方法,如下收拾:
根组件:
import { useState } from "react"
import Parent from "./Parent"
function MyApp() {
const [count, setCount] = useState(0)
return (
<>
<h1>根组件 -{count}</h1>
<Parent count={count} setCount={setCount}></Parent>
</>
)
}
export default MyApp
父组件:
import Child from "./Child"
function Parent(props) {
// eslint-disable-next-line react/prop-types
const { count, setCount } = props
return (
<>
<h2>父组件 -{count}</h2>
<Child count={count} setCount={setCount}></Child>
</>
)
}
export default Parent
子组件:
function Child(props) {
// eslint-disable-next-line react/prop-types
const { count, setCount } = props
return (
<>
<h3>子组件 -{count}</h3>
<button type="button" onClick={() => { setCount(count + 1) }}>+1</button>
</>
)
}
export default Child
效果展示:
当点击按钮后可以看到每个组件中的count状态都发生了改变,说明我在多个组件中确实是共享了数据,但是这种方式有些麻烦,假设底下还有组件,不可能接着往下传,跟套娃一样,不仅仅代码量比较大,更难以维护,因此react官方推出了useContext这个hooks,下面就来使用useContext同样来实现这个数据共享的能力。
首先,useCountext的意思的使用上下文,在使用上下文之前需要创建上下文,使用到了createContext来创建上下文。
创建Context实例
新建一个单独的文件用于创建Context实例,并导出该实例:
import { createContext } from "react";
// 创建环境对象
const MyContext = createContext()
export default MyContext
在根组件中提供Context值
在应用的根组件中使用Provider传递需要共享的状态:
import { useState } from "react"
import MyContext from "./MyContext"
import Parent from "./Parent"
function MyApp() {
const [count, setCount] = useState(0)
return (
<>
{/* value里面传的值,在里面的子组件以及子组件的子组件都能获取到 */}
<MyContext.Provider value={{ count, setCount }}>
<h1>根组件 -{count}</h1>
<Parent count={count} setCount={setCount}></Parent>
</MyContext.Provider>
</>
)
}
export default MyApp
在子组件中消费Context
任何层级的子组件都可以直接使用useContext获取共享状态:
import { useContext } from "react"
import MyContext from "./MyContext"
function Child() {
const { count, setCount } = useContext(MyContext)
return (
<>
<h3>子组件 -{count}</h3>
<button type="button" onClick={() => { setCount(count + 1) }}>+1</button>
</>
)
}
export default Child
这种模式避免了组件props的层层传递,使状态管理更加简洁高效。Context的Provider和Consumer机制实现了跨组件层级的数据共享。
结语:
React Hooks 提供的高效开发方式已成为现代前端开发的核心工具。本次重点介绍的 Hooks 功能虽然只是整体的一部分,但它们在实际项目中应用频率最高,能够显著提升代码的可维护性和开发效率。
掌握这些常用 Hooks 后,可以逐步探索更高级的用法,进一步优化组件逻辑和状态管理。熟练运用这些基础功能将为后续深入 React 生态打下坚实基础。