React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)

📘 React 组件核心语法知识点详解(类组件体系)


一、组件的定义

React 组件有两种定义方式:

  1. 函数组件(无状态组件) ------ 无内部状态,只接收 props 渲染 UI。
  2. 类组件(有状态组件) ------ 继承自 React.Component,可拥有 state 和生命周期。

✅ 案例 1:函数组件定义

jsx 复制代码
// 函数组件:接收 props,返回 JSX
function Welcome(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// 或使用箭头函数
const WelcomeArrow = (props) => <h1>Hello, {props.name}!</h1>;

✅ 案例 2:类组件定义

jsx 复制代码
import React, { Component } from 'react';

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

export default Welcome;

📌 注意:类组件必须实现 render() 方法,且必须返回合法的 JSX 或 null


二、props(属性)

props 是父组件传递给子组件的只读数据 。子组件不应修改 props

✅ 案例 3:props 基本使用

jsx 复制代码
// ParentComponent.jsx
import React from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends React.Component {
  render() {
    return (
      <div>
        {/* 传递字符串、数字、对象、函数等 */}
        <ChildComponent 
          name="Alice" 
          age={25} 
          hobbies={['reading', 'coding']} 
          greet={() => alert('Hello from Parent!')}
        />
      </div>
    );
  }
}

// ChildComponent.jsx
class ChildComponent extends React.Component {
  render() {
    const { name, age, hobbies, greet } = this.props;
    return (
      <div>
        <p>Name: {name}</p>
        <p>Age: {age}</p>
        <ul>
          {hobbies.map((hobby, index) => (
            <li key={index}>{hobby}</li>
          ))}
        </ul>
        <button onClick={greet}>Greet</button>
      </div>
    );
  }
}

⚠️ props 是只读的!不要在子组件中修改:this.props.name = 'Bob'


三、state(状态)

state 是组件内部管理的可变数据 ,改变 state 会触发组件重新渲染。

✅ 案例 4:state 初始化与更新

jsx 复制代码
class Counter extends React.Component {
  // 初始化 state(推荐在 constructor 中)
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      message: "点击按钮增加计数"
    };
  }

  // 更新 state 使用 this.setState()
  increment = () => {
    this.setState({
      count: this.state.count + 1,
      message: `当前计数:${this.state.count + 1}`
    });
  };

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

📌 setState() 是异步的!不要依赖当前 state 立即更新。

✅ 案例 5:使用函数式 setState(推荐用于依赖前一个状态)

jsx 复制代码
increment = () => {
  this.setState((prevState) => ({
    count: prevState.count + 1,
    message: `当前计数:${prevState.count + 1}`
  }));
};

四、render()

每个类组件必须实现 render() 方法,返回要渲染的 React 元素。

✅ 案例 6:render 返回多种内容

jsx 复制代码
class MyComponent extends React.Component {
  render() {
    const isLoggedIn = this.props.isLoggedIn;

    // 可返回:JSX / 数组 / 字符串 / 数字 / null / false
    if (!isLoggedIn) {
      return "请先登录";
    }

    return (
      <div>
        <h2>欢迎回来!</h2>
        {this.renderUserList()}
      </div>
    );
  }

  renderUserList() {
    return (
      <ul>
        <li>张三</li>
        <li>李四</li>
      </ul>
    );
  }
}

📌 render() 必须是纯函数 ------ 不能修改 state 或与浏览器交互。


五、有状态组件 vs 无状态组件

类型 是否有 state 是否有生命周期 使用场景
有状态组件 ✅ 有 ✅ 有 需要管理内部状态、交互逻辑
无状态组件 ❌ 无 ❌ 无 纯展示型组件,接收 props 渲染

✅ 案例 7:对比两种组件

jsx 复制代码
// 无状态组件(展示型)
function UserProfile(props) {
  return (
    <div>
      <img src={props.avatar} alt={props.name} />
      <h3>{props.name}</h3>
      <p>{props.bio}</p>
    </div>
  );
}

// 有状态组件(容器型)
class UserList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      users: [
        { id: 1, name: 'Alice', avatar: 'alice.jpg' },
        { id: 2, name: 'Bob', avatar: 'bob.jpg' }
      ],
      selectedUser: null
    };
  }

  selectUser = (user) => {
    this.setState({ selectedUser: user });
  };

  render() {
    return (
      <div>
        <h2>用户列表</h2>
        <ul>
          {this.state.users.map(user => (
            <li key={user.id} onClick={() => this.selectUser(user)}>
              {user.name}
            </li>
          ))}
        </ul>
        {this.state.selectedUser && (
          <UserProfile 
            name={this.state.selectedUser.name}
            avatar={this.state.selectedUser.avatar}
          />
        )}
      </div>
    );
  }
}

六、哪些组件应该有 state?

有状态组件 应该是:

  • 负责数据获取、管理用户交互、表单输入、动画状态等。
  • 通常是"容器组件"(Container Components)。

无状态组件 应该是:

  • 只负责 UI 展示,不处理业务逻辑。
  • 通常是"展示组件"(Presentational Components)。

🎯 原则:状态尽量提升到共同父组件(状态提升),避免重复或难以同步。


七、哪些数据应该放入 state 中?

✅ 应该放入 state 的数据:

  • 会随时间变化的数据(如计数器、表单值、开关状态)
  • 影响 UI 渲染的数据
  • 无法从 props 或其他 state 计算得出的数据

✅ 案例 8:合理使用 state

jsx 复制代码
class LoginForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: '',      // 用户输入,会变 → ✅ 放入 state
      password: '',      // 用户输入,会变 → ✅
      isLoading: false,  // 请求状态,影响 UI → ✅
      errors: {}         // 表单校验错误 → ✅
    };
  }

  handleChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  render() {
    return (
      <form>
        <input 
          name="username" 
          value={this.state.username} 
          onChange={this.handleChange} 
        />
        <input 
          name="password" 
          type="password" 
          value={this.state.password} 
          onChange={this.handleChange} 
        />
        {this.state.isLoading && <p>登录中...</p>}
      </form>
    );
  }
}

八、哪些数据不应该放入 state 中?

❌ 不应放入 state 的数据:

  • 从 props 直接计算得出的数据(应在 render 中计算)
  • 不影响 UI 的数据(如定时器 ID、非响应式数据)
  • 可通过其他 state 或 props 推导出的数据(避免冗余)

✅ 案例 9:避免冗余 state

jsx 复制代码
class UserDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      firstName: '张',
      lastName: '三'
      // ❌ 不要:fullName: '张三' ------ 可通过计算得到
    };
  }

  // ✅ 在 render 或 getter 中计算
  get fullName() {
    return `${this.state.firstName} ${this.state.lastName}`;
  }

  render() {
    return <h2>欢迎 {this.fullName}</h2>;
  }
}

📌 也可在 render() 中直接计算:{this.state.firstName + ' ' + this.state.lastName}


九、ref 引用(访问 DOM 或类组件实例)

用于直接访问 DOM 元素或类组件实例(函数组件不能直接 ref,需 forwardRef)。

三种方式:

  1. createRef()(推荐)
  2. 回调函数方式
  3. 字符串方式(已废弃,不推荐)

✅ 案例 10:createRef() 方式(推荐)

jsx 复制代码
class TextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建 ref
    this.inputRef = React.createRef();
  }

  focusInput = () => {
    // 访问原生 DOM 元素
    this.inputRef.current.focus();
  };

  render() {
    return (
      <div>
        <input 
          ref={this.inputRef} 
          type="text" 
          placeholder="点击按钮聚焦" 
        />
        <button onClick={this.focusInput}>聚焦输入框</button>
      </div>
    );
  }
}

✅ 案例 11:回调函数方式

jsx 复制代码
class TextInputCallback extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = null; // 保存 DOM 引用
  }

  setInputRef = (element) => {
    this.inputElement = element;
  };

  focusInput = () => {
    if (this.inputElement) {
      this.inputElement.focus();
    }
  };

  render() {
    return (
      <div>
        <input 
          ref={this.setInputRef} 
          type="text" 
        />
        <button onClick={this.focusInput}>聚焦(回调 ref)</button>
      </div>
    );
  }
}

❌ 案例 12:字符串方式(已废弃,仅作了解)

jsx 复制代码
// 不推荐!未来会被移除
class OldTextInput extends React.Component {
  focusInput = () => {
    this.refs.myInput.focus(); // ❌ 不推荐
  };

  render() {
    return (
      <input ref="myInput" /> {/* ❌ 字符串 ref */}
    );
  }
}

十、props 属性验证(PropTypes)

用于在开发环境中对 props 进行类型检查。

✅ 案例 13:PropTypes 基本使用

jsx 复制代码
import PropTypes from 'prop-types';

class UserProfile extends React.Component {
  render() {
    return (
      <div>
        <h3>{this.props.name}</h3>
        <p>年龄:{this.props.age}</p>
        <p>是否管理员:{this.props.isAdmin ? '是' : '否'}</p>
      </div>
    );
  }
}

// 定义 props 类型和是否必填
UserProfile.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  isAdmin: PropTypes.bool
};

// 设置默认 props
UserProfile.defaultProps = {
  age: 18,
  isAdmin: false
};

export default UserProfile;

💡 常用 PropTypes:

js 复制代码
PropTypes.string
PropTypes.number
PropTypes.bool
PropTypes.func
PropTypes.object
PropTypes.array
PropTypes.node          // 任何可渲染的东西
PropTypes.element       // React 元素
PropTypes.instanceOf(MyClass)
PropTypes.oneOf(['News', 'Photos'])
PropTypes.shape({
  color: PropTypes.string,
  fontSize: PropTypes.number
})

十一、组件的其他成员

1. 静态成员(static)

jsx 复制代码
class MyComponent extends React.Component {
  static myStaticMethod() {
    return '我是静态方法';
  }

  static defaultProps = { theme: 'light' };

  render() {
    return <div>主题:{this.props.theme}</div>;
  }
}

// 外部调用
console.log(MyComponent.myStaticMethod()); // ✅

2. 自定义方法(绑定 this)

jsx 复制代码
class ClickCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // 方法1:在 constructor 中 bind
    this.handleClick = this.handleClick.bind(this);
  }

  // 方法2:使用箭头函数(推荐)
  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return <button onClick={this.handleClick}>点击了 {this.state.count} 次</button>;
  }
}

3. 生命周期方法(简要提及)

jsx 复制代码
class LifecycleDemo extends React.Component {
  componentDidMount() {
    console.log('组件挂载完成');
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.count !== this.state.count) {
      console.log('计数更新了');
    }
  }

  componentWillUnmount() {
    console.log('组件即将卸载');
  }

  render() { ... }
}

🧩 综合性实战案例

✅ 案例 14:TodoList 应用(完整类组件实现)

jsx 复制代码
import React, { Component } from 'react';
import PropTypes from 'prop-types';

// 子组件:单个 Todo 项
class TodoItem extends Component {
  render() {
    const { todo, onDelete, onToggle } = this.props;
    return (
      <li style={{ 
        textDecoration: todo.completed ? 'line-through' : 'none',
        color: todo.completed ? '#999' : '#000'
      }}>
        <input 
          type="checkbox" 
          checked={todo.completed} 
          onChange={() => onToggle(todo.id)} 
        />
        <span>{todo.text}</span>
        <button onClick={() => onDelete(todo.id)}>删除</button>
      </li>
    );
  }
}

TodoItem.propTypes = {
  todo: PropTypes.shape({
    id: PropTypes.number.isRequired,
    text: PropTypes.string.isRequired,
    completed: PropTypes.bool.isRequired
  }).isRequired,
  onDelete: PropTypes.func.isRequired,
  onToggle: PropTypes.func.isRequired
};

// 主组件:TodoList
class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [
        { id: 1, text: '学习 React', completed: false },
        { id: 2, text: '写组件文档', completed: true }
      ],
      inputText: '',
      filter: 'all' // all / active / completed
    };

    this.inputRef = React.createRef();
  }

  addTodo = () => {
    const text = this.state.inputText.trim();
    if (text === '') return;

    const newTodo = {
      id: Date.now(),
      text: text,
      completed: false
    };

    this.setState(prevState => ({
      todos: [...prevState.todos, newTodo],
      inputText: ''
    }), () => {
      // 回调:添加后聚焦输入框
      this.inputRef.current.focus();
    });
  };

  deleteTodo = (id) => {
    this.setState(prevState => ({
      todos: prevState.todos.filter(todo => todo.id !== id)
    }));
  };

  toggleTodo = (id) => {
    this.setState(prevState => ({
      todos: prevState.todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    }));
  };

  handleInputChange = (e) => {
    this.setState({ inputText: e.target.value });
  };

  setFilter = (filter) => {
    this.setState({ filter });
  };

  getFilteredTodos = () => {
    const { todos, filter } = this.state;
    switch (filter) {
      case 'active':
        return todos.filter(t => !t.completed);
      case 'completed':
        return todos.filter(t => t.completed);
      default:
        return todos;
    }
  };

  render() {
    const { inputText, filter } = this.state;
    const filteredTodos = this.getFilteredTodos();
    const activeCount = this.state.todos.filter(t => !t.completed).length;

    return (
      <div style={{ padding: '20px', fontFamily: 'Arial' }}>
        <h1>Todo List</h1>
        
        {/* 输入区域 */}
        <div>
          <input
            ref={this.inputRef}
            type="text"
            value={inputText}
            onChange={this.handleInputChange}
            onKeyPress={(e) => e.key === 'Enter' && this.addTodo()}
            placeholder="添加新任务..."
            style={{ padding: '8px', width: '300px' }}
          />
          <button onClick={this.addTodo} style={{ padding: '8px 16px' }}>
            添加
          </button>
        </div>

        {/* 过滤器 */}
        <div style={{ margin: '10px 0' }}>
          <button 
            onClick={() => this.setFilter('all')}
            style={{ marginRight: '8px' }}
          >
            全部 ({this.state.todos.length})
          </button>
          <button 
            onClick={() => this.setFilter('active')}
            style={{ marginRight: '8px' }}
          >
            未完成 ({activeCount})
          </button>
          <button onClick={() => this.setFilter('completed')}>
            已完成 ({this.state.todos.length - activeCount})
          </button>
        </div>

        {/* 列表 */}
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {filteredTodos.map(todo => (
            <TodoItem
              key={todo.id}
              todo={todo}
              onDelete={this.deleteTodo}
              onToggle={this.toggleTodo}
            />
          ))}
        </ul>

        {filteredTodos.length === 0 && <p>暂无任务</p>}
      </div>
    );
  }
}

export default TodoList;

✅ 案例 15:表单验证组件(含 ref、state、props)

jsx 复制代码
class ValidatedForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: '',
      password: '',
      errors: {
        email: '',
        password: ''
      }
    };

    this.emailRef = React.createRef();
    this.passwordRef = React.createRef();
  }

  validateEmail = (email) => {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return re.test(email);
  };

  validatePassword = (pwd) => {
    return pwd.length >= 6;
  };

  handleChange = (field) => (e) => {
    const value = e.target.value;
    this.setState(prevState => ({
      [field]: value,
      errors: {
        ...prevState.errors,
        [field]: ''
      }
    }));
  };

  handleSubmit = (e) => {
    e.preventDefault();
    let valid = true;
    const newErrors = { email: '', password: '' };

    if (!this.validateEmail(this.state.email)) {
      newErrors.email = '请输入有效的邮箱';
      valid = false;
    }

    if (!this.validatePassword(this.state.password)) {
      newErrors.password = '密码至少6位';
      valid = false;
    }

    this.setState({ errors: newErrors });

    if (valid) {
      alert('表单提交成功!');
      // 提交逻辑
    } else {
      // 聚焦第一个错误字段
      if (newErrors.email) {
        this.emailRef.current.focus();
      } else if (newErrors.password) {
        this.passwordRef.current.focus();
      }
    }
  };

  render() {
    const { email, password, errors } = this.state;

    return (
      <form onSubmit={this.handleSubmit} style={{ maxWidth: '400px', margin: '0 auto' }}>
        <div style={{ marginBottom: '15px' }}>
          <label>Email:</label>
          <input
            ref={this.emailRef}
            type="email"
            value={email}
            onChange={this.handleChange('email')}
            style={{ display: 'block', width: '100%', padding: '8px' }}
          />
          {errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
        </div>

        <div style={{ marginBottom: '15px' }}>
          <label>密码:</label>
          <input
            ref={this.passwordRef}
            type="password"
            value={password}
            onChange={this.handleChange('password')}
            style={{ display: 'block', width: '100%', padding: '8px' }}
          />
          {errors.password && <span style={{ color: 'red' }}>{errors.password}</span>}
        </div>

        <button type="submit" style={{ padding: '10px 20px' }}>
          登录
        </button>
      </form>
    );
  }
}

✅ 总结要点

知识点 关键要点
组件定义 函数组件轻量,类组件有状态和生命周期
props 只读,父传子,类型检查用 PropTypes
state 可变状态,用 setState 更新,避免直接修改
render() 必须实现,返回 JSX/null/字符串等,必须是纯函数
有状态 vs 无状态 容器组件管状态,展示组件只渲染
state 数据选择 会变、影响 UI、不可推导的数据放入 state
ref createRef() 最推荐,用于访问 DOM 或组件实例
综合案例 TodoList、表单验证 ------ 涵盖 state、props、事件、ref、验证等完整流程

✅ 以上内容全面覆盖 React 类组件核心语法体系,适合系统学习和面试复习。建议结合 Hooks 体系(如 useState, useEffect)进一步学习现代 React 开发。

如需 Hooks 版本或函数组件进阶,欢迎继续提问!


📌 最后提醒 :虽然类组件仍广泛使用,但 React 官方推荐新项目使用 函数组件 + Hooks,更简洁、易测试、逻辑复用更方便。但理解类组件对阅读老项目和面试至关重要!

相关推荐
蚂蚁RichLab前端团队3 小时前
🚀🚀🚀 RichLab - 花呗前端团队招贤纳士 - 【转岗/内推/社招】
前端·javascript·人工智能
周周记笔记3 小时前
学习笔记:第一个Python程序
笔记·学习
孩子 你要相信光3 小时前
css之一个元素可以同时应用多个动画效果
前端·css
萌萌哒草头将军3 小时前
Oxc 和 Rolldown Q4 更新计划速览!🚀🚀🚀
javascript·vue.js·vite
优雅鹅3 小时前
ARM、AArch64、amd64、x86_64、x86有什么区别?
arm开发·学习
huangql5203 小时前
npm 发布流程——从创建组件到发布到 npm 仓库
前端·npm·node.js
..过云雨3 小时前
05.【Linux系统编程】进程(冯诺依曼体系结构、进程概念、进程状态(注意僵尸和孤儿)、进程优先级、进程切换和调度)
linux·笔记·学习
Qlittleboy3 小时前
uniapp如何使用本身的字体图标
javascript·vue.js·uni-app
咸甜适中3 小时前
rust语言 (1.88) egui (0.32.2) 学习笔记(逐行注释)(二十八)使用图片控件显示图片
笔记·学习·rust·egui