<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Duanhen's Blog</title>
    <link>https://www.duanhen.top</link>
    <description>一个计算机蒟蒻的博客</description>
    <language>zh-CN</language>
    <atom:link href="https://www.duanhen.top/rss.xml" rel="self" type="application/rss+xml"/>
<item>
      <title>Vue 学习笔记（二）</title>
      <link>https://www.duanhen.top/article/vue_note_2</link>
      <guid isPermaLink="true">https://www.duanhen.top/article/vue_note_2</guid>
      <pubDate>Sun, 07 Jun 2026 00:00:00 GMT</pubDate>
      <description>认识计算属性、侦听器、生命周期和组件</description>
      <content:encoded><![CDATA[<h2>计算属性</h2>
<h3>基础用法</h3>
<p>计算属性是基于响应式依赖进行缓存的计算结果。当依赖的响应式状态发生变化时，计算属性会重新计算。</p>
<pre><code class="language-javascript">import { ref, computed } from 'vue'

const firstName = ref('zhang')
const lastName = ref('san')

// 创建一个计算属性
const fullName = computed(() =&gt; {
  return firstName.value + lastName.value
})

console.log(fullName.value) // 输出：zhangsan

// 修改依赖项会触发计算属性重新计算
firstName.value = 'li'
console.log(fullName.value) // 输出：lisan

</code></pre>
<p>我们在这里定义了一个计算属性 <code>fullName</code>。<code>computed()</code> 方法期望接收一个 getter 函数，返回值为一个 计算属性 ref。和其他一般的 ref 类似，我们可以通过 <code>fullName.value</code> 访问计算结果。计算属性 ref 也会在模板中自动解包，因此在模板表达式中引用时无需添加 <code>.value</code>。</p>
<p>我们在表达式中像这样调用一个函数也会获得和计算属性相同的结果：</p>
<pre><code class="language-vue">&lt;p&gt;{{ fullName() }}&lt;/p&gt;
</code></pre>
<pre><code class="language-javascript">// 组件中
function fullName() {
  return firstName.value + lastName.value
}
</code></pre>
<p>若我们将同样的函数定义为一个方法而不是计算属性，两种方式在结果上确实是完全相同的，然而，不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 <code>firstName</code>  和 <code>lastName</code> 不改变，无论多少次访问 <code>fullName</code> 都会立即返回先前的计算结果，而不用重复执行 getter 函数，这可以节省很多不必要的计算。</p>
<h3>可写的计算属性</h3>
<p>计算属性默认是只读的。当尝试修改一个计算属性时，我们会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性，我们可以通过同时提供 getter 和 setter 来创建：</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意：我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
&lt;/script&gt;
</code></pre>
<p>现在当我们再运行 <code>fullName.value = 'John Doe'</code> 时，setter 会被调用而 <code>firstName</code> 和 <code>lastName</code> 会随之更新。</p>
<h2>侦听器</h2>
<p>计算属性允许我们声明性地计算衍生值。然而在有些情况下，我们需要在状态变化时执行一些“副作用”：例如更改 DOM，或是根据异步操作的结果去修改另一处的状态，这时候就需要用到侦听器。Vue3 提供了两种侦听器 API： <code>watch</code> 和 <code>watchEffect</code>。</p>
<h3>watch()</h3>
<p><code>watch</code> 用于监听一个或多个响应式数据源，在每次响应式数据源发生变化时触发回调函数。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) =&gt; {
  if (newQuestion.includes('?')) {
    loading.value = true
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    } finally {
      loading.value = false
    }
  }
})
&lt;/script&gt;

&lt;template&gt;
  &lt;p&gt;
    Ask a yes/no question:
    &lt;input v-model=&quot;question&quot; :disabled=&quot;loading&quot; /&gt;
  &lt;/p&gt;
  &lt;p&gt;{{ answer }}&lt;/p&gt;
&lt;/template&gt;
</code></pre>
<p><code>watch</code> 的第一个参数可以是不同形式的“数据源”，它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。</p>
<pre><code class="language-javascript">const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) =&gt; {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () =&gt; x.value + y.value,
  (sum) =&gt; {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () =&gt; y.value], ([newX, newY]) =&gt; {
  console.log(`x is ${newX} and y is ${newY}`)
})
</code></pre>
<p>但是我们不能直接侦听响应式对象的属性值：</p>
<pre><code class="language-javascript">const obj = reactive({ count: 0 })

// 错误，因为 watch() 得到的参数是一个 number
watch(obj.count, (count) =&gt; {
  console.log(`Count is: ${count}`)
})

// 这里需要用一个返回该属性的 getter 函数：
watch(
  () =&gt; obj.count,
  (count) =&gt; {
    console.log(`Count is: ${count}`)
  }
)
</code></pre>
<p><code>watch</code> 默认是懒执行的：仅当数据源变化时，才会执行回调。但在某些场景中，我们希望在创建侦听器时，立即执行一遍回调，这时我们可以通过传入 <code>immediate: true</code> 选项来强制侦听器的回调立即执行：</p>
<pre><code class="language-javascript">watch(
  source,
  (newValue, oldValue) =&gt; {
    // 立即执行，且当 `source` 改变时再次执行
  },
  { immediate: true }
)
</code></pre>
<p>每当被侦听源发生变化时，侦听器的回调就会执行。如果希望回调只在源变化时触发一次，我们可以使用 <code>once: true</code> 选项：</p>
<pre><code class="language-javascript">watch(
  source,
  (newValue, oldValue) =&gt; {
    // 当 `source` 变化时，仅触发一次
  },
  { once: true }
)
</code></pre>
<h3>watchEffect()</h3>
<p><code>watchEffect()</code> 允许我们自动跟踪回调的响应式依赖，在依赖变化时会重新执行。</p>
<pre><code>import { ref, watchEffect } from 'vue'

const count = ref(0)
const message = ref('Hello')

watchEffect(() =&gt; {
  console.log(`Count: ${count.value}, Message: ${message.value}`)
})
// 立即输出：Count: 0, Message: Hello

// 修改依赖会触发 watchEffect 回调
count.value++
// 输出：Count: 1, Message: Hello

message.value = 'World'
// 输出：Count: 1, Message: World
</code></pre>
<p>这个例子中，回调会立即执行，不需要指定 <code>immediate: true</code>。在执行期间，它会自动追踪 <code>count.value</code> 和 <code>message.value</code> 作为依赖（和计算属性类似）。每当这两个值变化时，回调会再次执行。有了 <code>watchEffect()</code>，我们不再需要明确传递 <code>count.value</code> 和 <code>message.value</code> 作为源值。</p>
<h3>停止侦听器</h3>
<p>在 <code>setup()</code> 或 <code>&lt;script setup&gt;</code> 中用同步语句创建的侦听器，会自动绑定到宿主组件实例上，并且会在宿主组件卸载时自动停止。因此，在大多数情况下，我们无需关心怎么停止一个侦听器。</p>
<p>一个关键点是，侦听器必须用同步语句创建：如果用异步回调创建一个侦听器，那么它不会绑定到当前组件上，必须手动停止它，以防内存泄漏。如下方这个例子：</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { watchEffect } from 'vue'

// 它会自动停止
watchEffect(() =&gt; {})

// ...这个则不会！
setTimeout(() =&gt; {
  watchEffect(() =&gt; {})
}, 100)
&lt;/script&gt;
</code></pre>
<p>要手动停止一个侦听器，我们可以调用 <code>watch</code> 或 <code>watchEffect</code> 返回的函数：</p>
<pre><code class="language-javascript">const unwatch = watchEffect(() =&gt; {})

// ...当该侦听器不再需要时
unwatch()
</code></pre>
<h2>生命周期钩子</h2>
<p>每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤，比如设置好数据侦听，编译模板，挂载实例到 DOM，以及在数据改变时更新 DOM。在此过程中，它也会运行被称为生命周期钩子的函数，让开发者有机会在特定阶段运行自己的代码。下面是一些常用的生命周期钩子。</p>
<h3>onMounted()</h3>
<p>注册一个回调函数，在组件挂载完成后执行，即在组件完成初始渲染并创建 DOM 节点后运行代码。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { onMounted } from 'vue'

onMounted(() =&gt; {
  console.log('组件已挂载')
})
&lt;/script&gt;
</code></pre>
<p>当调用 <code>onMounted</code> 时，Vue 会自动将回调函数注册到当前正被初始化的组件实例上。这意味着这些钩子应当在组件初始化时被同步注册。下面是错误做法：</p>
<pre><code class="language-javascript">setTimeout(() =&gt; {
  onMounted(() =&gt; {
    // 异步注册时当前组件实例已丢失
    // 这将不会正常工作
  })
}, 100)
</code></pre>
<h3>onUpdated()</h3>
<p>注册一个回调函数，在组件因为响应式状态变更而更新其 DOM 树之后调用。父组件的更新钩子将在其子组件的更新钩子之后调用。</p>
<p>这个钩子会在组件的任意 DOM 更新后被调用，这些更新可能是由不同的状态变更导致的，因为多个状态变更可以在同一个渲染周期中批量执行（考虑到性能因素）。这个钩子在服务器端渲染期间不会被调用。我们不应该在 updated 钩子中更改组件的状态，这可能会导致无限的更新循环。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref, onUpdated } from 'vue'

const count = ref(0)

onUpdated(() =&gt; {
  // 文本内容应该与当前的 `count.value` 一致
  console.log(document.getElementById('count').textContent)
})
&lt;/script&gt;

&lt;template&gt;
  &lt;button id=&quot;count&quot; @click=&quot;count++&quot;&gt;{{ count }}&lt;/button&gt;
&lt;/template&gt;
</code></pre>
<h3>onUnmounted()</h3>
<p>注册一个回调函数，在组件实例被卸载之后调用。可以在这个钩子中手动清理一些副作用，例如计时器、DOM 事件监听器或者与服务器的连接。这个钩子在服务器端渲染期间不会被调用。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { onMounted, onUnmounted } from 'vue'

let intervalId
onMounted(() =&gt; {
  intervalId = setInterval(() =&gt; {
    // ...
  })
})

onUnmounted(() =&gt; clearInterval(intervalId))
&lt;/script&gt;
</code></pre>
<h3>其他常用钩子</h3>
<h4>onBeforeMount()</h4>
<p>注册一个钩子，在组件被挂载之前被调用。当这个钩子被调用时，组件已经完成了其响应式状态的设置，但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。这个钩子在服务器端渲染期间不会被调用。</p>
<h4>onBeforeUpdate()</h4>
<p>注册一个钩子，在组件即将因为响应式状态变更而更新其 DOM 树之前调用。这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。这个钩子在服务器端渲染期间不会被调用。</p>
<h4>onBeforeUnmount()</h4>
<p>注册一个钩子，在组件实例被卸载之前调用。当这个钩子被调用时，组件实例依然还保有全部的功能。这个钩子在服务器端渲染期间不会被调用。</p>
<h2>组件</h2>
<p>组件允许我们将 UI 划分为独立的、可重用的部分，并且可以对每个部分进行单独的思考。在实际应用中，组件常常被组织成一个层层嵌套的树状结构。</p>
<p>将一个大型应用拆分成多个组件，既可以提高组件的复用性，也可以分离逻辑，让各组件专注于其对应的功能，这样能大大提高开发效率和代码的可维护性。</p>
<h3>注册及使用组件</h3>
<p>一个 Vue 组件在使用前需要先被“注册”，这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式：全局注册和局部注册。</p>
<h4>全局注册</h4>
<p>全局注册的组件可以在此应用的任意组件的模板中使用，所有的子组件也可以使用全局注册的组件，这意味着这全局注册的组件也都可以在彼此内部使用。</p>
<p>我们可以使用 Vue 应用实例的 <code>.component()</code> 方法，让组件在当前 Vue 应用中全局可用。</p>
<pre><code class="language-javascript">import { createApp } from 'vue'

const app = createApp({})

app.component(
  // 注册的名字
  'MyComponent',
  // 组件的实现
  {
    /* ... */
  }
)
</code></pre>
<p>如果使用单文件组件，我们可以注册被导入的 <code>.vue</code> 文件：</p>
<pre><code class="language-javascript">import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)
</code></pre>
<p><code>.component()</code> 方法可以被链式调用：</p>
<pre><code class="language-javascript">app
  .component('ComponentA', ComponentA)
  .component('ComponentB', ComponentB)
  .component('ComponentC', ComponentC)
</code></pre>
<h4>局部注册</h4>
<p>全局注册虽然很方便，但有以下几个问题：</p>
<ol>
<li>全局注册，但并没有被使用的组件无法在生产打包时被自动移除（即“tree-shaking”）。如果全局注册了一个组件，即使它并没有被实际使用，它仍然会出现在打包后的 JS 文件中。</li>
<li>全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时，不太容易定位子组件的实现。和使用过多的全局变量一样，这可能会影响应用长期的可维护性。</li>
</ol>
<p>相比之下，局部注册的组件需要在使用它的父组件中显式导入，并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确，并且对 tree-shaking 更加友好。</p>
<p>在使用 <code>&lt;script setup&gt;</code> 的单文件组件中，导入的组件可以直接在模板中使用，无需注册：</p>
<pre><code class="language-vue">&lt;script setup&gt;
import ComponentA from './ComponentA.vue'
&lt;/script&gt;

&lt;template&gt;
  &lt;ComponentA /&gt;
&lt;/template&gt;
</code></pre>
<p>如果没有使用 <code>&lt;script setup&gt;</code>，则需要使用 <code>components</code> 选项来显式注册：</p>
<pre><code class="language-javascript">import ComponentA from './ComponentA.js'

export default {
  components: {
    ComponentA
  },
  setup() {
    // ...
  }
}
</code></pre>
<h3>Props</h3>
<h4>Props 的声明</h4>
<p>props 是父组件向子组件传递数据的主要方式，它是一种特别的 attributes，我们可以在组件上声明注册。声明注册需要用到 <code>defineProps</code> 宏：</p>
<pre><code class="language-vue">&lt;script setup&gt;
defineProps(['title'])
&lt;/script&gt;

&lt;template&gt;
  &lt;h4&gt;{{ title }}&lt;/h4&gt;
&lt;/template&gt;
</code></pre>
<p><code>defineProps</code> 是一个仅 <code>&lt;script setup&gt;</code> 中可用的编译宏命令，并不需要显式地导入。声明的 props 会自动暴露给模板。<code>defineProps</code> 会返回一个对象，其中包含了可以传递给组件的所有 props：</p>
<pre><code class="language-javascript">const props = defineProps(['title'])
console.log(props.title)
</code></pre>
<p>如果没有使用 <code>&lt;script setup&gt;</code>，props 必须以 <code>props</code> 选项的方式声明，props 对象会作为 <code>setup()</code> 函数的第一个参数被传入：</p>
<pre><code class="language-javascript">export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}
</code></pre>
<p>除了使用字符串数组来声明 props 外，还可以使用对象的形式：</p>
<pre><code class="language-javascript">&lt;!-- ChildComponent.vue --&gt;
&lt;script setup&gt;
const props = defineProps({
  title: String,
  likes: {
    type: Number,
    default: 0
  },
  isPublished: Boolean,
  commentIds: Array,
  author: Object
})

console.log(props.title) // 访问 props
&lt;/script&gt;
&lt;template&gt;
  &lt;h1&gt;{{ title }}&lt;/h1&gt;
  &lt;p&gt;点赞数: {{ likes }}&lt;/p&gt;
&lt;/template&gt;
</code></pre>
<pre><code class="language-javascript">&lt;!-- ParentComponent.vue --&gt;
&lt;script setup&gt;
import ChildComponent from './ChildComponent.vue'
&lt;/script&gt;
&lt;template&gt;
  &lt;ChildComponent
    title=&quot;Vue3 组件教程&quot;
    :likes=&quot;42&quot;
    :is-published=&quot;true&quot;
    :comment-ids=&quot;[234, 266, 273]&quot;
    :author=&quot;{ name: '张三', bio: '前端开发者' }&quot;
  /&gt;
&lt;/template&gt;
</code></pre>
<p>带 <code>:</code> 的是动态绑定，它可以传入一个动态改变的值，静态绑定只能传入字符串。</p>
<h4>Props 校验</h4>
<p>Vue 组件可以更细致地声明对传入的 props 的校验要求。比如我们上面已经看到过的类型声明，如果传入的值不满足类型要求，Vue 会在浏览器控制台中抛出警告来提醒使用者。这在开发给其他开发者使用的组件时非常有用。</p>
<p>要声明对 props 的校验，可以向 <code>defineProps()</code> 宏提供一个带有 props 校验选项的对象，例如：</p>
<pre><code class="language-javascript">defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传，且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // 必传但可为 null 的字符串
  propD: {
    type: [String, null],
    required: true
  },
  // Number 类型的默认值
  propE: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propF: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // 自定义类型校验函数
  // 在 3.4+ 中完整的 props 作为第二个参数传入
  propG: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propH: {
    type: Function,
    // 不像对象或数组的默认，这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})
</code></pre>
<h3>事件</h3>
<p>子组件可以通过触发事件的方式向父组件传递信息。在组件的模板表达式中，可以直接使用 <code>$emit</code> 方法触发自定义事件 (例如：在 <code>v-on</code> 的处理函数中)：</p>
<pre><code class="language-vue">&lt;!-- MyComponent --&gt;
&lt;button @click=&quot;$emit('someEvent')&quot;&gt;Click Me&lt;/button&gt;
</code></pre>
<p>父组件可以通过 <code>v-on</code>（缩写为 <code>@</code>）来监听事件：</p>
<pre><code class="language-vue">&lt;MyComponent @some-event=&quot;callback&quot; /&gt;
</code></pre>
<p>有时候我们会需要在触发事件时附带一个特定的值，这时我们可以给 <code>$emit</code> 提供一个额外的参数：</p>
<pre><code class="language-vue">&lt;button @click=&quot;$emit('increaseBy', 1)&quot;&gt;
  Increase by 1
&lt;/button&gt;
</code></pre>
<p>然后我们在父组件中监听事件，我们可以先简单写一个内联的箭头函数作为监听器，此函数会接收到事件附带的参数：</p>
<pre><code class="language-vue">&lt;MyButton @increase-by=&quot;(n) =&gt; count += n&quot; /&gt;
</code></pre>
<p>或者，也可以用一个组件方法来作为事件处理函数：</p>
<pre><code class="language-vue">&lt;MyButton @increase-by=&quot;increaseCount&quot; /&gt;
</code></pre>
<p>该方法也会接收到事件所传递的参数：</p>
<pre><code class="language-vue">function increaseCount(n) {
  count.value += n
}
</code></pre>
<p>组件可以显式地通过 <code>defineEmits()</code> 宏来声明它要触发的事件：</p>
<pre><code class="language-vue">&lt;script setup&gt;
defineEmits(['inFocus', 'submit'])
&lt;/script&gt;
</code></pre>
<p>我们在 <code>&lt;template&gt;</code> 中使用的 <code>$emit</code> 方法不能在组件的 <code>&lt;script setup&gt;</code> 部分中使用，但 <code>defineEmits()</code> 会返回一个相同作用的函数供我们使用：</p>
<pre><code class="language-vue">&lt;script setup&gt;
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
&lt;/script&gt;
</code></pre>
<p><code>defineEmits()</code> 宏不能在子函数中使用。如上所示，它必须直接放置在 <code>&lt;script setup&gt;</code> 的顶级作用域下。</p>
<h3>插槽</h3>
<p>一些情况下我们会希望能和 HTML 元素一样向组件中传递内容，这可以通过 Vue 的自定义 <code>&lt;slot&gt;</code> 元素来实现。</p>
<h4>基本使用方式</h4>
<p>举例来说，这里有一个 <code>&lt;FancyButton&gt;</code> 组件，可以像这样使用：</p>
<pre><code class="language-vue">&lt;FancyButton&gt;
  Click me! &lt;!-- 插槽内容 --&gt;
&lt;/FancyButton&gt;
</code></pre>
<p>而 <code>&lt;FancyButton&gt;</code> 的模板是这样的：</p>
<pre><code class="language-vue">&lt;button class=&quot;fancy-btn&quot;&gt;
  &lt;slot&gt;&lt;/slot&gt; &lt;!-- 插槽出口 --&gt;
&lt;/button&gt;
</code></pre>
<p><code>&lt;slot&gt;</code> 元素是一个插槽出口（slot outlet），标示了父元素提供的插槽内容（(slot content）将在哪里被渲染。</p>
<p>最终渲染出的 DOM 是这样：</p>
<pre><code class="language-html">&lt;button class=&quot;fancy-btn&quot;&gt;Click me!&lt;/button&gt;
</code></pre>
<p>插槽内容可以是任意合法的模板内容，不局限于文本。例如我们可以传入多个元素，甚至是组件：</p>
<pre><code class="language-vue">&lt;FancyButton&gt;
  &lt;span style=&quot;color:red&quot;&gt;Click me!&lt;/span&gt;
  &lt;AwesomeIcon name=&quot;plus&quot; /&gt;
&lt;/FancyButton&gt;
</code></pre>
<h4>具名插槽</h4>
<p>有时在一个组件中包含多个插槽出口是很有用的。对于这种场景，<code>&lt;slot&gt;</code> 元素可以有一个特殊的 attribute <code>name</code>，用来给各个插槽分配唯一的 ID，以确定每一处要渲染的内容：</p>
<pre><code class="language-vue">&lt;!-- BaseLayout.vue --&gt;
&lt;div class=&quot;container&quot;&gt;
  &lt;header&gt;
    &lt;slot name=&quot;header&quot;&gt;&lt;/slot&gt;
  &lt;/header&gt;
  &lt;main&gt;
    &lt;slot&gt;&lt;/slot&gt;
  &lt;/main&gt;
  &lt;footer&gt;
    &lt;slot name=&quot;footer&quot;&gt;&lt;/slot&gt;
  &lt;/footer&gt;
&lt;/div&gt;
</code></pre>
<p>这类带 <code>name</code> 的插槽被称为具名插槽（named slots）。没有提供 <code>name</code> 的 <code>&lt;slot&gt;</code> 出口会隐式地命名为 <code>default</code>。</p>
<p>在使用时，我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到<strong>具名插槽</strong>了。</p>
<p>要为具名插槽传入内容，我们需要使用一个含 <code>v-slot</code> 指令的 <code>&lt;template&gt;</code> 元素，并将目标插槽的名字传给该指令：</p>
<pre><code class="language-vue">&lt;BaseLayout&gt;
  &lt;template v-slot:header&gt;
    &lt;!-- header 插槽的内容放这里 --&gt;
  &lt;/template&gt;
&lt;/BaseLayout&gt;
</code></pre>
<p><code>v-slot</code> 有对应的简写 <code>#</code>，因此 <code>&lt;template v-slot:header&gt;</code> 可以简写为 <code>&lt;template #header&gt;</code>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。下面是一个完整例子：</p>
<pre><code class="language-vue">&lt;BaseLayout&gt;
  &lt;template #header&gt;
    &lt;h1&gt;Here might be a page title&lt;/h1&gt;
  &lt;/template&gt;

  &lt;template #default&gt;
    &lt;p&gt;A paragraph for the main content.&lt;/p&gt;
    &lt;p&gt;And another one.&lt;/p&gt;
  &lt;/template&gt;

  &lt;template #footer&gt;
    &lt;p&gt;Here's some contact info&lt;/p&gt;
  &lt;/template&gt;
&lt;/BaseLayout&gt;
</code></pre>
<p>当一个组件同时接收默认插槽和具名插槽时，所有位于顶级的非 <code>&lt;template&gt;</code> 节点都被隐式地视为默认插槽的内容。所以上面也可以写成：</p>
<pre><code class="language-vue">&lt;BaseLayout&gt;
  &lt;template #header&gt;
    &lt;h1&gt;Here might be a page title&lt;/h1&gt;
  &lt;/template&gt;

  &lt;!-- 隐式的默认插槽 --&gt;
  &lt;p&gt;A paragraph for the main content.&lt;/p&gt;
  &lt;p&gt;And another one.&lt;/p&gt;

  &lt;template #footer&gt;
    &lt;p&gt;Here's some contact info&lt;/p&gt;
  &lt;/template&gt;
&lt;/BaseLayout&gt;
</code></pre>
<h4>条件插槽</h4>
<p>有时我们需要根据内容是否被传入了插槽来渲染某些内容，这时可以结合使用 $slots 属性与 v-if 来实现。</p>
<p>下面的示例中定义了一个卡片组件，它拥有三个条件插槽：<code>header</code>、<code>footer</code> 和 <code>default</code>。 当 header、footer 或 default 的内容存在时，我们希望包装它以提供额外的样式：</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;div class=&quot;card&quot;&gt;
    &lt;div v-if=&quot;$slots.header&quot; class=&quot;card-header&quot;&gt;
      &lt;slot name=&quot;header&quot; /&gt;
    &lt;/div&gt;
    
    &lt;div v-if=&quot;$slots.default&quot; class=&quot;card-content&quot;&gt;
      &lt;slot /&gt;
    &lt;/div&gt;
    
    &lt;div v-if=&quot;$slots.footer&quot; class=&quot;card-footer&quot;&gt;
      &lt;slot name=&quot;footer&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<h4>作用域插槽</h4>
<p>通过上面的学习我们知道，插槽的内容无法访问到子组件的状态。然而在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点，就需要一种方法来让子组件在渲染时将一部分数据提供给插槽。</p>
<p>我们可以像对组件传递 props 那样，向一个插槽的出口上传递 attributes：</p>
<pre><code class="language-vue">&lt;!-- &lt;MyComponent&gt; 的模板 --&gt;
&lt;div&gt;
  &lt;slot :text=&quot;greetingMessage&quot; :count=&quot;1&quot;&gt;&lt;/slot&gt;
&lt;/div&gt;
</code></pre>
<p>当需要接收插槽 props 时，默认插槽和具名插槽的使用方式有一些小区别。下面先展示默认插槽如何接受 props，通过子组件标签上的 <code>v-slot</code> 指令，直接接收到了一个插槽 props 对象：</p>
<pre><code class="language-vue">&lt;MyComponent v-slot=&quot;slotProps&quot;&gt;
  {{ slotProps.text }} {{ slotProps.count }}
&lt;/MyComponent&gt;
</code></pre>
<p>子组件传入插槽的 props 作为了 <code>v-slot</code> 指令的值，可以在插槽内的表达式中访问。我们也可以在 <code>v-slot</code> 中使用解构：</p>
<pre><code class="language-vue">&lt;MyComponent v-slot=&quot;{ text, count }&quot;&gt;
  {{ text }} {{ count }}
&lt;/MyComponent&gt;
</code></pre>
<p>具名作用域插槽的工作方式也是类似的，插槽 props 可以作为 <code>v-slot</code> 指令的值被访问到：<code>v-slot:name=&quot;slotProps&quot;</code>。当使用缩写时是这样：</p>
<pre><code class="language-vue">&lt;MyComponent&gt;
  &lt;template #header=&quot;headerProps&quot;&gt;
    {{ headerProps }}
  &lt;/template&gt;

  &lt;template #default=&quot;defaultProps&quot;&gt;
    {{ defaultProps }}
  &lt;/template&gt;

  &lt;template #footer=&quot;footerProps&quot;&gt;
    {{ footerProps }}
  &lt;/template&gt;
&lt;/MyComponent&gt;
</code></pre>
<p>向具名插槽中传入 props：</p>
<pre><code class="language-vue">&lt;slot name=&quot;header&quot; message=&quot;hello&quot;&gt;&lt;/slot&gt;
</code></pre>
<p>注意插槽上的 <code>name</code> 是一个 Vue 特别保留的 attribute，不会作为 props 传递给插槽。因此最终 <code>headerProps</code> 的结果是 <code>{ message: 'hello' }</code>。</p>
<p>如果同时使用了具名插槽与默认插槽，则需要为默认插槽使用显式的 <code>&lt;template&gt;</code> 标签。尝试直接为组件添加 <code>v-slot</code> 指令将导致编译错误。这是为了避免因默认插槽的 props 的作用域而困惑。</p>
<p>下面是作用域插槽的一个使用场景。</p>
<p>我们来看一个 <code>&lt;FancyList&gt;</code> 组件的例子。它会渲染一个列表，并同时会封装一些加载远端数据的逻辑、使用数据进行列表渲染、或者是像分页或无限滚动这样更进阶的功能。然而我们希望它能够保留足够的灵活性，将对单个列表元素内容和样式的控制权留给使用它的父组件。所以用法是这样的：</p>
<pre><code class="language-vue">&lt;FancyList :api-url=&quot;url&quot; :per-page=&quot;10&quot;&gt;
  &lt;template #item=&quot;{ body, username, likes }&quot;&gt;
    &lt;div class=&quot;item&quot;&gt;
      &lt;p&gt;{{ body }}&lt;/p&gt;
      &lt;p&gt;by {{ username }} | {{ likes }} likes&lt;/p&gt;
    &lt;/div&gt;
  &lt;/template&gt;
&lt;/FancyList&gt;
</code></pre>
<p>在 <code>&lt;FancyList&gt;</code> 之中，我们可以多次渲染 <code>&lt;slot&gt;</code> 并每次都提供不同的数据 (注意这里使用了 <code>v-bind</code> 来传递插槽的 props)：</p>
<pre><code class="language-vue">&lt;ul&gt;
  &lt;li v-for=&quot;item in items&quot;&gt;
    &lt;slot name=&quot;item&quot; v-bind=&quot;item&quot;&gt;&lt;/slot&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<h3>动态组件</h3>
<p>有些场景会需要在两个组件间来回切换，比如 Tab 界面。Vue 提供了 <code>&lt;component&gt;</code> 元素和特殊的 <code>is</code> 属性来实现这个功能。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref } from 'vue'
import TabHome from './TabHome.vue'
import TabPosts from './TabPosts.vue'
import TabArchive from './TabArchive.vue'

const currentTab = ref('Home')
const tabs = {
  Home: TabHome,
  Posts: TabPosts,
  Archive: TabArchive
}
&lt;/script&gt;
&lt;template&gt;
  &lt;div class=&quot;demo&quot;&gt;
    &lt;button
      v-for=&quot;(_, tab) in tabs&quot;
      :key=&quot;tab&quot;
      :class=&quot;['tab-button', { active: currentTab === tab }]&quot;
      @click=&quot;currentTab = tab&quot;
    &gt;
      {{ tab }}
    &lt;/button&gt;
    &lt;component :is=&quot;tabs[currentTab]&quot; class=&quot;tab&quot;&gt;&lt;/component&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>在上面的例子中，被传给 <code>:is</code> 的值可以是以下几种：</p>
<ul>
<li>被注册的组件名</li>
<li>导入的组件对象</li>
</ul>
<p>我们也可以使用 <code>is</code> 属性来创建一般的 HTML 元素。当使用 <code>&lt;component :is=&quot;...&quot;&gt;</code> 来在多个组件间作切换时，被切换掉的组件会被卸载。</p>
<h3>异步组件</h3>
<p>对于大型应用,我们可能需要将应用拆分为更小的代码块,并且只在需要时才加载组件。Vue提供了 <code>defineAsyncComponent</code> 函数来实现这一点:</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =&gt;
  import('./components/HeavyComponent.vue')
)
&lt;/script&gt;
&lt;template&gt;
  &lt;AsyncComp /&gt;
&lt;/template&gt;
</code></pre>
<p>最后得到的 <code>AsyncComp</code> 是一个外层包装过的组件，仅在页面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的 props 和插槽传给内部组件，所以我们可以使用这个异步的包装组件无缝地替换原始组件，同时实现延迟加载。</p>
<p>与普通组件一样，异步组件可以使用 <code>app.component()</code> 全局注册：</p>
<pre><code class="language-javascript">app.component('MyComponent', defineAsyncComponent(() =&gt;
  import('./components/MyComponent.vue')
))
</code></pre>
<p>异步操作不可避免地会涉及到加载和错误状态，因此 <code>defineAsyncComponent()</code> 也支持在高级选项中处理这些状态：</p>
<pre><code class="language-javascript">const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () =&gt; import('./Foo.vue'),

  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间，默认为 200ms
  delay: 200,

  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制，并超时了
  // 也会显示这里配置的报错组件，默认值是：Infinity
  timeout: 3000
})
</code></pre>
<p>如果提供了一个加载组件，它将在内部组件加载时先行显示。在加载组件显示之前有一个默认的 200ms 延迟——这是因为在网络状况较好时，加载完成得很快，加载组件和最终组件之间的替换太快可能产生闪烁，反而影响用户感受。</p>
<p>如果提供了一个报错组件，则它会在加载器函数返回的 Promise 抛错时被渲染。我们还可以指定一个超时时间，在请求耗时超过指定时间时也会渲染报错组件。</p>
]]></content:encoded>
    </item>
<item>
      <title>Vue 学习笔记（一）</title>
      <link>https://www.duanhen.top/article/vue_note_1</link>
      <guid isPermaLink="true">https://www.duanhen.top/article/vue_note_1</guid>
      <pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate>
      <description>了解 Vue 是什么，认识组合式 API、响应式基础和常用指令</description>
      <content:encoded><![CDATA[<h2>前言</h2>
<p>这段时间用 Vue 写了包括本站在内的一些项目，或多或少地跟 Vue 代码混了个眼熟，虽然在这个大 AI Coding 时代，写项目很多时候并不需要熟悉一个语言的语法、特性或者优势，但秉承着来都来了的道理，我还是决定把 Vue 基础给学了。</p>
<h2>Vue 是什么</h2>
<h3>介绍</h3>
<p>Vue  是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建，并提供了一套声明式的、组件化的编程模型，帮助我们高效地开发用户界面。同时也是一个采用声明式渲染、组件化开发、响应式数据绑定和渐进式架构的现代前端框架。</p>
<h3>单文件页面</h3>
<p>在 Vue 项目中，有<strong>单文件组件</strong>（也被称为 <code>*.vue</code> 文件，英文 Single-File Components，缩写为 <strong>SFC</strong>）这种组件书写方式，它将一个组件的逻辑 (JavaScript)，模板 (HTML) 和样式 (CSS) 封装在同一个文件里。</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;button @click=&quot;count++&quot;&gt;Count is: {{ count }}&lt;/button&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref } from 'vue'
const count = ref(0)
&lt;/script&gt;

&lt;style scoped&gt;
button {
  font-weight: bold;
}
&lt;/style&gt;
</code></pre>
<h3>组合式 API</h3>
<p>组合式 API（Composition API）是 Vue3 的核心特性，相比于 Vue2 的选项式 API，它提供了更灵活的代码组织方式和逻辑复用能力，组合式API允许我们按照逻辑关注点组织代码,而不是按照选项类型。</p>
<h4>setup 函数</h4>
<p><code>setup()</code> 函数是在组件中使用组合式 API 的入口，我们可以使用响应式 API 来声明响应式的状态，在 <code>setup()</code> 函数中返回的对象会暴露给模板和组件实例。其他的选项也可以通过组件实例来获取 <code>setup()</code> 暴露的属性。</p>
<pre><code class="language-javascript">import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      count.value++
    }
    
    return {
      count,
      increment
    }
  }
}
</code></pre>
<p><code>setup</code> 函数的第一个参数是组件的 <code>props</code>。和标准的组件一致，一个 <code>setup</code> 函数的 <code>props</code> 是响应式的，并且会在传入新的 props 时同步更新。</p>
<pre><code class="language-javascript">export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}
</code></pre>
<p>传入 <code>setup</code> 函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 <code>setup</code> 中可能会用到的值：</p>
<pre><code class="language-javascript">export default {
  setup(props, context) {
    // 透传 Attributes (非响应式的对象，等价于 $attrs)
    console.log(context.attrs)

    // 插槽(非响应式的对象，等价于 $slots)
    console.log(context.slots)

    // 触发事件(函数，等价于 $emit)
    console.log(context.emit)

    // 暴露公共属性(函数)
    console.log(context.expose)
  }
}
</code></pre>
<h4>script setup 语法糖</h4>
<p>在 Vue3.2 中引入了 <code>&lt;script setup&gt;</code> 语法糖， 我们可以通过在单文件组件（SFC）中使用 <code>&lt;script setup&gt;</code> 来大幅度地简化代码：</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click=&quot;increment&quot;&gt;{{ count }}&lt;/button&gt;
&lt;/template&gt;
</code></pre>
<h2>创建 Vue 应用</h2>
<p>在创建 Vue 项目之前先安装好 <a href="https://nodejs.org/zh-cn">Node.js</a>，安装后在命令行输入 <code>npm create vue@latest</code>，这一指令将会安装并执行 create-vue，它是 Vue 官方的项目脚手架工具。接下来按照提示输入项目名并选择各项配置以完成项目创建。</p>
<div align="center">
  <img src="/articles/vue_note/2.png"/>
</div>
<div align="center">
  <img src="/articles/vue_note/3.png"/>
</div>
<p>创建项目后在命令行中依次输入：</p>
<pre><code>cd &lt;your-project-name&gt;
npm install
npm run dev
</code></pre>
<p>完成依赖安装并启动项目，此时就成功运行了一个 Vue 项目。</p>
<p>在 <code>main.js</code> 文件中有如下代码：</p>
<pre><code class="language-javascript">import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)
app.mount('#app')
</code></pre>
<p>其中，<code>createApp</code> 函数通过接收 <code>App</code> 这个根组件创建一个应用实例，应用实例必须在调用了 <code>.mount()</code> 方法后才会渲染出来，该方法接收一个“容器”参数，应用根组件的内容将会被渲染在容器元素里面。</p>
<h2>响应式基础</h2>
<p>Vue3 中有两个核心响应式 API，分别是 <code>ref()</code> 和 <code>reactive()</code>。</p>
<h3>ref()</h3>
<p>在组合式 API 中，推荐使用 <code>ref()</code> 函数来声明响应式状态，它可以创建任何类型值的响应式引用。<code>ref()</code> 接收参数，并将其包裹在一个带有 <code>.value</code> 属性的 ref 对象中返回：</p>
<pre><code class="language-javascript">import { ref } from 'vue'

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
</code></pre>
<p>在模板中使用 ref 时，我们不需要附加 <code>.value</code>，当在模板中使用时，ref 会自动解包：</p>
<pre><code class="language-vue">&lt;div&gt;{{ count }}&lt;/div&gt;
&lt;button @click=&quot;count++&quot;&gt;
  {{ count }}
&lt;/button&gt;
</code></pre>
<h3>reactive()</h3>
<p>另一种声明响应式状态的方式，即使用 <code>reactive()</code> API。与将内部值包装在特殊对象中的 ref 不同，<code>reactive()</code> 将使对象本身具有响应性：</p>
<pre><code class="language-javascript">import { reactive } from 'vue'

const state = reactive({ count: 0 })
</code></pre>
<p>在模板中使用：</p>
<pre><code class="language-vue">&lt;button @click=&quot;state.count++&quot;&gt;
  {{ state.count }}
&lt;/button&gt;
</code></pre>
<p><code>reactive()</code> 返回的是一个原始对象的 Proxy，它和原始对象是不相等的，为保证访问代理的一致性，对同一个原始对象调用 <code>reactive()</code> 会总是返回同样的代理对象，而对一个已存在的代理对象调用 <code>reactive()</code> 会返回其本身。这个规则对嵌套对象也适用。依靠深层响应性，响应式对象内的嵌套对象依然是代理：</p>
<pre><code class="language-javascript">const raw = {}
const proxy = reactive(raw)

// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

const proxy = reactive({})

proxy.nested = raw

console.log(proxy.nested === raw) // false
</code></pre>
<h3>两者对比</h3>
<p>1、<code>ref()</code> 可以包装任何类型的值，<code>reactive()</code> 只能用于对象类型（如 Map、Set）。</p>
<p>2、<code>ref()</code> 需要使用 <code>.value</code> 访问值，<code>reactive()</code> 可直接访问属性。</p>
<p>3、<code>ref()</code> 可以单独传递和解构并保持响应性，<code>reactive()</code> 对象解构后会失去响应性。</p>
<pre><code class="language-javascript">// 使用 ref
const user = ref({ name: '张三', age: 30 })

// 可以保持响应性地传递
function updateUser(userRef) {
  userRef.value.age = 31
}
updateUser(user)

// 使用 reactive
const state = reactive({
  user: { name: '张三', age: 30 }
})

// 解构后会失去响应性
const { user } = state
user.age = 31 // 不会触发更新

// 正确的做法是使用计算属性或直接访问
const userAge = computed(() =&gt; state.user.age)
// 或者
function updateAge() {
  state.user.age = 31 // 会触发更新
}
</code></pre>
<h2>常用指令</h2>
<h3>v-text 与 v-html</h3>
<p><code>v-text</code> 用于更新元素的文本内容，<code>v-text</code> 通过设置元素的 textContent 属性来工作，因此它将覆盖元素中所有现有的内容。</p>
<pre><code class="language-vue">&lt;span v-text=&quot;msg&quot;&gt;&lt;/span&gt;
&lt;!-- 等同于 --&gt;
&lt;span&gt;{{msg}}&lt;/span&gt;
</code></pre>
<p><code>v-html</code> 用来更新元素的 innerHTML，<code>v-html</code> 的内容直接作为普通 HTML 插入—— Vue 模板语法是不会被解析的。</p>
<pre><code class="language-vue">&lt;p&gt;Using text interpolation: {{ rawHtml }}&lt;/p&gt;
&lt;p&gt;Using v-html directive: &lt;span v-html=&quot;rawHtml&quot;&gt;&lt;/span&gt;&lt;/p&gt;

&lt;!-- 结果为： 
  Using text interpolation: &lt;span style=&quot;color: red&quot;&gt;This should be red.&lt;/span&gt;
  Using v-html directive: This should be red.
--&gt;
</code></pre>
<h3>v-show 与 v-if</h3>
<p><code>v-show</code> 基于表达式值的真假性，来改变元素的可见性，<code>v-show</code> 通过设置内联样式的 <code>display</code> CSS 属性来工作，当元素可见时将使用初始 <code>display</code> 值。</p>
<p><code>v-if</code> 基于表达式值的真假性，来条件性地渲染元素或者模板片段。当 <code>v-if</code> 元素被触发，元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假，那么其内部的内容根本都不会被渲染。可用于 <code>&lt;template&gt;</code> 表示仅包含文本或多个元素的条件块。当条件改变时会触发过渡效果。</p>
<p>此外，还有 <code>v-else</code> 和 <code>v-else-if</code> 指令可以配合 <code>v-if</code> 使用，实现更复杂的条件渲染。两者必须紧跟在 <code>v-if</code> 后面使用，不然将不会被识别。</p>
<pre><code class="language-vue">&lt;div v-if=&quot;type === 'A'&quot;&gt;
  A
&lt;/div&gt;
&lt;div v-else-if=&quot;type === 'B'&quot;&gt;
  B
&lt;/div&gt;
&lt;div v-else-if=&quot;type === 'C'&quot;&gt;
  C
&lt;/div&gt;
&lt;div v-else&gt;
  Not A/B/C
&lt;/div&gt;
</code></pre>
<h3>v-for</h3>
<p><code>v-for</code> 指令基于一个数组来渲染一个列表。<code>v-for</code> 指令的值需要使用 <code>item in items</code> 形式的特殊语法，其中 <code>items</code> 是源数据的数组，而 <code>item</code> 是迭代项的别名。</p>
<pre><code class="language-javascript">const items = ref([{ message: 'A' }, { message: 'B' }])
</code></pre>
<pre><code class="language-vue">&lt;li v-for=&quot;item in items&quot;&gt;
  {{ item.message }}
&lt;/li&gt;
</code></pre>
<p>在 <code>v-for</code> 块中可以完整地访问父作用域内的属性和变量。<code>v-for</code> 也支持使用可选的第二个参数表示当前项的位置索引。</p>
<pre><code class="language-javascript">const parentMessage = ref('Parent')
const items = ref([{ message: 'A' }, { message: 'B' }])
</code></pre>
<pre><code class="language-vue">&lt;li v-for=&quot;(item, index) in items&quot;&gt;
  {{ parentMessage }} - {{ index }} - {{ item.message }}
&lt;/li&gt;
</code></pre>
<p>结果：</p>
<ul>
<li>
<p>Parent - 0 - A</p>
</li>
<li>
<p>Parent - 1 - B</p>
</li>
</ul>
<p><code>v-for</code> 也可以用来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 <code>Object.values()</code> 的返回值来决定。</p>
<pre><code class="language-javascript">const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
</code></pre>
<pre><code class="language-vue">&lt;ul&gt;
  &lt;li v-for=&quot;value in myObject&quot;&gt;
    {{ value }}
  &lt;/li&gt;
&lt;/ul&gt;

&lt;!-- 可以通过提供第二个参数表示属性名 --&gt;
&lt;li v-for=&quot;(value, key) in myObject&quot;&gt;
  {{ key }}: {{ value }}
&lt;/li&gt;

&lt;!-- 第三个参数表示位置索引 --&gt;
&lt;li v-for=&quot;(value, key, index) in myObject&quot;&gt;
  {{ index }}. {{ key }}: {{ value }}
&lt;/li&gt;
</code></pre>
<p><code>v-for</code> 也可以直接接受一个整数值。在这种用例中，取值范围是 <code>1...n</code>。</p>
<pre><code class="language-vue">&lt;span v-for=&quot;n in 10&quot;&gt;{{ n }}&lt;/span&gt;
</code></pre>
<p>Vue 默认按照“就地更新”的策略来更新通过 <code>v-for</code> 渲染的元素列表。当数据项的顺序改变时，Vue 不会随之移动 DOM 元素的顺序，而是就地更新每个元素，确保它们在原本指定的索引位置上渲染。我们可以为每个元素对应的块提供一个唯一的 <code>key</code> attribute，<code>key</code> 会帮 Vue 识别节点，从而高效地更新 DOM。</p>
<pre><code class="language-vue">&lt;div v-for=&quot;item in items&quot; :key=&quot;item.id&quot;&gt;
  &lt;!-- 内容 --&gt;
&lt;/div&gt;
</code></pre>
<h3>v-on</h3>
<p><code>v-on</code> 用来给元素绑定事件监听器，可缩写为 <code>@</code>，</p>
<p>事件类型由参数来指定。表达式可以是一个方法名，一个内联声明，如果有修饰符则可省略。当用于普通元素，只监听原生 DOM 事件。当用于自定义元素组件，则监听子组件触发的自定义事件。</p>
<p>当监听原生 DOM 事件时，方法接收原生事件作为唯一参数。如果使用内联声明，声明可以访问一个特殊的 <code>$event</code> 变量：<code>v-on:click=&quot;handle('ok', $event)&quot;</code>。</p>
<p><code>v-on</code> 还支持绑定不带参数的事件/监听器对的对象。当使用对象语法时，不支持任何修饰符。</p>
<pre><code class="language-vue">&lt;!-- 方法处理函数 --&gt;
&lt;button v-on:click=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 动态事件 --&gt;
&lt;button v-on:[event]=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 内联声明 --&gt;
&lt;button v-on:click=&quot;doThat('hello', $event)&quot;&gt;&lt;/button&gt;

&lt;!-- 缩写 --&gt;
&lt;button @click=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 使用缩写的动态事件 --&gt;
&lt;button @[event]=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 停止传播 --&gt;
&lt;button @click.stop=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 阻止默认事件 --&gt;
&lt;button @click.prevent=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 不带表达式地阻止默认事件 --&gt;
&lt;form @submit.prevent&gt;&lt;/form&gt;

&lt;!-- 链式调用修饰符 --&gt;
&lt;button @click.stop.prevent=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 按键用于 keyAlias 修饰符--&gt;
&lt;input @keyup.enter=&quot;onEnter&quot; /&gt;

&lt;!-- 点击事件将最多触发一次 --&gt;
&lt;button v-on:click.once=&quot;doThis&quot;&gt;&lt;/button&gt;

&lt;!-- 对象语法 --&gt;
&lt;button v-on=&quot;{ mousedown: doThis, mouseup: doThat }&quot;&gt;&lt;/button&gt;
</code></pre>
<h3>v-bind</h3>
<p><code>v-bind</code> 用来动态的绑定一个或多个 attribute，即 HTML 属性，可缩写为 <code>:</code>。</p>
<p>当用于绑定 <code>class</code> 或 <code>style</code> attribute，<code>v-bind</code> 支持额外的值类型如数组或对象。当不带参数使用时，可以用于绑定一个包含了多个 attribute 名称-绑定值对的对象。</p>
<pre><code class="language-vue">&lt;!-- 绑定 attribute --&gt;
&lt;img v-bind:src=&quot;imageSrc&quot; /&gt;

&lt;!-- 动态 attribute 名 --&gt;
&lt;button v-bind:[key]=&quot;value&quot;&gt;&lt;/button&gt;

&lt;!-- 缩写 --&gt;
&lt;img :src=&quot;imageSrc&quot; /&gt;

&lt;!-- 缩写形式的动态 attribute 名 (3.4+)，扩展为 :src=&quot;src&quot; --&gt;
&lt;img :src /&gt;

&lt;!-- 动态 attribute 名的缩写 --&gt;
&lt;button :[key]=&quot;value&quot;&gt;&lt;/button&gt;

&lt;!-- 内联字符串拼接 --&gt;
&lt;img :src=&quot;'/path/to/images/' + fileName&quot; /&gt;

&lt;!-- class 绑定 --&gt;
&lt;div :class=&quot;{ red: isRed }&quot;&gt;&lt;/div&gt;
&lt;div :class=&quot;[classA, classB]&quot;&gt;&lt;/div&gt;
&lt;div :class=&quot;[classA, { classB: isB, classC: isC }]&quot;&gt;&lt;/div&gt;

&lt;!-- style 绑定 --&gt;
&lt;div :style=&quot;{ fontSize: size + 'px' }&quot;&gt;&lt;/div&gt;
&lt;div :style=&quot;[styleObjectA, styleObjectB]&quot;&gt;&lt;/div&gt;

&lt;!-- 绑定对象形式的 attribute --&gt;
&lt;div v-bind=&quot;{ id: someProp, 'other-attr': otherProp }&quot;&gt;&lt;/div&gt;

&lt;!-- prop 绑定。“prop” 必须在子组件中已声明。 --&gt;
&lt;MyComponent :prop=&quot;someThing&quot; /&gt;

&lt;!-- 传递子父组件共有的 prop --&gt;
&lt;MyComponent v-bind=&quot;$props&quot; /&gt;

&lt;!-- XLink --&gt;
&lt;svg&gt;&lt;a :xlink:special=&quot;foo&quot;&gt;&lt;/a&gt;&lt;/svg&gt;
</code></pre>
<h3>v-model</h3>
<p><code>v-model</code> 用于在表单输入元素或组件上创建双向绑定。仅限：<code>&lt;input&gt;</code>、<code>&lt;select&gt;</code>、<code>&lt;textarea&gt;</code> 及 components 上使用。</p>
<pre><code class="language-vue">&lt;input v-model=&quot;message&quot;&gt;
</code></pre>
<h3>v-once</h3>
<p><code>v-once</code> 用来说明只渲染元素和组件一次，并跳过之后的更新。在随后的重新渲染，元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。</p>
<pre><code class="language-vue">&lt;!-- 单个元素 --&gt;
&lt;span v-once&gt;This will never change: {{msg}}&lt;/span&gt;
&lt;!-- 带有子元素的元素 --&gt;
&lt;div v-once&gt;
  &lt;h1&gt;Comment&lt;/h1&gt;
  &lt;p&gt;{{msg}}&lt;/p&gt;
&lt;/div&gt;
&lt;!-- 组件 --&gt;
&lt;MyComponent v-once :comment=&quot;msg&quot; /&gt;
&lt;!-- `v-for` 指令 --&gt;
&lt;ul&gt;
  &lt;li v-for=&quot;i in list&quot; v-once&gt;{{i}}&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<h3>v-pre</h3>
<p><code>v-pre</code> 用来跳过该元素及其所有子元素的编译。当元素内具有 <code>v-pre</code>，所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。</p>
<pre><code class="language-vue">&lt;span v-pre&gt;{{ this will not be compiled }}&lt;/span&gt;
</code></pre>
]]></content:encoded>
    </item>
<item>
      <title>Hello World</title>
      <link>https://www.duanhen.top/article/helloworld</link>
      <guid isPermaLink="true">https://www.duanhen.top/article/helloworld</guid>
      <pubDate>Sun, 31 May 2026 00:00:00 GMT</pubDate>
      <description>我的个人博客成功问世！</description>
      <content:encoded><![CDATA[<h2>新开始</h2>
<p>其实在不久前就用 Next.js 写了一个博客，但刚开始真不知道要写成什么样子，稀里糊涂写了个破烂就去部署了，实在是没脸看，打算重新写一个，于是有了现在这个，跟之前的比好了不少（虽然还是很拉，个人审美有限）。网站搭完了，要慢慢开始写写文章练练文笔了。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>