一、函数组件开发规范
React 组件就是返回 JSX 的函数,命名必须大写开头:
// 函数组件:首字母大写 + 返回 JSX
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Welcome;
组件组成规则
| 规则 | 说明 |
|---|---|
| 首字母大写 | 小写会被当作原生 HTML 标签 |
| 单一根节点 | 返回单个根(可用 Fragment <>) |
| props 只读 | 组件不能修改传入的 props |
| 纯函数思想 | 相同输入 → 相同输出,不产生副作用 |
// 组合使用
function App() {
return (
<>
<Welcome name="React" />
<Welcome name="Vue" />
</>
);
}
二、Props 传参:父传子与只读特性
2.1 基础传参
// 父组件
function App() {
const user = { name: "张三", age: 18 };
return <UserCard name={user.name} age={user.age} title="成员卡片" />;
}
// 子组件(解构接收)
function UserCard({ name, age, title }) {
return (
<div className="card">
<h3>{title}</h3>
<p>{name} - {age} 岁</p>
</div>
);
}
2.2 children:插槽内容
function Card({ children }) {
return <div className="card">{children}</div>;
}
// 使用:标签内的内容作为 children 传入
<Card>
<p>这是卡片内容</p>
</Card>
2.3 props 只读(单向数据流)
props 是只读的,子组件直接修改会报错(Cannot assign to read only property)。需要变化的数据由父组件用 state 管理,通过回调函数让父组件改:
// 子组件:通过回调通知父组件
function Counter({ count, onIncrement }) {
return <button onClick={onIncrement}>计数:{count}</button>;
}
// 父组件:state 变化驱动重渲染
function App() {
const [count, setCount] = useState(0);
return <Counter count={count} onIncrement={() => setCount(count + 1)} />;
}
三、props 默认值与类型校验
3.1 默认值(解构默认参数)
function Button({ text = "确定", type = "primary" }) {
return <button className={`btn-${type}`}>{text}</button>;
}
3.2 PropTypes 类型校验
npm install prop-types
import PropTypes from "prop-types";
function UserCard({ name, age }) {
return <p>{name},{age} 岁</p>;
}
// 声明类型与必填
UserCard.propTypes = {
name: PropTypes.string.isRequired, // 必填字符串
age: PropTypes.number,
tags: PropTypes.arrayOf(PropTypes.string),
onDelete: PropTypes.func,
};
// 默认值(另一种写法)
UserCard.defaultProps = {
age: 0,
};
提示:TypeScript 项目直接用类型注解替代 PropTypes,校验发生在编译期。
四、useState 定义状态
4.1 基本用法
import { useState } from "react";
function App() {
const [count, setCount] = useState(0); // 返回 [值, 更新函数]
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
4.2 函数式更新(基于上一次值)
// 连续多次更新依赖旧值时,必须用函数式写法
setCount((prev) => prev + 1);
setCount((prev) => prev + 1); // 结果 +2,而非只加一次
异步批量更新下,
setCount(count + 1)连续调用可能只生效一次;函数式更新(prev) => prev + 1永远基于最新值。
五、状态不可变原则(面试重点)
5.1 核心规则:不能直接修改 state
const [user, setUser] = useState({ name: "张三", age: 18 });
// ❌ 错误:直接修改,不触发重渲染
user.age = 19;
// ✅ 正确:创建新对象替换
setUser({ ...user, age: 19 });
5.2 对象与数组的不可变更新
// 对象:展开运算符
setUser({ ...user, age: 19 });
// 数组新增
setList([...list, newItem]);
// 数组删除
setList(list.filter((item) => item.id !== id));
// 数组更新某项
setList(list.map((item) => (item.id === id ? { ...item, done: true } : item)));
5.3 为什么不可变(面试回答要点)
- 触发更新:React 通过引用对比判断 state 是否变化,直接修改引用不变 → 不重渲染;
- 可回溯:每次更新都是新对象,便于实现时间旅行调试(DevTools 撤销/重放);
- 性能优化:memo / useMemo 依赖引用比较,不可变更新才能命中缓存。
六、基础页面交互案例
import { useState } from "react";
export default function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: "学习 React", done: false },
]);
const [input, setInput] = useState("");
const addTodo = () => {
if (!input.trim()) return;
setTodos([...todos, { id: Date.now(), text: input, done: false }]); // 不可变新增
setInput("");
};
const toggle = (id) =>
setTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t))); // 不可变更新
const remove = (id) => setTodos(todos.filter((t) => t.id !== id)); // 不可变删除
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={addTodo}>添加</button>
<ul>
{todos.map((t) => (
<li key={t.id}>
<span
onClick={() => toggle(t.id)}
style={{ textDecoration: t.done ? "line-through" : "none" }}
>
{t.text}
</span>
<button onClick={() => remove(t.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
七、总结
- 组件:函数 + 大写开头 + 返回 JSX,props 只读;
- props:父传子、children 插槽、默认参数 + PropTypes 校验;
- state:
useState返回 [值, 更新函数],函数式更新防闭包; - 不可变原则:永远创建新对象替换,不直接改原 state(面试必问);
- 数据驱动:setState → 引用变化 → React 重渲染 → 视图更新,这就是 React 的"数据驱动视图"。