React JSX与函数组件Hooks

一、组件(Component)

组件是 React 应用的构建块,每个组件代表 UI 的一部分,可以独立开发、测试和复用。

1. 函数组件(Function Component)

  • 定义:使用 JavaScript 函数定义的组件。

  • 特点:轻量、简洁,配合 Hooks 实现状态管理。

  • 示例

    function Greeting(props) {
      return <h1>Hello, {props.name}</h1>;
    }

2. 类组件(Class Component)

  • 定义:使用 ES6 类继承 React.Component 的组件。

  • 特点:支持生命周期方法和 this.state(已逐渐被函数组件 + Hooks 取代)。

  • 示例

    class Greeting extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }

3. 组件核心原则

  • 可组合性:组件可以嵌套其他组件,形成组件树。

  • 单一职责:每个组件专注于一个功能。

  • 复用性:通过 Props 定制组件行为。


二、JSX(JavaScript XML)

JSX 是 React 的核心语法扩展,允许在 JavaScript 中编写类似 HTML 的结构。

1. JSX 基础

  • 语法:类似 HTML,但本质是 JavaScript。

  • 编译过程:Babel 将 JSX 转换为 React.createElement()

    // JSX 代码
    const element = <div className="app">Hello React</div>;
    
    // 编译后的 JavaScript
    const element = React.createElement("div", { className: "app" }, "Hello React");

2. JSX 规则

  • 必须闭合标签:如 <img />

  • 使用 className 代替 class:避免与 JavaScript 关键字冲突。

  • 表达式插值:用 {} 包裹 JavaScript 表达式。

    const name = "Alice";
    const element = <p>{name} is learning React.</p>;

3. JSX 与组件结合

  • 渲染组件:将组件当作标签使用。

    function App() {
      return <Greeting name="Bob" />;
    }

三、状态(State)

状态是组件内部管理的可变数据,驱动 UI 更新。

1. 使用 useState Hook(函数组件)

  • 定义:通过 useState 定义状态变量和更新函数。

  • 示例

    import { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0); // 初始值为 0
    
      return (
        <div>
          <button onClick={() => setCount(count + 1)}>+1</button>
          <p>Count: {count}</p>
        </div>
      );
    }

2. 类组件的 State(传统方式)

  • 初始化:通过 this.state 定义。

  • 更新:通过 this.setState() 方法。

    class Counter extends React.Component {
      constructor(props) {
        super(props);
        this.state = { count: 0 };
      }
    
      render() {
        return (
          <div>
            <button onClick={() => this.setState({ count: this.state.count + 1 })}>
              +1
            </button>
            <p>Count: {this.state.count}</p>
          </div>
        );
      }
    }

3. State 注意事项

  • 不可直接修改:必须通过 setState 或状态更新函数。

  • 异步更新:多个 setState 调用可能被合并。

  • 状态提升:如果多个组件需要共享状态,将状态提升到共同父组件。

4. 常见问题与解决方案

1. 为什么组件不更新?

  • 原因:可能直接修改了 State(如 state.count = 5)。

  • 解决:始终使用 setState 或状态更新函数。

2. 如何避免不必要的渲染?

  • 方法:使用 React.memo(函数组件)或 shouldComponentUpdate(类组件)。

3. Props 如何传递复杂对象?

  • 示例

    <UserProfile user={{ name: "Alice", age: 30 }} />

四、核心 Hooks

1. useState:状态管理

  • 作用:为函数组件添加局部状态。

  • 语法

    const [state, setState] = useState(initialState);
  • 示例

    function Counter() {
      const [count, setCount] = useState(0);
      return (
        <button onClick={() => setCount(count + 1)}>
          Clicked {count} times
        </button>
      );
    }
  • 注意

    • 状态更新是异步的,且会触发组件重新渲染。

    • 若新状态依赖旧状态,使用函数式更新:setCount(prev => prev + 1)

    • 复杂对象状态应确保不可变性(如使用扩展运算符或 immer 库)。

  • 闭包陷阱(异步更新问题)

    // 直接使用 count 值
    const fn1 = () => {
      setCount(count + 1);
      setCount(count + 1); // 两次更新都基于同一个旧值
    }
    
    // 使用函数式更新
    const fn2 = () => {
      setCount(prev => prev + 1);
      setCount(prev => prev + 1); // 每次更新都基于最新状态
    }
  • 直接更新:同步代码中连续调用时,所有更新都基于同一个旧的 count 值(闭包陷阱)

  • 函数式更新:确保每次更新都基于最新的状态值


2. useEffect:处理副作用

useEffect 是专门用于处理副作用(Side Effects)的 Hook。它通过将副作用逻辑与渲染逻辑分离,保证组件的纯函数特性

①为什么不能在渲染函数中直接执行副作用?

纯函数(Pure Function)

定义:纯函数是满足以下两个条件的函数:

  • 相同的输入永远返回相同的输出(确定性)

  • 不会修改外部状态或产生副作用(无污染)

核心特征

  • 不依赖外部变量(只使用自己的参数)

  • 不改变外部变量(如全局变量、参数对象等)

  • 不执行任何 I/O 操作(如网络请求、文件读写)

// ✅ 纯函数示例
function add(a, b) {
  return a + b; // 只依赖参数,不修改外部状态
}

const result = add(2, 3); // 永远是 5

// ✅ 另一个纯函数
function toUpperCase(str) {
  return str.toUpperCase(); // 原始字符串未被修改
}

const text = toUpperCase("hello"); // 永远是 "HELLO"



// 非纯函数反例

// ❌ 非纯函数:依赖外部变量
let base = 10;
function impureAdd(a) {
  return base + a; // 结果受外部变量 base 影响
}

// ❌ 非纯函数:修改外部状态
function changeColor(element) {
  element.style.color = 'red'; // 修改了 DOM(外部状态)
}

// ❌ 非纯函数:产生副作用(控制台输出)
function logSum(a, b) {
  console.log(a + b); // 产生 I/O 操作
  return a + b;
}

副作用(Side Effect)

定义:任何与函数外部世界发生交互的操作,包括:

  • 修改全局变量

  • 操作 DOM

  • 发送 HTTP 请求

  • 读写文件或数据库

  • 调用有副作用的函数(如 console.log

React 组件的渲染函数(如函数组件的函数体)必须是纯函数。如果在渲染函数中直接执行副作用:

function Component() {
  // ❌ 直接在渲染函数中触发副作用
  fetchData(); // 网络请求
  document.title = "Updated"; // 修改 DOM
  return <div>...</div>;
}

会导致:

  • 无限循环:如果副作用触发了状态更新(如 setState),组件会重新渲染 → 再次执行副作用 → 再次更新状态 → 循环。

  • 不可预测的 UI:副作用可能破坏 React 的渲染时序(如异步操作未完成时 UI 已渲染)。


②useEffect 的核心作用

useEffect 将副作用逻辑延迟到渲染完成后执行,确保渲染过程的纯粹性。它的基本结构为:

useEffect(() => {
  // 副作用逻辑
  return () => { /* 清理逻辑 */ }; // 可选
}, [dependencies]); // 依赖项数组(可选)
执行时机:
依赖项数组执行时机
useEffect(() => {})每次渲染后都执行(类似 componentDidMount + componentDidUpdate
useEffect(() => {}, [])仅首次渲染后执行(类似 componentDidMount
useEffect(() => {}, [dep])依赖项变化时执行(类似 componentDidUpdate

常见副作用场景
  1. 修改 DOM

    // React 组件中的副作用(通常在 useEffect 中处理)
    function Button() {
      const handleClick = () => {
        document.title = "Clicked"; // 副作用:修改页面标题
      };
      return <button onClick={handleClick}>Click</button>;
    }
  2. 数据请求

    // 在 useEffect 中发起网络请求
    useEffect(() => {
      fetch('/api/data') // 副作用:网络请求
        .then(res => res.json());
    }, []);
  3. 定时器操作

    useEffect(() => {
      const timer = setInterval(() => {
        console.log("Running..."); // 副作用:持续输出
      }, 1000);
      return () => clearInterval(timer); // 清理副作用
    }, []);

useEffect 的最佳实践

1. 始终处理清理逻辑

如果副作用需要清理(如订阅、计时器、DOM 事件),在 return 函数中执行:

useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer); // 清理计时器
}, []);

2. 精确控制依赖项

  • 依赖项数组决定了何时重新执行副作用。

  • 如果省略依赖项,可能导致访问过时的闭包变量

    const [count, setCount] = useState(0);
    
    useEffect(() => {
      const timer = setInterval(() => {
        // ❌ 由于依赖项为空,count 始终是初始值 0
        setCount(count + 1)
      }, 1000);
      return () => clearInterval(timer);
    }, []); // 错误写法
    
    // ✅ 正确写法:将 count 加入依赖项
    useEffect(() => {
      const timer = setInterval(() => {
        setCount(cnt => cnt + 1) // 使用函数式更新
      }, 1000);
      return () => clearInterval(timer);
    }, [count]);

3. 避免无限循环

如果副作用中更新了依赖项,确保依赖项变化是可控的:

const [data, setData] = useState([]);

// ❌ 无限循环:data 变化 → 触发 useEffect → 更新 data → 再次触发...
useEffect(() => {
  setData([...data, newItem]);
}, [data]);

// ✅ 使用函数式更新避免依赖 data
useEffect(() => {
  setData((prevData) => [...prevData, newItem]);
}, []);

⑤总结

useEffect 的用途可以归纳为:

  1. 隔离副作用:保证渲染函数的纯粹性。

  2. 生命周期管理:替代类组件的 componentDidMountcomponentDidUpdatecomponentWillUnmount

  3. 依赖驱动执行:通过依赖项数组精确控制副作用触发时机。

React 中的核心规则:

  1. 渲染阶段必须保持纯净
    React 要求组件在渲染时:

    • 不直接修改 DOM(渲染函数应该是纯的)

    • 不执行网络请求

    • 不访问全局变量

  2. 副作用必须隔离
    所有副作用应放在:

    • useEffect 钩子中

    • 事件处理函数中(如 onClick


3. useContext:访问上下文

  • 作用:读取 React Context 的值,避免逐层传递 Props。

  • 示例

    const ThemeContext = createContext('light');
    
    function App() {
      return (
        <ThemeContext.Provider value="dark">
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    
    function Toolbar() {
      const theme = useContext(ThemeContext);
      return <button>Theme: {theme}</button>;
    }

五、性能优化

1. memo(高阶组件)

作用:
  • 防止子组件不必要的重新渲染。当父组件重新渲染时,若子组件的 props 没有变化,memo 会跳过子组件的渲染。

  • 通过浅比较(shallow compare)props 是否变化来决定是否重新渲染。

使用场景:
  • 子组件渲染成本高(如复杂计算、大量 DOM 操作)。

  • 父组件频繁重新渲染,但子组件的 props 不变

  • 传递的 props 是基本类型或引用稳定的对象/函数

代码示例:
import { memo } from 'react';

// 用 memo 包裹子组件
const ChildComponent = memo(function ChildComponent({ data }) {
  console.log('子组件渲染');
  return <div>{data}</div>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>点击 {count}</button>
      {/* data 是固定值,父组件更新时子组件不会重新渲染 */}
      <ChildComponent data="静态数据" />
    </div>
  );
}

2. useMemo(钩子)

作用:
  • 缓存计算结果。当依赖项不变时,直接返回缓存值,避免重复计算。

  • 适用于需要复杂计算的场景(如过滤列表、数学运算)。

使用场景:
  • 计算代价高昂的衍生数据

  • 依赖项变化时才需要重新计算

  • 避免因父组件无关状态变化导致子组件重复计算

代码示例
import { useMemo } from 'react';

function App({ items, filter }) {
  // 依赖项 [items, filter] 变化时,重新计算 filteredItems
  const filteredItems = useMemo(() => {
    console.log('重新计算列表');
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

  return <List items={filteredItems} />;
}

3. useCallback(钩子)

作用:
  • 缓存函数实例。当依赖项不变时,返回同一个函数引用。

  • 避免因父组件重新渲染导致传递给子组件的函数引用变化,触发子组件不必要的渲染。

使用场景:
  • 将函数作为 props 传递给子组件,且子组件被 memo 优化。

  • 函数作为其他 Hook 的依赖项(如 useEffect)。

  • 事件处理函数需要保持引用稳定

代码示例:
import { useCallback, memo } from 'react';

const ChildButton = memo(function ChildButton({ onClick }) {
  console.log('子按钮渲染');
  return <button onClick={onClick}>点击</button>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);

  // 使用 useCallback 缓存函数
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 空依赖项:函数引用始终不变

  return (
    <div>
      <div>计数:{count}</div>
      <ChildButton onClick={handleClick} />
    </div>
  );
}

三者的核心区别

工具解决的问题输入输出适用对象
memo子组件不必要的渲染组件优化后的组件函数组件
useMemo避免重复计算高开销操作计算函数 + 依赖项缓存值计算结果
useCallback保持函数引用稳定函数 + 依赖项缓存函数函数引用

使用注意事项

  1. 避免滥用

    • 优先考虑代码可读性,仅在性能瓶颈明确时优化。

    • 过度使用可能导致内存开销增加。

  2. 与 memo 配合使用

    • 如果子组件用 memo 包裹,父组件传递的 props(包括函数)需保持引用稳定,此时 useCallback 和 useMemo 是必要的。

实战场景总结

  • 场景 1:父组件频繁更新,但子组件 props 不变 → memo

  • 场景 2:复杂计算(如数据过滤、排序) → useMemo

  • 场景 3:函数作为 props 传递且子组件用 memo 优化 → useCallback


六、高级 Hooks

6. useReducer:复杂状态逻辑

  • 作用:类似 Redux 的状态管理,适用于多操作或嵌套状态。

  • 语法

    const [state, dispatch] = useReducer(reducer, initialState);
  • 示例

    import { useReducer } from 'react'
    
    const initState = { count: 0 }
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 }
        case 'decrement':
          return { count: state.count - 1 }
        case 'update':
          return { count: action.payload }
        default:
          return state.count
      }
    }
    
    function App() {
      const [state, dispatch] = useReducer(reducer, initState)
    
      return (
        <>
          <button onClick={() => dispatch({ type: 'increment' })}>+</button>
          <span> {state.count} </span>
          <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
          <br />
          <button onClick={() => dispatch({ type: 'update', payload: 100 })}>update</button>
        </>
      )
    }
    
    export default App
    

7. useRef:持久化引用

  • 作用:访问 DOM 节点或保存可变值(不触发重新渲染)。

  • 语法

    const ref = useRef(initialValue);
  • 示例

    import { useRef } from 'react'
    
    function Input({ placeholder, ref }) {
      return <input type='text' placeholder={placeholder} ref={ref}></input>
    }
    
    function App() {
      const inputRef = useRef(null)
      const focusInput = () => inputRef.current.focus()
    
      return (
        <>
          <Input ref={inputRef} placeholder="请输入内容" />
          <button onClick={focusInput}>focus</button>
        </>
      )
    }
    
    export default App
    

七、自定义 Hooks

封装可复用逻辑
  • 规则:函数名以 use 开头,内部可调用其他 Hooks。

  • 将重复的组件逻辑(如表单处理、数据请求)封装成可复用的函数。

    // 自定义 Hook:表单输入管理
    function useFormInput(initialValue) {
      const [value, setValue] = useState(initialValue);
      const handleChange = (e) => setValue(e.target.value);
      return { value, onChange: handleChange };
    }
    
    // 使用
    function LoginForm() {
      const username = useFormInput('');
      const password = useFormInput('');
      return (
        <form>
          <input {...username} placeholder="Username" />
          <input {...password} placeholder="Password" />
        </form>
      );
    }

八、总结

  • useState:管理组件内部状态。

  • useEffect:处理副作用与生命周期逻辑。

  • useContext:简化跨组件数据传递。

  • useMemo/useCallback:优化计算与渲染性能。

  • useReducer:处理复杂状态逻辑。

  • useRef:访问 DOM 或保存可变值。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值