Last active
July 14, 2022 08:56
-
-
Save nanxiaobei/383f0657e639fb4246332ff8b977b858 to your computer and use it in GitHub Desktop.
returns isDark state, and a switch function for dark mode button
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const useDarkMode = (): [boolean, () => void] => { | |
const [isDark, setIsDark] = useState(false); | |
const setDark = (newDark: boolean) => { | |
document.documentElement.classList[newDark ? 'add' : 'remove']('dark'); | |
localStorage.setItem('dark', `${newDark}`); | |
setIsDark(newDark); | |
}; | |
useEffect(() => { | |
const darkScheme = window.matchMedia('(prefers-color-scheme: dark)'); | |
// init | |
setDark(localStorage.getItem('dark') === 'true' || darkScheme.matches); | |
// listen system | |
const onChange = (event: MediaQueryListEvent) => { | |
setDark(event.matches); | |
}; | |
darkScheme.addEventListener('change', onChange); | |
return () => { | |
darkScheme.removeEventListener('change', onChange); | |
}; | |
}, []); | |
// manual switch | |
return [ | |
isDark, | |
useCallback(() => { | |
setDark(localStorage.getItem('dark') !== 'true'); | |
}, []), | |
]; | |
}; | |
export default useDarkMode; |
为什么觉得会调用两次呢?
小白不懂就问,darkScheme.addEventListener 两次会不会导致系统主题变化时 setDark 也会被调用两次,最后没有变化?
在react18新严格模式下会调两次,但第一次调用后会被卸载 所以结果是一样的,而且只会在开发环境下调两次,生产环境仍然一次,严格模式的作用是帮助你暴露问题
import { useCallback, useEffect, useState } from 'react'
type SetDarkMode = {
(value: boolean | ((prevValue: boolean) => boolean)): void
toggle: () => void
toDark: () => void
toLight: () => void
}
const darkScheme = window?.matchMedia('(prefers-color-scheme: dark)')
const defaultIsDark =
localStorage.getItem('dark') === 'true' || darkScheme.matches
const useDarkMode = (): [boolean, SetDarkMode] => {
const [isDark, setIsDark] = useState(defaultIsDark)
const setDarkMode = <SetDarkMode>((newDark) => {
setIsDark(newDark)
const next = typeof newDark === 'function' ? newDark(isDark) : newDark
document.documentElement.classList[next ? 'add' : 'remove']('dark')
localStorage.setItem('dark', `${next}`)
})
setDarkMode.toggle = () => {
setDarkMode((prev) => !prev)
}
setDarkMode.toDark = () => {
setDarkMode(true)
}
setDarkMode.toLight = () => {
setDarkMode(false)
}
useEffect(
function handleDarkModeChange() {
// listen system
const onChange = (event: MediaQueryListEvent) => {
setDarkMode(event.matches)
}
darkScheme.addEventListener('change', onChange)
return () => {
darkScheme.removeEventListener('change', onChange)
}
},
[setDarkMode]
)
// manual switch
return [isDark, useCallback(setDarkMode, [setDarkMode])]
}
export default useDarkMode
一点点改动看小北哥喜不喜欢
不错~
我写这个是在 next 里用,window 放在最外面就会报错,所以写在 useEffect 里了 😁
嗯嗯可以兼容一下 const darkScheme = window?.matchMedia('(prefers-color-scheme: dark)')
两次指的是有两个组件分别调用了 useDarkMode
,这样 media query 的 listener 就被注册了两次,因而产生一些意料之外的效果。这么想的原因是我感觉这个 hook 应该是可以被多次调用的,一个 app 中有多个地方需要判断和设置 dark mode 也比较常见。
嗯有道理,如果两个组件使用,会被注册两次,不过行为应该是一致的,因为系统的 Dark Mode 情况是唯一的。
担心错乱可以设置全局标记,在注册前判断是否已注册。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
小白不懂就问,darkScheme.addEventListener 两次会不会导致系统主题变化时 setDark 也会被调用两次,最后没有变化?