🐸 青蛙编程网
首页 / 前端 / React / 7. 组件通信:props

7. 组件通信:props

这是什么:props = 父组件传给子组件的数据,只能往下传、只能读;想往回传,就传一个函数下去。

为什么用:上一章拆出的 TaskRow 自己没有任务数据,删除动作也该由持有数据的父组件来做,props 就是这条通道。

如何用:本章从基础 props、children 讲到回调 props,先让子组件喊一声,再由父组件真正把这条任务删掉。

7.1 props 基础

整段可跑:

<!DOCTYPE html>
<html lang='zh-CN'>
<head>
  <meta charset='utf-8'>
  <script src='https://registry.npmmirror.com/react/18.3.1/files/umd/react.production.min.js'></script>
  <script src='https://registry.npmmirror.com/react-dom/18.3.1/files/umd/react-dom.production.min.js'></script>
  <script src='https://registry.npmmirror.com/@babel/standalone/7.29.11/files/babel.min.js'></script>
</head>
<body>
  <div id='root'></div>

  <script type='text/babel' data-presets='react'>
    function Greeting({ name, age }) {          // 子组件声明「我需要什么」
      return <p>{name} 今年 {age} 岁</p>
    }

    function App() {
      return (
        <div>
          <Greeting name='青蛙' age={11} />    {/* 字符串直接写,JS 表达式加花括号 */}
          <Greeting name='熊猫' age={3} />
        </div>
      )
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App />)
  </script>
</body>
</html>

你会看到:「青蛙 今年 11 岁」「熊猫 今年 3 岁」两行。

要点:

  • props 只能读,不能改(数据是父的)
  • 字符串直接写 name='青蛙',JS 表达式加花括号 age={11}
  • 常用解构 ({ name }),可设默认值 function G({ page = 1 })

7.2 children:标签中间的内容

整段可跑:

<!DOCTYPE html>
<html lang='zh-CN'>
<head>
  <meta charset='utf-8'>
  <script src='https://registry.npmmirror.com/react/18.3.1/files/umd/react.production.min.js'></script>
  <script src='https://registry.npmmirror.com/react-dom/18.3.1/files/umd/react-dom.production.min.js'></script>
  <script src='https://registry.npmmirror.com/@babel/standalone/7.29.11/files/babel.min.js'></script>
</head>
<body>
  <div id='root'></div>

  <script type='text/babel' data-presets='react'>
    function Card({ title, children }) {
      return (
        <section
          style={{ border: '1px solid #ddd', padding: '12px', maxWidth: '360px' }}
        >
          <h3>{title}</h3>
          {children}          {/* 承接标签中间的内容(React 版 <slot>) */}
        </section>
      )
    }

    function App() {
      return (
        <Card title='公告'>
          <p>任意内容,JSX 全收</p>
          <a href='https://react.dev'>链接也行</a>
        </Card>
      )
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App />)
  </script>
</body>
</html>

你会看到:一张带「公告」标题的卡片,里面装着一段文字和一个链接——组合别人写好的 JSX,交给 Card 负责外皮。

7.3 回调 props:子往上报

需求:列表项有「删除」按钮,但数据在父组件——把「删除函数」传下去。整段可跑:

<!DOCTYPE html>
<html lang='zh-CN'>
<head>
  <meta charset='utf-8'>
  <script src='https://registry.npmmirror.com/react/18.3.1/files/umd/react.production.min.js'></script>
  <script src='https://registry.npmmirror.com/react-dom/18.3.1/files/umd/react-dom.production.min.js'></script>
  <script src='https://registry.npmmirror.com/@babel/standalone/7.29.11/files/babel.min.js'></script>
</head>
<body>
  <div id='root'></div>

  <script type='text/babel' data-presets='react'>
    function TaskRow({ task, onDelete }) {      // 子组件:只负责喊
      return (
        <li>
          {task.text}
          <button onClick={() => onDelete(task.id)}>×</button>
        </li>
      )
    }

    function TaskList() {                       // 父组件:负责真操作
      const [tasks, setTasks] = React.useState([
        { id: 1, text: '学 React' },
        { id: 2, text: '写教程' }
      ])

      function handleDelete(id) {
        setTasks(tasks.filter((t) => t.id !== id))   // 不可变删除
      }

      return (
        <ul>
          {tasks.map((t) => (
            <TaskRow key={t.id} task={t} onDelete={handleDelete} />
          ))}
        </ul>
      )
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<TaskList />)
  </script>
</body>
</html>

你会看到:两条任务各带一颗「×」,点哪条删哪条——子组件只调 onDelete(id),真正改数据的始终是父组件。

数据流(和 Vue props/emit 同构):

[TaskList 的 tasks state]
   ↓ props: task
[TaskRow 显示]
   ↓ props: onDelete(函数)
[TaskRow 点击 → 调 onDelete(id)]
…… 事情又在父组件发生:setTasks 不可变更新 → 子组件们自动重新渲染

口诀:改数据的永远只有持有者。子组件不持有数据,所以只「递话」,不越权。

7.4 props 穿透问题(先了解)

层级一深 props 就得一层层接力,江湖戏称 props drilling。中级解法(本教程不展开):Context API(内置)或社区状态库(Redux/Zustand)。入门期保持「父管数据」的清晰流即可——三五个组件完全够用。

7.5 动手改

  1. 给 7.3 的 TaskRow 加「完成」回传 onToggle,父组件不可变更新 done
  2. 在 7.2 的 Card 的 children 里再嵌一层 Card,看渲染
  3. 给 Greeting 的 age 加默认值 0,故意不传看效果

7.6 小结

  • props 单向下行只读,children 接嵌套内容
  • 子改父 = 父传函数 + 子调用(对位 Vue 的 emit)
  • 数据永远只在一个组件里改;深了切 Context(后续)

下一章:综合练习:React 版待办清单 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。