仅当模态内容更改时防止模态重新渲染:React Hooks useReducer
Prevent Modal Rerendering when only Modal Content Change: React Hooks useReducer
我的 React App 中有一个父子组件 Modal 和 ModalContent 都可以正常工作。
1) 我在 App.js 中创建了一个 AppContext,以便在所有组件中全局访问它。
const [state, dispatch] = useReducer(reducer, {modalOpen: false, content: []});
<AppContext.Provider value={{ state, dispatch }} >
//BrowserRouter and Routes Definition
</App>
这里 reducer 是一个相当简单的函数,在 content 中带有 modalOpen 和 push/pop 功能的开关(数组).
2) 我的 Modal
组件使用了
const { state, dispatch } = useContext(AppContext);
<Modal open={state.modalOpen} />
获取模态可见性状态以设置它open/closed。
3) 我的 ModalContent
组件使用了
const { state, dispatch } = useContext(AppContext);
<ModalContent data={state.content} />
//Dispatch from the `ModalContent`
dispatch({ type: 'UPDATE_CONTENT', data: 'newata' });
4) 这是我的减速器。
export const reducer = (state, action) => {
switch (action.type) {
case 'TOGGLE_MODAL':
return {...state, modalOpen: !state.modalOpen};
case 'UPDATE_CONTENT':
return { ...state, content: [...state.content, action.data]};
default:
return {modalOpen: false,content: []};
}
}
我在 ModalContent
中设置了一些代码来更新数据 content
属性 使用 dispatch 并且 reducer store 被完美更新并且 returns fresh/updated:
{modalOpen: true/false, content: updatedContentArray}
问题是:每当我通过 ModalContent
发送一个动作时,reducer 会返回完整状态(预期),并且 Modal 在侦听 state.modalOpen。
不成功的尝试:我试图在各个组件中专门提取所需的属性。但是 Modal
组件仍然会重新呈现,即使只是更改了内容。有什么方法可以只监视特定状态
如何通过仅重新渲染 ModalContent
而不是 Modal
.
来完成这项工作
编辑:使用我的模拟(工作)reducer 代码和 ModalContent 本身的调度语句更新了问题。
Modal
和 ModalContent
在内容更改时重新呈现的原因是因为两个组件都使用相同的上下文,并且当上下文值更改时,所有侦听上下文的组件都是重新渲染
解决这个重新渲染的方法是使用multiple contexts
like
const modalContextVal = useMemo(() => ({ modalOpen: state.modalOpen, dispatch}), [state.modalOpen]);
const contentContextVal = useMemo(() => ({ content: state.content, dispatch}), [state.content]);
....
<ModalContext.Provider value={modalContextVal}>
<ContentContext.Provider value={contentContextVal}>
//BrowserRouter and Routes Definition
</ContentContext.Provider>
</ModalContext.Provider>
并像
一样使用它
在Modal.js
const {modalOpen, dispatch} = useContext(ModalContext);
在ModalContent.js
const {content, dispatch} = useContext(ContentContext);
正如@Shubham 所说,您必须将模态状态和模态内容分开。
可以通过单独的上下文甚至简单的 useState 来完成
示例片段
const { useReducer, createContext, useContext, useState, useEffect, memo, useMemo } = React;
const AppContext = createContext();
const reducer = (state, action) => {
if(action.type == 'toggleModal') {
return {
...state,
modalOpen: !state.modalOpen
}
}
return state;
}
const AppContextProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, {modalOpen: false, content: [{id: 1, value: 'test'}]});
return <AppContext.Provider value={{state, dispatch}}>
{children}
</AppContext.Provider>
}
const Modal = ({children, modalOpen}) => {
const { state, dispatch } = useContext(AppContext);
console.log('Render Modal');
return <div className={`modal ${modalOpen ? 'modal--open': null}`}>
{children}
</div>
}
const ModalContent = ({data, onClick}) => {
console.log('Render Modal Content');
return <div className="modal__content">
{data.map(({id, value}) => <div className="item" key={id}>{value}</div>)}
<button onClick={onClick} className="modal__close">Close</button>
</div>
}
const App = () => {
const { state, dispatch } = useContext(AppContext);
const { modalOpen, } = state;
const [content, setContent] = useState([]);
const onClick = () => {
dispatch({ type: 'toggleModal' });
}
return <div>
<Modal modalOpen={modalOpen}>
{useMemo(() => {
console.log('render useMemo');
return <ModalContent onClick={onClick} data={content}></ModalContent>
}, [content])}
</Modal>
<button onClick={onClick}>Open Modal</button>
</div>
}
ReactDOM.render(
<AppContextProvider>
<App />
</AppContextProvider>,
document.getElementById('root')
);
.modal {
background: black;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
z-index: -1;
transition: all .3s ease-in;
}
.modal__close {
padding: .5rem 1rem;
color: white;
border: 1px solid white;
background: transparent;
cursor: pointer;
}
.modal--open {
opacity: 1;
z-index: 1;
}
.item {
padding: 1rem;
color: white;
border: 1px solid white;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>
我的 React App 中有一个父子组件 Modal 和 ModalContent 都可以正常工作。
1) 我在 App.js 中创建了一个 AppContext,以便在所有组件中全局访问它。
const [state, dispatch] = useReducer(reducer, {modalOpen: false, content: []});
<AppContext.Provider value={{ state, dispatch }} >
//BrowserRouter and Routes Definition
</App>
这里 reducer 是一个相当简单的函数,在 content 中带有 modalOpen 和 push/pop 功能的开关(数组).
2) 我的 Modal
组件使用了
const { state, dispatch } = useContext(AppContext);
<Modal open={state.modalOpen} />
获取模态可见性状态以设置它open/closed。
3) 我的 ModalContent
组件使用了
const { state, dispatch } = useContext(AppContext);
<ModalContent data={state.content} />
//Dispatch from the `ModalContent`
dispatch({ type: 'UPDATE_CONTENT', data: 'newata' });
4) 这是我的减速器。
export const reducer = (state, action) => {
switch (action.type) {
case 'TOGGLE_MODAL':
return {...state, modalOpen: !state.modalOpen};
case 'UPDATE_CONTENT':
return { ...state, content: [...state.content, action.data]};
default:
return {modalOpen: false,content: []};
}
}
我在 ModalContent
中设置了一些代码来更新数据 content
属性 使用 dispatch 并且 reducer store 被完美更新并且 returns fresh/updated:
{modalOpen: true/false, content: updatedContentArray}
问题是:每当我通过 ModalContent
发送一个动作时,reducer 会返回完整状态(预期),并且 Modal 在侦听 state.modalOpen。
不成功的尝试:我试图在各个组件中专门提取所需的属性。但是 Modal
组件仍然会重新呈现,即使只是更改了内容。有什么方法可以只监视特定状态
如何通过仅重新渲染 ModalContent
而不是 Modal
.
编辑:使用我的模拟(工作)reducer 代码和 ModalContent 本身的调度语句更新了问题。
Modal
和 ModalContent
在内容更改时重新呈现的原因是因为两个组件都使用相同的上下文,并且当上下文值更改时,所有侦听上下文的组件都是重新渲染
解决这个重新渲染的方法是使用multiple contexts
like
const modalContextVal = useMemo(() => ({ modalOpen: state.modalOpen, dispatch}), [state.modalOpen]);
const contentContextVal = useMemo(() => ({ content: state.content, dispatch}), [state.content]);
....
<ModalContext.Provider value={modalContextVal}>
<ContentContext.Provider value={contentContextVal}>
//BrowserRouter and Routes Definition
</ContentContext.Provider>
</ModalContext.Provider>
并像
一样使用它在Modal.js
const {modalOpen, dispatch} = useContext(ModalContext);
在ModalContent.js
const {content, dispatch} = useContext(ContentContext);
正如@Shubham 所说,您必须将模态状态和模态内容分开。
可以通过单独的上下文甚至简单的 useState 来完成
示例片段
const { useReducer, createContext, useContext, useState, useEffect, memo, useMemo } = React;
const AppContext = createContext();
const reducer = (state, action) => {
if(action.type == 'toggleModal') {
return {
...state,
modalOpen: !state.modalOpen
}
}
return state;
}
const AppContextProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, {modalOpen: false, content: [{id: 1, value: 'test'}]});
return <AppContext.Provider value={{state, dispatch}}>
{children}
</AppContext.Provider>
}
const Modal = ({children, modalOpen}) => {
const { state, dispatch } = useContext(AppContext);
console.log('Render Modal');
return <div className={`modal ${modalOpen ? 'modal--open': null}`}>
{children}
</div>
}
const ModalContent = ({data, onClick}) => {
console.log('Render Modal Content');
return <div className="modal__content">
{data.map(({id, value}) => <div className="item" key={id}>{value}</div>)}
<button onClick={onClick} className="modal__close">Close</button>
</div>
}
const App = () => {
const { state, dispatch } = useContext(AppContext);
const { modalOpen, } = state;
const [content, setContent] = useState([]);
const onClick = () => {
dispatch({ type: 'toggleModal' });
}
return <div>
<Modal modalOpen={modalOpen}>
{useMemo(() => {
console.log('render useMemo');
return <ModalContent onClick={onClick} data={content}></ModalContent>
}, [content])}
</Modal>
<button onClick={onClick}>Open Modal</button>
</div>
}
ReactDOM.render(
<AppContextProvider>
<App />
</AppContextProvider>,
document.getElementById('root')
);
.modal {
background: black;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
z-index: -1;
transition: all .3s ease-in;
}
.modal__close {
padding: .5rem 1rem;
color: white;
border: 1px solid white;
background: transparent;
cursor: pointer;
}
.modal--open {
opacity: 1;
z-index: 1;
}
.item {
padding: 1rem;
color: white;
border: 1px solid white;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>