You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.memo结合useContext无法缓存组件问题求助

解决React.memo结合useContext时缓存失效的问题

问题根源

你当前的代码里,AppContext的Provider每次状态更新(比如counter或search变化),都会创建一个新的数组作为value传递给Provider。React会判定context值发生了变化,所有消费这个context的组件(包括Post)都会强制重新渲染,哪怕React.memo对props的比较结果是没变化。

解决方案

方案1:拆分Context,按需消费

把大的Context拆分成多个独立的Context,让Post组件只消费它实际需要的部分,避免无关状态更新触发渲染。

修改上下文代码:

import { createContext, useState } from 'react';
import P from 'prop-types';

// 拆分出独立的Counter和Search Context
export const CounterContext = createContext();
export const SearchContext = createContext();

export const AppContext = ({children}) => {
    const [search, setSearch] = useState('');
    const [counter, setCounter] = useState(0);

    return (
        <CounterContext.Provider value={[counter, setCounter]}>
            <SearchContext.Provider value={[search, setSearch]}>
                {children}
            </SearchContext.Provider>
        </CounterContext.Provider>
    );
};

AppContext.propTypes = {
    children: P.node
};

修改Post组件,只消费需要的setter:

import React, { useContext } from 'react';
import P from 'prop-types';
import { CounterContext, SearchContext } from '../../contexts/App';

export const Post = React.memo(function Post(props) {
    console.log('Son rendered');

    // 只提取需要的set方法,不消费不需要的状态
    const [, setCounter] = useContext(CounterContext);
    const [, setSearch] = useContext(SearchContext);

    return (
        <div className="post">
            <h1 onClick={() => {
                setSearch(props.post.title);
                props.refInput.current.focus();
                setCounter((prevValue) => prevValue + 1);
            }}>
                {props.post.title}
            </h1>
            <p>{props.post.body}</p>
        </div>
    );
});

Post.propTypes = {
    post: P.shape({
        id: P.number,
        title: P.string,
        body: P.string,
    }),
    refInput: P.object,
};

方案2:用useMemo稳定Context Value的引用

如果不想拆分Context,可以通过useMemo缓存Context的value,确保只有当组件依赖的状态变化时,value的引用才会更新。

修改上下文代码:

import { createContext, useState, useMemo } from 'react';
import P from 'prop-types';

export const buttonContext = createContext();

export const AppContext = ({children}) => {
    const [search, setSearch] = useState('');
    const [counter, setCounter] = useState(0);

    // 用useMemo缓存value对象,仅当counter或search变化时重新创建
    const contextValue = useMemo(() => ({
        counter,
        setCounter,
        search,
        setSearch
    }), [counter, search]);

    return <buttonContext.Provider value={contextValue}>{children}</buttonContext.Provider>;
};

AppContext.propTypes = {
    children: P.node
};

修改Post组件,只提取需要的方法:

import React, { useContext } from 'react';
import P from 'prop-types';
import { buttonContext } from '../../contexts/App';

export const Post = React.memo(function Post(props) {
    console.log('Son rendered');

    const { setCounter, setSearch } = useContext(buttonContext);

    return (
        <div className="post">
            <h1 onClick={() => {
                setSearch(props.post.title);
                props.refInput.current.focus();
                setCounter((prevValue) => prevValue + 1);
            }}>
                {props.post.title}
            </h1>
            <p>{props.post.body}</p>
        </div>
    );
});

Post.propTypes = {
    post: P.shape({
        id: P.number,
        title: P.string,
        body: P.string,
    }),
    refInput: P.object,
};

方案3:抽离依赖Context的逻辑到子组件

把需要用到Context的点击逻辑抽成单独的子组件,让Post主组件不直接消费Context,这样Post就能被React.memo正确缓存。

示例代码:

import React, { useContext } from 'react';
import P from 'prop-types';
import { buttonContext } from '../../contexts/App';

// 抽离点击逻辑的子组件,负责消费Context
const PostTitle = ({ title, refInput }) => {
    const { setCounter, setSearch } = useContext(buttonContext);

    const handleClick = () => {
        setSearch(title);
        refInput.current.focus();
        setCounter(prev => prev + 1);
    };

    return <h1 onClick={handleClick}>{title}</h1>;
};

export const Post = React.memo(function Post(props) {
    console.log('Son rendered');

    return (
        <div className="post">
            <PostTitle title={props.post.title} refInput={props.refInput} />
            <p>{props.post.body}</p>
        </div>
    );
});

Post.propTypes = {
    post: P.shape({
        id: P.number,
        title: P.string,
        body: P.string,
    }),
    refInput: P.object,
};

方案选择建议

  • 若Context内状态较多、各组件依赖差异大,优先选方案1,能最小化不必要的渲染范围。
  • 若不想拆分Context,选方案2,通过缓存稳定引用,避免无意义更新。
  • 若仅组件局部逻辑依赖Context,选方案3,保持主组件纯净,缓存逻辑更简单。

内容的提问来源于stack exchange,提问作者Rafael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:34:52