学习 Hooks【Plan - June - Week 2】 - 实践

一、React API

React 提供了丰富的核心 API,用于创建组件、管理状态、处理副作用、优化性能等。本文档总结 React 常用的 API 方法和组件。


1. React 核心 API

React.createElement(type, props, …children)

  • 用于创建 React 元素,JSX 会被编译成该函数调用。
  • type:标签名或组件函数
  • props:属性对象
  • children:子节点列表

React.Component

  • React 组件基类,用于创建类组件。
  • 语法:
class MyComponent
extends React.Component {
render() {
return <div>Hello<
  /div>
  ;
  }
  }

React.PureComponent

  • 类组件基类,带浅层比较的性能优化。
  • 如果 props 和 state 没有变化,不会重新渲染。

React.Fragment

  • 用于返回多个子元素的容器,不会产生额外的 DOM 节点。
  • 语法:
<React.Fragment>
  <Child1 />
    <Child2 />
      <
      /React.Fragment>

或者简写为:

<
>
<Child1 />
  <Child2 />
    <
    />

2. React Hooks API

useState(initialState)

  • 声明状态变量,返回 [state, setState]

useEffect(effect, deps)

  • 用于副作用操作,类似生命周期。
  • deps 数组决定何时重新执行副作用。

useContext(Context)

  • 订阅 React Context。

useReducer(reducer, initialState)

  • 管理复杂状态,类似 Redux。

useMemo(factory, deps)

  • 缓存计算结果,避免重复计算。

useCallback(callback, deps)

  • 缓存函数引用,避免子组件不必要渲染。

useRef(initialValue)

  • 创建可变引用对象,常用于获取 DOM 或存储变量。

useImperativeHandle(ref, createHandle, deps)

  • 自定义暴露给父组件的实例值。

useLayoutEffect(effect, deps)

  • 与 useEffect 类似,但在 DOM 变更后同步触发。

3. 辅助工具函数

React.cloneElement(element, [props], […children])

  • 克隆并修改已有 React 元素。

React.isValidElement(object)

  • 判断对象是否是有效的 React 元素。

React.Children

  • 工具对象,操作 props.children
    • React.Children.map
    • React.Children.forEach
    • React.Children.count
    • React.Children.only
    • React.Children.toArray

4. 其他重要 API

React.createContext(defaultValue)

  • 创建上下文(Context)。

React.forwardRef(renderFunction)

  • 转发 ref,允许父组件访问子组件 DOM。

React.memo(Component, [areEqual])

  • 组件的性能优化,高阶组件,类似 PureComponent。

React.lazy(factory)

  • 支持代码分割,懒加载组件。

React.Suspense

  • 配合 React.lazy 使用,显示加载状态。

二、useEffect

useEffect 是 React 中管理副作用(side effects)的 Hook。副作用包括数据获取、订阅、手动 DOM 操作等。


1. 基础用法

import React, { useEffect, useState
} from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() =>
{
// 每次渲染后执行副作用
document.title = `你点击了 ${count
}`;
});
return (
<div>
  <p>你点击了 {count
    }<
    /p>
    <button onClick={
    () =>
    setCount(count + 1)
    }>
    点击我
    <
    /button>
    <
    /div>
    );
    }

2. 参数说明

useEffect(effect: () =>
(void | (() =>
void)), deps?: DependencyList)
  • effect:副作用函数,函数内部可以执行副作用代码,且可返回清理函数。
  • deps:依赖数组(可选),用于控制副作用执行的时机。

3. 依赖数组

  • 无依赖数组:副作用在每次组件渲染后执行。
  • 空依赖数组 ([]):副作用只在组件挂载(Mount)和卸载(Unmount)时执行一次。
  • 有依赖项数组:只有当依赖项发生变化时,副作用才执行。

示例:

useEffect(() =>
{
console.log('只在挂载和卸载时运行');
}, []);
useEffect(() =>
{
console.log('count 发生变化时运行');
}, [count]);

4. 清理副作用

  • effect 函数可以返回一个清理函数,在组件卸载或依赖更新前调用。
useEffect(() =>
{
const id = setInterval(() =>
{
console.log('定时器运行中');
}, 1000);
// 返回清理函数,清除定时器
return () =>
clearInterval(id);
}, []);

5. 副作用示例

  • 数据请求(fetch)
  • 事件监听和解绑
  • 订阅和取消订阅
  • 手动 DOM 操作
  • 设置定时器

6. 注意事项

  • 避免无限循环:确保依赖数组正确,防止副作用无限触发。
  • 同步 vs 异步useEffect 不支持直接声明为 async 函数,但可在内部调用异步函数。

示例:

useEffect(() =>
{
async function fetchData() {
const res = await fetch('/api/data');
const data = await res.json();
// 处理数据
}
fetchData();
}, []);

7. useEffect 与生命周期对应关系

生命周期方法useEffect 变体
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {})
componentWillUnmountuseEffect(() => { return () => {} }, [])

8. React 18+ 特别说明

React 18 开启严格模式下,useEffect 会在开发环境中执行两次以帮助发现副作用问题。

9. 示例完整代码

import React, { useState, useEffect
} from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() =>
{
const timerId = setInterval(() =>
{
setCount(c => c + 1);
}, 1000);
return () =>
clearInterval(timerId);
}, []);
return <h1>计时器:{count
  }<
  /h1>
  ;
  }

三、useContext

useContext 用于在函数组件中访问 React Context 的值,简化了跨组件传递数据的过程。


1. 什么是 Context?

  • Context 提供了一种在组件树中传递数据的方法,无需通过每一级组件的 props。
  • 适用于主题、语言、认证信息等全局数据。

2. useContext 的用法

const value = useContext(MyContext);
  • MyContext 是通过 React.createContext 创建的 Context 对象。
  • useContext 返回当前 Context 的值,即最近的 <MyContext.Provider> 所提供的值。
  • 当 Provider 的 value 改变时,组件会重新渲染。

3. 创建 Context 示例

import React, { createContext, useContext
} from 'react';
// 创建 Context
const ThemeContext = createContext('light');
function Toolbar() {
return (
<div>
  <ThemedButton />
    <
    /div>
    );
    }
    function ThemedButton() {
    // 读取 Context 值
    const theme = useContext(ThemeContext);
    return <button style={
    {
    background: theme === 'dark' ? '#333' : '#ccc'
    }
    }>按钮<
    /button>
    ;
    }
    function App() {
    return (
    // 提供 Context 值
    <ThemeContext.Provider value="dark">
      <Toolbar />
        <
        /ThemeContext.Provider>
        );
        }

4. 注意事项

  • 组件必须在对应 Context 的 Provider 内部,否则使用默认值。
  • 只有当 Context 的值发生变化时,使用该 Context 的组件才会重新渲染。
  • 不要在组件内直接修改 Context 的值,应通过 Provider 传递新的值。

5. 常见使用场景

  • 主题切换(light/dark)
  • 用户认证信息
  • 国际化语言设置
  • 全局配置参数

6. 组合多个 Context

  • 可以多次调用 useContext 读取多个不同的 Context。
const theme = useContext(ThemeContext);
const user = useContext(UserContext);

7. 对比 Context.Consumer

  • useContext 更简洁,适用于函数组件。
  • 类组件中仍可用 <Context.Consumer> 读取 Context。

四、用 Reducer 和 Context 扩展状态管理

当应用变复杂,单纯用 useState 管理状态变得笨重。此时可以使用 useReducer 管理复杂状态逻辑,结合 Context 实现跨组件共享状态,替代 Redux 等库。


1. 为什么用 Reducer?

  • 多个状态相互关联,修改逻辑复杂
  • 需要明确状态变化的过程和原因(动作)
  • 状态逻辑集中,更易维护和测试

2. useReducer 简介

const [state, dispatch] = useReducer(reducer, initialState);
  • state:当前状态
  • dispatch:派发动作(action)
  • reducer:状态变更函数 (state, action) => newState

示例:计数器

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();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {
count: 0
});
return (
<
>
Count: {state.count
}
<button onClick={
() =>
dispatch({
type: 'increment'
})
}>
+1<
/button>
<button onClick={
() =>
dispatch({
type: 'decrement'
})
}>
-1<
/button>
<
/>
);
}

3. 结合 Context 实现跨组件状态共享

  • statedispatch 放入 Context,供多个组件访问。
  • 这样,父组件可以集中管理状态,子组件通过 useContext 访问和派发动作。
const CountContext = React.createContext();
function CounterProvider({ children
}) {
const [state, dispatch] = useReducer(reducer, {
count: 0
});
return (
<CountContext.Provider value={
{ state, dispatch
}
}>
{children
}
<
/CountContext.Provider>
);
}
function CounterDisplay() {
const { state
} = React.useContext(CountContext);
return <div>Count: {state.count
  }<
  /div>
  ;
  }
  function CounterButtons() {
  const { dispatch
  } = React.useContext(CountContext);
  return (
  <
  >
  <button onClick={
  () =>
  dispatch({
  type: 'increment'
  })
  }>
  +1<
  /button>
  <button onClick={
  () =>
  dispatch({
  type: 'decrement'
  })
  }>
  -1<
  /button>
  <
  />
  );
  }

4. 应用结构示例

function App() {
return (
<CounterProvider>
  <CounterDisplay />
    <CounterButtons />
      <
      /CounterProvider>
      );
      }

5. 优缺点

优点缺点
明确的状态管理流程和结构代码稍显复杂,学习曲线陡峭
集中管理状态,方便维护与测试过度使用可能导致冗余
方便实现复杂状态变化和回退等功能状态共享时 Context 过度更新会导致性能问题

6. 最佳实践

  • 只对需要共享的状态使用 Context 和 Reducer
  • 将逻辑拆分成多个 reducer 和 Context(模块化)
  • 使用 React.memo、useMemo 优化性能
  • 明确 Action 类型和 Payload,写清楚 Reducer 逻辑

以下是 React 官方文档 《井字棋教程 (Tic Tac Toe Tutorial)》 的详细 Markdown 学习笔记整理:


五、React 井字棋教程

通过构建一个经典的井字棋游戏,学习 React 基础知识,包括组件设计、状态管理、事件处理、以及提升组件能力。


1. 项目介绍

  • 井字棋是一个简单的 3 x 3 网格游戏,两名玩家轮流放置 X 和 O。
  • 目标是先在水平、垂直或对角线上连成一条线。
  • 教程通过这个项目介绍 React 的核心概念。

2. 构建游戏的步骤

  1. 创建一个 Square 组件,表示棋盘中的一个格子。
  2. 创建一个 Board 组件,包含 9 个 Square
  3. 维护棋盘状态,响应用户点击。
  4. 判断胜负逻辑。
  5. 添加游戏历史记录,实现时间旅行功能。

3. 组件设计

Square 组件

  • 功能:显示一个按钮,表示一个格子。
  • 接收 props:value (X, O 或 null),onClick 点击事件处理。
function Square({ value, onClick
}) {
return (
<button className="square" onClick={onClick
}>
{value
}
<
/button>
);
}

Board 组件

  • 维护 9 个格子的状态。
  • 渲染 9 个 Square,传入对应的 valueonClick
function Board() {
const [squares, setSquares] = React.useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice();
nextSquares[i] = 'X';
setSquares(nextSquares);
}
return (
<div>
  <div className="board-row">
    <Square value={squares[0]
    } onClick={
    () =>
    handleClick(0)
    } />
    {
    /* 其他格子 */
    }
    <
    /div>
    {
    /* 其他行 */
    }
    <
    /div>
    );
    }

4. 状态提升(Lifting State Up)

  • 当需要多个组件共享状态时,将状态提升到它们最近的共同父组件。
  • 在教程中,Board 组件的状态被提升到 Game 组件管理。
  • Game 组件管理历史状态,实现回退功能。

5. 计算游戏胜负

  • 实现一个函数 calculateWinner(squares),判断当前棋盘是否有玩家获胜。
  • 如果获胜,显示胜者信息,游戏结束。
function calculateWinner(squares) {
const lines = [
[0,1,2], [3,4,5], [6,7,8],
[0,3,6], [1,4,7], [2,5,8],
[0,4,8], [2,4,6]
];
for (let line of lines) {
const [a,b,c] = line;
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}

6. 处理用户交互

  • 点击格子时更新状态,切换玩家。
  • 禁止点击已被占用的格子。
  • 游戏结束后禁止继续点击。

7. 时间旅行(历史记录)

  • Game 组件维护棋盘的历史数组。
  • 用户可点击历史按钮,回到之前的任一步骤。
  • 利用数组和状态管理,实现“时间旅行”效果。
function Game() {
const [history, setHistory] = React.useState([Array(9).fill(null)]);
const [stepNumber, setStepNumber] = React.useState(0);
const [xIsNext, setXIsNext] = React.useState(true);
function handleClick(i) {
const historyUpToStep = history.slice(0, stepNumber + 1);
const current = historyUpToStep[historyUpToStep.length - 1];
const squares = current.slice();
if (calculateWinner(squares) || squares[i]) return;
squares[i] = xIsNext ? 'X' : 'O';
setHistory([...historyUpToStep, squares]);
setStepNumber(historyUpToStep.length);
setXIsNext(!xIsNext);
}
function jumpTo(step) {
setStepNumber(step);
setXIsNext((step % 2) === 0);
}
// 渲染历史按钮,调用 jumpTo
}

学习资料来源

React 参考
useEffect
useContext
使用 Reducer 和 Context
教程:井字棋游戏

posted @ 2025-07-14 20:27  yjbjingcha  阅读(5)  评论(0)    收藏  举报