【问题标题】:How to implement observable watching for value in React Context如何在 React Context 中实现 observable 观察值
【发布时间】:2021-03-01 07:57:10
【问题描述】:

假设我有一个Parent 组件,它提供了一个Context,它是一个Store 对象。为简单起见,假设这个 Store 有一个 value 和一个 update this value

的函数
class Store {
// value

// function updateValue() {}

}

const Parent = () => {
  const [rerender, setRerender] = useState(false);
  const ctx = new Store();

  return (
    <SomeContext.Provider value={ctx}>
      <Children1 />
      <Children2 />
      .... // and alot of component here
    </SomeContext.Provider>
  );
};

const Children1 = () => {
 const ctx = useContext(SomeContext);
 return (<div>{ctx.value}</div>)
}

const Children2 = () => {
 const ctx = useContext(SomeContext);
 const onClickBtn = () => {ctx.updateValue('update')}
 return (<button onClick={onClickBtn}>Update Value </button>)
}

所以基本上Children1会显示值,而在Children2组件中,有一个更新值的按钮。

所以我现在的问题是当Children2 更新 Store 值时,Children1 不会重新渲染。 以反映新值。

堆栈溢出的一种解决方案是here。这个想法是在Parent 中创建一个state 并使用它将context 传递给孩子们。这将有助于重新渲染 Children1 因为 Parent 被重新渲染。 但是,我不想 Parent 重新渲染,因为在Parent 中有很多其他组件。我只想重新渲染Children1

那么有什么办法可以解决这个问题吗?我应该使用 RxJS 进行响应式编程还是应该更改代码中的某些内容?谢谢

【问题讨论】:

标签: javascript reactjs typescript reactive-programming react-context


【解决方案1】:

你可以使用像 redux lib 这样的上下文,如下所示

这很容易使用,以后如果您想迁移到 redux,您只需更改 store 文件,整个状态管理将被迁移到 redux 或任何其他库。

运行示例: https://stackblitz.com/edit/reactjs-usecontext-usereducer-state-management

文章:https://rsharma0011.medium.com/state-management-with-react-hooks-and-context-api-2968a5cf5c83

Reducers.js

import { combineReducers } from "./Store";

const countReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };
    case "DECREMENT":
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

export default combineReducers({ countReducer });

Store.js

import React, { useReducer, createContext, useContext } from "react";

const initialState = {};
const Context = createContext(initialState);

const Provider = ({ children, reducers, ...rest }) => {
  const defaultState = reducers(undefined, initialState);
  if (defaultState === undefined) {
    throw new Error("reducer's should not return undefined");
  }
  const [state, dispatch] = useReducer(reducers, defaultState);
  return (
    <Context.Provider value={{ state, dispatch }}>{children}</Context.Provider>
  );
};

const combineReducers = reducers => {
  const entries = Object.entries(reducers);
  return (state = {}, action) => {
    return entries.reduce((_state, [key, reducer]) => {
      _state[key] = reducer(state[key], action);
      return _state;
    }, {});
  };
};

const Connect = (mapStateToProps, mapDispatchToProps) => {
  return WrappedComponent => {
    return props => {
      const { state, dispatch } = useContext(Context);
      let localState = { ...state };
      if (mapStateToProps) {
        localState = mapStateToProps(state);
      }
      if (mapDispatchToProps) {
        localState = { ...localState, ...mapDispatchToProps(dispatch, state) };
      }
      return (
        <WrappedComponent
          {...props}
          {...localState}
          state={state}
          dispatch={dispatch}
        />
      );
    };
  };
};

export { Context, Provider, Connect, combineReducers };

App.js

import React from "react";
import ContextStateManagement from "./ContextStateManagement";
import CounterUseReducer from "./CounterUseReducer";
import reducers from "./Reducers";
import { Provider } from "./Store";

import "./style.css";

export default function App() {
  return (
    <Provider reducers={reducers}>
      <ContextStateManagement />
    </Provider>
  );
}

组件.js

import React from "react";
import { Connect } from "./Store";

const ContextStateManagement = props => {
  return (
    <>
      <h3>Global Context: {props.count} </h3>
      <button onClick={props.increment}>Global Increment</button>
      <br />
      <br />
      <button onClick={props.decrement}>Global Decrement</button>
    </>
  );
};

const mapStateToProps = ({ countReducer }) => {
  return {
    count: countReducer.count
  };
};

const mapDispatchToProps = dispatch => {
  return {
    increment: () => dispatch({ type: "INCREMENT" }),
    decrement: () => dispatch({ type: "DECREMENT" })
  };
};

export default Connect(mapStateToProps, mapDispatchToProps)(
  ContextStateManagement
);

【讨论】:

    【解决方案2】:

    如果您不希望您的 Parent 组件在状态更新时重新渲染,那么您使用了错误的状态管理模式,完全没有问题。相反,您应该使用 Redux 之类的东西,它从 React 组件树中完全删除“状态”,并允许组件直接订阅状态更新。

    Redux 将允许 only 订阅特定存储值的组件在这些值更新时更新 only。因此,您的父组件和调度更新操作的子组件不会更新,而只有订阅状态更新的子组件。效率很高!

    https://codesandbox.io/s/simple-redux-example-y3t32

    【讨论】:

    • 您好,感谢您的推荐。实际上我的意思不是我不希望Parent 在其状态更改时重新渲染,而是在context 更改时重新渲染。而Parent 只是负责将上下文注入孩子。除非您的观点是 context 始终需要映射到 Parent 状态,否则它是另一回事
    • 就我而言,我不能使用 Redux,因为我不是在构建应用程序,而是在构建组件库,因此在内部使用 Redux 并不常见。
    • @JakeLam 这正是我的观点,使用 Redux,您可以在不重新渲染 Parent 的情况下更新您的应用程序状态,因为它没有订阅这些值。你看我的例子了吗?此外,据我所知,没有什么能阻止您在组件库中使用 Redux。您只需要确保所有组件都使用相同的 Redux 存储 - 与 Context 没有太大不同,您必须确保所有组件都可以访问相同的上下文。
    【解决方案3】:

    React 组件仅在以下任一情况下更新

    1. 自己的props被改了
    2. state改了
    3. 父母的state已更改

    正如您所指出的,state 需要保存在父组件中并传递给上下文。

    你的要求是

    1. state 更改时,父级不应重新渲染。
    2. 只有 Child1 应该在 state 更改时重新渲染
    const SomeContext = React.createContext(null);
    

    孩子 1 和 2

    const Child1 = () => {
      const ctx = useContext(SomeContext);
    
      console.log(`child1: ${ctx}`);
    
      return <div>{ctx.value}</div>;
    };
    
    const Child2 = () => {
      const ctx = useContext(UpdateContext);
    
      console.log("child 2");
    
      const onClickBtn = () => {
        ctx.updateValue("updates");
       
      };
    
      return <button onClick={onClickBtn}>Update Value </button>;
    };
    

    现在是添加 state 的上下文提供程序

    const Provider = (props) => {
      const [state, setState] = useState({ value: "Hello" });
    
      const updateValue = (newValue) => {
        setState({
          value: newValue
        });
      };
    
      useEffect(() => {
        document.addEventListener("stateUpdates", (e) => {
          updateValue(e.detail);
        });
      }, []);
    
      const getState = () => {
        return {
          value: state.value,
          updateValue
        };
      };
    
      return (
        <SomeContext.Provider value={getState()}>
          {props.children}. 
        </SomeContext.Provider>
      );
    };
    
    

    同时呈现Child1Child2 的父组件

    const Parent = () => {
     // This is only logged once
      console.log("render parent");
    
      return (
          <Provider>
            <Child1 />
            <Child2 />
          </Provider>
      );
    };
    

    现在,当您通过单击child2 中的按钮更新state 时,第一个要求Parent 不会重新渲染,因为Context Provider 不是它的父级。

    state 更改时,只有Child1Child2 会重新渲染。

    现在对于第二个要求,只有 Child1 需要重新渲染。

    为此,我们需要进行一些重构。

    这就是反应性的来源。只要Child2 是 Provider 的子级,只要 state 发生更改,它也会得到更新。

    Child2 从提供程序中取出。

    const Parent = () => {
      console.log("render parent");
    
      return (
        <>
          <Provider>
            <Child1 />
          </Provider>
          <Child2 />
        </>
      );
    };
    

    现在我们需要一些方法来更新 Child2 的状态。

    为了简单起见,我在这里使用了浏览器自定义事件。你可以使用 RxJs。

    Provider 正在监听状态更新,Child2 将在单击按钮并更新状态时触发事件。

    const Provider = (props) => {
      const [state, setState] = useState({ value: "Hello" });
    
      const updateValue = (e) => {
        setState({
          value: e.detail
        });
      };
    
      useEffect(() => {
        document.addEventListener("stateUpdates", updateValue);
    
    
    return ()=>{
       document.addEventListener("stateUpdates", updateValue);
    }
      }, []);
    
      return (
        <SomeContext.Provider value={state}>{props.children}</SomeContext.Provider>
      );
    };
    
    const Child2 = () => {
    
      console.log("child 2");
    
      const onClickBtn = () => {
        const event = new CustomEvent("stateUpdates", { detail: "Updates" });
    
        document.dispatchEvent(event);
      };
    
      return <button onClick={onClickBtn}>Update Value </button>;
    };
    

    注意:Child2 将无法访问上下文

    如果你有什么不明白的地方,我希望这有助于告诉我。

    【讨论】:

      猜你喜欢
      • 2021-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-22
      相关资源
      最近更新 更多