Props与State #
一、Props 概述 #
1.1 什么是 Props #
Props(properties)是组件的输入参数,从父组件传递给子组件,是只读的。
text
父组件
│
│ Props (单向数据流)
↓
子组件
1.2 Props 特点 #
| 特点 | 说明 |
|---|---|
| 只读 | 子组件不能修改 Props |
| 单向 | 从父到子传递 |
| 任意类型 | 可传递任何 JavaScript 值 |
二、Props 使用 #
2.1 传递 Props #
jsx
function App() {
return (
<div>
{/* 字符串 */}
<Greeting name="Alice" />
{/* 数字 */}
<Counter count={10} />
{/* 布尔值 */}
<Toggle isActive={true} />
{/* 对象 */}
<UserCard user={{ name: 'Bob', age: 25 }} />
{/* 数组 */}
<ItemList items={['a', 'b', 'c']} />
{/* 函数 */}
<Button onClick={() => console.log('clicked')} />
{/* 组件 */}
<Layout header={<Header />} />
</div>
);
}
2.2 接收 Props #
jsx
// 解构接收
function Greeting({ name, age }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
</div>
);
}
// 通过 props 对象
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 带默认值
function Greeting({ name = 'Guest' }) {
return <h1>Hello, {name}!</h1>;
}
2.3 children 属性 #
jsx
function Card({ children, title }) {
return (
<div class="card">
{title && <h2>{title}</h2>}
<div class="card-body">
{children}
</div>
</div>
);
}
// 使用
<Card title="Welcome">
<p>Card content here</p>
<button>Action</button>
</Card>
2.4 展开 Props #
jsx
function App() {
const buttonProps = {
type: 'submit',
disabled: false,
class: 'btn-primary'
};
return <button {...buttonProps}>Submit</button>;
}
// 传递所有 props
function Input(props) {
return <input {...props} class={`input ${props.class || ''}`} />;
}
2.5 Props 验证 #
jsx
// 使用 TypeScript
interface UserCardProps {
name: string;
age: number;
isActive?: boolean;
}
function UserCard({ name, age, isActive = true }: UserCardProps) {
return (
<div>
<h3>{name}</h3>
<p>Age: {age}</p>
<span>{isActive ? 'Active' : 'Inactive'}</span>
</div>
);
}
三、State 概述 #
3.1 什么是 State #
State 是组件内部的状态数据,可以被组件修改,修改后会触发重新渲染。
text
State 变化 → 重新渲染
3.2 State 特点 #
| 特点 | 说明 |
|---|---|
| 可变 | 组件可以修改自己的 State |
| 私有 | 属于组件内部 |
| 触发渲染 | 修改后自动重新渲染 |
四、State 使用 #
4.1 useState Hook #
jsx
import { useState } from 'preact/hooks';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
4.2 初始化 State #
jsx
// 直接值
const [count, setCount] = useState(0);
const [name, setName] = useState('Alice');
const [isActive, setIsActive] = useState(false);
// 对象
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
// 数组
const [items, setItems] = useState([]);
// 惰性初始化
const [state, setState] = useState(() => {
const saved = localStorage.getItem('state');
return saved ? JSON.parse(saved) : initialValue;
});
4.3 更新 State #
jsx
function Counter() {
const [count, setCount] = useState(0);
// 直接更新
const increment = () => {
setCount(count + 1);
};
// 函数式更新(推荐)
const incrementSafe = () => {
setCount(prev => prev + 1);
};
// 批量更新
const incrementMultiple = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 最终 count 增加 3
};
return (
<div>
<p>{count}</p>
<button onClick={incrementSafe}>+</button>
</div>
);
}
4.4 更新对象 State #
jsx
function Form() {
const [user, setUser] = useState({
name: '',
email: '',
address: {
city: '',
street: ''
}
});
// 更新顶层属性
const updateName = (name) => {
setUser(prev => ({ ...prev, name }));
};
// 更新嵌套属性
const updateCity = (city) => {
setUser(prev => ({
...prev,
address: {
...prev.address,
city
}
}));
};
return (
<form>
<input
value={user.name}
onInput={(e) => updateName(e.target.value)}
/>
<input
value={user.address.city}
onInput={(e) => updateCity(e.target.value)}
/>
</form>
);
}
4.5 更新数组 State #
jsx
function TodoList() {
const [todos, setTodos] = useState([]);
// 添加元素
const addTodo = (text) => {
setTodos(prev => [...prev, { id: Date.now(), text }]);
};
// 删除元素
const removeTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
// 更新元素
const updateTodo = (id, newText) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, text: newText } : todo
));
};
// 排序
const sortTodos = () => {
setTodos(prev => [...prev].sort((a, b) => a.text.localeCompare(b.text)));
};
return (
<div>
<button onClick={() => addTodo('New Task')}>Add</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
五、Props vs State #
5.1 对比 #
| 方面 | Props | State |
|---|---|---|
| 来源 | 父组件传入 | 组件内部定义 |
| 可变性 | 只读 | 可修改 |
| 用途 | 配置组件 | 管理内部状态 |
| 触发渲染 | 父组件传入新值 | 调用 setState |
5.2 使用场景 #
jsx
// Props:外部配置
function Button({ text, onClick, disabled }) {
return (
<button onClick={onClick} disabled={disabled}>
{text}
</button>
);
}
// State:内部状态
function Toggle() {
const [isOn, setIsOn] = useState(false);
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? 'ON' : 'OFF'}
</button>
);
}
// 组合使用
function Counter({ initialCount, step }) {
const [count, setCount] = useState(initialCount);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + step)}>
+{step}
</button>
</div>
);
}
六、状态提升 #
6.1 概念 #
当多个组件需要共享状态时,将状态提升到它们的共同父组件。
text
Parent (共享 State)
/ \
↓ ↓
Child A Child B
(读取) (读取)
6.2 示例 #
jsx
function Parent() {
const [text, setText] = useState('');
return (
<div>
<InputA value={text} onChange={setText} />
<InputB value={text} onChange={setText} />
<Display value={text} />
</div>
);
}
function InputA({ value, onChange }) {
return (
<input
value={value}
onInput={(e) => onChange(e.target.value)}
placeholder="Input A"
/>
);
}
function InputB({ value, onChange }) {
return (
<input
value={value}
onInput={(e) => onChange(e.target.value)}
placeholder="Input B"
/>
);
}
function Display({ value }) {
return <p>Current value: {value}</p>;
}
七、派生状态 #
7.1 从 Props 派生 #
jsx
function UserList({ users, filter }) {
// 从 props 派生的状态
const filteredUsers = users.filter(user =>
user.name.includes(filter)
);
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
7.2 使用 useMemo 优化 #
jsx
import { useMemo } from 'preact/hooks';
function UserList({ users, filter }) {
const filteredUsers = useMemo(() => {
return users.filter(user => user.name.includes(filter));
}, [users, filter]);
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
八、受控与非受控组件 #
8.1 受控组件 #
jsx
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onInput={(e) => setValue(e.target.value)}
/>
);
}
8.2 非受控组件 #
jsx
import { useRef } from 'preact/hooks';
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<div>
<input ref={inputRef} defaultValue="initial" />
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
8.3 对比 #
| 方面 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据源 | State | DOM |
| 验证 | 即时 | 提交时 |
| 默认值 | value | defaultValue |
| 适用 | 表单验证 | 简单表单 |
九、最佳实践 #
9.1 State 最小化 #
jsx
// 避免:冗余状态
function BadExample({ firstName, lastName }) {
const [fullName, setFullName] = useState(`${firstName} ${lastName}`);
// fullName 是冗余的,可以从 props 派生
}
// 推荐:只存储必要状态
function GoodExample({ firstName, lastName }) {
const fullName = `${firstName} ${lastName}`;
return <span>{fullName}</span>;
}
9.2 状态下沉 #
jsx
// 避免:状态在顶层
function App() {
const [isHovered, setIsHovered] = useState(false);
return (
<div>
<Header />
<Main />
<Footer />
<Tooltip visible={isHovered} />
</div>
);
}
// 推荐:状态就近管理
function TooltipContainer() {
const [isHovered, setIsHovered] = useState(false);
return (
<div>
<Trigger onHover={() => setIsHovered(true)} />
<Tooltip visible={isHovered} />
</div>
);
}
9.3 不可变更新 #
jsx
// 错误:直接修改
const handleClick = () => {
user.name = 'New Name';
setUser(user);
};
// 正确:创建新对象
const handleClick = () => {
setUser({ ...user, name: 'New Name' });
};
十、总结 #
| 要点 | Props | State |
|---|---|---|
| 定义 | 父组件传入 | 组件内部 |
| 修改 | 只读 | 可变 |
| 更新 | 父组件重新传入 | 调用 setter |
| 用途 | 配置 | 内部状态 |
核心原则:
- Props 向下流动
- State 私有管理
- 共享状态提升
- 派生状态不存储
最后更新:2026-03-28