React Context Consumer 不会被动改变或重新渲染

React Context Consumer not changing reactively or rerendering

我有一种奇怪的情况,状态变量在一个组件中成功更新,但在另一个组件中却没有。我成功更改了 state,因为我可以看到它反映在我触发 dispatch 的组件中。但是在另一个包裹在同一个 Provider 中的组件中,我没有看到任何变化。

其他 Stack Overflow 答案似乎主要推荐 ,但在这里我没有这样做 - 我正在调度一个更新状态的操作,类似于您在 Redux 语法中看到的内容。

CodeSandbox Demo

TeaSettingsContext.tsx

const TeaSettingsContext = createContext<{ state: State; dispatch: Dispatch } | undefined>(undefined);

const teaSettingsReducer = (state: State, action: Action) => {
  switch (action.type) {
    case TeaSettingsActions.ChooseTea: {
      return { ...state, chosenTea: action.payload };
    }
    case TeaSettingsActions.ChangeStrength: {
      return { ...state, desiredStrength: action.payload };
    }
    default: {
      throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
};

export function TeaSettingsProvider({
  children,
}: TeaSettingsProviderProps): Context {
  const [state, dispatch] = useReducer(teaSettingsReducer, {
    chosenTea: {},
    desiredStrength: 0.5,
  });
  return (
    <TeaSettingsContext.Provider value={{ state, dispatch }}>
      {children}
    </TeaSettingsContext.Provider>
  );
}

export const useTeaSettingsContext = (): TeaSettingsContext => {
  const context = useContext(TeaSettingsContext);
  if (context === undefined) {
    return;
  }
  return context;
};

这是“成功”的部分:

Home.tsx

<TeaSettingsProvider>
  <StrengthSlider />
</TeaSettingsProvider>

StrengthSlider.tsx

export default function StrengthSlider(): ReactNode {
  const { state, dispatch } = useTeaSettingsContext();
  console.log(state.desiredStrength) // logs the increment on each press.

  const incrementStrength = () => {
    dispatch({
      payload: state.desiredStrength + 0.1,
      type: "change-strength",
    });
  };

  return (
    <Box position="relative" w="100%">
      <Button title="Press" onPress={incrementStrength} />
      <Text>{state.desiredStrength}</Text>
    </Box>
  );
}

...重新渲染失败发生在这个组件中:

TeaPage.tsx

const Component = () => {
    if (type === "tea") {
      return data.map((teaObj) => (
        <TeaCard id={teaObj.id} teaData={teaObj.data} key={teaObj.id} />
      ));
    }
  };

  return (
    <TeaSettingsProvider>
      <Component />
    </TeaSettingsProvider>
  );

TeaCard.tsx

export function TeaCard({ id, teaData }: TeaCardProps): ReactNode {
  const { state, dispatch } = useTeaSettingsContext();
  console.log(state.desiredStrength); // this always logs the starting value of 0.5 and doesn't log each time I press the button above.
// ...
}

仅供参考:我的代码基于 Kent C Dodds 的文章:https://kentcdodds.com/blog/how-to-use-react-context-effectively

我认为您对上下文的工作原理有点误解。

基本上(如react docs建议的那样):

Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes.

The Provider component accepts a value prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree.

All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update.

简而言之,当您创建具有值的提供者时,您可以从多个消费者(需要是该提供者的子级)访问该信息。到目前为止一切顺利。

除了: Home.tsxTeaPage.tsx 都初始化上下文的不同实例(因此每个实例都有自己的提供者)。 为了解决这个问题,您应该只在 HomeTeaPage.

的父组件中创建一个提供程序

React context 不能像 redux 一样工作(你有一个全局商店,你可以从任何地方访问)。它让您可以选择为一个大组件(在子组件中使用)创建上下文(具有可重用数据)。

另请注意开头引用的最后一段:基本上,当上下文发生变化时,这会导致所有消费者重新渲染(这有时可能会导致一些性能问题),因此请明智地使用它。 Here 是一篇关于此的短文。