3、React组件基础:函数组件与类组件、组件的props(属性传递)、组件的children、组件组合模式

好,咱们今天来聊聊React组件的核心概念。

说实话,我刚接触React那会儿,最困惑的就是「组件到底是个啥」。后来我明白了——组件就是页面上的一块积木。你搭积木,我搭积木,大家拼在一起就成了一个完整的页面。

3.1 函数组件 vs 类组件

React里写组件,主要有两种方式。我个人习惯用函数组件,但类组件你也要认识,毕竟老项目里到处都是。

函数组件

说白了,就是一个普通的JavaScript函数。它接收一个参数(我们叫它props),然后返回一段JSX。

// 最简单的函数组件
function Welcome(props) {
  return <h1>你好,{props.name}</h1>;
}

// 或者用箭头函数
const Welcome = (props) => {
  return <h1>你好,{props.name}</h1>;
};

嗯,这里要注意:函数组件的名字首字母必须大写。不然React会把它当成普通HTML标签处理。

类组件

类组件是用ES6的class写的。它必须继承React.Component,而且里面要有一个render方法。

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

我在项目中遇到过不少老代码,全是类组件。那时候还没有hooks,大家只能用class。现在新项目我建议都用函数组件,代码更简洁。

核心区别:
  • 函数组件:简单、轻量、推荐使用
  • 类组件:功能完整、有生命周期、适合老项目维护

3.2 组件的props(属性传递)

props就是父组件传给子组件的数据。你想想看,一个组件如果啥数据都不接收,那它就是个死组件,没法复用。

// 父组件
function App() {
  return (
    <div>
      <UserCard name="张三" age={25} isVIP={true} />
      <UserCard name="李四" age={30} isVIP={false} />
    </div>
  );
}

// 子组件
function UserCard(props) {
  return (
    <div className="card">
      <h3>{props.name}</h3>
      <p>年龄:{props.age}</p>
      <p>{props.isVIP ? 'VIP用户' : '普通用户'}</p>
    </div>
  );
}

这里有几个坑,我曾经踩过:

避坑指南:
  • props是只读的!千万别在子组件里修改props
  • 传数字要用花括号:age={25},不是age="25"
  • 传布尔值也一样:isVIP={true}

为什么会这样?因为React是单向数据流。数据从父组件流向子组件,子组件只能读,不能改。你要是改了,React会报错。

3.3 组件的children

children是个特殊的东西。它代表组件标签之间的内容。

// 使用children
function Card(props) {
  return (
    <div className="card">
      {props.children}
    </div>
  );
}

// 调用时
<Card>
  <h2>标题</h2>
  <p>这是一段描述文字</p>
</Card>

你看,Card组件不知道里面会放什么。它只是把children渲染出来。这就很灵活了。

我个人习惯用children来做布局组件。比如一个弹窗组件,内容千变万化,用children最合适。

小技巧:

children可以是任何东西——字符串、JSX、甚至是一个函数。我做过一个项目,用children传了一个回调函数进去,实现了「渲染劫持」的效果。

3.4 组件组合模式

组合模式,说白了就是「把组件拼在一起」。这是React最强大的设计思想之一。

3.4.1 容器组件 + 展示组件

// 容器组件:负责数据逻辑
function UserListContainer() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetchUsers().then(data => setUsers(data));
  }, []);
  
  return <UserList users={users} />;
}

// 展示组件:只负责渲染
function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

这种模式的好处是:逻辑和UI分离。你改样式不会影响数据,改数据不会影响样式。

3.4.2 复合组件模式

// 一个Select组件,由多个子组件组成
function Select(props) {
  return <select>{props.children}</select>;
}

Select.Option = function Option(props) {
  return <option value={props.value}>{props.children}</option>;
};

// 使用
<Select>
  <Select.Option value="1">选项一</Select.Option>
  <Select.Option value="2">选项二</Select.Option>
</Select>

我在项目中遇到过这种场景:一个复杂的表单组件,里面有输入框、下拉框、日期选择器。用复合组件模式,调用方可以自由组合,非常灵活。

3.4.3 高阶组件(HOC)

高阶组件是个函数,它接收一个组件,返回一个新组件。说白了就是「给组件加料」。

function withLogging(WrappedComponent) {
  return function(props) {
    console.log('组件渲染了');
    return <WrappedComponent {...props} />;
  };
}

const EnhancedButton = withLogging(Button);
组合模式的核心思想:
  • 小组件拼成小组件
  • 小组件拼成大组件
  • 大组件拼成页面
  • 页面拼成应用

你想想看,这种模式的好处是什么?每个组件只关心自己的事。出了问题,定位也快。改代码也不怕影响其他地方。

嗯,最后说一句。组件组合不是银弹。有时候过度拆分反而让代码变得复杂。我的经验是:一个组件如果超过200行,或者它做的事情太多,就该考虑拆分了。

好了,这一章的内容就到这里。下一章咱们聊聊React的状态管理——useState和useReducer,这可是React开发的重头戏。