Background

我想了解Material UI对切换UI暗/亮模式主题的实现.Link.

我已经将其实现封装到一个自定义钩子中,在调用时返回与主题相关的属性.这是我的App()级.


import { createContext, useState, useMemo } from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';


export default function useTheme() {

    const ColorModeContext = createContext({ toggleColorMode: () => {}, mode: null})
    const [ mode, setMode ] = useState('light')

    const colorMode = useMemo(
        () => ({
            toggleColorMode : () => {
              setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'))  
            },
        })
    , [])  


    // recreate theme everytime mode changes
    const appTheme = useMemo( () => (createTheme({
        palette: {
          mode: mode,
          primary: ...
        },
      })), [mode])
    
    
    
    return {
        ColorModeContext,
        colorMode,
        ThemeProvider,
        appTheme
    }
}

App()级,我让它返回以下应用程序组件:

...
    const {
        ColorModeContext,
        colorMode,
        ThemeProvider,
        appTheme
    } = useTheme()

    // another custom hook to return auth related properties
    const {
        AuthContext,
        authed,
        ...
    } = useAuth()
   return (
     <ColorModeContext.Provider value={colorMode}>
            <ThemeProvider theme={appTheme}>
                <CssBaseline />
                <AuthContext.Provider value=...>
                    <Box>
                        <Router>
                            <Header 
                                ColorModeContext={ColorModeContext}
                                theme={appTheme}
                                authed={authed}
                                ...
                            /> 
                            <Routes>
                                <Route 
                                    path="/" 
                                    element={
                                        <Signin AuthContext={AuthContext} />
                                    } 
                                />
                                <Route 
                                    path="/create" 
                                    element={
                                        <RequiredAuth authed={authed}>
                                            <Create />
                                        </RequiredAuth>
                                    } 
                                />
                                <Route 
                                    path="/query" 
                                    element={
                                        <RequiredAuth authed={authed}>
                                            <Query />
                                        </RequiredAuth>
                                    } 
                                />
                            </Routes>
                            ...
                        </Router>
                    </Box>
                </AuthContext.Provider>
            </ThemeProvider>
        </ColorModeContext.Provider>
)

最后,在my header组件中(作为上下文使用者,在header中公开toggle回调函数)

...
const { ColorModeContext, authed, ... } = props;

return (
        <ColorModeContext.Consumer>
            {
                ({toggleColorMode}) => (
                    <Box>
                        <AppBar position="static" enableColorOnDark>
                            <Toolbar>
                              ...
                                <IconButton
                                  size="small"
                                  onClick={toggleColorMode}
                                  color="inherit"
                                >
                                   {
                                       theme.palette.mode === 'dark' ? 
                                          <LightModeIcon /> : <Brightness3Icon />
                                   }
                               </IconButton>
                            ...                   

                            </Toolbar>
                        </AppBar>
                    </Box>
                )
            }
        </ColorModeContext.Consumer>
)

ISSUE

但是,如果我在组件<Create /><Query />中切换主题,则整个组件树(创建或查询)将被重新装载,组件中的所有状态都将刷新到其初始状态.

基本上,如果我在我的<Create />组件中,我正在填写创建表单,表单的值由useState维护,只要我切换主题,所有的值都会重置为初始值(传递到useState钩子中)

QUESTION

有没有办法防止这种重新装载的发生?我知道这不仅仅是重新招标组件,因为状态已经重新初始化.如果这不是重播的问题,那么React.memo在这里行吗?如果不是,那么在子组件内部切换主题(父组件lvl的上下文)的最佳方式是什么,而无需重新安装组件.


添加codesandbox链接.它应该包括一个简单的例子,我的问题.当我切换父对象的主题模式时,子组件将重新装载(由子对象的useEffect记录).

推荐答案

useTheme钩子应该返回提供的上下文值,而不是上下文本身.这是在重新创建上下文提供程序并重新装入子对象.

例子:

useTheme

import { createContext, useState, useMemo, useContext } from "react";
import { ThemeProvider, createTheme } from "@mui/material/styles";

const ColorModeContext = createContext({
  toggleColorMode: () => {},
  mode: null
});

const ColorModeContextProvider = ({ children }) => {
  const [mode, setMode] = useState("light");

  const toggleColorMode = () => {
    setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
  };

  // recreate theme everytime mode changes
  const appTheme = useMemo(
    () =>
      createTheme({
        palette: {
          mode
        }
      }),
    [mode]
  );

  return (
    <ColorModeContext.Provider
      value={{
        toggleColorMode,
        mode
      }}
    >
      <ThemeProvider theme={appTheme}>{children}</ThemeProvider>
    </ColorModeContext.Provider>
  );
};

export const useTheme = () => useContext(ColorModeContext);

export default ColorModeContextProvider;

应用程序

导入新的ColorModeContextProvider provider组件以提供主题和 colored颜色 模式上下文,并导入useTheme钩子以在Header组件中使用以访问toggleColorMode回调.

import { useState, useEffect } from "react";
import { Container, CssBaseline } from "@mui/material";
import ColorModeContextProvider, { useTheme } from "./useTheme";
import useAuth from "./useAuth";

function 应用程序() {
  const { AuthContext, authed, user, login, logout } = useAuth();

  return (
    <ColorModeContextProvider>
      <CssBaseline />
      <AuthContext.Provider value={{ authed, user, login, logout }}>
        <Container maxWidth="lg">
          <Header />
        </Container>
        <Child />
      </AuthContext.Provider>
    </ColorModeContextProvider>
  );
}

function Header() {
  const { toggleColorMode } = useTheme();
  return <button onClick={toggleColorMode}>ToggleTheme</button>;
}

function Child() {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    return () => {
      console.log("Child component unmounting");
    };
  }, []);

  return (
    <>
      <button
        onClick={() => {
          setCounter((count) => count + 1);
        }}
      >
        Add
      </button>
      <div>{counter}</div>
    </>
  );
}

export default 应用程序;

Edit react-material-ui-best-way-to-prevent-child-tree-from-remount-when-toggling

useAuth挂钩也需要进行类似的重构.

Reactjs相关问答推荐

如何使用React防止并发注册并处理Firestore数据库中的插槽可用性

使用Thattle和Use Effect setTimeout进行搜索有什么不同

在下一个js中使用ESTAccountWithEmailAndPassword函数时循环

在Reaction中单击并显示子组件延迟/动画

如何在与AntD的react 中限制文件上传和显示消息?

如何解决Reaction中遗留的上下文API错误?

状态更改时的rtk查询触发器

设置自定义形状的纹理

强制 useEffect 仅运行一次

在 monorepo 内的工作区或库之间共享 redux 状态

解决在React测试库中的act()警告

React中的功能性组件克隆优化

如何读取 null 的属性?

如何在悬停时更改 MUI 卡内容

基于标记名的博客详细信息分组没有发生

React Three Fiber mesh 标签在此浏览器中无法识别

刷新页面时状态改变问题

哪个是创建 React 应用程序的更好方法?

.filter() 函数在删除函数中创建循环 - React

有没有办法只针对 React map 中的一个按钮?