【问题标题】:Prevent Modal Rerendering when only Modal Content Change: React Hooks useReducer仅在模态内容更改时防止模态重新渲染:React Hooks useReducer
【发布时间】:2020-09-18 00:58:02
【问题描述】:

我的 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 是一个相当简单的函数,带有 modalOpen 切换和 content(数组)中的推送/弹出功能。

2) 我的Modal 组件使用了

const { state, dispatch } = useContext(AppContext); 
<Modal open={state.modalOpen} />

获取模态可见性状态以将其设置为打开/关闭。

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 属性,并且reducer 存储完美更新并返回新鲜/更新:

 {modalOpen: true/false, content: updatedContentArray}

问题是:每当我通过ModalContent 调度一个动作时,reducer 会返回(预期的)完整状态,并且 Modal 在侦听 state.modalOpen 时会重新打开。

不成功的尝试:我试图在各个组件中专门提取所需的属性。但是Modal 组件仍然会重新渲染,即使只是更改了内容。有没有办法只关注特定的状态

如何通过仅重新渲染 ModalContent 而不是 Modal 来完成这项工作。

编辑:用我的模拟(工作)reducer 代码和来自 ModalContent 本身的调度语句更新了问题。

【问题讨论】:

  • 你能展示你的减速器和调度调用吗?还有问题只是 Modal 正在重新渲染或 openModal 错误地将 modelOpen 值设为 true
  • 您可以将ModalReact.memo 增强为记忆组件,如果state.modalOpen 具有相同的值,则不会重新渲染。
  • @ShubhamKhatri 我已经添加了我的减速器(工作)的更简单版本,并在上面的代码 sn-p 中添加了一个调度调用。当我关闭它关闭的模式并将状态更新为 false 时,我可以看到 openModal 工作正常。
  • 好的,所以问题只是在仅内容更改时重新渲染模态组件?
  • 是的@ShubhamKhatri

标签: javascript reactjs react-hooks react-context use-reducer


【解决方案1】:

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,实际上使用这种方法就像一个魅力,我什至可以访问单个组件中的多个上下文(如果需要),而无需担心重新渲染,直到我专门更新那个状态!跨度>
【解决方案2】:

正如@Shubham 所说,您必须将模态状态和模态内容分开。

可以使用单独的上下文甚至简单的 useState 来完成

示例 sn-p

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>

【讨论】:

    猜你喜欢
    • 2019-07-28
    • 2021-07-09
    • 2020-04-24
    • 2020-10-20
    • 1970-01-01
    • 1970-01-01
    • 2018-01-03
    • 2021-11-14
    相关资源
    最近更新 更多