React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React useState 使用

React useState 使用从基础到高级应用示例小结

作者:北辰alk

React 的 useState 是一个内置的 Hook,它允许我们在函数组件中添加和管理状态,本文给大家介绍React useState 使用从基础到高级应用示例小结,感兴趣的朋友一起看看吧

1. 什么是 useState?

React 的 useState 是一个内置的 Hook,它允许我们在函数组件中添加和管理状态。在 React 16.8 之前,函数组件只能作为无状态组件使用,但有了 Hooks 之后,函数组件现在可以拥有和类组件相似的状态管理能力。

2. useState 基础用法

2.1 基本语法

import React, { useState } from 'react';
function ExampleComponent() {
  // 声明一个状态变量count,初始值为0
  // setCount是更新count的函数
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>你点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>
        点击我
      </button>
    </div>
  );
}

2.2 初始化状态的多种方式

useState 可以接受任何类型的值作为初始状态:

function VariousStateTypes() {
  // 数字类型
  const [count, setCount] = useState(0);
  // 字符串类型
  const [name, setName] = useState('张三');
  // 布尔类型
  const [isVisible, setIsVisible] = useState(false);
  // 数组类型
  const [items, setItems] = useState([]);
  // 对象类型
  const [user, setUser] = useState({
    name: '李四',
    age: 25,
    email: 'lisi@example.com'
  });
  // 函数类型(惰性初始化)
  const [value, setValue] = useState(() => {
    // 复杂的初始化逻辑
    const initialValue = localStorage.getItem('myValue');
    return initialValue ? JSON.parse(initialValue) : 'default';
  });
  return <div>示例组件</div>;
}

3. useState 的工作原理

为了更好地理解 useState 的工作原理,让我们看一下它的内部机制流程图:

React 使用闭包链表的方式来管理组件的状态。每个 useState 调用都会在组件的"记忆单元"中创建一个状态节点,这些节点按照调用的顺序被存储。

4. 更新状态的正确方式

4.1 直接更新 vs 函数式更新

function Counter() {
  const [count, setCount] = useState(0);
  // 直接更新(可能有问题)
  const incrementDirect = () => {
    setCount(count + 1);
    setCount(count + 1); // 这不会工作,因为count还是旧值
  };
  // 函数式更新(推荐)
  const incrementFunctional = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1); // 这会正确工作
  };
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={incrementDirect}>直接更新 (+2应该但不会)</button>
      <button onClick={incrementFunctional}>函数式更新 (+2会正确工作)</button>
    </div>
  );
}

4.2 对象和数组的更新

function UserProfile() {
  const [user, setUser] = useState({
    name: '王五',
    age: 30,
    address: {
      city: '北京',
      street: '朝阳路'
    }
  });
  const [list, setList] = useState([1, 2, 3]);
  // 更新对象 - 错误方式(会丢失其他属性)
  const updateNameWrong = (newName) => {
    setUser({ name: newName }); // age和address会丢失!
  };
  // 更新对象 - 正确方式
  const updateNameCorrect = (newName) => {
    setUser(prevUser => ({
      ...prevUser,        // 展开原有属性
      name: newName       // 覆盖需要更新的属性
    }));
  };
  // 更新嵌套对象
  const updateCity = (newCity) => {
    setUser(prevUser => ({
      ...prevUser,
      address: {
        ...prevUser.address, // 展开嵌套对象
        city: newCity        // 更新嵌套属性
      }
    }));
  };
  // 添加数组元素
  const addToList = (item) => {
    setList(prevList => [...prevList, item]); // 使用展开运算符
  };
  // 删除数组元素
  const removeFromList = (index) => {
    setList(prevList => prevList.filter((_, i) => i !== index));
  };
  // 更新数组元素
  const updateListItem = (index, newValue) => {
    setList(prevList => prevList.map((item, i) => 
      i === index ? newValue : item
    ));
  };
  return (
    <div>
      <p>姓名: {user.name}</p>
      <p>城市: {user.address.city}</p>
    </div>
  );
}

5. 常见使用场景和示例

5.1 表单处理

function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  // 处理输入变化
  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prevData => ({
      ...prevData,
      [name]: value
    }));
    // 清除对应字段的错误
    if (errors[name]) {
      setErrors(prevErrors => ({
        ...prevErrors,
        [name]: ''
      }));
    }
  };
  // 表单验证
  const validateForm = () => {
    const newErrors = {};
    if (!formData.name.trim()) {
      newErrors.name = '姓名不能为空';
    }
    if (!formData.email.trim()) {
      newErrors.email = '邮箱不能为空';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = '邮箱格式不正确';
    }
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  // 提交表单
  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!validateForm()) {
      return;
    }
    setIsSubmitting(true);
    try {
      // 模拟API调用
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log('表单提交成功:', formData);
      alert('提交成功!');
      // 重置表单
      setFormData({ name: '', email: '', message: '' });
    } catch (error) {
      console.error('提交失败:', error);
    } finally {
      setIsSubmitting(false);
    }
  };
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>姓名:</label>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleInputChange}
        />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>
      <div>
        <label>邮箱:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleInputChange}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      <div>
        <label>留言:</label>
        <textarea
          name="message"
          value={formData.message}
          onChange={handleInputChange}
        />
      </div>
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '提交中...' : '提交'}
      </button>
    </form>
  );
}

5.2 计数器应用

function AdvancedCounter() {
  const [count, setCount] = useState(0);
  const [history, setHistory] = useState([]);
  const [step, setStep] = useState(1);
  // 增加计数
  const increment = () => {
    setCount(prevCount => {
      const newCount = prevCount + step;
      setHistory(prevHistory => [...prevHistory, {
        type: 'increment',
        from: prevCount,
        to: newCount,
        step: step,
        timestamp: new Date().toISOString()
      }]);
      return newCount;
    });
  };
  // 减少计数
  const decrement = () => {
    setCount(prevCount => {
      const newCount = prevCount - step;
      setHistory(prevHistory => [...prevHistory, {
        type: 'decrement',
        from: prevCount,
        to: newCount,
        step: step,
        timestamp: new Date().toISOString()
      }]);
      return newCount;
    });
  };
  // 重置计数
  const reset = () => {
    setCount(0);
    setHistory(prevHistory => [...prevHistory, {
      type: 'reset',
      from: count,
      to: 0,
      timestamp: new Date().toISOString()
    }]);
  };
  // 清除历史
  const clearHistory = () => {
    setHistory([]);
  };
  return (
    <div>
      <h2>高级计数器</h2>
      <div className="counter-controls">
        <label>
          步长:
          <input
            type="number"
            value={step}
            onChange={(e) => setStep(Number(e.target.value))}
            min="1"
          />
        </label>
        <button onClick={decrement}>-{step}</button>
        <span className="count-value">{count}</span>
        <button onClick={increment}>+{step}</button>
        <button onClick={reset}>重置</button>
      </div>
      <div className="history-section">
        <h3>操作历史</h3>
        <button onClick={clearHistory}>清除历史</button>
        {history.length === 0 ? (
          <p>暂无操作历史</p>
        ) : (
          <ul>
            {history.map((record, index) => (
              <li key={index}>
                {new Date(record.timestamp).toLocaleTimeString()} - 
                从 {record.from} {record.type === 'increment' ? '+' : '-'}
                {record.step || ''} 到 {record.to}
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  );
}

5.3 自定义 Hook 封装 useState

// 自定义 Hook: 使用 localStorage 持久化状态
function useLocalStorageState(key, defaultValue) {
  // 从 localStorage 读取初始值
  const [state, setState] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
      console.error(`Error reading localStorage key "${key}":`, error);
      return defaultValue;
    }
  });
  // 当状态变化时更新 localStorage
  const setPersistedState = (value) => {
    try {
      // 允许值是一个函数(与 useState 相同)
      const valueToStore = value instanceof Function ? value(state) : value;
      setState(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error);
    }
  };
  return [state, setPersistedState];
}
// 使用自定义 Hook
function ThemeSwitcher() {
  const [theme, setTheme] = useLocalStorageState('theme', 'light');
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  return (
    <div className={`app ${theme}`}>
      <h2>当前主题: {theme}</h2>
      <button onClick={toggleTheme}>
        切换主题
      </button>
    </div>
  );
}

6. 性能优化和最佳实践

6.1 避免不必要的重新渲染

// 使用 React.memo 避免不必要的重新渲染
const ExpensiveComponent = React.memo(({ data }) => {
  console.log('ExpensiveComponent 渲染了');
  return <div>数据: {data.value}</div>;
});
function OptimizationExample() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState({ value: '测试数据' });
  // 使用 useCallback 避免函数引用变化
  const updateData = useCallback(() => {
    setData(prevData => ({ ...prevData }));
  }, []);
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>增加计数</button>
      {/* 这个组件不会因为计数变化而重新渲染 */}
      <ExpensiveComponent data={data} />
      <button onClick={updateData}>更新数据</button>
    </div>
  );
}

6.2 使用 useReducer 处理复杂状态逻辑

当状态逻辑变得复杂时,可以考虑使用 useReducer:

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, {
        id: Date.now(),
        text: action.text,
        completed: false
      }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}
function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [inputValue, setInputValue] = useState('');
  const addTodo = () => {
    if (inputValue.trim()) {
      dispatch({ type: 'ADD_TODO', text: inputValue });
      setInputValue('');
    }
  };
  return (
    <div>
      <h2>待办事项</h2>
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && addTodo()}
      />
      <button onClick={addTodo}>添加</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
              onClick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}
            >
              {todo.text}
            </span>
            <button
              onClick={() => dispatch({ type: 'DELETE_TODO', id: todo.id })}
            >
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

7. 常见问题和解决方案

7.1 状态更新后立即使用新值

function ImmediateUpdateProblem() {
  const [count, setCount] = useState(0);
  // 错误:在状态更新后立即使用新值
  const incrementProblematic = () => {
    setCount(count + 1);
    console.log('当前计数:', count); // 这里还是旧值
  };
  // 正确:使用 useEffect 响应状态变化
  useEffect(() => {
    console.log('计数已更新:', count);
  }, [count]);
  // 如果需要基于当前状态进行连续更新,使用函数式更新
  const incrementTwice = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1); // 这会正确工作
  };
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={incrementProblematic}>有问题的方式</button>
      <button onClick={incrementTwice}>增加两次</button>
    </div>
  );
}

7.2 处理异步状态更新

function AsyncStateUpdate() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const fetchData = async () => {
    setLoading(true);
    setError(null);
    try {
      // 模拟API调用
      const response = await new Promise((resolve, reject) => {
        setTimeout(() => {
          Math.random() > 0.2 
            ? resolve({ message: '数据获取成功!' })
            : reject(new Error('获取数据失败'));
        }, 1000);
      });
      setData(response);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };
  return (
    <div>
      <button onClick={fetchData} disabled={loading}>
        {loading ? '加载中...' : '获取数据'}
      </button>
      {error && <div className="error">错误: {error}</div>}
      {data && <div>数据: {data.message}</div>}
    </div>
  );
}

8. 总结

React 的 useState Hook 是函数组件状态管理的核心工具。通过本文的学习,你应该已经掌握了:

  1. useState 的基本用法和语法
  2. 如何正确更新各种类型的状态(数字、字符串、对象、数组)
  3. 状态更新的最佳实践(函数式更新、不可变数据)
  4. 常见使用场景和示例(表单处理、计数器等)
  5. 性能优化技巧和自定义 Hook
  6. 常见问题的解决方案

记住,useState 虽然简单易用,但要真正掌握它需要理解其工作原理和最佳实践。随着你对 React 的深入使用,你会发现 useState 与其他 Hook(如 useEffect、useContext、useReducer)的组合使用能解决各种复杂的状态管理问题。

继续练习并在实际项目中应用这些知识,你会逐渐成为 React 状态管理的高手!

到此这篇关于React useState 使用详解:从基础到高级应用的文章就介绍到这了,更多相关React useState 使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文