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

自定义Hook无法获取Context更新值,原因是什么?

问题描述

我尝试在自定义Hook里调用一个能在多个组件和Context中复用的函数,但这个Hook始终拿不到更新后的Context值,一直是空数组。但Context里的状态明明已经更新了,组件也能正常接收到更新后的Context值,这是怎么回事?看起来Context的值没传递给Hook,但我找不到原因。

代码示例

Context

import { createContext, useState, useEffect } from "react";
import useTest from "./useTest";

const TestContext = createContext({
  test: [],
  testContextFn: () => {}
});

export const TestProvider = ({ children }) => {
  const [testState, setTestState] = useState([]);

  const { testFn } = useTest();

  const testHandlerHandler = () => {
    const test = 1;
    setTestState((current) => [...current, test]);
    testFn();
  };

  const contextValue = {
    test: testState,
    testContextFn: testHandlerHandler
  };

  useEffect(() => {
    console.log("state: ", testState); // => 能拿到更新后的值
  }, [testState]);

  return (
    <TestContext.Provider value={contextValue}>{children}</TestContext.Provider>
  );
};

自定义Hook

import { useContext } from "react";

import { TestContext } from "./hooks/textContext";

const useTest = () => {
  const testCtx = useContext(TestContext);

  const testFn = () => {
    console.log("testCtx: ", testCtx.test); // => 始终是空数组
  };

  return { testFn };
};

export default useTest;

组件

const App = () => {
  return (
    <TestProvider>
      <ExampleComponent />
    </TestProvider>
  );
};

const ExampleComponent = () => {
  const testCtx = useContext(TestContext);
    
 console.log("testCtx: ", testCtx.test); // => 能拿到更新后的值

  return <div onClick={testCtx.testContextFn}>CLICK HERE</div>;
};
问题原因与解决方法

核心问题

  1. Provider自身无法获取自己提供的Context值
    你的TestProvider是TestContext.Provider的定义组件,但它自己并没有被包裹在这个Provider内部。所以当你在TestProvider里调用useTest时,useTest通过useContext拿到的是Context的默认空数组,根本不是TestProvider动态生成的最新值。这就是testFn始终打印空数组的直接原因。

  2. 闭包捕获旧值
    退一步说,就算Provider能拿到自己的Context值,testFn在创建时会捕获当时的testCtx引用。后续Context更新时,旧的testFn依然会指向旧的testCtx,除非testFn重新创建并关联最新值。

解决方法

方案一:调用testFn时直接传入最新状态

这种方法简单直接,绕过Context的闭包问题:

修改TestProvider中的testHandlerHandler,计算新状态后直接传给testFn:

const testHandlerHandler = () => {
  const test = 1;
  const newTestState = [...testState, test];
  setTestState(newTestState);
  // 把新状态作为参数传给testFn
  testFn(newTestState);
};

修改useTest,接收传入的状态值:

const useTest = () => {
  const testFn = (updatedTest) => {
    console.log("testCtx: ", updatedTest);
  };

  return { testFn };
};

方案二:在子组件中使用useTest(推荐)

既然useTest需要访问最新的Context值,就应该把它放到能获取到Context的子组件里,而不是在Provider内部调用:

首先修改TestProvider,移除内部的useTest调用:

export const TestProvider = ({ children }) => {
  const [testState, setTestState] = useState([]);

  const testHandlerHandler = () => {
    const test = 1;
    setTestState((current) => [...current, test]);
  };

  const contextValue = {
    test: testState,
    testContextFn: testHandlerHandler
  };

  useEffect(() => {
    console.log("state: ", testState);
  }, [testState]);

  return (
    <TestContext.Provider value={contextValue}>{children}</TestContext.Provider>
  );
};

然后修改ExampleComponent,在组件内部调用useTest并执行testFn:

const ExampleComponent = () => {
  const testCtx = useContext(TestContext);
  const { testFn } = useTest();

  console.log("testCtx: ", testCtx.test);

  const handleClick = () => {
    testCtx.testContextFn();
    // 这里调用testFn,能拿到最新的Context值
    testFn();
  };

  return <div onClick={handleClick}>CLICK HERE</div>;
};

这样useTest在子组件中执行,自然能获取到Provider提供的最新Context值,testFn里打印的就是更新后的数组了。

方案三:让Provider内部能访问自己的Context(进阶)

如果确实需要在Provider内部使用自己的Context,可以把Provider的逻辑拆分为内部子组件,让内部组件被Provider包裹:

export const TestProvider = ({ children }) => {
  const [testState, setTestState] = useState([]);

  // 内部组件,被Provider包裹,能访问Context
  const InnerProvider = () => {
    const { testFn } = useTest();

    const testHandlerHandler = () => {
      const test = 1;
      setTestState((current) => [...current, test]);
      testFn();
    };

    const contextValue = {
      test: testState,
      testContextFn: testHandlerHandler
    };

    return (
      <TestContext.Provider value={contextValue}>{children}</TestContext.Provider>
    );
  };

  // 先渲染一层Provider,让InnerProvider能访问到Context
  return (
    <TestContext.Provider value={{ test: testState, testContextFn: () => {} }}>
      <InnerProvider />
    </TestContext.Provider>
  );
};

不过这种写法比较绕,除非有特殊需求,否则不推荐。

总结

  • React Context的Provider仅为它的子组件提供Context值,Provider自身无法直接获取自己提供的Context值,必须通过内部子组件间接获取。
  • 依赖Context的自定义Hook,必须在Provider的子组件中使用,才能拿到最新的Context值。
  • 异步状态更新时,直接调用依赖状态的函数会捕获旧值,要么传入最新值,要么在组件渲染时获取最新的Context引用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 10:45:25