Files
test-repo/docs/.vitepress/theme/components/appendix/web-basics/VueReactComparisonDemo.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

353 lines
7.5 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.
<!--
VueReactComparisonDemo.vue
用可视化方式对比 Vue vs React语法状态更新渲染心智模型
-->
<template>
<div class="vr-demo">
<div class="header">
<div class="title">Vue vs React它们哪里像哪里不一样</div>
<div class="subtitle">
选一个标签页然后点+1看看背后发生了什么示意
</div>
</div>
<div class="tabs">
<button
v-for="t in tabs"
:key="t.key"
class="tab"
:class="{ active: currentTab === t.key }"
@click="currentTab = t.key"
>
{{ t.label }}
</button>
</div>
<div class="grid">
<div class="panel">
<div class="panel-title">Vue</div>
<div class="preview">
<div class="row">
count: <strong>{{ count }}</strong>
</div>
<button class="btn vue" @click="inc('vue')">+1</button>
</div>
<div class="code">
<div class="code-title">典型写法示意</div>
<pre><code class="language-vue">{{ vueCode }}</code></pre>
</div>
</div>
<div class="panel">
<div class="panel-title">React</div>
<div class="preview">
<div class="row">
count: <strong>{{ count }}</strong>
</div>
<button class="btn react" @click="inc('react')">+1</button>
</div>
<div class="code">
<div class="code-title">典型写法示意</div>
<pre><code class="language-jsx">{{ reactCode }}</code></pre>
</div>
</div>
</div>
<div class="what">
<div class="what-title">点击 +1 时发生了什么</div>
<div class="steps">
<div
v-for="(s, idx) in steps"
:key="idx"
class="step"
:class="{ highlight: idx === lastStepIndex }"
>
<span class="num">{{ idx + 1 }}</span>
<span class="text">{{ s }}</span>
</div>
</div>
<div class="note">
说明这是为了建立心智模型的<strong>简化示意</strong>真实框架内部更复杂
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const tabs = [
{ key: 'syntax', label: '语法(Template vs JSX' },
{ key: 'state', label: '状态更新(ref vs useState' },
{ key: 'render', label: '渲染心智模型' }
]
const currentTab = ref('syntax')
const count = ref(1)
const lastClicked = ref('vue')
const lastStepIndex = ref(-1)
const inc = (who) => {
lastClicked.value = who
count.value += 1
// 简单动画:把最后一步高亮一下
lastStepIndex.value = 2
setTimeout(() => (lastStepIndex.value = -1), 600)
}
const vueCode = computed(() => {
if (currentTab.value === 'syntax') {
// NOTE: Avoid literal closing script tag inside a script block (HTML parser would terminate early).
return [
`<template>`,
` <button @click="count++">+1</button>`,
` <div>count: {{ count }}</div>`,
`</template>`,
``,
`<script setup>`,
`import { ref } from 'vue'`,
`const count = ref(1)`,
`</scr` + `ipt>`
].join('\n')
}
if (currentTab.value === 'state') {
return `import { ref } from 'vue'
const count = ref(1)
function inc() {
count.value++
}`
}
return `// Vue:响应式系统会“追踪依赖”
// count 变了 -> 用到 count 的地方自动更新`
})
const reactCode = computed(() => {
if (currentTab.value === 'syntax') {
return `function App() {
const [count, setCount] = useState(1)
return (
<>
<button onClick={() => setCount(count + 1)}>+1</button>
<div>count: {count}</div>
</>
)
}`
}
if (currentTab.value === 'state') {
return `const [count, setCount] = useState(1)
function inc() {
setCount(count + 1)
}`
}
return `// Reactstate 变了 -> 组件函数重新执行(重新渲染)
// 然后 React 决定哪些 DOM 需要更新`
})
const steps = computed(() => {
if (currentTab.value === 'syntax') {
return [
'你写 UI 的方式:Vue 常用 TemplateReact 常用 JSX',
'点击按钮触发事件处理函数',
'count 更新后,界面显示跟着变'
]
}
if (currentTab.value === 'state') {
return [
'Vue:用 ref/ reactive 保存状态;React:用 useState 保存状态',
lastClicked.value === 'vue'
? '你修改了 count.value'
: '你调用 setCount(...)',
'框架把变化反映到界面'
]
}
return [
'Vue:更偏“依赖追踪”,谁用到了 count,就更新谁',
'React:更偏“重新执行组件函数”,得到新的 UI 描述',
'最终都会只更新需要变化的 DOM(避免全量重画)'
]
})
</script>
<style scoped>
.vr-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;
}
.tabs {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.tab {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
padding: 0.35rem 0.75rem;
border-radius: 999px;
font-size: 0.85rem;
cursor: pointer;
}
.tab.active {
border-color: #3b82f6;
color: #1d4ed8;
background: rgba(59, 130, 246, 0.12);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1rem;
}
.panel {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 12px;
padding: 0.75rem;
}
.panel-title {
font-weight: 800;
margin-bottom: 0.75rem;
}
.preview {
border: 1px dashed var(--vp-c-divider);
border-radius: 10px;
padding: 0.9rem;
background: var(--vp-c-bg-soft);
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.row {
font-size: 0.95rem;
}
.btn {
border: none;
padding: 0.45rem 0.8rem;
border-radius: 10px;
color: #fff;
cursor: pointer;
font-weight: 700;
font-size: 0.85rem;
}
.btn.vue {
background: #22c55e;
}
.btn.react {
background: #0ea5e9;
}
.code {
margin-top: 0.9rem;
}
.code-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.35rem;
}
pre {
margin: 0;
padding: 0.75rem;
border-radius: 10px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
overflow: auto;
}
code {
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
color: var(--vp-c-text-1);
}
.what {
margin-top: 1rem;
border-top: 1px dashed var(--vp-c-divider);
padding-top: 1rem;
}
.what-title {
font-weight: 700;
margin-bottom: 0.6rem;
}
.steps {
display: grid;
gap: 0.5rem;
}
.step {
display: flex;
gap: 0.6rem;
align-items: flex-start;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 10px;
padding: 0.55rem 0.65rem;
}
.step.highlight {
border-color: rgba(34, 197, 94, 0.5);
background: rgba(34, 197, 94, 0.08);
}
.num {
width: 1.6rem;
height: 1.6rem;
border-radius: 999px;
background: rgba(99, 102, 241, 0.15);
color: #4338ca;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 0.85rem;
flex: 0 0 auto;
}
.text {
font-size: 0.9rem;
color: var(--vp-c-text-2);
line-height: 1.35;
}
.note {
margin-top: 0.7rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
</style>