Skip to content

useDeferredValue改进

学习目标

通过本章学习,你将掌握:

  • React 19中useDeferredValue的改进
  • 初始值参数
  • 与React 18的对比
  • 实际应用场景
  • 性能优化技巧
  • 与其他Hook配合
  • 最佳实践
  • 常见问题解决

第一部分:React 18的useDeferredValue

1.1 基础用法

jsx
// React 18的useDeferredValue
import { useState, useDeferredValue } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  
  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      
      <Results query={deferredQuery} />
    </div>
  );
}

function Results({ query }) {
  // 使用deferredQuery进行搜索
  // 当query快速变化时,这个组件不会立即更新
  const results = searchData(query);
  
  return (
    <ul>
      {results.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

1.2 初始渲染问题

jsx
// ❌ React 18:初始渲染时没有值
function SearchResults() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  
  console.log('query:', query);
  console.log('deferredQuery:', deferredQuery);
  
  // 首次渲染:
  // query: ''
  // deferredQuery: ''
  
  // 问题:初始状态下,deferredQuery没有特殊处理
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results query={deferredQuery} />
    </div>
  );
}

1.3 加载状态处理

jsx
// ❌ React 18:需要手动处理加载状态
function SearchResults() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  
  // 手动检测是否正在defer
  const isPending = query !== deferredQuery;
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      
      <div style={{ opacity: isPending ? 0.5 : 1 }}>
        <Results query={deferredQuery} />
      </div>
      
      {isPending && <div>加载中...</div>}
    </div>
  );
}

第二部分:React 19的改进

2.1 初始值参数

jsx
// ✅ React 19:可以指定初始值
import { useState, useDeferredValue } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  
  // 第二个参数是初始值
  const deferredQuery = useDeferredValue(query, '');
  
  console.log('首次渲染:');
  console.log('query:', query);
  console.log('deferredQuery:', deferredQuery);
  
  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      
      <Results query={deferredQuery} />
    </div>
  );
}

2.2 默认值的作用

jsx
// ✅ 使用有意义的默认值
function ProductFilter() {
  const [category, setCategory] = useState('all');
  
  // 提供默认值'all'
  const deferredCategory = useDeferredValue(category, 'all');
  
  return (
    <div>
      <select value={category} onChange={(e) => setCategory(e.target.value)}>
        <option value="all">全部</option>
        <option value="electronics">电子产品</option>
        <option value="clothing">服装</option>
      </select>
      
      <ProductList category={deferredCategory} />
    </div>
  );
}

function ProductList({ category }) {
  const products = getProductsByCategory(category);
  
  // 首次渲染时,category已经是'all',而不是undefined
  
  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
}

2.3 避免初始闪烁

jsx
// ✅ 防止初始加载状态闪烁
function DataTable() {
  const [filter, setFilter] = useState('');
  const deferredFilter = useDeferredValue(filter, '');
  
  const isPending = filter !== deferredFilter;
  
  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="过滤数据..."
      />
      
      {/* 不会在首次渲染时显示加载状态 */}
      {isPending && <div>更新中...</div>}
      
      <Table filter={deferredFilter} />
    </div>
  );
}

第三部分:实际应用场景

3.1 搜索功能

jsx
// ✅ 优化的搜索体验
function Search() {
  const [searchTerm, setSearchTerm] = useState('');
  const deferredSearchTerm = useDeferredValue(searchTerm, '');
  
  const isPending = searchTerm !== deferredSearchTerm;
  
  return (
    <div className="search-container">
      <div className="search-input-wrapper">
        <input
          type="search"
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          placeholder="搜索文章、用户、标签..."
        />
        {isPending && <Spinner className="input-spinner" />}
      </div>
      
      <div className={isPending ? 'results-pending' : ''}>
        <SearchResults query={deferredSearchTerm} />
      </div>
    </div>
  );
}

function SearchResults({ query }) {
  if (!query) {
    return <div className="empty-state">输入关键词开始搜索</div>;
  }
  
  const results = performSearch(query);
  
  return (
    <div className="results">
      {results.map(result => (
        <ResultCard key={result.id} result={result} />
      ))}
    </div>
  );
}

3.2 数据筛选

jsx
// ✅ 大数据量筛选
function DataGrid() {
  const [filters, setFilters] = useState({
    category: 'all',
    priceRange: 'all',
    rating: 'all'
  });
  
  // 对每个filter使用deferredValue
  const deferredFilters = {
    category: useDeferredValue(filters.category, 'all'),
    priceRange: useDeferredValue(filters.priceRange, 'all'),
    rating: useDeferredValue(filters.rating, 'all')
  };
  
  const isPending = JSON.stringify(filters) !== JSON.stringify(deferredFilters);
  
  const handleFilterChange = (key, value) => {
    setFilters(prev => ({ ...prev, [key]: value }));
  };
  
  return (
    <div>
      <div className="filters">
        <FilterSelect
          label="分类"
          value={filters.category}
          onChange={(value) => handleFilterChange('category', value)}
          options={categoryOptions}
        />
        
        <FilterSelect
          label="价格"
          value={filters.priceRange}
          onChange={(value) => handleFilterChange('priceRange', value)}
          options={priceOptions}
        />
        
        <FilterSelect
          label="评分"
          value={filters.rating}
          onChange={(value) => handleFilterChange('rating', value)}
          options={ratingOptions}
        />
      </div>
      
      <div className={isPending ? 'grid-loading' : ''}>
        <ProductGrid filters={deferredFilters} />
      </div>
    </div>
  );
}

3.3 实时预览

jsx
// ✅ Markdown编辑器实时预览
function MarkdownEditor() {
  const [markdown, setMarkdown] = useState('# Hello\n\nStart typing...');
  const deferredMarkdown = useDeferredValue(markdown, '');
  
  const isPending = markdown !== deferredMarkdown;
  
  return (
    <div className="editor-container">
      <div className="editor-pane">
        <textarea
          value={markdown}
          onChange={(e) => setMarkdown(e.target.value)}
          placeholder="输入Markdown..."
        />
      </div>
      
      <div className="preview-pane">
        <div className="preview-header">
          预览
          {isPending && <span className="badge">更新中...</span>}
        </div>
        <div className={isPending ? 'preview-updating' : ''}>
          <MarkdownPreview content={deferredMarkdown} />
        </div>
      </div>
    </div>
  );
}

function MarkdownPreview({ content }) {
  // 渲染Markdown可能很慢
  const html = renderMarkdown(content);
  
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

第四部分:性能优化

4.1 与memo配合

jsx
// ✅ 结合React.memo优化
import { memo } from 'react';

const ExpensiveList = memo(function ExpensiveList({ items }) {
  console.log('ExpensiveList渲染');
  
  return (
    <ul>
      {items.map(item => (
        <ExpensiveItem key={item.id} item={item} />
      ))}
    </ul>
  );
});

function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query, '');
  
  const filteredItems = useMemo(() => {
    return items.filter(item => 
      item.name.toLowerCase().includes(deferredQuery.toLowerCase())
    );
  }, [deferredQuery]);
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <ExpensiveList items={filteredItems} />
    </div>
  );
}

4.2 与Suspense配合

jsx
// ✅ 与Suspense配合使用
import { Suspense } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query, '');
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      
      <Suspense fallback={<SearchSkeleton />}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </div>
  );
}

4.3 与startTransition配合

jsx
// ✅ 组合使用
import { useState, useDeferredValue, startTransition } from 'react';

function TabsWithSearch() {
  const [tab, setTab] = useState('all');
  const [query, setQuery] = useState('');
  
  const deferredQuery = useDeferredValue(query, '');
  
  const handleTabChange = (newTab) => {
    startTransition(() => {
      setTab(newTab);
    });
  };
  
  return (
    <div>
      <Tabs activeTab={tab} onChange={handleTabChange} />
      
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      
      <Results tab={tab} query={deferredQuery} />
    </div>
  );
}

第五部分:与React 18对比

5.1 API对比

jsx
// React 18
const deferred = useDeferredValue(value);

// React 19
const deferred = useDeferredValue(value, initialValue);

5.2 行为对比

jsx
// React 18
function Component() {
  const [state, setState] = useState('hello');
  const deferred = useDeferredValue(state);
  
  // 首次渲染:state === deferred === 'hello'
  // 没有特殊的初始值处理
}

// React 19
function Component() {
  const [state, setState] = useState('hello');
  const deferred = useDeferredValue(state, 'default');
  
  // 首次渲染:可以指定初始值
  // 更灵活的控制
}

5.3 迁移指南

jsx
// 检查是否需要迁移
function SearchComponent() {
  const [query, setQuery] = useState('');
  
  // React 18
  const deferredQuery = useDeferredValue(query);
  
  // React 19(可选)
  // 如果需要特殊的初始值
  const deferredQuery = useDeferredValue(query, '');
  
  // 如果不需要初始值,旧代码仍然有效
  return <Results query={deferredQuery} />;
}

第六部分:高级应用场景

6.1 复杂表单验证

jsx
// ✅ 实时表单验证with防抖效果
function FormWithValidation() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: ''
  });
  
  // 使用deferred value延迟验证
  const deferredFormData = {
    username: useDeferredValue(formData.username, ''),
    email: useDeferredValue(formData.email, ''),
    password: useDeferredValue(formData.password, '')
  };
  
  const isPending = JSON.stringify(formData) !== JSON.stringify(deferredFormData);
  
  // 验证逻辑only在deferred value变化时执行
  const validationErrors = useMemo(() => {
    return validateForm(deferredFormData);
  }, [deferredFormData]);
  
  return (
    <form>
      <div>
        <input
          value={formData.username}
          onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value }))}
          placeholder="用户名"
        />
        {!isPending && validationErrors.username && (
          <span className="error">{validationErrors.username}</span>
        )}
      </div>
      
      <div>
        <input
          type="email"
          value={formData.email}
          onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
          placeholder="邮箱"
        />
        {!isPending && validationErrors.email && (
          <span className="error">{validationErrors.email}</span>
        )}
      </div>
      
      <div>
        <input
          type="password"
          value={formData.password}
          onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
          placeholder="密码"
        />
        {!isPending && validationErrors.password && (
          <span className="error">{validationErrors.password}</span>
        )}
        {isPending && <span className="validating">验证中...</span>}
      </div>
    </form>
  );
}

6.2 图表实时更新

jsx
// ✅ 延迟图表数据更新
import { LineChart, Line, XAxis, YAxis } from 'recharts';

function RealTimeChart() {
  const [dataPoints, setDataPoints] = useState([]);
  const deferredData = useDeferredValue(dataPoints, []);
  
  const isPending = dataPoints.length !== deferredData.length;
  
  // 模拟实时数据流
  useEffect(() => {
    const interval = setInterval(() => {
      setDataPoints(prev => [
        ...prev,
        { timestamp: Date.now(), value: Math.random() * 100 }
      ].slice(-50)); // 只保留最近50个点
    }, 100);
    
    return () => clearInterval(interval);
  }, []);
  
  return (
    <div>
      <div className="chart-header">
        实时数据
        {isPending && <span className="badge">更新中...</span>}
      </div>
      <LineChart width={600} height={300} data={deferredData}>
        <XAxis dataKey="timestamp" />
        <YAxis />
        <Line type="monotone" dataKey="value" stroke="#8884d8" />
      </LineChart>
    </div>
  );
}

6.3 虚拟列表优化

jsx
// ✅ 虚拟列表with延迟滚动
import { FixedSizeList } from 'react-window';

function VirtualizedList() {
  const [filter, setFilter] = useState('');
  const deferredFilter = useDeferredValue(filter, '');
  
  const allItems = useMemo(() => {
    // 假设有10000个项目
    return Array.from({ length: 10000 }, (_, i) => ({
      id: i,
      name: `Item ${i}`,
      description: `Description for item ${i}`
    }));
  }, []);
  
  const filteredItems = useMemo(() => {
    if (!deferredFilter) return allItems;
    
    return allItems.filter(item =>
      item.name.toLowerCase().includes(deferredFilter.toLowerCase()) ||
      item.description.toLowerCase().includes(deferredFilter.toLowerCase())
    );
  }, [deferredFilter, allItems]);
  
  const isPending = filter !== deferredFilter;
  
  const Row = ({ index, style }) => {
    const item = filteredItems[index];
    return (
      <div style={style} className="list-item">
        <h3>{item.name}</h3>
        <p>{item.description}</p>
      </div>
    );
  };
  
  return (
    <div>
      <div className="filter-bar">
        <input
          type="search"
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
          placeholder="过滤10000个项目..."
        />
        {isPending && <span className="loading">过滤中...</span>}
      </div>
      
      <div className="results-info">
        找到 {filteredItems.length} 个结果
      </div>
      
      <FixedSizeList
        height={600}
        itemCount={filteredItems.length}
        itemSize={80}
        width="100%"
      >
        {Row}
      </FixedSizeList>
    </div>
  );
}

6.4 代码编辑器语法高亮

jsx
// ✅ 延迟语法高亮
import Prism from 'prismjs';
import 'prismjs/themes/prism-tomorrow.css';

function CodeEditor() {
  const [code, setCode] = useState('const hello = "world";');
  const deferredCode = useDeferredValue(code, '');
  
  const highlightedCode = useMemo(() => {
    // 语法高亮是昂贵的操作
    return Prism.highlight(deferredCode, Prism.languages.javascript, 'javascript');
  }, [deferredCode]);
  
  const isPending = code !== deferredCode;
  
  return (
    <div className="code-editor">
      <textarea
        value={code}
        onChange={(e) => setCode(e.target.value)}
        className="code-input"
        spellCheck={false}
      />
      
      <div className="code-preview">
        <div className="preview-header">
          预览
          {isPending && <span className="badge">高亮中...</span>}
        </div>
        <pre className={isPending ? 'preview-updating' : ''}>
          <code dangerouslySetInnerHTML={{ __html: highlightedCode }} />
        </pre>
      </div>
    </div>
  );
}

6.5 实时协作编辑

jsx
// ✅ 多人协作编辑with冲突解决
function CollaborativeEditor() {
  const [localContent, setLocalContent] = useState('');
  const [remoteContent, setRemoteContent] = useState('');
  
  // 延迟本地内容的同步,减少网络请求
  const deferredLocalContent = useDeferredValue(localContent, '');
  
  const isSyncing = localContent !== deferredLocalContent;
  
  // 同步到服务器
  useEffect(() => {
    if (deferredLocalContent) {
      syncToServer(deferredLocalContent);
    }
  }, [deferredLocalContent]);
  
  // 接收远程更新
  useEffect(() => {
    const ws = new WebSocket('ws://example.com/collab');
    
    ws.onmessage = (event) => {
      const update = JSON.parse(event.data);
      setRemoteContent(update.content);
      
      // 合并远程更新(简化版)
      if (!isSyncing) {
        setLocalContent(update.content);
      }
    };
    
    return () => ws.close();
  }, [isSyncing]);
  
  return (
    <div className="collaborative-editor">
      <div className="editor-header">
        协作编辑器
        {isSyncing && <span className="badge">同步中...</span>}
      </div>
      
      <textarea
        value={localContent}
        onChange={(e) => setLocalContent(e.target.value)}
        placeholder="开始输入..."
      />
      
      <div className="collaborators">
        <div className="collaborator">用户A 正在编辑</div>
        <div className="collaborator">用户B 正在查看</div>
      </div>
    </div>
  );
}

6.6 地图搜索与标记

jsx
// ✅ 地图搜索with延迟标记更新
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';

function MapSearch() {
  const [searchQuery, setSearchQuery] = useState('');
  const deferredQuery = useDeferredValue(searchQuery, '');
  
  const markers = useMemo(() => {
    // 搜索和过滤标记点(可能很多)
    return searchLocations(deferredQuery);
  }, [deferredQuery]);
  
  const isSearching = searchQuery !== deferredQuery;
  
  return (
    <div className="map-search">
      <div className="search-bar">
        <input
          type="search"
          value={searchQuery}
          onChange={(e) => setSearchQuery(e.target.value)}
          placeholder="搜索位置..."
        />
        {isSearching && <span className="searching">搜索中...</span>}
        <span className="results-count">
          找到 {markers.length} 个位置
        </span>
      </div>
      
      <MapContainer center={[51.505, -0.09]} zoom={13} style={{ height: '500px' }}>
        <TileLayer
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          attribution='&copy; OpenStreetMap contributors'
        />
        {markers.map(marker => (
          <Marker key={marker.id} position={marker.position}>
            <Popup>{marker.name}</Popup>
          </Marker>
        ))}
      </MapContainer>
    </div>
  );
}

第七部分:TypeScript支持

7.1 基本类型定义

tsx
// ✅ TypeScript中的useDeferredValue
import { useState, useDeferredValue } from 'react';

function TypedComponent() {
  const [query, setQuery] = useState<string>('');
  
  // TypeScript会推断deferredQuery的类型为string
  const deferredQuery = useDeferredValue(query, '');
  
  return <Results query={deferredQuery} />;
}

// 复杂类型
interface SearchFilters {
  query: string;
  category: string;
  priceRange: [number, number];
}

function ComplexTypedComponent() {
  const [filters, setFilters] = useState<SearchFilters>({
    query: '',
    category: 'all',
    priceRange: [0, 1000]
  });
  
  // 初始值类型必须匹配
  const deferredFilters = useDeferredValue<SearchFilters>(filters, {
    query: '',
    category: 'all',
    priceRange: [0, 1000]
  });
  
  return <Products filters={deferredFilters} />;
}

7.2 泛型Hook

tsx
// ✅ 创建泛型useDeferredValue wrapper
function useDeferredState<T>(initialValue: T): [T, T, (value: T) => void, boolean] {
  const [state, setState] = useState<T>(initialValue);
  const deferredState = useDeferredValue(state, initialValue);
  const isPending = state !== deferredState;
  
  return [state, deferredState, setState, isPending];
}

// 使用
function SearchComponent() {
  const [query, deferredQuery, setQuery, isPending] = useDeferredState('');
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {isPending && <Spinner />}
      <Results query={deferredQuery} />
    </div>
  );
}

7.3 类型安全的初始值

tsx
// ✅ 确保初始值类型安全
interface User {
  id: number;
  name: string;
  email: string;
}

function UserSearch() {
  const [selectedUser, setSelectedUser] = useState<User | null>(null);
  
  // TypeScript会检查初始值类型
  const deferredUser = useDeferredValue<User | null>(selectedUser, null);
  
  return (
    <div>
      <UserPicker onSelect={setSelectedUser} />
      {deferredUser && <UserProfile user={deferredUser} />}
    </div>
  );
}

注意事项

1. 初始值类型要匹配

jsx
// ✅ 类型匹配
const [count, setCount] = useState(0);
const deferredCount = useDeferredValue(count, 0);

// ❌ 类型不匹配
const [count, setCount] = useState(0);
const deferredCount = useDeferredValue(count, '0');  // 错误!

// ✅ 复杂类型也要匹配
const [obj, setObj] = useState({ name: 'Alice', age: 30 });
const deferredObj = useDeferredValue(obj, { name: '', age: 0 });

// ❌ 结构不匹配
const deferredObj = useDeferredValue(obj, null);  // 可能引起错误

2. 不要过度使用

jsx
// ❌ 不必要的defer
function SimpleComponent() {
  const [name, setName] = useState('');
  const deferredName = useDeferredValue(name, '');
  
  // 如果渲染不复杂,不需要defer
  return <div>{deferredName}</div>;
}

// ✅ 只在渲染开销大时使用
function ExpensiveComponent() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query, '');
  
  // 复杂的列表渲染
  return <HugeList query={deferredQuery} />;
}

// ✅ 判断是否需要defer的标准
// 1. 渲染时间超过50ms
// 2. 有明显的UI卡顿
// 3. 用户输入时感到延迟

3. 注意初始值的选择

jsx
// ✅ 合理的初始值
const deferredValue = useDeferredValue(value, defaultValue);

// ❌ 无意义的初始值
const deferredValue = useDeferredValue(value, null);  // 可能引起错误

// ✅ 根据场景选择初始值
// 搜索:空字符串
const deferredQuery = useDeferredValue(query, '');

// 列表过滤:空数组
const deferredFilters = useDeferredValue(filters, []);

// 用户选择:null或undefined
const deferredUser = useDeferredValue(user, null);

// 数值范围:最小值或0
const deferredValue = useDeferredValue(value, 0);

4. 注意pending状态的使用

jsx
// ✅ 正确使用pending状态
function Component() {
  const [value, setValue] = useState('');
  const deferredValue = useDeferredValue(value, '');
  const isPending = value !== deferredValue;
  
  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      
      {/* ✅ 显示pending状态 */}
      {isPending && <LoadingIndicator />}
      
      {/* ✅ 降低透明度 */}
      <div style={{ opacity: isPending ? 0.5 : 1 }}>
        <Results data={deferredValue} />
      </div>
    </div>
  );
}

// ❌ 不要阻止用户输入
function BadComponent() {
  const [value, setValue] = useState('');
  const deferredValue = useDeferredValue(value, '');
  const isPending = value !== deferredValue;
  
  return (
    <div>
      {/* ❌ 错误:禁用输入会影响用户体验 */}
      <input 
        value={value} 
        onChange={(e) => setValue(e.target.value)}
        disabled={isPending}  // 不要这样做!
      />
    </div>
  );
}

5. 与Server Components配合

jsx
// ✅ 在Client Component中使用
'use client';

function ClientSearch() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query, '');
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results query={deferredQuery} />
    </div>
  );
}

// ❌ Server Component不能使用hooks
// 这不会工作
export default function ServerSearch() {
  const [query, setQuery] = useState('');  // 错误!
  const deferredQuery = useDeferredValue(query, '');  // 错误!
  
  return <div>{deferredQuery}</div>;
}

6. 性能监控

jsx
// ✅ 监控defer的性能影响
function MonitoredComponent() {
  const [value, setValue] = useState('');
  const deferredValue = useDeferredValue(value, '');
  
  const isPending = value !== deferredValue;
  
  // 记录defer时间
  useEffect(() => {
    if (isPending) {
      const start = performance.now();
      
      return () => {
        const duration = performance.now() - start;
        console.log(`Defer duration: ${duration}ms`);
        
        // 发送到分析服务
        analytics.track('defer_duration', { duration });
      };
    }
  }, [isPending]);
  
  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <ExpensiveComponent data={deferredValue} />
    </div>
  );
}

7. 与并发特性配合

jsx
// ✅ 与Suspense、startTransition等配合
import { Suspense, startTransition } from 'react';

function ConcurrentDemo() {
  const [tab, setTab] = useState('posts');
  const [query, setQuery] = useState('');
  
  const deferredQuery = useDeferredValue(query, '');
  
  const handleTabChange = (newTab) => {
    startTransition(() => {
      setTab(newTab);
    });
  };
  
  return (
    <div>
      <Tabs value={tab} onChange={handleTabChange} />
      
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      
      <Suspense fallback={<Skeleton />}>
        <TabContent tab={tab} query={deferredQuery} />
      </Suspense>
    </div>
  );
}

常见问题

Q1: 什么时候应该使用useDeferredValue?

A: 当状态变化导致大量渲染开销时使用。判断标准:

使用场景:

  • 实时搜索:搜索结果列表很长
  • 大列表过滤:超过1000项的列表
  • 实时预览:Markdown、代码高亮等
  • 复杂计算:数据分析、图表渲染
  • 表单验证:复杂的实时验证逻辑
jsx
// 示例:判断是否需要defer
function Component() {
  const [query, setQuery] = useState('');
  
  // 测量渲染时间
  const startTime = performance.now();
  const results = heavyComputation(query);
  const renderTime = performance.now() - startTime;
  
  // 如果渲染时间超过50ms,考虑使用defer
  console.log(`Render time: ${renderTime}ms`);
  
  // 如果renderTime > 50ms,应该使用:
  const deferredQuery = useDeferredValue(query, '');
  const results = heavyComputation(deferredQuery);
  
  return <ResultsList results={results} />;
}

不需要使用的场景:

  • 简单的文本显示
  • 少于100项的列表
  • 渲染时间小于16ms(一帧的时间)

Q2: useDeferredValue和useTransition有什么区别?

A: 两者都用于优化性能,但用途不同:

特性useDeferredValueuseTransition
用途延迟值的更新标记更新为非紧急
控制对象特定的值状态更新函数
返回值延迟的值[isPending, startTransition]
使用场景你有一个值需要延迟你要执行非紧急更新
jsx
// useDeferredValue:延迟值
function SearchWithDeferred() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query, '');  // 延迟query的值
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results query={deferredQuery} />  {/* 使用延迟的值 */}
    </div>
  );
}

// useTransition:标记更新
function SearchWithTransition() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    startTransition(() => {
      setQuery(value);  // 标记这个更新为非紧急
    });
  };
  
  return (
    <div>
      <input onChange={handleChange} />  {/* input不受影响 */}
      {isPending && <Spinner />}
      <Results query={query} />
    </div>
  );
}

选择建议:

  • 如果你控制状态更新的代码:使用useTransition
  • 如果你不控制状态(props传入):使用useDeferredValue
  • 两者可以组合使用

Q3: 初始值是必须的吗?

A: 不是,React 19中初始值是可选的,为了向后兼容:

jsx
// React 18和React 19都支持
const deferred = useDeferredValue(value);  // 没有初始值

// React 19新增
const deferred = useDeferredValue(value, initialValue);  // 有初始值

// 实际使用建议
function Component() {
  const [query, setQuery] = useState('');
  
  // 如果不需要特殊的初始值,可以不提供
  const deferred1 = useDeferredValue(query);
  
  // 如果想避免初始渲染的问题,提供初始值
  const deferred2 = useDeferredValue(query, '');
  
  // 两种方式都可以,根据具体需求选择
}

Q4: 如何决定初始值?

A: 初始值应该与state的初始值或默认状态保持一致:

jsx
// 原则:初始值应该是"安全"的值

// 字符串:空字符串
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query, '');

// 数字:0或最小值
const [count, setCount] = useState(0);
const deferredCount = useDeferredValue(count, 0);

// 布尔值:false或默认状态
const [enabled, setEnabled] = useState(false);
const deferredEnabled = useDeferredValue(enabled, false);

// 数组:空数组
const [items, setItems] = useState([]);
const deferredItems = useDeferredValue(items, []);

// 对象:空对象或默认对象
const [filters, setFilters] = useState({ category: 'all', price: [0, 1000] });
const deferredFilters = useDeferredValue(filters, { category: 'all', price: [0, 1000] });

// null/undefined:保持一致
const [user, setUser] = useState(null);
const deferredUser = useDeferredValue(user, null);

Q5: useDeferredValue会影响首次渲染吗?

A: React 19中不会,如果提供了初始值:

jsx
// React 18:可能有初始渲染问题
function Component() {
  const [value, setValue] = useState('hello');
  const deferred = useDeferredValue(value);  // 首次渲染:deferred === 'hello'
  
  // 首次渲染没有问题,因为value和deferred都是'hello'
}

// React 19:更明确的控制
function Component() {
  const [value, setValue] = useState('hello');
  const deferred = useDeferredValue(value, 'default');  // 首次渲染:deferred === 'default'
  
  // 如果需要,可以提供不同的初始值
}

// 实际场景
function SearchResults() {
  const [query, setQuery] = useState('');
  const deferred = useDeferredValue(query, '');
  
  // 首次渲染:query === '' && deferred === ''
  // 不会触发不必要的加载状态
  const isPending = query !== deferred;  // false
  
  return (
    <div>
      {isPending && <Loading />}  {/* 首次不显示 */}
      <Results query={deferred} />
    </div>
  );
}

Q6: 如何测试使用useDeferredValue的组件?

A: 使用React Testing Library,注意异步行为:

jsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('defers value updates', async () => {
  const user = userEvent.setup();
  
  render(<SearchComponent />);
  
  const input = screen.getByPlaceholderText('搜索...');
  
  // 输入搜索词
  await user.type(input, 'react');
  
  // 立即检查:input值已更新
  expect(input).toHaveValue('react');
  
  // 但deferred值可能还没更新,需要等待
  await waitFor(() => {
    expect(screen.getByText(/找到.*结果/)).toBeInTheDocument();
  });
});

test('shows pending state', async () => {
  const user = userEvent.setup();
  
  render(<SearchComponent />);
  
  const input = screen.getByPlaceholderText('搜索...');
  
  await user.type(input, 'test');
  
  // 检查pending状态
  expect(screen.getByText('搜索中...')).toBeInTheDocument();
  
  // 等待pending结束
  await waitFor(() => {
    expect(screen.queryByText('搜索中...')).not.toBeInTheDocument();
  });
});

Q7: useDeferredValue和防抖(debounce)有什么区别?

A: 两者目的不同:

特性useDeferredValue防抖(debounce)
机制React调度系统定时器
响应性立即响应用户输入延迟响应
取消自动中断旧渲染手动清除定时器
用户体验更流畅可能有延迟感
适用场景UI更新优化API请求节流
jsx
// useDeferredValue:优化渲染
function WithDeferred() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query, '');
  
  // 输入立即响应,但渲染可以延迟
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <HeavyList query={deferredQuery} />  {/* 延迟渲染 */}
    </div>
  );
}

// 防抖:延迟执行
function WithDebounce() {
  const [query, setQuery] = useState('');
  const [debouncedQuery, setDebouncedQuery] = useState('');
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedQuery(query);  // 500ms后才更新
    }, 500);
    
    return () => clearTimeout(timer);
  }, [query]);
  
  // 输入也会感到延迟
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results query={debouncedQuery} />  {/* 延迟500ms */}
    </div>
  );
}

// 组合使用:最佳实践
function BestPractice() {
  const [query, setQuery] = useState('');
  const [debouncedQuery, setDebouncedQuery] = useState('');
  
  // 防抖用于API请求
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedQuery(query);
    }, 300);
    return () => clearTimeout(timer);
  }, [query]);
  
  // useDeferredValue用于本地UI渲染
  const deferredQuery = useDeferredValue(query, '');
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      
      {/* 本地过滤:使用deferred */}
      <LocalResults query={deferredQuery} />
      
      {/* API搜索:使用debounced */}
      <APIResults query={debouncedQuery} />
    </div>
  );
}

Q8: 多个useDeferredValue会互相影响吗?

A: 不会,每个useDeferredValue独立工作:

jsx
function MultipleDeferred() {
  const [query, setQuery] = useState('');
  const [category, setCategory] = useState('all');
  const [sort, setSort] = useState('name');
  
  // 三个独立的deferred值
  const deferredQuery = useDeferredValue(query, '');
  const deferredCategory = useDeferredValue(category, 'all');
  const deferredSort = useDeferredValue(sort, 'name');
  
  // 每个都可以独立延迟
  const queryPending = query !== deferredQuery;
  const categoryPending = category !== deferredCategory;
  const sortPending = sort !== deferredSort;
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {queryPending && <span>搜索中...</span>}
      
      <select value={category} onChange={(e) => setCategory(e.target.value)}>
        <option value="all">全部</option>
        <option value="tech">技术</option>
      </select>
      {categoryPending && <span>过滤中...</span>}
      
      <select value={sort} onChange={(e) => setSort(e.target.value)}>
        <option value="name">名称</option>
        <option value="date">日期</option>
      </select>
      {sortPending && <span>排序中...</span>}
      
      <Results 
        query={deferredQuery} 
        category={deferredCategory} 
        sort={deferredSort} 
      />
    </div>
  );
}

Q9: 在Server Components中可以使用useDeferredValue吗?

A: 不可以,所有Hooks只能在Client Components中使用:

jsx
// ❌ Server Component(不能使用hooks)
export default function ServerPage() {
  const data = await fetchData();
  const deferred = useDeferredValue(data);  // 错误!
  
  return <div>{deferred}</div>;
}

// ✅ Client Component
'use client';

export function ClientSearch() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query, '');
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results query={deferredQuery} />
    </div>
  );
}

// ✅ 组合使用
// app/page.tsx (Server Component)
export default function Page() {
  return (
    <div>
      <h1>搜索页面</h1>
      <ClientSearch />  {/* Client Component处理交互 */}
    </div>
  );
}

Q10: useDeferredValue的性能开销大吗?

A: 开销很小,React高效处理:

jsx
// 性能对比
function PerformanceComparison() {
  const [value, setValue] = useState('');
  
  // 不使用defer:每次都重渲染
  const withoutDefer = () => {
    return <HeavyComponent value={value} />;  // 可能卡顿
  };
  
  // 使用defer:React智能调度
  const deferredValue = useDeferredValue(value, '');
  const withDefer = () => {
    return <HeavyComponent value={deferredValue} />;  // 流畅
  };
  
  // defer的开销主要是:
  // 1. 一次额外的state比较
  // 2. React调度系统的开销
  // 这些开销远小于避免的重渲染开销
}

// 测量实际影响
function MeasuredComponent() {
  const [value, setValue] = useState('');
  const deferredValue = useDeferredValue(value, '');
  
  // 测量渲染时间
  useEffect(() => {
    console.log('Component rendered');
    performance.mark('render-end');
  });
  
  performance.mark('render-start');
  
  return <HeavyComponent value={deferredValue} />;
}

总结

React 19改进要点

✅ 可选的初始值参数
✅ 更灵活的控制
✅ 避免初始闪烁
✅ 向后兼容
✅ 更好的TypeScript支持

使用场景

✅ 实时搜索
✅ 大数据筛选
✅ 实时预览
✅ 复杂列表
✅ 图表更新
✅ 文本格式化

最佳实践

✅ 只在必要时使用
✅ 提供合理的初始值
✅ 结合memo优化
✅ 显示pending状态
✅ 配合Suspense使用
✅ 测试性能改进

useDeferredValue的改进让性能优化更加灵活和强大!