Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/css/34.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/cocoa/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何在react应用程序中设置系统首选项暗模式,同时允许用户来回切换当前主题_Javascript_Css_Reactjs_React Hooks_React Context - Fatal编程技术网

Javascript 如何在react应用程序中设置系统首选项暗模式,同时允许用户来回切换当前主题

Javascript 如何在react应用程序中设置系统首选项暗模式,同时允许用户来回切换当前主题,javascript,css,reactjs,react-hooks,react-context,Javascript,Css,Reactjs,React Hooks,React Context,我有一个react web应用程序,在导航上有一个主题切换。我有一个ThemeProviderContext,它具有自动检测用户系统主题首选项并进行设置的逻辑。然而,我觉得用户应该能够在网站上来回切换主题,不管他们的系统偏好如何。下面是ThemeContext.js文件,其中包含所有主题逻辑,包括切换方法 import React, { useState, useLayoutEffect } from 'react'; const ThemeContext = React.createCont

我有一个react web应用程序,在导航上有一个主题切换。我有一个
ThemeProviderContext
,它具有自动检测用户系统主题首选项并进行设置的逻辑。然而,我觉得用户应该能够在网站上来回切换主题,不管他们的系统偏好如何。下面是
ThemeContext.js
文件,其中包含所有主题逻辑,包括
切换
方法

import React, { useState, useLayoutEffect } from 'react';

const ThemeContext = React.createContext({
    dark: false,
    toggle: () => {},
});

export default ThemeContext;

export function ThemeProvider({ children }) {
    // keeps state of the current theme
    const [dark, setDark] = useState(false);

    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')
        .matches;
    const prefersLight = window.matchMedia('(prefers-color-scheme: light)')
        .matches;
    const prefersNotSet = window.matchMedia(
        '(prefers-color-scheme: no-preference)'
    ).matches;

    // paints the app before it renders elements
    useLayoutEffect(() => {
        // Media Hook to check what theme user prefers
        if (prefersDark) {
            setDark(true);
        }

        if (prefersLight) {
            setDark(false);
        }

        if (prefersNotSet) {
            setDark(true);
        }

        applyTheme();

        // if state changes, repaints the app
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [dark]);

    // rewrites set of css variablels/colors
    const applyTheme = () => {
        let theme;
        if (dark) {
            theme = darkTheme;
        }
        if (!dark) {
            theme = lightTheme;
        }

        const root = document.getElementsByTagName('html')[0];
        root.style.cssText = theme.join(';');
    };

    const toggle = () => {
        console.log('Toggle Method Called');

        // A smooth transition on theme switch
        const body = document.getElementsByTagName('body')[0];
        body.style.cssText = 'transition: background .5s ease';

        setDark(!dark);
    };

    return (
        <ThemeContext.Provider
            value={{
                dark,
                toggle,
            }}>
            {children}
        </ThemeContext.Provider>
    );
}

// styles
const lightTheme = [
    '--bg-color: var(--color-white)',
    '--text-color-primary: var(--color-black)',
    '--text-color-secondary: var(--color-prussianBlue)',
    '--text-color-tertiary:var(--color-azureRadiance)',
    '--fill-switch: var(--color-prussianBlue)',
    '--fill-primary:var(--color-prussianBlue)',
];

const darkTheme = [
    '--bg-color: var(--color-mirage)',
    '--text-color-primary: var(--color-white)',
    '--text-color-secondary: var(--color-iron)',
    '--text-color-tertiary: var(--color-white)',
    '--fill-switch: var(--color-gold)',
    '--fill-primary:var(--color-white)',
];

import React,{useState,useLayoutEffect}来自'React';
const ThemeContext=React.createContext({
黑暗:错,
切换:()=>{},
});
导出默认主题文本;
导出函数ThemeProvider({children}){
//保持当前主题的状态
const[dark,setDark]=useState(false);
const prefersDark=window.matchMedia(‘(首选颜色方案:深色)’)
.比赛;
const prefersLight=window.matchMedia(‘(首选颜色方案:灯光)’)
.比赛;
const prefersNotSet=window.matchMedia(
“(首选配色方案:无首选项)”
).比赛;
//在渲染元素之前绘制应用程序
useLayoutEffect(()=>{
//媒体挂钩,检查用户喜欢什么主题
如果(首选标记){
setDark(真);
}
如果(轻微){
setDark(假);
}
如果(首选设置){
setDark(真);
}
applyTheme();
//如果状态更改,则重新绘制应用程序
//eslint禁用下一行react HOOK/deps
},[暗];
//重写css变量/颜色集
常量applyTheme=()=>{
让主题;
如果(暗){
主题=黑暗主题;
}
如果(!黑暗){
主题=轻主题;
}
const root=document.getElementsByTagName('html')[0];
root.style.cssText=theme.join(“;”);
};
常量切换=()=>{
log('Toggle方法调用');
//主题切换的平稳过渡
const body=document.getElementsByTagName('body')[0];
body.style.cssText='transition:background.5s ease';
setDark(!dark);
};
返回(
{儿童}
);
}
//风格
常量lightTheme=[
“--bg颜色:var(--颜色白色)”,
'--text color primary:var(--color black)',
'--text color secondary:var(--颜色普鲁士蓝)',
“--文本颜色三级:var(-color azureRadiance)”,
“--填充开关:var(--颜色普鲁士蓝)”,
“--填充主:var(--颜色普鲁士蓝)”,
];
常数暗色=[
“--bg color:var(-color mirage)”,
“--文本主颜色:var(--color white)”,
'--text color secondary:var(--color iron)',
'--text color:var(-color white)',
“--填充开关:var(--金色)”,
“--填充主:变量(--白色)”,
];
因此,当页面加载时,显示用户的系统偏好,但也允许用户通过单击触发
toggle
功能的切换按钮来切换主题。在我当前的代码中,当调用
toggle
时,状态似乎会发生两次更改,因此主题保持不变。如何确保
切换
方法正常工作


这里有一个问题

问题是
useLayoutEffect
的整个块在
dark
值更改时运行。因此,当用户切换
dark
时,
首选…
if语句运行,
setDark
返回系统首选项

要解决这个问题,您需要跟踪用户手动切换主题,然后阻止
prefers…
if语句运行

在您的
主题Provider中执行以下操作:

  • 添加状态以监视用户是否使用了切换
  • 更新您的
    切换功能:
  • 最后,更新
    useLayout
    ,如下所示:
您的切换组件不必更改

更新:

萨尔的回答是一个很好的选择。Mine指出了现有代码中的缺陷以及如何添加这些缺陷。这指出了如何更有效地使用您的代码

export function ThemeProvider({ children }) {
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

  const [dark, setDark] = useState(prefersDark);

  useLayoutEffect(() => {
    applyTheme();
  }, [dark]);

  ...

}

尽管Barry的解决方案可行,但请注意,与其添加更多代码,还可以通过略读代码来获得相同的结果:

关键是将用户的首选项设置为初始状态,并停止检查其效果:

导出函数ThemeProvider({children}){
/*因为您将初始主题设置为非暗,
你可以假设你的初始状态应该是黑暗的
当用户的首选项设置为“暗”时*/
const prefersDark=window.matchMedia(‘(首选颜色方案:深色)’)
.比赛;
//如果首选项设置为“暗”,则为True,否则为false。
const[dark,setDark]=使用状态(首选标记);
/*注:初始状态在安装时设置,因此更好

把树放在树上,靠近树根

我已经回答完你的问题,这应该会让你走:)这是一种更干净、更聪明的方式。
const toggle = () => {
  console.log('Toggle Method Called');

  const body = document.getElementsByTagName('body')[0];
  body.style.cssText = 'transition: background .5s ease';

  setUserPick(true) // Add this line
  setDark(!dark);
};
useLayoutEffect(() => {
  if (!userPicked) { // This will stop the system preferences from taking place if the user manually toggles the them
    if (prefersDark) {
      setDark(true);
    }

    if (prefersLight) {
      setDark(false);
    }

    if (prefersNotSet) {
      setDark(true);
    }
  }

  applyTheme();
}, [dark]);
export function ThemeProvider({ children }) {
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

  const [dark, setDark] = useState(prefersDark);

  useLayoutEffect(() => {
    applyTheme();
  }, [dark]);

  ...

}