useMemo5种官方推荐使用场景

154 阅读2分钟

react18官方文档

useMemo

useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。

const cachedValue = useMemo(calculateValue, dependencies)

// 1. 跳过代价昂贵的重新计算

import React, { useState, useMemo } from 'react';

function ExpensiveCalculationExample() {
    const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);
    const [count, setCount] = useState(0);

    // 使用 useMemo 缓存计算结果
    const expensiveSum = useMemo(() => {
        console.log('Computing expensive sum...');
        return numbers.reduce((acc, curr) => {
            // 模拟耗时计算
            for(let i = 0; i < 1000000; i++) {}
            return acc + curr;
        }, 0);
    }, [numbers]); // 只在 numbers 改变时重新计算

    return (
        <div>
            <p>Sum: {expensiveSum}</p>
            <button onClick={() => setCount(c => c + 1)}>
                Increment Count: {count}
            </button>
            <button onClick={() => setNumbers([...numbers, numbers.length + 1])}>
                Add Number
            </button>
        </div>
    );
}

// 2. 跳过组件的重新渲染
function MemoizedChildComponent({ data }) {
    console.log('Child component rendered');
    return <div>{data.value}</div>;
}

function ParentComponent() {
    const [count, setCount] = useState(0);
    const [value, setValue] = useState('Hello');

    // 使用 useMemo 缓存对象
    const memoizedData = useMemo(() => ({
        value: value
    }), [value]);

    return (
        <div>
            <button onClick={() => setCount(c => c + 1)}>
                Count: {count}
            </button>
            <MemoizedChildComponent data={memoizedData} />
        </div>
    );
}

// 3. 防止过于频繁地触发 Effect
function SearchComponent() {
    const [searchTerm, setSearchTerm] = useState('');
    const [results, setResults] = useState([]);
    const [filters, setFilters] = useState({ category: 'all', price: 'any' });

    // 使用 useMemo 缓存搜索条件
    const searchConfig = useMemo(() => ({
        term: searchTerm,
        filters: filters
    }), [searchTerm, filters]);

    useEffect(() => {
        const fetchResults = async () => {
            const response = await fetch(
                `/api/search?term=${searchConfig.term}&filters=${JSON.stringify(searchConfig.filters)}`
            );
            const data = await response.json();
            setResults(data);
        };
        fetchResults();
    }, [searchConfig]); // 只在搜索配置改变时触发

    return (
        <div>
            <input 
                value={searchTerm}
                onChange={e => setSearchTerm(e.target.value)}
            />
            <select 
                value={filters.category}
                onChange={e => setFilters({...filters, category: e.target.value})}
            >
                <option value="all">All</option>
                <option value="electronics">Electronics</option>
                <option value="books">Books</option>
            </select>
            <ul>
                {results.map(result => (
                    <li key={result.id}>{result.title}</li>
                ))}
            </ul>
        </div>
    );
}

// 4. 记忆另一个 Hook 的依赖
function DataVisualization() {
    const [data, setData] = useState([]);
    const [viewConfig, setViewConfig] = useState({ type: 'bar', color: 'blue' });

    // 使用 useMemo 处理数据转换
    const processedData = useMemo(() => {
        return data.map(item => ({
            ...item,
            value: item.value * 2,
            color: viewConfig.color
        }));
    }, [data, viewConfig.color]);

    // 使用处理后的数据作为自定义 Hook 的依赖
    useChartEffect(processedData, viewConfig.type);

    return (
        <div>
            <button onClick={() => setViewConfig({
                ...viewConfig,
                type: viewConfig.type === 'bar' ? 'line' : 'bar'
            })}>
                Toggle Chart Type
            </button>
            <div id="chart-container">
                {/* Chart rendering logic */}
            </div>
        </div>
    );
}

// 5. 记忆一个函数
function TodoList() {
    const [todos, setTodos] = useState([]);
    const [filter, setFilter] = useState('all');

    // 使用 useMemo 缓存过滤函数
    const getFilteredTodos = useMemo(() => {
        return (todoList) => {
            console.log('Filtering todos...');
            switch(filter) {
                case 'completed':
                    return todoList.filter(todo => todo.completed);
                case 'active':
                    return todoList.filter(todo => !todo.completed);
                default:
                    return todoList;
            }
        };
    }, [filter]);

    // 使用记忆的函数
    const filteredTodos = getFilteredTodos(todos);

    const addTodo = (text) => {
        setTodos([...todos, { 
            id: Date.now(), 
            text, 
            completed: false 
        }]);
    };

    const toggleTodo = (id) => {
        setTodos(todos.map(todo =>
            todo.id === id 
                ? { ...todo, completed: !todo.completed }
                : todo
        ));
    };

    return (
        <div>
            <div>
                <button onClick={() => setFilter('all')}>All</button>
                <button onClick={() => setFilter('active')}>Active</button>
                <button onClick={() => setFilter('completed')}>Completed</button>
            </div>
            <ul>
                {filteredTodos.map(todo => (
                    <li 
                        key={todo.id}
                        onClick={() => toggleTodo(todo.id)}
                        style={{
                            textDecoration: todo.completed ? 'line-through' : 'none'
                        }}
                    >
                        {todo.text}
                    </li>
                ))}
            </ul>
            <AddTodoForm onAdd={addTodo} />
        </div>
    );
}

// 辅助组件:添加待办事项表单
function AddTodoForm({ onAdd }) {
    const [text, setText] = useState('');

    const handleSubmit = (e) => {
        e.preventDefault();
        if (!text.trim()) return;
        onAdd(text);
        setText('');
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                value={text}
                onChange={e => setText(e.target.value)}
                placeholder="Add new todo"
            />
            <button type="submit">Add</button>
        </form>
    );
}

// 自定义 Hook 示例
function useChartEffect(data, chartType) {
    useEffect(() => {
        // 模拟图表渲染逻辑
        console.log(`Rendering ${chartType} chart with data:`, data);
        
        // 清理函数
        return () => {
            console.log('Cleaning up chart');
        };
    }, [data, chartType]);
}

// 完整应用示例
function App() {
    return (
        <div>
            <h2>Expensive Calculation Example</h2>
            <ExpensiveCalculationExample />
            
            <h2>Component Re-rendering Example</h2>
            <ParentComponent />
            
            <h2>Search Component Example</h2>
            <SearchComponent />
            
            <h2>Data Visualization Example</h2>
            <DataVisualization />
            
            <h2>Todo List Example</h2>
            <TodoList />
        </div>
    );
}

export default App;