如何在 React Context 中实现 observable 观察值
Posted
技术标签:
【中文标题】如何在 React Context 中实现 observable 观察值【英文标题】:How to implement observable watching for value in React Context 【发布时间】: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 进行响应式编程还是应该更改代码中的某些内容?谢谢
【问题讨论】:
也许你可以在这里获得更多信息。 ***.com/questions/50817672/…updateValue('update')
它看起来怎么样?似乎可能存在无法更新正确对象属性的问题。
【参考方案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 组件仅在以下任一情况下更新
-
自己的
props
被改了
state
改了
父母的state
已更改
正如您所指出的,state
需要保存在父组件中并传递给上下文。
你的要求是
-
当
state
更改时,父级不应重新渲染。
只有 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>
);
;
同时呈现Child1
和Child2
的父组件
const Parent = () =>
// This is only logged once
console.log("render parent");
return (
<Provider>
<Child1 />
<Child2 />
</Provider>
);
;
现在,当您通过单击child2
中的按钮更新state
时,第一个要求Parent
不会重新渲染,因为Context Provider
不是它的父级。
当state
更改时,只有Child1
和Child2
会重新渲染。
现在对于第二个要求,只有 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 将无法访问上下文
如果你有什么不明白的地方,我希望这有助于告诉我。
【讨论】:
以上是关于如何在 React Context 中实现 observable 观察值的主要内容,如果未能解决你的问题,请参考以下文章
[react] 写例子说明React如何在JSX中实现for循环
如何在 React 中实现 Cloudinary 上传小部件?