React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)
📘 React 组件核心语法知识点详解(类组件体系)
一、组件的定义
React 组件有两种定义方式:
- 函数组件(无状态组件) —— 无内部状态,只接收 props 渲染 UI。
- 类组件(有状态组件) —— 继承自
React.Component
,可拥有 state 和生命周期。
✅ 案例 1:函数组件定义
// 函数组件:接收 props,返回 JSX
function Welcome(props) {return <h1>Hello, {props.name}!</h1>;
}// 或使用箭头函数
const WelcomeArrow = (props) => <h1>Hello, {props.name}!</h1>;
✅ 案例 2:类组件定义
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 基本使用
// 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 初始化与更新
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(推荐用于依赖前一个状态)
increment = () => {this.setState((prevState) => ({count: prevState.count + 1,message: `当前计数:${prevState.count + 1}`}));
};
四、render()
每个类组件必须实现 render()
方法,返回要渲染的 React 元素。
✅ 案例 6:render 返回多种内容
class MyComponent extends React.Component {render() {const isLoggedIn = this.props.isLoggedIn;// 可返回:JSX / 数组 / 字符串 / 数字 / null / falseif (!isLoggedIn) {return "请先登录";}return (<div><h2>欢迎回来!</h2>{this.renderUserList()}</div>);}renderUserList() {return (<ul><li>张三</li><li>李四</li></ul>);}
}
📌
render()
必须是纯函数 —— 不能修改 state 或与浏览器交互。
五、有状态组件 vs 无状态组件
类型 | 是否有 state | 是否有生命周期 | 使用场景 |
---|---|---|---|
有状态组件 | ✅ 有 | ✅ 有 | 需要管理内部状态、交互逻辑 |
无状态组件 | ❌ 无 | ❌ 无 | 纯展示型组件,接收 props 渲染 |
✅ 案例 7:对比两种组件
// 无状态组件(展示型)
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
class LoginForm extends React.Component {constructor(props) {super(props);this.state = {username: '', // 用户输入,会变 → ✅ 放入 statepassword: '', // 用户输入,会变 → ✅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
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() 方式(推荐)
class TextInput extends React.Component {constructor(props) {super(props);// 创建 refthis.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:回调函数方式
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:字符串方式(已废弃,仅作了解)
// 不推荐!未来会被移除
class OldTextInput extends React.Component {focusInput = () => {this.refs.myInput.focus(); // ❌ 不推荐};render() {return (<input ref="myInput" /> {/* ❌ 字符串 ref */});}
}
十、props 属性验证(PropTypes)
用于在开发环境中对 props 进行类型检查。
✅ 案例 13:PropTypes 基本使用
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:
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)
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)
class ClickCounter extends React.Component {constructor(props) {super(props);this.state = { count: 0 };// 方法1:在 constructor 中 bindthis.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. 生命周期方法(简要提及)
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 应用(完整类组件实现)
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><inputref={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 => (<TodoItemkey={todo.id}todo={todo}onDelete={this.deleteTodo}onToggle={this.toggleTodo}/>))}</ul>{filteredTodos.length === 0 && <p>暂无任务</p>}</div>);}
}export default TodoList;
✅ 案例 15:表单验证组件(含 ref、state、props)
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><inputref={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><inputref={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,更简洁、易测试、逻辑复用更方便。但理解类组件对阅读老项目和面试至关重要!