📘 React 组件核心语法知识点详解(类组件体系)
一、组件的定义
React 组件有两种定义方式:
- 函数组件(无状态组件) ------ 无内部状态,只接收 props 渲染 UI。
- 类组件(有状态组件) ------ 继承自
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
)。
三种方式:
createRef()
(推荐)- 回调函数方式
- 字符串方式(已废弃,不推荐)
✅ 案例 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,更简洁、易测试、逻辑复用更方便。但理解类组件对阅读老项目和面试至关重要!