簡體   English   中英

如何在 React Context 中實現可觀察的值監視

[英]How to implement observable watching for value in React Context

假設我有一個Parent組件提供一個Context ,它是一個Store對象。 為簡單起見,假設這個 Store 有一個和一個函數來更新這個值

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 進行響應式編程還是應該更改代碼中的某些內容? 謝謝

您可以使用像 redux lib 這樣的上下文,如下所示

這很容易使用,以后如果你想轉移到 redux,你只需要更改存儲文件,整個狀態管理內容將轉移到 redux 或任何其他庫。

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

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

減速器.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 });

商店.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 };

應用程序.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
);

如果你不希望你的Parent組件在狀態更新時重新渲染,那么你使用了錯誤的狀態管理模式,完全。 相反,您應該使用Redux 之類的東西,它從 React 組件樹中完全刪除“狀態”,並允許組件直接訂閱狀態更新。

Redux 將允許訂閱特定存儲值的組件在這些值更新時更新。 因此,您的父組件和分派更新操作的子組件不會更新,而只有訂閱狀態的子組件會更新。 這是非常有效的!

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

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的按鈕更新stateParent將不會重新呈現,因為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 將無法訪問上下文

我希望這有助於讓我知道如果你不明白什么。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM