Props与State #

一、Props(属性) #

1.1 什么是Props #

Props 是组件的输入参数,从父组件传递给子组件,是只读的。

javascript
function Welcome({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// 传递props
<Welcome name="Alice" />

1.2 Props特点 #

特点 说明
只读 子组件不能修改 Props
单向流动 从父组件流向子组件
任意类型 可以传递任何类型的值

1.3 传递各种类型的Props #

javascript
function Card({
  title,           // 字符串
  count,           // 数字
  isActive,        // 布尔值
  items,           // 数组
  user,            // 对象
  onConfirm,       // 函数
  header,          // React元素
  children         // 子组件
}) {
  return (
    <div className={`card ${isActive ? 'active' : ''}`}>
      <h2>{title}</h2>
      <p>Count: {count}</p>
      <ul>
        {items.map(item => <li key={item}>{item}</li>)}
      </ul>
      <p>User: {user.name}</p>
      {header}
      <div className="card-body">{children}</div>
      <button onClick={onConfirm}>确认</button>
    </div>
  );
}

// 使用
<Card
  title="Card Title"
  count={10}
  isActive={true}
  items={['a', 'b', 'c']}
  user={{ name: 'Alice', age: 25 }}
  onConfirm={() => console.log('confirmed')}
  header={<h3>Header</h3>}
>
  <p>Card content</p>
</Card>

1.4 解构Props #

javascript
// 方式一:在参数中解构
function UserCard({ name, age, email }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{age}</p>
      <p>{email}</p>
    </div>
  );
}

// 方式二:在函数体内解构
function UserCard(props) {
  const { name, age, email } = props;
  return (
    <div>
      <h2>{name}</h2>
      <p>{age}</p>
      <p>{email}</p>
    </div>
  );
}

// 方式三:解构并设置默认值
function UserCard({ name, age = 0, email = '' }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{age}</p>
      <p>{email}</p>
    </div>
  );
}

1.5 展开运算符传递Props #

javascript
function App() {
  const user = {
    name: 'Alice',
    age: 25,
    email: 'alice@example.com'
  };

  return <UserCard {...user} />;
}

// 等价于
<UserCard name={user.name} age={user.age} email={user.email} />

1.6 children属性 #

javascript
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// 使用
<Card title="My Card">
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</Card>

1.7 Props验证 #

javascript
import PropTypes from 'prop-types';

function UserCard({ name, age, email, onEdit }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{age}</p>
      <p>{email}</p>
      <button onClick={onEdit}>编辑</button>
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  email: PropTypes.string,
  onEdit: PropTypes.func
};

UserCard.defaultProps = {
  age: 0,
  email: '',
  onEdit: () => {}
};

二、State(状态) #

2.1 什么是State #

State 是组件内部的数据,可以改变,改变时会触发组件重新渲染。

javascript
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加
      </button>
    </div>
  );
}

2.2 State特点 #

特点 说明
私有性 组件内部管理
可变性 可以通过 setState 修改
响应式 修改后自动重新渲染

2.3 useState基础 #

javascript
import { useState } from 'react';

function Example() {
  // 基本类型
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  const [isActive, setIsActive] = useState(false);

  // 对象类型
  const [user, setUser] = useState({ name: '', age: 0 });

  // 数组类型
  const [items, setItems] = useState([]);

  return <div>...</div>;
}

2.4 更新State #

javascript
function Counter() {
  const [count, setCount] = useState(0);

  // 方式一:直接设置新值
  const increment = () => {
    setCount(count + 1);
  };

  // 方式二:使用函数更新(推荐)
  const incrementSafe = () => {
    setCount(prev => prev + 1);
  };

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>增加</button>
    </div>
  );
}

2.5 更新对象State #

javascript
function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });

  // ❌ 错误:直接修改
  const wrongUpdate = () => {
    user.name = 'Alice';
  };

  // ✅ 正确:创建新对象
  const correctUpdate = (field, value) => {
    setUser({
      ...user,
      [field]: value
    });
  };

  // ✅ 使用函数更新
  const updateName = (name) => {
    setUser(prev => ({
      ...prev,
      name
    }));
  };

  return (
    <form>
      <input
        value={user.name}
        onChange={(e) => correctUpdate('name', e.target.value)}
      />
      <input
        value={user.email}
        onChange={(e) => correctUpdate('email', e.target.value)}
      />
    </form>
  );
}

2.6 更新数组State #

javascript
function TodoList() {
  const [todos, setTodos] = useState([]);

  // 添加元素
  const addTodo = (text) => {
    setTodos([...todos, { id: Date.now(), text, done: false }]);
  };

  // 删除元素
  const removeTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  // 更新元素
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };

  // 清空数组
  const clearTodos = () => {
    setTodos([]);
  };

  return (
    <div>
      <button onClick={() => addTodo('New Task')}>添加</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

三、Props vs State #

3.1 对比表 #

特性 Props State
来源 父组件传入 组件内部定义
可变性 只读 可通过setState修改
用途 配置组件 存储动态数据
所有权 父组件 当前组件

3.2 数据流向 #

text
┌─────────────────────────────────────────────────────┐
│                    父组件                           │
│  ┌─────────────┐         ┌─────────────┐           │
│  │   State     │────────►│   Props     │           │
│  └─────────────┘         └──────┬──────┘           │
└─────────────────────────────────│───────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────┐
│                    子组件                           │
│  ┌─────────────┐         ┌─────────────┐           │
│  │   Props     │         │   State     │           │
│  │  (只读)     │         │  (可变)     │           │
│  └─────────────┘         └─────────────┘           │
└─────────────────────────────────────────────────────┘

3.3 使用场景 #

javascript
function UserCard({ user, onEdit }) {
  // Props: 从父组件接收的数据和回调
  const { name, email } = user;

  // State: 组件内部的状态
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <div className="card">
      <h2>{name}</h2>
      <p>{email}</p>
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {isExpanded ? '收起' : '展开'}
      </button>
      {isExpanded && (
        <div>
          <button onClick={onEdit}>编辑</button>
        </div>
      )}
    </div>
  );
}

四、状态提升 #

4.1 概念 #

当多个组件需要共享状态时,将状态提升到它们的共同父组件。

javascript
// ❌ 状态分散在各个组件
function TemperatureInput() {
  const [temperature, setTemperature] = useState('');
  // ...
}

// ✅ 状态提升到父组件
function Calculator() {
  const [temperature, setTemperature] = useState('');

  return (
    <div>
      <TemperatureInput
        temperature={temperature}
        onTemperatureChange={setTemperature}
      />
      <BoilingVerdict temperature={temperature} />
    </div>
  );
}

4.2 完整示例 #

javascript
function TemperatureInput({ temperature, onTemperatureChange, scale }) {
  return (
    <fieldset>
      <legend>输入{scale === 'c' ? '摄氏' : '华氏'}温度:</legend>
      <input
        value={temperature}
        onChange={(e) => onTemperatureChange(e.target.value)}
      />
    </fieldset>
  );
}

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <p>水会沸腾</p>;
  }
  return <p>水不会沸腾</p>;
}

function Calculator() {
  const [state, setState] = useState({
    temperature: '',
    scale: 'c'
  });

  const handleCelsiusChange = (temperature) => {
    setState({ scale: 'c', temperature });
  };

  const handleFahrenheitChange = (temperature) => {
    setState({ scale: 'f', temperature });
  };

  const toCelsius = (fahrenheit) => (fahrenheit - 32) * 5 / 9;
  const toFahrenheit = (celsius) => celsius * 9 / 5 + 32;

  const celsius = state.scale === 'f'
    ? toCelsius(parseFloat(state.temperature) || 0)
    : state.temperature;

  const fahrenheit = state.scale === 'c'
    ? toFahrenheit(parseFloat(state.temperature) || 0)
    : state.temperature;

  return (
    <div>
      <TemperatureInput
        scale="c"
        temperature={celsius}
        onTemperatureChange={handleCelsiusChange}
      />
      <TemperatureInput
        scale="f"
        temperature={fahrenheit}
        onTemperatureChange={handleFahrenheitChange}
      />
      <BoilingVerdict celsius={parseFloat(celsius)} />
    </div>
  );
}

五、受控与非受控组件 #

5.1 受控组件 #

表单数据由 React State 控制:

javascript
function ControlledForm() {
  const [value, setValue] = useState('');

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交:', value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={value} onChange={handleChange} />
      <button type="submit">提交</button>
    </form>
  );
}

5.2 非受控组件 #

表单数据由 DOM 控制:

javascript
import { useRef } from 'react';

function UncontrolledForm() {
  const inputRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交:', inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="默认值" />
      <button type="submit">提交</button>
    </form>
  );
}

5.3 对比 #

特性 受控组件 非受控组件
数据源 React State DOM
实时验证 支持 不支持
默认值 value defaultValue
推荐程度 推荐 特定场景

六、最佳实践 #

6.1 Props最佳实践 #

javascript
// ✅ 使用解构和默认值
function Button({ 
  text = 'Button', 
  type = 'primary', 
  onClick = () => {} 
}) {
  return (
    <button className={`btn btn-${type}`} onClick={onClick}>
      {text}
    </button>
  );
}

// ✅ 避免传递过多props
function UserCard({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

6.2 State最佳实践 #

javascript
// ✅ 状态最小化原则
function UserForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  
  // ❌ 避免冗余状态
  // const [isValid, setIsValid] = useState(false);
  
  // ✅ 使用计算值
  const isValid = name.length > 0 && email.includes('@');

  return <form>...</form>;
}

// ✅ 状态就近原则
function TodoItem({ todo }) {
  const [isEditing, setIsEditing] = useState(false);
  
  return (
    <div>
      {isEditing ? <EditForm /> : <TodoDisplay />}
    </div>
  );
}

6.3 避免常见错误 #

javascript
// ❌ 错误:直接修改state
const [items, setItems] = useState([]);
items.push('new item');

// ✅ 正确:创建新数组
setItems([...items, 'new item']);

// ❌ 错误:异步更新后使用旧值
const increment = () => {
  setCount(count + 1);
  console.log(count); // 旧值
};

// ✅ 正确:使用函数更新
const increment = () => {
  setCount(prev => {
    const newCount = prev + 1;
    console.log(newCount);
    return newCount;
  });
};

七、总结 #

概念 要点
Props 只读,从父到子传递
State 可变,组件内部管理
状态提升 共享状态放到共同父组件
受控组件 表单由 State 控制

核心原则:

  • Props 是只读的,不要尝试修改
  • State 更新要创建新对象/数组
  • 多组件共享状态时进行状态提升
  • 保持状态最小化,避免冗余
最后更新:2026-03-26