2026-02-06 03:34:50 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="redux-flow-demo">
|
|
|
|
|
|
<div class="demo-header">
|
2026-02-13 22:10:03 +08:00
|
|
|
|
<span class="icon">🔄</span>
|
|
|
|
|
|
<span class="title">Redux 数据流</span>
|
|
|
|
|
|
<span class="subtitle">单向循环的数据管道</span>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
<div class="intro-text">
|
|
|
|
|
|
想象你在<span class="highlight">图书馆</span>工作:读者(View)填写借书单(Action),管理员(Reducer)审核后更新库存记录(Store),新通知(View更新)就会显示在公告栏。
|
|
|
|
|
|
</div>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
<div class="demo-content">
|
|
|
|
|
|
<div class="counter-display">
|
|
|
|
|
|
<span class="counter-label">当前库存:</span>
|
|
|
|
|
|
<span class="counter-value" :class="{ changed: countChanged }">{{ count }}</span>
|
|
|
|
|
|
<span class="counter-unit">本书</span>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
<div class="action-buttons">
|
|
|
|
|
|
<button class="action-btn" @click="dispatchAction('INCREMENT')">
|
|
|
|
|
|
<span class="btn-icon">➕</span>
|
|
|
|
|
|
进货 (+1)
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="action-btn" @click="dispatchAction('DECREMENT')">
|
|
|
|
|
|
<span class="btn-icon">➖</span>
|
|
|
|
|
|
出货 (-1)
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="action-btn reset" @click="dispatchAction('RESET')">
|
|
|
|
|
|
<span class="btn-icon">🔄</span>
|
|
|
|
|
|
重置库存
|
|
|
|
|
|
</button>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
<Transition name="fade">
|
|
|
|
|
|
<div v-if="flowStage" class="flow-stages">
|
|
|
|
|
|
<div class="flow-stage" :class="{ active: flowStage === 'action' }">
|
|
|
|
|
|
<span class="stage-icon">📝</span>
|
|
|
|
|
|
<span class="stage-text">Action: {{ currentAction.type }}</span>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
2026-02-13 22:10:03 +08:00
|
|
|
|
<div class="flow-arrow">→</div>
|
|
|
|
|
|
<div class="flow-stage" :class="{ active: flowStage === 'reducer' }">
|
|
|
|
|
|
<span class="stage-icon">⚙️</span>
|
|
|
|
|
|
<span class="stage-text">Reducer 处理中...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flow-arrow">→</div>
|
|
|
|
|
|
<div class="flow-stage" :class="{ active: flowStage === 'store' }">
|
|
|
|
|
|
<span class="stage-icon">📦</span>
|
|
|
|
|
|
<span class="stage-text">Store 已更新</span>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-13 22:10:03 +08:00
|
|
|
|
</Transition>
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
<div class="info-box">
|
|
|
|
|
|
<span class="icon">💡</span>
|
|
|
|
|
|
<strong>核心思想:</strong>Redux 是单向数据流循环:View 触发 Action → Reducer 纯函数处理 → 更新 Store → 通知 View 重新渲染。状态可预测,易于调试。
|
2026-02-06 03:34:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, reactive } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
const count = ref(0)
|
|
|
|
|
|
const countChanged = ref(false)
|
|
|
|
|
|
const flowStage = ref('')
|
|
|
|
|
|
|
|
|
|
|
|
const currentAction = reactive({
|
2026-02-13 22:10:03 +08:00
|
|
|
|
type: ''
|
2026-02-06 03:34:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const dispatchAction = async (actionType) => {
|
|
|
|
|
|
flowStage.value = 'action'
|
|
|
|
|
|
currentAction.type = actionType
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
await wait(500)
|
2026-02-06 03:34:50 +08:00
|
|
|
|
flowStage.value = 'reducer'
|
2026-02-13 22:10:03 +08:00
|
|
|
|
await wait(500)
|
2026-02-06 03:34:50 +08:00
|
|
|
|
flowStage.value = 'store'
|
|
|
|
|
|
|
|
|
|
|
|
switch (actionType) {
|
|
|
|
|
|
case 'INCREMENT':
|
|
|
|
|
|
count.value++
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'DECREMENT':
|
|
|
|
|
|
count.value--
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'RESET':
|
|
|
|
|
|
count.value = 0
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
countChanged.value = true
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
countChanged.value = false
|
|
|
|
|
|
}, 300)
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
await wait(300)
|
2026-02-06 03:34:50 +08:00
|
|
|
|
flowStage.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.redux-flow-demo {
|
|
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
padding: 0.75rem;
|
|
|
|
|
|
margin: 0.5rem 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.demo-header {
|
|
|
|
|
|
display: flex;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
margin-bottom: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.demo-header .icon {
|
|
|
|
|
|
font-size: 1.25rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.demo-header .title {
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
font-size: 1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.demo-header .subtitle {
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
margin-left: 0.5rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.intro-text {
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
|
|
padding: 0.75rem;
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.intro-text .highlight {
|
|
|
|
|
|
color: var(--vp-c-brand-1);
|
|
|
|
|
|
font-weight: 500;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.demo-content {
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
|
margin-bottom: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.counter-display {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
padding: 2rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
margin-bottom: 1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.counter-label {
|
2026-02-13 22:10:03 +08:00
|
|
|
|
font-size: 1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.counter-value {
|
2026-02-13 22:10:03 +08:00
|
|
|
|
font-size: 3rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: var(--vp-c-brand);
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.counter-value.changed {
|
|
|
|
|
|
transform: scale(1.2);
|
|
|
|
|
|
color: #22c55e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.counter-unit {
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 03:34:50 +08:00
|
|
|
|
.action-buttons {
|
|
|
|
|
|
display: flex;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
gap: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex-wrap: wrap;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
margin-bottom: 1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
padding: 0.75rem 1.5rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
background: var(--vp-c-brand);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border: none;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
font-size: 0.9rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-btn:hover {
|
2026-02-13 22:10:03 +08:00
|
|
|
|
opacity: 0.9;
|
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-btn.reset {
|
|
|
|
|
|
background: var(--vp-c-text-2);
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-icon {
|
2026-02-13 22:10:03 +08:00
|
|
|
|
font-size: 1rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.flow-stages {
|
2026-02-06 03:34:50 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 0.5rem;
|
2026-02-14 20:23:34 +08:00
|
|
|
|
padding: 0.75rem;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.flow-stage {
|
2026-02-06 03:34:50 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
padding: 0.75rem 1rem;
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border: 2px solid var(--vp-c-divider);
|
2026-02-14 20:23:34 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
font-size: 0.85rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.flow-stage.active {
|
|
|
|
|
|
border-color: var(--vp-c-brand);
|
|
|
|
|
|
background: var(--vp-c-brand-soft);
|
|
|
|
|
|
box-shadow: 0 0 0 3px var(--vp-c-brand-delta);
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.stage-icon {
|
|
|
|
|
|
font-size: 1.25rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.stage-text {
|
|
|
|
|
|
font-weight: 500;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.flow-arrow {
|
|
|
|
|
|
font-size: 1.5rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
color: var(--vp-c-text-3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.fade-enter-active,
|
|
|
|
|
|
.fade-leave-active {
|
|
|
|
|
|
transition: opacity 0.2s ease;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.fade-enter-from,
|
|
|
|
|
|
.fade-leave-to {
|
|
|
|
|
|
opacity: 0;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.info-box {
|
|
|
|
|
|
background: var(--vp-c-bg-alt);
|
|
|
|
|
|
padding: 0.75rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-13 22:10:03 +08:00
|
|
|
|
font-size: 0.85rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.info-box .icon {
|
|
|
|
|
|
margin-right: 0.25rem;
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.flow-stages {
|
2026-02-06 03:34:50 +08:00
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 22:10:03 +08:00
|
|
|
|
.flow-arrow {
|
|
|
|
|
|
transform: rotate(90deg);
|
2026-02-06 03:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|