3. 前端基础(二):React Hooks(useState、useEffect)、事件处理、条件渲染与列表渲染

好,咱们接着聊。上一章我们把 React 的环境搭好了,也写了个最简单的组件。这一章,咱们要真正开始写点有“交互”的东西了。

说白了,前端页面不就是“数据 + 事件 + 界面”这三样吗?数据变了,界面跟着变;用户点了按钮,触发事件,数据再变。React 里怎么玩这套?核心就是 Hooks。

3.1 状态管理:useState

先问个问题:你写了一个变量 let count = 0,然后在页面上显示它。用户点了个按钮,你执行 count++,页面会更新吗?

答案是:不会。React 不知道这个变量变了。你得用 React 的方式来声明“状态”。

useState 就是干这个的。它让函数组件拥有了“记忆”能力。

import React, { useState } from 'react';

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

  return (
    <div>
      <p>你点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>点我</button>
    </div>
  );
}

这里有个小细节:useState(0) 里的 0 是初始值。count 是当前值,setCount 是更新函数。你只能通过 setCount 来改 count,直接 count++ 是没用的。

个人习惯:我习惯把状态变量名和 set 函数名写成一对,比如 usersetUserloadingsetLoading。这样一眼就能看出哪个是状态,哪个是更新函数。

3.2 副作用处理:useEffect

有了状态,我们还得处理“副作用”。什么是副作用?比如:发请求、操作 DOM、设置定时器。这些事不能写在组件渲染的主流程里,否则会出大问题。

useEffect 就是用来处理这些的。它会在组件渲染完成后执行。

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 这里发请求
    fetch(`/api/user/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]); // 依赖数组

  if (!user) return <p>加载中...</p>;
  return <p>用户名:{user.name}</p>;
}

注意第二个参数 [userId],这叫“依赖数组”。它的意思是:只有当 userId 变了,才重新执行这个 effect。如果传空数组 [],那就只在组件挂载时执行一次。

我曾经踩过的坑:有一次我忘了写依赖数组,结果 effect 在每次渲染后都执行,导致无限循环发请求,浏览器直接卡死。嗯,从那以后我写 useEffect 第一件事就是检查依赖数组。

3.3 事件处理

React 里的事件处理,跟原生 DOM 有点像,但写法更简洁。比如点击事件:

function Button() {
  const handleClick = (e) => {
    console.log('按钮被点击了', e);
  };

  return <button onClick={handleClick}>点我</button>;
}

注意几点:

  • 事件名用驼峰命名:onClickonChangeonSubmit
  • 传的是函数引用,不是调用结果:onClick={handleClick} 而不是 onClick={handleClick()}
  • 事件对象 e 是合成事件,跨浏览器兼容

我建议把事件处理函数单独抽出来,别在 JSX 里写箭头函数。一来可读性好,二来避免每次渲染都创建新函数。

3.4 条件渲染

条件渲染说白了就是“根据状态显示不同的内容”。React 里没有特殊的语法,直接用 JavaScript 的 if 或三元运算符就行。

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>欢迎回来!</h1>;
  }
  return <h1>请先登录</h1>;
}

更常见的写法是三元表达式:

function Status({ loading, error }) {
  return (
    <div>
      {loading ? <p>加载中...</p> : 
       error ? <p>出错了:{error}</p> : 
       <p>数据加载完成</p>}
    </div>
  );
}

还有一种情况:只显示或隐藏某个元素,可以用 && 运算符:

{unreadCount > 0 && <span>你有 {unreadCount} 条未读消息</span>}
注意:别用 0 && <Component /> 这种写法,因为 0 会被渲染出来。我一般写成 count > 0 && ... 或者用 !!count 转成布尔值。

3.5 列表渲染

列表渲染,核心就是 map 方法。把数组映射成一组 JSX 元素。

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

这里有个关键点:key。每个列表项必须有一个唯一的 key,React 用它来识别哪些元素变了、哪些需要重新渲染。

key 的选择 推荐吗? 原因
数据中的 id ✅ 推荐 稳定、唯一
数组索引 index ⚠️ 不推荐 列表顺序变化时会导致性能问题或 bug
随机数 ❌ 不要用 每次渲染都变,失去缓存意义
我的经验:如果列表是静态的、不会增删改,用 index 做 key 也没大问题。但一旦涉及到排序、删除、插入,一定要用唯一 id。我之前有个项目用了 index,结果删除第一项后,后面的项全部重新渲染,性能直接崩了。

3.6 综合示例:一个简单的待办事项

把上面这些知识点串起来,写个简单的 Todo 应用:

import React, { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (input.trim() === '') return;
    setTodos([...todos, { id: Date.now(), text: input, done: false }]);
    setInput('');
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };

  return (
    <div>
      <h2>我的待办</h2>
      <input 
        value={input} 
        onChange={(e) => setInput(e.target.value)} 
        placeholder="输入待办事项"
      />
      <button onClick={addTodo}>添加</button>

      <ul>
        {todos.length === 0 ? (
          <li>暂无待办事项</li>
        ) : (
          todos.map(todo => (
            <li 
              key={todo.id}
              onClick={() => toggleTodo(todo.id)}
              style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
            >
              {todo.text}
            </li>
          ))
        )}
      </ul>
    </div>
  );
}

这个例子涵盖了:useState 管理输入框和列表、事件处理(点击、输入)、条件渲染(空列表提示)、列表渲染(map + key)。你想想看,是不是把这一章的知识点都用上了?

好,这一章就到这。下一章我们会深入聊聊组件通信和表单处理,到时候你会发现,这些基础 Hooks 组合起来能玩出很多花样。