从一个 Todo 小项目聊聊 React 组件化

66 阅读6分钟

最近刚开始学 React,感觉组件化这东西有点像拼乐高,但一开始真的有点懵。今天就借着写 Todo 待办项目的经历,跟大家唠唠我这个新手的学习过程,可能说得不太专业,但都是自己踩过的坑和心得,希望能帮到同样刚开始的小伙伴。

第一步、先搭个架子

刚开始学的时候,听说要配置开发环境就头大。不过前辈推荐了 Vite,说是 “零配置快速启动”,试了一下真的很简单。就像点外卖一样,几步就能拿到一个现成的项目模板。

Vite 就像是一个现成的工具箱,里面装好了各种开发需要的工具,不用自己一点点配置。比如要创建一个 Todo 项目,只需要在命令行敲几行命令,它就会帮你生成好基本的项目结构,里面已经包含了 React 的基础配置,直接就能开工写代码。

# 跟着敲就能创建项目,超级简单
npm create vite@latest todoListComponents  --template react
cd todoListComponents 
npm install
npm run dev

这样一个能跑起来的项目就有了,里面的文件结构也很清晰,src 目录下是我们写代码的地方,组件可以放在 components 文件夹里,每个组件单独一个文件,看起来清清爽爽。

第二步、把页面分成小块

拿到一个页面,先别急着写代码,想想怎么把它拆分成小块。就拿 Todo 应用来说,一眼看去,页面上有标题、输入框、添加按钮、待办事项列表,这几个部分明显可以分开处理。在 React 里,每个部分都可以做成一个组件,就像把蛋糕切成几块,每块负责不同的功能。

比如:

  • TodoList 组件:这是核心组件,就像一个大盒子,里面装着整个待办事项的逻辑,比如管理数据、协调其他组件。
  • TodoForm 组件:专门负责输入框和添加按钮这部分,处理用户输入的内容。
  • Todos 组件:只负责把待办事项列表显示出来,不用管其他逻辑。
  • App 组件:作为最顶层的组件,就像一个总调度员,把 TodoList 组件放进去,页面就成型了。
function App() {
  return (
    <>
      <TodoList /> 
    </>
  );
}

第三步、写函数组件

在 React 里,写组件最常用的方式是用函数。比如 TodoList 组件,就是一个普通的 JavaScript 函数,只不过这个函数会返回一段类似 HTML 的代码(JSX),告诉页面该长啥样。函数里面可以放变量、写逻辑,比传统的 HTML 模板灵活多了。

// TodoList.jsx 组件的核心代码
function TodoList() {
  // 用 useState 来保存数据
  const [todos, setTodos] = useState([{ id: 1, text: '吃饭', completed: false }]);
  const [title, setTitle] = useState('Todo List');

  // 处理添加待办事项的函数
  const handleAdd = (text) => {
    setTodos([...todos, { id: todos.length + 1, text, completed: false }]);
  };

  // 返回页面的结构
  return (
    <div className="container">
      <h1>{title}</h1>      {/* 直接把 title 变量放在大括号里,页面就会显示它的值 */}
      <TodoForm onAdd={handleAdd} /> {/* 把 handleAdd 函数传给 TodoForm 组件,让它能调用 */}
      <Todos todos={todos} /> {/* 把 todos 数据传给 Todos 组件,让它负责显示 */}
    </div>
  );
}

这里面的 useState 是个很关键的东西,它就像是一个开关,告诉 React 哪些数据是会变的,数据一变,页面就会自动刷新。比如上面的 todos 数组,里面存着所有待办事项,当我们添加新的事项时,调用 setTodos 改变这个数组,页面上的列表就会自动更新,不用自己去操作 DOM,省了很多麻烦。

第四步、让组件之间说话

组件之间不是孤立的,需要互相传递信息。比如 TodoForm 组件里的输入框,用户输入内容点击添加按钮后,需要把这个内容告诉 TodoList 组件,让它把新事项加到列表里。这时候就需要用到 props 和回调函数。

父组件给子组件传数据

TodoList 组件要把待办事项列表的数据传给 Todos 组件显示,怎么做呢?很简单,在调用 Todos 组件的时候,像这样写:<Todos todos={todos} />,这里的 todos 就是 props,相当于把 todos 数组递给 Todos 组件。Todos 组件收到后,就可以用 props.todos 来拿到数据,然后循环渲染成列表:

// Todos.jsx 组件,负责显示列表
function Todos(props) {
  const todos = props.todos; // 从 props 里取出 todos 数据
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

这里的 key 属性很重要,它就像每个列表项的身份证,告诉 React 哪个项是哪个,这样在列表更新时,React 就能快速找到变化的地方,避免不必要的重新渲染,提升性能。

子组件给父组件传消息

反过来,TodoForm 组件需要把用户输入的文本传给 TodoList 组件,这时候就需要父组件先给子组件传一个回调函数。比如在 TodoList 里,我们把 handleAdd 函数作为 props 传给 TodoForm:<TodoForm onAdd={handleAdd} />,然后在 TodoForm 组件里,当用户提交表单时,调用这个函数,把文本传出去:

// TodoForm.jsx 组件,处理用户输入
function TodoForm(props) {
  const [text, setText] = useState(''); // 保存输入框的内容

  // 表单提交时的处理函数
  const handleSubmit = (e) => {
    e.preventDefault(); 
    props.onAdd(text); 
    setText(''); 
  };
  
  const handleChange = (e) => {
      setText(e.target.value)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        placeholder="请输入代办事项" 
        value={text} 
        onChange={handleChange} 
      />
      <button type="submit">添加</button>
    </form>
  );
}

这样一来,子组件就像是给父组件打了个电话,说 “我这里有新数据了”,父组件接到电话后,就可以处理数据,更新状态,页面也就跟着变了。这种方式很清晰,数据往哪流一看就明白,不会乱成一团。

通过上面的步骤,一个组件化的todo代办事项就做好了。

总结

作为新手,学 React 组件化的过程其实就是不断拆组件、传数据、处理状态的过程。刚开始可能会觉得麻烦,甚至搞不懂为什么要这么做,但写了几个小项目后,发现组件化真的能让代码更有条理,改起来也方便。

比如 Todo 项目里,每个组件只做一件事,TodoForm 只负责输入,Todos 只负责显示,TodoList 只负责管理数据,这样哪里出问题了,直接找对应的组件就行,不用在一堆代码里乱翻。而且组件还能复用,比如以后做其他项目需要输入框,直接把 TodoForm 拿过来改改就能用,节省了很多时间。

现在我还在学习阶段,可能理解得不够深,但感觉 React 组件化的核心就是 “分而治之”,把大问题拆成小问题,每个小问题用一个组件解决,最后像拼乐高一样组合起来。虽然中间会遇到很多坑,比如 props 传错名字、状态没更新、组件层级太深等等,但多写写代码,慢慢就会找到感觉。

最后,附上完整的代码结构,大家可以照着敲一敲,自己跑起来看看效果,边写边理解会更深刻:

image.png

看一看最终效果

image.png 希望这篇文章能让你对 React 组件化有更直观的理解,少走一些弯路,享受用组件搭积木的乐趣!