Next+Antd 实现 Theme 切换

750 阅读3分钟

前言

紧接前文,前面说到只要我们通过修改antd 的 theme 算法就可以实现动态主题切换了,这把我们来实操下,那,进入小试牛刀环节,其实在进入小试牛刀环节之前还有一个准备环节,我知道你们都准备好了,所以我们直接开干,但是在浏览的过程中有一个问题需要你们思考下,就是为什么要这么写,可不可以那样写,那样写了会怎样,好了,开冲 ⛷。

小试牛刀

  1. 编写 useTheme.ts
import { useEffect, useState } from 'react';  
  
const useTheme = (): [string, () => void] => {  
    const [theme, setTheme] = useState('light');  
  
    useEffect(() => {  
        const storeTheme = window.localStorage.getItem('theme');  
        if (storeTheme) setTheme(storeTheme);  
    }, []);  
  
    const toggleTheme = () => {  
        setTheme((prevTheme) => {  
            const value = prevTheme === 'light' ? 'dark' : 'light';  
            window.localStorage.setItem('theme', value);  
            return value;  
        });  
    };  
  
    return [theme, toggleTheme];  
};  
  
export default useTheme;
  1. 创建 ThemeContext.ts
import React, { useContext } from 'react';  
  
export interface ThemeContextValue {  
    theme: string;  
    toggleTheme?: () => void;  
}  
  
export const ThemeContext = React.createContext<ThemeContextValue>({  
    theme: 'light',  
});  
  
export const useThemeContext = () => {  
    return useContext(ThemeContext);  
};
  
export default BaseLayout;
  1. 创建 ThemeProvider.tsx
import React from 'react';  
import { ThemeContext } from '@/components/Provider/ThemeContext';  
import { ConfigProvider, theme } from 'antd';  
import useTheme from '@/hooks/useTheme';  
import locale from 'antd/locale/zh_CN';  
import 'dayjs/locale/zh-cn';  
  
export interface ThemeProviderProps {  
    children: React.ReactNode;  
}  
  
export function ThemeProvider(props: ThemeProviderProps) {  
    const [value, toggleTheme] = useTheme();  
  
    return (  
        <ThemeContext.Provider value={{ theme: value, toggleTheme }}>  
            <ConfigProvider  
                locale={locale}  
                theme={{  
                    algorithm:  
                    value === 'light' ? theme.defaultAlgorithm : theme.darkAlgorithm,  
                }}  
            >  
                {props.children}  
            </ConfigProvider>  
        </ThemeContext.Provider>  
    );  
}
  1. _app.tsx 中使用 ThemeProvider
import React from 'react';  
import '@/styles/globals.css';  
import { AppProps } from 'next/app';  
import { AuthProvider } from '@/components/Provider/AuthProvider';  
import { ThemeProvider } from '@/components/Provider/ThemeProvider';  
  
export default function App({  
    Component,  
    pageProps: { ...pageProps },  
}: AppProps) {  
    return (  
        <>  
            <ThemeProvider>  
                 <AuthProvider>  
                    <Component {...pageProps} />  
                </AuthProvider>  
            </ThemeProvider>  
        </>  
    );  
}

问答环节

Q1. 为什么要使用将 ConfigProvider 放在 _app.tsx 里,直接像下面这样在 BaseLayout 中使用 ConfigProvider 不好吗?

//...
const BaseLayout = ({ children }: any) => {  
const [value, setValue] = useTheme();  
  
    return (  
        <ConfigProvider  
            locale={locale}  
            theme={{  
            algorithm:  
            value === 'light' ? theme.defaultAlgorithm : theme.darkAlgorithm,  
            }}  
            >  
            <Layout style={{ minHeight: '100vh' }}>
                1111
            </Layout>
        </ConfigProvider>
    )
}

使用 BaseLayout

const TableList: React.FC = () => {

    return (  
        <BaseLayout>
            1122
        </BaseLayout>
    )
}

答:因为在暗黑模式下,这样使用在切换页面时会有闪烁问题,就是每次切换页面都有一个短暂的由白变黑的页面闪烁问题,这个之前忘记录频了,自行脑补下哈。

但是当你把 ConfigProvider 放在 _app.tsx 下就不会有闪烁的问题了,知道为什么吧,当你把 ConfigProvider 放在 _app.tsx 下,这玩意它就是全局的,每次打开新页面都不用再去问当前是什么主题,所以不会闪烁,了解了吧。

Q2. 为什么要使用 react 的 Context 和 Provider

有人可能会问直接使用 useThemeConfigProvider 实现不行吗,为什么还要使用 react 的 ContextProvider?其实核心就一句话,Context 和 Provider 这哥们俩实现了组件间数据共享。解释一下就是当你在其他组件中(例如在 Header 组件)直接用 useTheme 切换主题时,_app.tsx 中的 ConfigProvider 是感知不到主题变化的,而改变 Context 是全局都可以感知到的,所以,这就是使用 ContextProvider 的原因。

Q3. 那怎么切换主题啊

ok,示例:通过点击 button 来切换主题

const Header: React.FC<Props> = () => {  
    const { user } = useAuthContext();  
    const { theme, toggleTheme } = useThemeContext();  
    return (  
    <Layout.Header>  

        <Button  
            type='text'  
            onClick={() => toggleTheme && toggleTheme()} 
            icon={  
                theme === 'dark' ? (  
                    <IconDark style={{ height: 20, width: 20 }} />  
                ) : (  
                    <IconLight style={{ height: 24, width: 24 }} />  
                )  
            }  
        />  

    </Layout.Header>  
    );  
};  
  
export default Header;

Q4. 你还想到了其他的实现方式吗,欢迎在评论区留言。

最后

听说你想直接用这个模板项目(Next集成Antd 模板项目),安排,上链接:

github.com/lijunping36…

上篇链接:juejin.cn/post/731056…

感谢老铁的三连🤞