我知道我可以用自己的<Context.Provider>
包装HOC,并在所有子组件中使用它。
我想在两个单独的组件中使用上下文,但是它们嵌套在很深的位置,而它们的最接近的父级位于应用程序根目录中。我不想为几乎所有组件提供上下文,所以我想知道是否可以仅包装这两个组件?
我尝试这样做,但是只有第一个组件可以获取上下文。
App结构如下:
<App>
<A1>
<A2>
<MyContext.Provider>
<Consumer1/>
</MyContext.Provider>
</A2>
</A1>
<B1>
<B2>
<MyContext.Provider>
<Consumer2/>
</MyContext.Provider>
</B2>
</B1>
</App>
编辑:我错了,认为包装根组件会使上下文更改重新呈现所有子组件。只有使用者会重新渲染,因此包装根组件是完全可以的。
答案 0 :(得分:1)
如果要在应用程序的多个部分之间共享一个值,则需要以某种形式将该值上移到需要使用该值的公共祖先组件中。正如您在评论中提到的那样,您的问题是性能问题之一,并且尝试不重新呈现所有内容。拥有两个提供程序并不能真正解决这个问题,因为仍然需要一些组件来确保两个提供程序都提供相同的价值。因此,该组件最终需要成为这两个提供者的共同祖先。
相反,您可以使用shouldComponentUpdate(对于类组件)或React.memo(对于功能组件)来阻止重新渲染过程沿组件树向下进行。正在使用Context.Consumer的深层后代仍将重新渲染,因此您可以跳过树的中间部分。这是一个示例(注意在中间组件上使用React.memo):
const Context = React.createContext(undefined);
const useCountRenders = (name) => {
const count = React.useRef(0);
React.useEffect(() => {
count.current++;
console.log(name, count.current);
});
}
const App = () => {
const [val, setVal] = React.useState(1);
useCountRenders('App');
React.useEffect(() => {
setTimeout(() => {
console.log('updating app');
setVal(val => val + 1)
}, 1000);
}, [])
return (
<Context.Provider value={val}>
<IntermediateComponent />
</Context.Provider>
);
}
const IntermediateComponent = React.memo((props) => {
useCountRenders('intermediate');
return (
<div>
<Consumer name="first consumer"/>
<UnrelatedComponent/>
<Consumer name="second consumer"/>
</div>
);
})
const Consumer = (props) => {
useCountRenders(props.name);
return (
<Context.Consumer>
{val => {
console.log('running consumer child', props.name);
return <div>consuming {val}</div>
}}
</Context.Consumer>
)
}
const UnrelatedComponent = (props) => {
useCountRenders('unrelated');
return props.children || null;
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.development.js"></script>
<div id="root"></div>
运行上面的代码时,请检查日志以查看重新渲染了哪些组件。在第一次通过时,所有内容都会呈现,但是在应用状态更改后的一秒钟之后,只有应用会重新呈现。 IntermediateComponent,UnrelatedComponent甚至Consumer都不会重新呈现。 Context.Consumer内部的函数会重新运行,并且该函数返回的所有内容(在本例中为div)都将重新呈现。
答案 1 :(得分:0)
按照OP的要求,此解决方案主要使用钩子,但useReducer无法在单独的提供程序下实现状态共享(据我所试)。
它不需要一个提供者位于应用程序的根目录,并且可以为每个提供者使用不同的reducer。
它使用静态状态管理器,但这是一个实现细节,用于在不同上下文发起者下的多个组件之间共享状态,这将需要诸如对状态的共享引用,更改状态和通知状态的某种方式。这些变化。
使用摘要时,第一个按钮显示共享状态并在单击时递增foo
,第二个按钮显示相同的共享状态并在单击时递增bar
:
// The context
const MyContext = React.createContext();
// the shared static state
class ProviderState {
static items = [];
static register(item) {
ProviderState.items.push(item);
}
static unregister(item) {
const idx = ProviderState.items.indexOf(item);
if (idx !== -1) {
ProviderState.items.splice(idx, 1);
}
}
static notify(newState) {
ProviderState.state = newState;
ProviderState.items.forEach(item => item.setState(newState));
}
static state = { foo: 0, bar: 0 };
}
// the state provider which registers to (listens to) the shared state
const Provider = ({ reducer, children }) => {
const [state, setState] = React.useState(ProviderState.state);
React.useEffect(
() => {
const entry = { reducer, setState };
ProviderState.register(entry);
return () => {
ProviderState.unregister(entry);
};
},
[]
);
return (
<MyContext.Provider
value={{
state,
dispatch: action => {
const newState = reducer(ProviderState.state, action);
if (newState !== ProviderState.state) {
ProviderState.notify(newState);
}
}
}}
>
{children}
</MyContext.Provider>
);
}
// several consumers
const Consumer1 = () => {
const { state, dispatch } = React.useContext(MyContext);
// console.log('render1');
return <button onClick={() => dispatch({ type: 'inc_foo' })}>foo {state.foo} bar {state.bar}!</button>;
};
const Consumer2 = () => {
const { state, dispatch } = React.useContext(MyContext);
// console.log('render2');
return <button onClick={() => dispatch({ type: 'inc_bar' })}>bar {state.bar} foo {state.foo}!</button>;
};
const reducer = (state, action) => {
console.log('reducing action:', action);
switch(action.type) {
case 'inc_foo':
return {
...state,
foo: state.foo + 1,
};
case 'inc_bar':
return {
...state,
bar: state.bar + 1,
};
default:
return state;
}
}
// here the providers are used on the same level but any depth would work
class App extends React.Component {
render() {
console.log('render app');
return (
<div>
<Provider reducer={reducer}>
<Consumer1 />
</Provider>
<Provider reducer={reducer}>
<Consumer2 />
</Provider>
<h2>I'm not rerendering my children</h2>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
最后,我们重新创建了一个类似于redux的东西,其静态状态由多个提供者/消费者集合共享。
无论提供者和使用者位于何处以及深度有多深,都无需依赖长嵌套的更新链,便可以发挥同样的作用。
请注意,根据OP的要求,此处无需在应用程序的根目录提供提供程序,因此不会将上下文提供给每个组件,仅提供您选择的子集即可。