一、React 创建组件
在 React 中,组件是构建用户界面的基本单元。主要有两种创建组件的方式:函数式组件function和类组件class。
在hooks出现之前,函数式组件是无状态的,无法管理组件的状态。而类组件可以通过this.state和this.setState来管理状态。使用hooks后,函数式组件也可以管理状态。
函数式组件语法简洁,易于开发、理解和测试,适合简单的 UI 组件。类组件适合需要复杂状态管理和生命周期方法的场景,但由于大量使用this,可能会让人感到困惑。随着hooks的引入,函数式组件的功能和灵活性大大增强,成为现代 React 开发的主流选择。
二、hooks(钩子函数)
由 React 官方封装好的一系列函数,它们的用法和作用:
- useState —— 定义一个响应式变量,提供专门的方法修改该变量值。
import { useState } from "react";
function One(){
let [num, setNum] = useState(1); // [1, func]
// 响应式方法监听住这个 num,num 变化了,视图也会跟着变化
function handle(num){
// num++; // num 改了但是无法触发视图更新
setNum(num + 1); // 负责将 num 修改为 参数值,并触发视图更新
console.log(num);
}
return (
<div>
<button onClick={() => {handle(num)}}>{num}</button>
</div>
)
}
export default One;
2. useEffect —— 副作用函数,作为异步函数来执行。
- 组件每次加载(挂载)就会触发;
import { useState, useEffect } from "react";
// 模拟一个接口请求函数
async function queryData() {
const data = await new Promise((resolve)=>{
setTimeout(() => {
resolve(666);
}, 2000);
})
return data
}
function One(){
let [num, setNum] = useState(1); // [1, func]
// useState 不支持异步代码
// 异步代码可以使用 useEffect 来实现
useEffect(()=>{
console.log('useEffect');
queryData().then((data)=>{
setNum(data);
})
})
return (
<div>
<button onClick={() => {}}>{num}</button>
</div>
)
}
export default One;
- useEffect第二个参数为一个空数组时,只会在初次渲染(挂载)时触发;
useEffect(()=>{
console.log('useEffect');
queryData().then((data)=>{
setNum(data);
})
},[])
- useEffect第二个参数为一个数组时,数组中传入一个变量时,该变量每次修改值都会带来useEffect的重新执行;
import { useState, useEffect } from "react";
// 模拟一个接口请求函数
async function queryData() {
const data = await new Promise((resolve)=>{
setTimeout(() => {
resolve(666);
}, 2000);
})
return data
}
function One(){
let [num, setNum] = useState(1); // [1, func]
let [age, setAge] = useState(18);// [18, func]
useEffect(()=>{
console.log('useEffect');
queryData().then((data)=>{
setNum(data);
})
},[age])
return (
<div>
<button onClick={() => {}}>{num}</button>
<h2 onClick={() => {setAge(age + 1)}}>{age}</h2>
</div>
)
}
export default One;
- useEffect第一个参数是函数,该函数内部返回出来一个新的函数,新函数会在组件不展示(卸载) 时才触发。
import { useState, useEffect } from "react";
function One(){
let [num, setNum] = useState(1); // [1, func]
useEffect(() => {
console.log('useEffect');
let timer = setInterval(() => {
console.log(num);
}, 1000); // setInterval()按照指定的时间间隔重复调用函数或执行代码片段
return () => {
console.log('组件卸载');
clearInterval(timer); // 组件卸载时停止定时器
}
}, [num])
return (
<div>
<button onClick={(pre) => {setNum(pre + 1)}}>{num}</button>
</div>
)
}
export default One;
3. useLayoutEffect ——作为同步函数来执行。
| 维度 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | 浏览器绘制(paint)之后,异步调度 | DOM 更新完成后、浏览器绘制之前,同步执行 |
| 是否阻塞渲染 | 否,不阻塞页面绘制 | 是,会阻塞页面绘制直到回调结束 |
| 典型用途 | 数据获取、订阅、日志、事件绑定等大多数副作用 | 需要同步读取/修改 DOM 布局的场景:测量元素尺寸、位置,避免闪烁 |
| 性能影响 | 影响小,可放心使用 | 可能因阻塞渲染而降低帧率,需谨慎 |
| 服务端渲染 | 可正常用 | SSR 下会报警告,通常需改成只在客户端执行 |
import { useLayoutEffect, useState } from "react"
function App(){
const [num, setNum] = useState(1)
// 当 num 值变更时,浏览器的那个 useLayoutEfect 中的 effect 函数执行完毕再渲染
// effect 会阻塞很久,超过 500ms 会导致掉帧
useLayoutEffect(() => {
console.log('useLayoutEffect');
setNum(2);
}, [num])
return(
<div>
<button onClick={() => setNum(num + 1)}>{num}</button>
</div>
)
}
export default App;
- useReducer —— 当修改 state 的逻辑比较复杂时,用useReducer。
- 传入的
reducer函数中不能直接修改原state,必须要返回一个新对象。 - useReducer + immer 常常搭配使用。
import { useState, useReducer } from "react"
import { produce } from "immer"
function reducer(state, action){
switch(action.type){
case 'add':
// return{
// result: state.result + action.num
// }
return produce(state, (state) => {
state.a.b.c += action.num
})
case 'minus':
return{
result: state.result - action.num
}
}
}
function App() {
// userReducer 接收的第二个参数 作为 reducer 的第一个参数
// dispatch 接收的参数 作为 reducer 的第二个参数
const[res, dispatch] = useReducer(reducer, {result: 0, a: {b: {c: 1, d: {e: 2}}}})
// const[res, dispatch] = useReducer(reducer, {result: 0})
const[num, setState] = useState({result: 0});
return (
<div>
<h3>{res.a.b.c}</h3>
{/* <h3>{res.result}</h3> */}
<button onClick={() => dispatch({type: 'add', num: 2})}>+</button>
<button onClick={() => dispatch({type: 'minus', num: 1})}>-</button>
<h2>{num.result}</h2>
<button onClick={() => setState({result: num.result + 2})}>+</button>
</div>
)
}
export default App
5. useRef —— 获取 DOM 结构。
import { useRef, useEffect } from 'react'
function App() {
const ipt = useRef(null) // 创建一个 ref 对象 ipt,初始值为 null。
useEffect(() => {
// console.log(ipt.current); // 现在可以拿到 <input> 组件的引用
ipt.current.focus() // // 调用原生 DOM 方法:让输入框自动获得焦点
}, [])
return(
<div>
<input type="text" ref={ipt}/>
{/* 将 ipt 这个 ref 绑定到 <input> 组件上。 */}
{/* 组件挂载时,useEffect 会执行,ipt.current 指向 <input> 组件。 */}
</div>
)
}
export default App;
6. useContext 跨多层组件进行数据传递。实现 跨组件树的数据共享与实时更新。
| 作用点 | 说明 |
|---|---|
| 跨层级共享数据 | 避免 “props drilling”(一级一级手动传参)。父组件只要在最外层包一个 <Provider>,任何后代组件(无论多深)都能直接拿到数据。 |
| 精确更新 | 只有真正使用 useContext 的组件会在值变化时重新渲染;中间没用到 Context 的组件不会白白更新。 |
| 逻辑解耦 | UI 组件不再需要显式接收某些仅用于向下传递的 props,使组件树更扁平、更易重构。 |
import { createContext, useContext } from "react"
function Child2(){
const count = useContext(numContext)
// 只要离我最近的那个 <numContext.Provider> 的 value 发生变化,就立刻把我重新渲染,并把最新的值赋给 count。
// 成员收消息
return(
<div>
<h3>孙子组件 -- {count}</h3>
</div>
)
}
function Child1(){
const count = useContext(numContext)
// 成员收消息
return(
<div>
<h2>子组件 -- {count}</h2>
<Child2/>
</div>
)
}
const numContext = createContext() // 建群
function App() {
const num = 100
return(
<div>
<numContext.Provider value={num}>
{/* 群主发消息 */}
<h1>父组件</h1>
<Child1/>
</numContext.Provider>
</div>
)
}
export default App;