仅当模态内容更改时防止模态重新渲染:React Hooks useReducer

Prevent Modal Rerendering when only Modal Content Change: React Hooks useReducer

我的 React App 中有一个父子组件 ModalModalContent 都可以正常工作。

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 本身的调度语句更新了问题。

ModalModalContent 在内容更改时重新呈现的原因是因为两个组件都使用相同的上下文,并且当上下文值更改时,所有侦听上下文的组件都是重新渲染

解决这个重新渲染的方法是使用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>