简体   繁体   English

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

[英]React Context Consumer not changing reactively or rerendering

I have an odd situation where a state variable is updating successfully in one component but not in the other.我有一个奇怪的情况,状态变量在一个组件中成功更新,但在另一个组件中没有。 I'm successfully changing the state , because I can see it reflected in the component where I'm triggering a dispatch .我成功地改变了state ,因为我可以看到它反映在我触发dispatch的组件中。 But in another component that is wrapped in the same Provider , I see no changes.但是在包装在同一个Provider中的另一个组件中,我看不到任何变化。

Other Stack Overflow answers seem to mostly recommend not directly mutating state , but here I'm not doing that - I'm dispatching an action to update the state, similar to what you'd see in Redux syntax.其他 Stack Overflow 答案似乎主要建议不要直接改变 state ,但在这里我没有这样做 - 我正在调度一个操作来更新状态,类似于您在 Redux 语法中看到的内容。

CodeSandbox Demo 代码沙盒演示

TeaSettingsContext.tsx 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;
};

Here's the "successful" component:这是“成功”的组成部分:

Home.tsx主页.tsx

<TeaSettingsProvider>
  <StrengthSlider />
</TeaSettingsProvider>

StrengthSlider.tsx强度滑块.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>
  );
}

...and the unsuccessful re-render happens in this component: ...并且不成功的重新渲染发生在此组件中:

TeaPage.tsx茶页.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茶卡.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.
// ...
}

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

I think you misunderstood a little how context works.我认为您误解了上下文的工作原理。

Basically (as react docs suggest):基本上(如反应文档所建议的那样):

Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes.每个 Context 对象都带有一个 Provider React 组件,该组件允许消费组件订阅上下文更改。

The Provider component accepts a value prop to be passed to consuming components that are descendants of this Provider. Provider 组件接受要传递给作为此 Provider 后代的消费组件的 value prop。 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.每当 Provider 的 value 属性发生变化时,所有作为 Provider 后代的消费者都会重新渲染。 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.从 Provider 到其后代消费者(包括 .contextType 和 useContext)的传播不受 shouldComponentUpdate 方法的影响,因此即使祖先组件跳过更新,消费者也会更新。

On short when you create a provider with a value you are able to access that information from multiple consumers (that need to be children of that provider).简而言之,当您创建一个具有值的提供者时,您可以从多个消费者(需要是该提供者的孩子)访问该信息。 So far so good.到现在为止还挺好。

Except: Home.tsx and TeaPage.tsx both initialize a different instance of the context (so each one has it's own provider).除了: Home.tsxTeaPage.tsx都初始化了一个不同的上下文实例(所以每个实例都有自己的提供者)。 In order to fix this you should only create one provider in a component that is parent of both Home and TeaPage .为了解决这个问题,您应该只在HomeTeaPage父组件中创建一个提供程序。

React context is not working as redux (where you have a global store that you can access from everywhere). React 上下文不能作为 redux(你有一个可以从任何地方访问的全局存储)。 It rather give you the option to create a context (with reusable data) for a big component (to consume in the children).它让您可以选择为大组件(在子组件中使用)创建上下文(使用可重用数据)。

Also please notice the last quoted paragraph from the beginning: basically when the context changes this cause all consumers to re-render (which might sometimes lead to some performance issues) so use it wisely.另请注意开头引用的最后一段:基本上当上下文发生变化时,这会导致所有消费者重新渲染(有时可能会导致一些性能问题),因此请明智地使用它。 Here is a short article about this. 是一篇关于此的简短文章。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM