操作方法:使用 TailwindCSS + Vue3 + Vite 切换暗模式



我是关于 Vite/Vue3 的初学者,目前我面临着一个需要社区综合知识的问题。

我创建了一个 Vite/Vue3 应用程序并安装了 TailwindCSS:

npm create vite@latest my-vite-vue-app -- --template vue
cd my-vite-vue-app
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

然后我按照Tailwind主页上的说明进行操作:

将路径添加到 tailwind.config.js 文件中的所有模板文件。
将新创建的 ./src/index.css 文件导入 ./src/main.js 文件中。
创建一个 ./src/index.css 文件,并为 Tailwind 的每个层添加 @tailwind 指令。

现在我有一个工作的 Vite/Vue3/TailwindCSS 应用程序,并希望添加该功能来切换暗模式。

Tailwind 文档说,这可以通过将darkMode: 'class'添加到tailwind.config.js然后切换<html>标签的类dark来存档。

我使用以下代码完成了这项工作:

  1. 内部索引.html
<html lang="en" id="html-root">
(...)
<body class="antialiased text-slate-500 dark:text-slate-400 bg-white dark:bg-slate-900">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
  1. Inside About.vue
<template>
<div>
<h1>This is an about page</h1>
<button @click="toggleDarkMode">Toggle</botton>
</div>
</template>
<script>
export default {
methods: {
toggleDarkMode() {
const element = document.getElementById('html-root')
if (element.classList.contains('dark')) {
element.classList.remove('dark')
} else {
element.classList.add('dark')
}
},
},
};
</script>

是的,我知道这不是 Vue3 风格的代码。而且,是的,我知道一个人可以做element.classList.toggle()而不是.remove().add().但也许其他一些像我这样的初学者将来会看看这个,并且会感谢一些低复杂的代码开始。所以请怜悯...

现在我终于要问社区了我想问的问题:

我知道像这样操纵 DOM 不是 Vue 的做事方式。而且,当然,我想以正确的方式存档我的目标。但是我该怎么做呢?

相信我,我用谷歌搜索了几个小时,但我没有找到一个解决方案,如果不安装这个、这个和这个额外的 npm 模块就可以工作。

但我想有一个极简主义的方法。尽可能少的依赖,以免压倒我和其他想要开始学习的人。

以此为背景 - 你们和女孩对我和其他新手有解决方案吗?:-)

事件的目标元素位于应用程序外部。这意味着除了通过 DOM 可用方法查询它之外,没有其他方法可以与之交互。

换句话说,你做对了。 如果元素在应用程序中,那么您只需将类链接到您的属性,并让 Vue 处理 DOM 操作的细节:

:class="{ dark: darkMode }"

但事实并非如此。


作为旁注,为了决定是否应该应用/删除它,切换方法不依赖于<body>元素是否具有类,这一点非常重要。你应该将值保存在你的应用程序状态中,这应该是你唯一的事实来源.
这是你不想打破的 Vue 原则:让数据驱动 DOM 状态,而不是相反。

可以从<body>的当前状态获取值(挂载时),但从那时起,对应用状态的更改将确定该类是否存在于元素上。

vue2 示例:

Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
darkMode: document.body.classList.contains('dark')
}),
methods: {
applyDarkMode() {
document.body.classList[
this.darkMode ? 'add' : 'remove'
]('dark')
}
},
watch: {
darkMode: 'applyDarkMode'
}
})
body.dark {
background-color: #191919;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<label>
<input type="checkbox" v-model="darkMode">
dark mode
</label>
</div>

vue3 示例:

const {
createApp,
ref,
watchEffect
} = Vue;
createApp({
setup() {
const darkMode = ref(document.body.classList.contains('dark'));
const applyDarkMode = () => document.body.classList[
darkMode.value ? 'add' : 'remove'
]('dark');
watchEffect(applyDarkMode);
return { darkMode };
}
}).mount('#app')
body.dark {
background-color: #191919;
color: white;
}
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>
<div id="app">
<label>
<input type="checkbox" v-model="darkMode">
dark mode
</label>
</div>

显然,您可能希望在data中保留darkMode的状态,而不是本地存储(并通过computed在组件中提供它),如果您在多个组件中使用它。

你正在寻找的是绑定类,但你陷入困境的是试图操纵<body>,它超出了你的主 Vue 实例挂载的<div>

现在您的问题是您的按钮可能与根<div id="app">位于不同的文件中,该根从样板代码开始App.vue。您的两个解决方案是研究状态管理(更好地实现可伸缩性),或者在父级和子级之间执行一些简单的变量传递。我将展示后者:

从交换机组件开始:

// DarkButton.vue
<template>
<div>
<h1>This is an about page</h1>
<button @click="toggleDarkMode">Toggle</button>
</div>
</template>
<script>
export default {
methods: {
toggleDarkMode() {
this.$emit('dark-switch');
},
},
};
</script>

这使用组件事件 ($emit)

然后你的父/根 App.vue 将侦听该切换事件并以 Vue 方式更新其类:

<template>
<div id="app" :class="{ dark: darkmode }">
<p>Darkmode: {{ darkmode }}</p>
<DarkButton @dark-switch="onDarkSwitch" />
</div>
</template>
<script>
import DarkButton from './components/DarkButton.vue';
export default {
name: 'App',
components: {
DarkButton,
},
data: () => ({
darkmode: false,
}),
methods: {
onDarkSwitch() {
this.darkmode = !this.darkmode;
},
},
};
</script>

虽然顺风说香草JS将其添加到您的<body>中,但您通常不应该从那时起操纵它。相反,不要操纵你的<body>,只用你想要在 Vue 范围内的东西来达到你的<div id="app">

最新更新