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