写好 React 组件,从这三步开始( ̄︶ ̄)↗ 

32 阅读7分钟

在现代前端开发中,React 已经成为构建用户界面的主流框架。它通过“组件化”的思想,将复杂的页面拆解为可复用、可维护的小单元。本文将带你从零开始,结合实际代码示例,深入理解 React 的核心概念:组件、props、JSX 行内样式、CSS 样式管理以及组件间的通信机制。

我们将按照以下学习路径展开:

1️⃣ 创建基础组件
2️⃣ 实现弹窗组件(Modal)
3️⃣ 封装卡片组件(Card)

并重点解析 JSX 中的行内样式 与传统 CSS 的区别,帮助你建立清晰的技术认知。


🧱 第一步:认识组件 —— 拼乐高积木

💡 组件是 React 开发中的最小单元,就像乐高积木一样,可以组合成复杂结构。

我们先来定义一个最简单的组件——Greeting,用于向用户打招呼:

function Greeting(props) {
  const { name, message, showIcon } = props;
  return (
    <div>
      {showIcon && <span>👋</span>}
      <h1>Hello, {name}!</h1>
      <p>{message}</p>
    </div>
  );
}

🔍 关键点解析

  • props 是父组件传递给子组件的数据,相当于函数参数。
  • 使用 {} 在 JSX 中嵌入 JavaScript 表达式,比如条件渲染 {showIcon && <span>👋</span>}
  • 解构赋值 const { name, message } = props; 让代码更简洁易读。
  • 如果不传 showIcon,默认为 false,就不会显示表情符号。

✅ 提示:你可以使用 PropTypesprops 类型进行校验,提升代码健壮性。

import PropTypes from 'prop-types';

Greeting.propTypes = {
  name: PropTypes.string.isRequired,
  message: PropTypes.string,
  showIcon: PropTypes.bool
};

这有助于你在开发过程中提前发现错误,尤其是在团队协作时非常有用。

❓ 答疑解惑:关于 Props 和组件

Q1:为什么 props 不能修改?
A:React 遵循“单向数据流”原则。props 是只读的,确保数据从父到子单向流动,避免副作用和状态混乱。如果子组件需要修改数据,应通过回调函数通知父组件更新状态。

Q2:{showIcon && <span>👋</span>} 是什么语法?
A:这是 JavaScript 的“短路求值”逻辑。如果 showIcontrue,表达式返回 <span>👋</span> 并渲染;否则返回 false,React 会忽略它(不渲染任何内容)。

Q3:PropTypes 是必须的吗?
A:不是必须,但强烈推荐!它能在开发阶段提供警告,防止传错类型(比如把数字当成字符串),大幅提升代码可维护性。


🎯 第二步:打造灵活的弹窗组件(Modal)

接下来,我们设计一个通用的弹窗组件,支持自定义头部和底部内容。

function Modal(props) {
  const { HeaderComponent, FooterComponent, children } = props;

  return (
    <div style={styles.overlay}>
      <div style={styles.modal}>
        <HeaderComponent />
        <div style={styles.content}>
          {children}
        </div>
        <FooterComponent />
      </div>
    </div>
  );
}

🎨 行内样式的重点讲解

❗️ 什么是行内样式?

在 JSX 中,你可以直接用 style 属性添加样式,但它的写法与普通 CSS 不同:

<div style={{ backgroundColor: 'red', padding: '10px' }}>

⚠️ 注意:

  • 必须使用 对象语法 {} 包裹。
  • 所有属性名采用 驼峰命名法,如 backgroundColor 而不是 background-color
  • 值可以是字符串或数字(单位会自动处理)。

📊 与 CSS 的对比

特性行内样式(JSX)外部 CSS 文件
写法{} + 驼峰命名.class { property: value; }
变量支持支持 JS 表达式不支持动态值
作用域局部作用于元素全局作用域(需注意冲突)
性能略低(频繁更新)更高效
可维护性不适合复杂样式更适合大型项目

✅ 推荐:简单样式用行内,复杂样式用外部 CSS 或 CSS Modules

在这个例子中,我们用了 CSS-in-JS 的方式:

const styles = {
  overlay: {
    backgroundColor: 'rgba(41, 96, 249, 0.1)',
    position: 'fixed',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modal: {
    backgroundColor: 'white',
    padding: '1rem',
    borderRadius: '8px',
    width: '400px'
  },
  content: {
    margin: '1rem 0'
  }
};

这种方式便于逻辑与样式绑定,特别适合动态样式需求。

❓ 答疑解惑:关于行内样式与 Modal 设计

Q1:为什么要把样式抽成 styles 对象?直接写不行吗?
A:可以,但不推荐!把样式提取为变量:

  • 提高可读性(避免 JSX 过长)
  • 方便复用(比如多个地方用 overlay
  • 便于调试和维护

Q2:HeaderComponent 是怎么作为 prop 传进去的?它是个组件还是函数?
A:它是一个 React 组件(函数) 。在 JSX 中,组件名首字母大写表示它是组件。你传的是函数本身(不是调用结果),所以内部要用 <HeaderComponent /> 来渲染。

Q3:children 是什么?为什么不用显式声明?
A:children 是 React 的特殊 prop,代表组件标签之间的内容。比如:

<Modal>这里是 children</Modal>

React 自动把它注入 props.children,无需额外定义。


🖼️ 第三步:优雅的卡片组件(Card)

现在我们来创建一个美观且可复用的卡片组件,用于展示用户信息。

const Card = ({ children, className = '' }) => {
  return (
    <div className={`card ${className}`}>
      {children}
    </div>
  );
};

🎨 样式设计亮点

我们在对应的 CSS 文件中定义了完整的视觉效果:

.card {
  background-color: #ffffff;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin: 16px auto;
  max-width: 400px;
  transition: all 0.3s ease;
  overflow: hidden;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}

✅ 设计原则总结

  • 语义化类名.card, .card h2 等,便于维护。
  • 悬停动画:轻微上浮 + 阴影加深,增强交互感。
  • 响应式宽度max-width: 400px 避免过宽。
  • 过渡效果transition: all 0.3s ease 让变化更自然。

💡 小技巧:className="user-card" 会被自动合并为 card user-card,实现主题扩展。

❓ 答疑解惑:关于 CSS 与 className

Q1:为什么用 className 而不是 class
A:因为 class 是 JavaScript 的保留关键字!JSX 最终会被编译成 JS 函数调用,所以必须用 className 来避免语法错误。

Q2:模板字符串 `card ${className}` 有什么好处?
A:它能安全地拼接类名。如果 className 为空字符串,结果就是 "card";如果有值(如 "user-card"),结果就是 "card user-card",不会多出空格或报错。

Q3:CSS 文件里的 .card 会不会和其他组件冲突?
A:有可能!在大型项目中,建议使用 CSS ModulesCSS-in-JS 库(如 styled-components) 来实现局部作用域,避免全局污染。


🔄 组件通信:父子关系与数据流动

在 React 中,数据单向流动 是基本原则:

  • 父组件持有状态(state)
  • 子组件接收数据(props)
  • 子组件不能修改父组件的数据(除非通过回调)

例如,在主应用中使用这些组件:

function App() {
  return (
    <div>
      {/* 自定义卡片 */}
      <Card className="user-card">
        <h2>张三</h2>
        <p>高级前端工程师</p>
        <button>查看详情</button>
      </Card>

      {/* 弹窗组件 */}
      <Modal
        HeaderComponent={MyHeader}
        FooterComponent={MyFooter}
      >
        <p>这是一个弹窗</p>
        <p>你可以在这里显示任何JSX</p>
      </Modal>
    </div>
  );
}

🧩 组件嵌套与复用

  • App 是顶层组件(老板),负责协调其他组件。
  • CardModal 是功能组件(员工),专注于单一职责。
  • MyHeaderMyFooter 是无状态函数组件,仅用于 UI 渲染。

这种结构使得代码高度模块化,易于测试和维护。

❓ 答疑解惑:关于组件通信

Q1:子组件怎么“告诉”父组件发生了点击?
A:父组件传一个函数作为 prop(比如 onClose),子组件在事件中调用它。例如:

// 父组件
<Modal onClose={() => setIsOpen(false)} />

// 子组件
<button onClick={props.onClose}>关闭</button>

Q2:能不能让 Modal 默认显示关闭按钮?
A:可以!通过设置 FooterComponent 的默认值,或者在 Modal 内部加一个默认 footer。但为了灵活性,当前设计让用户完全控制内容,更符合“组合优于配置”原则。

Q3:为什么 App 里很多代码被注释掉了?
A:这是教学常见做法!注释掉的部分展示了不同用法(比如带 icon 的 Greeting、多个 Card),你可以随时取消注释来观察效果,边学边试。


🚀 总结:你的 React 学习路线图

步骤目标技术要点
1️⃣创建基础组件props, 解构, PropTypes
2️⃣实现弹窗组件children, 行内样式, CSS-in-JS
3️⃣封装卡片组件className, 外部 CSS, 悬停动画

🌟 最佳实践建议

  1. 优先使用外部 CSS:避免大量行内样式污染。
  2. 合理使用 children:让组件更具扩展性。
  3. 保持组件单一职责:一个组件只做一件事。
  4. 善用 PropTypes:提高团队协作效率。
  5. 利用 className 合并:方便主题定制。

📚 结语

通过这三个步骤,你已经掌握了 React 的核心技能:组件化思维、数据传递、样式控制与交互设计。未来你可以继续深入学习:

  • useStateuseEffect 状态管理
  • 组件生命周期
  • Context API 实现跨层级通信
  • React Router 实现页面跳转

但记住:一切始于一个小小的组件。只要坚持练习,你就能写出优雅、高效的 React 应用!

💬 “React 不是魔法,而是规律。” —— 一位资深前端开发者

🚀 现在就动手试试吧!把你的想法变成一个个小积木,拼出属于你的数字世界。