Files
test-repo/docs/.vitepress/theme/components/appendix/web-basics/JQueryVsStateDemo.vue
T
sanbuphy d35211071a style: update border-radius and padding values across components
- standardize border-radius from 8px to 6px for consistent styling
- adjust padding values from 1rem to 0.75rem for better visual hierarchy
- remove redundant overflow-y properties for cleaner code
2026-02-14 20:23:34 +08:00

384 lines
8.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
JQueryVsStateDemo.vue
用可视化方式解释jQuery = 手动改 DOM框架 = State 自动同步
-->
<template>
<div class="jq-demo">
<div class="header">
<div class="title">什么是 jQuery购物车数量秒懂</div>
<div class="subtitle">
左边 jQuery 一样手动改页面容易漏右边 Vue/React
一样只改状态
</div>
</div>
<div class="panes">
<!-- jQuery-like -->
<div class="pane">
<div class="pane-title">jQuery 思路到处改 DOM</div>
<div class="mock-app">
<div class="topbar">
<span>🛒 角标</span>
<span class="badge" :class="{ wrong: jqBadgeWrong }">{{
jqBadge
}}</span>
</div>
<div class="content">
<div class="row">
购物车页数量
<span class="num" :class="{ wrong: jqPageWrong }">{{
jqPage
}}</span>
</div>
<div class="row">
结算按钮
<button class="checkout">去结算 ({{ jqButtonLabel }})</button>
</div>
</div>
</div>
<div class="controls">
<div class="control-title">模拟你写的命令</div>
<div class="btns">
<button @click="jqIncreaseData">数据 +1但还没改页面</button>
<button @click="jqUpdateBadge">改角标</button>
<button @click="jqUpdateCartPage">改购物车页</button>
<button @click="jqUpdateCheckoutButton">改结算按钮</button>
</div>
<div class="hint" :class="{ danger: jqInconsistent }">
{{ jqHint }}
</div>
<div class="log">
<div class="log-title">命令日志</div>
<div v-if="jqLogs.length === 0" class="log-empty">
还没有操作
</div>
<div v-else class="log-list">
<div v-for="(l, idx) in jqLogs" :key="idx" class="log-item">
{{ l }}
</div>
</div>
</div>
</div>
</div>
<!-- State-driven -->
<div class="pane">
<div class="pane-title">Vue/React 思路只改 State</div>
<div class="mock-app">
<div class="topbar">
<span>🛒 角标</span>
<span class="badge">{{ state }}</span>
</div>
<div class="content">
<div class="row">
购物车页数量 <span class="num">{{ state }}</span>
</div>
<div class="row">
结算按钮
<button class="checkout">去结算 ({{ state }} )</button>
</div>
</div>
</div>
<div class="controls">
<div class="control-title">你只需要做一件事</div>
<div class="btns">
<button class="primary" @click="state = state + 1">state +1</button>
<button class="secondary" @click="resetAll">重置</button>
</div>
<div class="hint ok">
State 变了界面三处会自动同步不需要你手动找 DOM 去改
</div>
<div class="mini">
<div class="mini-title">这里的两个新词</div>
<div class="mini-item">
<strong>DOM</strong>浏览器里的页面结构按钮/文字/图片都在里面
</div>
<div class="mini-item">
<strong>State</strong>页面的数据比如购物车数量
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const state = ref(1)
// jQuery side: "real data" + "DOM" values displayed at multiple places
const jqData = ref(1)
const jqBadge = ref(1)
const jqPage = ref(1)
const jqButtonLabel = ref('1 件')
const jqLogs = ref([])
const log = (txt) => {
jqLogs.value.unshift(
`${new Date().toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})} - ${txt}`
)
jqLogs.value = jqLogs.value.slice(0, 8)
}
const jqIncreaseData = () => {
jqData.value += 1
log(`数据 +1(现在真实数据 = ${jqData.value}`)
}
const jqUpdateBadge = () => {
jqBadge.value = jqData.value
log(`更新角标 DOM = ${jqBadge.value}`)
}
const jqUpdateCartPage = () => {
jqPage.value = jqData.value
log(`更新购物车页 DOM = ${jqPage.value}`)
}
const jqUpdateCheckoutButton = () => {
jqButtonLabel.value = `${jqData.value}`
log(`更新结算按钮 DOM = ${jqButtonLabel.value}`)
}
const jqInconsistent = computed(() => {
return (
jqBadge.value !== jqData.value ||
jqPage.value !== jqData.value ||
jqButtonLabel.value !== `${jqData.value}`
)
})
const jqBadgeWrong = computed(() => jqBadge.value !== jqData.value)
const jqPageWrong = computed(() => jqPage.value !== jqData.value)
const jqHint = computed(() => {
if (!jqInconsistent.value) return '✅ 三处显示一致(恭喜你都改对了)'
return '⚠️ 数据和页面不一致:你可能漏更新了某一处 DOM(真实项目里这就是 bug)'
})
const resetAll = () => {
state.value = 1
jqData.value = 1
jqBadge.value = 1
jqPage.value = 1
jqButtonLabel.value = '1 件'
jqLogs.value = []
}
</script>
<style scoped>
.jq-demo {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.header {
margin-bottom: 1rem;
}
.title {
font-weight: 700;
font-size: 1.05rem;
}
.subtitle {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.panes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1rem;
}
.pane {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 12px;
padding: 0.75rem;
}
.pane-title {
font-weight: 700;
font-size: 0.95rem;
margin-bottom: 0.75rem;
}
.mock-app {
border: 1px dashed var(--vp-c-divider);
border-radius: 12px;
overflow: hidden;
}
.topbar {
padding: 0.6rem 0.75rem;
background: var(--vp-c-bg-soft);
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2ch;
padding: 0.1rem 0.45rem;
border-radius: 999px;
background: rgba(59, 130, 246, 0.15);
color: #1d4ed8;
font-weight: 700;
}
.content {
padding: 0.75rem;
}
.row {
margin-bottom: 0.6rem;
font-size: 0.92rem;
}
.num {
font-weight: 800;
padding: 0.05rem 0.25rem;
border-radius: 6px;
background: rgba(34, 197, 94, 0.12);
color: #15803d;
}
.checkout {
border: none;
background: var(--vp-c-brand);
color: #fff;
padding: 0.4rem 0.8rem;
border-radius: 10px;
font-size: 0.85rem;
}
.controls {
margin-top: 0.9rem;
}
.control-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.5rem;
}
.btns {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.btns button {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
padding: 0.35rem 0.65rem;
border-radius: 10px;
cursor: pointer;
font-size: 0.85rem;
}
.btns button.primary {
border: none;
background: #22c55e;
color: #fff;
}
.btns button.secondary {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
}
.hint {
margin-top: 0.65rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
border: 1px dashed var(--vp-c-divider);
background: var(--vp-c-bg-soft);
padding: 0.6rem 0.7rem;
border-radius: 10px;
}
.hint.danger {
color: #b91c1c;
border-color: rgba(239, 68, 68, 0.4);
background: rgba(239, 68, 68, 0.08);
}
.hint.ok {
color: #166534;
border-color: rgba(34, 197, 94, 0.35);
background: rgba(34, 197, 94, 0.08);
}
.wrong {
background: rgba(239, 68, 68, 0.12) !important;
color: #b91c1c !important;
}
.log {
margin-top: 0.75rem;
}
.log-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.35rem;
}
.log-empty {
color: var(--vp-c-text-3);
font-size: 0.85rem;
}
.log-list {
display: grid;
gap: 0.25rem;
}
.log-item {
font-family: var(--vp-font-family-mono);
font-size: 0.78rem;
color: var(--vp-c-text-2);
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 6px;
padding: 0.35rem 0.5rem;
}
.mini {
margin-top: 0.75rem;
border-top: 1px dashed var(--vp-c-divider);
padding-top: 0.75rem;
}
.mini-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.4rem;
}
.mini-item {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.25rem;
}
</style>