2、React组件基础:函数组件与类组件、Props与State、组件生命周期

好,咱们今天聊聊React组件的根基。说实话,这部分内容我每次带新人时都会反复强调。因为组件就是React应用的砖瓦,你连砖瓦都搞不明白,后面搭房子肯定要塌。

2.1 函数组件 vs 类组件

先说说最直观的区别。函数组件就是一个普通的JavaScript函数,接收props,返回JSX。类组件呢?它是一个ES6 class,必须继承React.Component,还得有个render方法。

我个人习惯,现在新项目一律用函数组件。为什么?因为简单。你想想看,类组件里那些this绑定、constructor、生命周期方法,写起来多累啊。

函数组件示例:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

类组件示例:

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

看到区别了吗?函数组件直接拿props当参数,类组件得用this.props。我在项目中遇到过好几次,新人把类组件里的this.props写成props,结果页面白屏了。嗯,这种坑踩过一次就记住了。

我的建议:新项目优先用函数组件 + Hooks。除非你维护的是老项目,否则别碰类组件了。

2.2 Props:组件的输入

Props说白了就是父组件传给子组件的数据。它是只读的,你千万别在子组件里修改props。为什么?因为React是单向数据流,改了父组件也不知道,容易出bug。

// 父组件
function App() {
  return <Child name="张三" age={25} />;
}

// 子组件
function Child(props) {
  return <p>我叫{props.name},今年{props.age}岁</p>;
}

这里有个细节要注意:props可以传任何类型——字符串、数字、对象、函数,甚至React元素。我在项目中经常传回调函数给子组件,让子组件通知父组件「我这边发生了什么事」。

避坑指南:我曾经遇到一个bug,子组件里直接修改了props中的对象属性。虽然对象引用没变,但属性值变了,导致父组件状态和UI不一致。排查了一下午才发现。记住:props不可变!

2.3 State:组件的记忆

State和props最大的区别是什么?State是组件内部自己管理的数据,可以变化。props是外部传进来的,不能改。

在类组件里,state是个对象,用this.setState来更新。在函数组件里,我们用useState这个Hook。

类组件中的State:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>计数:{this.state.count}</p>
        <button onClick={this.increment}>+1</button>
      </div>
    );
  }
}

函数组件中的State:

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

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

你发现没有?函数组件用useState,代码量少了一半。而且不用写constructor,不用操心this绑定。我个人觉得,这就是React团队推函数组件的原因——开发体验好太多了。

小技巧:setState是异步的。如果你要基于当前state计算新值,最好传一个函数:setCount(prev => prev + 1)。这样能避免多次调用时的竞态问题。

2.4 组件生命周期

生命周期这个概念,说白了就是组件从出生到死亡的过程。类组件有明确的生命周期方法,函数组件用useEffect来模拟。

阶段 类组件方法 函数组件等效 说明
挂载 componentDidMount useEffect(() => {}, []) 组件第一次渲染到DOM后执行
更新 componentDidUpdate useEffect(() => {}, [deps]) 组件更新后执行,依赖变化时触发
卸载 componentWillUnmount useEffect的清理函数 组件从DOM移除前执行

我刚开始学React时,总觉得生命周期方法太多了,记不住。后来发现,其实大部分场景只需要关注三个:挂载时发请求、更新时做同步、卸载时清理资源。

类组件生命周期示例:

class UserProfile extends React.Component {
  componentDidMount() {
    // 组件挂载后,请求用户数据
    fetchUser(this.props.userId);
  }

  componentDidUpdate(prevProps) {
    // 如果userId变了,重新请求
    if (prevProps.userId !== this.props.userId) {
      fetchUser(this.props.userId);
    }
  }

  componentWillUnmount() {
    // 组件卸载前,清理定时器或取消请求
    clearTimeout(this.timer);
  }

  render() {
    return <div>用户信息...</div>;
  }
}

函数组件生命周期等效:

function UserProfile({ userId }) {
  useEffect(() => {
    fetchUser(userId);

    // 清理函数,相当于componentWillUnmount
    return () => {
      clearTimeout(timer);
    };
  }, [userId]); // 依赖数组,相当于componentDidUpdate的对比逻辑

  return <div>用户信息...</div>;
}

你看,函数组件用useEffect一个Hook,就把三个生命周期方法全搞定了。是不是清爽多了?

注意:我曾经在项目里看到有人把useEffect的依赖数组写成空数组,但里面却用了外部变量。结果组件更新时,useEffect不执行,bug藏得很深。记住:依赖数组里要列出所有你用到的外部变量。

2.5 总结一下

好了,咱们把今天的内容捋一捋:

  • 函数组件 vs 类组件:函数组件更简洁,推荐新项目使用
  • Props:父传子,只读不可改
  • State:组件内部状态,可变,用setState或useState更新
  • 生命周期:类组件有明确方法,函数组件用useEffect模拟

说实话,这些基础概念你花一天时间就能掌握。但真正用好它们,需要在实际项目中反复练习。我建议你从现在开始,把所有新组件都写成函数组件,用useState和useEffect。等你写了几十个组件后,自然就熟练了。

下一章咱们聊React Hooks的进阶用法,包括自定义Hook、useReducer这些好东西。到时候你会发现,函数组件比类组件强大太多了。