vue3学习-1(基础)

vue3学习-1(基础)

    • 1. 开始
      • API 风格
        • 选项式 API (Options API)
        • 组合式 API (Composition API)
      • 快速创建个应用
    • 2.基础
      • 1. 创建个应用
      • 2.模板语法
      • 3.响应式基础
      • `reactive()` 的局限性[](https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive)
      • 4.计算属性
      • 5. 组件上使用 `v-for`
      • 6. 事件处理
        • 内联事件处理器
        • 方法事件处理器
      • 7. 表单输入绑定
      • 8.侦听器
        • 深层侦听器
        • 即时回调的侦听器
        • 一次性侦听器
        • `watchEffect`
        • 副作用清理
        • 同步侦听器
        • 停止侦听器
      • 9. 模板引用
      • 10.组件基础
        • 定义一个组件
        • 传递 props
        • 监听事件
        • 插槽
        • 动态组件
        • DOM 内模板解析注意事项
      • 11.生命周期钩子
        • 注册周期钩子
        • 生命周期图示
    • 3.深入组件
    • 4.逻辑复用
    • 5.内置组件
    • 6.应用规模化
    • 7.最佳实践
    • 8.进阶主题

1. 开始

API 风格

选项式 API组合式 API

选项式 API (Options API)
<script>
export default {
  // data() 返回的属性将会成为响应式的状态
  // 并且暴露在 `this` 上
  data() {
    return {
      count: 0
    }
  },

  // methods 是一些用来更改状态与触发更新的函数
  // 它们可以在模板中作为事件处理器绑定
  methods: {
    increment() {
      this.count++
    }
  },

  // 生命周期钩子会在组件生命周期的各个不同阶段被调用
  // 例如这个函数就会在组件挂载完成后被调用
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
组合式 API (Composition API)
<script setup>
import { ref, onMounted } from 'vue'

// 响应式状态
const count = ref(0)

// 用来修改状态、触发更新的函数
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
  • 在生产项目中:
    • 当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。
    • 当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件。

快速创建个应用

安装 18.3 或更高版本的 Node.js,使用nvm最好,可以切换版本,就算是老项目的vue2也可以不影响

npm create vue@latest
cd <your-project-name>
npm install
npm run dev
# npm run build 如果是生产 需要build出个文件,一般编译好的文件是放在dist目录

使用全局构建版本,写个html直接打开就行

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
    <script src="main.js"></script>
  </head>
  <body></body>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

  <div id="app">{{ message }}</div>

  <script>
    const { createApp, ref } = Vue;

    createApp({
      setup() {
        const message = ref('Hello vue!');
        return {
          message,
        };
      },
    }).mount('#app');
  </script>
</html>

使用 ES 模块构建版本,启用 Import maps,拆分模块,这些都不太重要不影响主流程看不看都可以以下是链接,https://cn.vuejs.org/guide/quick-start.html

2.基础

1. 创建个应用

  1. 每个 Vue 应用都是通过 createApp 函数创建一个新的 应用实例

    import { createApp } from 'vue'
    
    const app = createApp({
      /* 根组件选项 */
    })
    
  2. 我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。

    如果你使用的是单文件组件,我们可以直接从另一个文件中导入根组件。

    import { createApp } from 'vue'
    // 从一个单文件组件中导入根组件
    import App from './App.vue'
    
    const app = createApp(App)
    
  3. 项目中整体结构大概会是这种组件树的形式

    App (root component)
    ├─ TodoList
    │  └─ TodoItem
    │     ├─ TodoDeleteButton
    │     └─ TodoEditButton
    └─ TodoFooter
       ├─ TodoClearButton
       └─ TodoStatistics
    
  4. 挂在应用

    1. html
    <div id="app">
      <button @click="count++">{{ count }}</button>
    </div>
    
    1. js

      import { createApp } from 'vue'
      
      const app = createApp({
        data() {
          return {
            count: 0
          }
        }
      })
      
      app.mount('#app')
      
  5. 多个应用实例

    const app1 = createApp({
      /* ... */
    })
    app1.mount('#container-1')
    
    const app2 = createApp({
      /* ... */
    })
    app2.mount('#container-2')
    
    1. 如果你正在使用 Vue 来增强服务端渲染 HTML,并且只想要 Vue 去控制一个大型页面中特殊的一小部分,应避免将一个单独的 Vue 应用实例挂载到整个页面上,而是应该创建多个小的应用实例,将它们分别挂载到所需的元素上去。

2.模板语法

  1. # 文本插值
    <span>Message: {{ msg }}</span>
    # 原始 HTML  html插值很容易XSS 漏洞 少用
    <p>Using v-html directive: <span v-html="rawHtml"></span></p>
    # Attribute 绑定
    # v-bind 指令
    <div v-bind:id="dynamicId"></div>
    # 简写 
    <div :id="dynamicId"></div>
    # 同名简写 仅支持 3.4 版本及以上
    <!-- 与 :id="id" 相同 -->
    <div :id></div>
    # 布尔型 Attribute
    <button :disabled="isButtonDisabled">Button</button>
    # 动态绑定多个值
    const objectOfAttrs = {
      id: 'container',
      class: 'wrapper',
      style: 'background-color:green'
    }
    <div v-bind="objectOfAttrs"></div>
    
    # 可以在绑定的表达式中使用一个组件暴露的方法:toTitleDate是组件中的方法
    <time :title="toTitleDate(date)" :datetime="date">
      {{ formatDate(date) }}
    </time>
    # 指令是带有 v- 前缀的特殊 attribute
    

3.响应式基础

  1. 在组合式 API 中,推荐使用 ref() 函数来声明响应式状态:ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

    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
    

    要在组件模板中访问 ref,请从组件的 setup() 函数中声明并返回它们:

    import { ref } from 'vue'
    
    export default {
      // `setup` 是一个特殊的钩子,专门用于组合式 API。
      setup() {
        const count = ref(0)
    
        // 将 ref 暴露给模板
        return {
          count
        }
      }
    }
    
    <div>{{ count }}</div>
    

    在模板中使用 ref 时,我们需要附加 .value。为了方便起见,当在模板中使用时,ref 会自动解包 (有一些注意事项)。

    setup() 函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用 <script setup> 来大幅度地简化代码:

    <script setup>
    import { ref } from 'vue'
    
    const count = ref(0)
    
    function increment() {
      count.value++
    }
    </script>
    
    <template>
      <button @click="increment">
        {{ count }}
      </button>
    </template>
    

    Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到

    还有另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:

    import { reactive } from 'vue'
    
    const state = reactive({ count: 0 })
    
    <button @click="state.count++">
      {{ state.count }}
    </button>
    

    reactive() 的局限性

    reactive() API 有一些局限性:

    1. 有限的值类型:它只能用于对象类型 (对象、数组和如 MapSet 这样的集合类型)。它不能持有如 stringnumberboolean 这样的原始类型。

    2. 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:

      js

      let state = reactive({ count: 0 })
      
      // 上面的 ({ count: 0 }) 引用将不再被追踪
      // (响应性连接已丢失!)
      state = reactive({ count: 1 })
      
    3. 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:

      js

      const state = reactive({ count: 0 })
      
      // 当解构时,count 已经与 state.count 断开连接
      let { count } = state
      // 不会影响原始的 state
      count++
      
      // 该函数接收到的是一个普通的数字
      // 并且无法追踪 state.count 的变化
      // 我们必须传入整个对象以保持响应性
      callSomeFunction(state.count)
      

    由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。

4.计算属性

​ 可以缓存,如果属性无变化不会重复执行,比函数性能好,Getter 不应有副作用(不要改变其他状态、在 getter 中做异步请求或者更改 DOM!),避免直接修改计算属性值

<script setup>
import { ref, computed } from 'vue'

const count = ref(2)
// 这个计算属性在 count 的值小于或等于 3 时,将返回 count 的值。
// 当 count 的值大于等于 4 时,将会返回满足我们条件的最后一个值
// 直到 count 的值再次小于或等于 3 为止。
const alwaysSmall = computed({
  get(previous) {
    if (count.value <= 3) {
      return count.value
    }

    return previous
  },
  set(newValue) {
    count.value = newValue * 2
  }
})
</script>
# 类样式绑定
:class
:style
# 条件渲染
v-if
# 一个 v-else 元素必须跟在一个 v-if 或者 v-else-if 元素后面,否则它将不会被识别。
v-else
v-else-if
# v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性。
# v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。
v-show

总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

v-ifv-for 同时存在于一个元素上的时候,v-if 会首先被执行

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
  {{ item.message }}
</li>
<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>
<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义,避免v-if和v-for在一个标签中使用,v-if在-v-for内部使用
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

5. 组件上使用 v-for

<MyComponent
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
/>
  • :item="item":将当前迭代项的值绑定到子组件的 item 属性上。

  • :index="index":将当前迭代项的索引绑定到子组件的 index 属性上。

  • 替换一个数组,当遇到的是非变更方法时,我们需要将旧的数组替换为新的:

    // `items` 是一个数组的 ref
    items.value = items.value.filter((item) => item.message.match(/Foo/))
    

6. 事件处理

内联事件处理器
const count = ref(0)

<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
方法事件处理器
const name = ref('Vue.js')

function greet(event) {
  alert(`Hello ${name.value}!`)
  // `event` 是 DOM 原生事件
  if (event) {
    alert(event.target.tagName)
  }
}

<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>

7. 表单输入绑定

# 不使用v-model
<input
  :value="text"
  @input="event => text = event.target.value">
#  使用v-model
  <input v-model="text">

# 注意在 <textarea> 中是不支持插值表达式的。请使用 v-model 来替代:
<!-- 错误 -->
<textarea>{{ text }}</textarea>

<!-- 正确 -->
<textarea v-model="text"></textarea>

# 复选框
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

# 复选框多值
const checkedNames = ref([])

<div>Checked names: {{ checkedNames }}</div>

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>

<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>

<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>

# 单选按钮
<div>Picked: {{ picked }}</div>

<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>

<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>

# 选择器
<div>Selected: {{ selected }}</div>

<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

# 选择器的选项可以使用 v-for 动态渲染:
const selected = ref('A')

const options = ref([
  { text: 'One', value: 'A' },
  { text: 'Two', value: 'B' },
  { text: 'Three', value: 'C' }
])

<select v-model="selected">
  <option v-for="option in options" :value="option.value">
    {{ option.text }}
  </option>
</select>

<div>Selected: {{ selected }}</div>
# 值绑定
<!-- `picked` 在被选择时是字符串 "a" -->
<input type="radio" v-model="picked" value="a" />

<!-- `toggle` 只会为 true 或 false -->
<input type="checkbox" v-model="toggle" />

<!-- `selected` 在第一项被选中时为字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

# 但有时我们可能希望将该值绑定到当前组件实例上的动态数据。这可以通过使用 v-bind 来实现。
# 复选框 
<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no" />
# true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 'yes',取消选择时设为 'no'。你同样可以通过 v-bind 将其绑定为其他动态值:
<input
  type="checkbox"
  v-model="toggle"
  :true-value="dynamicTrueValue"
  :false-value="dynamicFalseValue" />
  
# 输入自动转为数字
<input v-model.number="age" />
# 自动去除两端的空格
<input v-model.trim="msg" />

8.侦听器

const x = ref(0)
const y = ref(0)

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

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

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

const obj = reactive({ count: 0 })

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

// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`Count is: ${count}`)
  }
)
深层侦听器

直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 在嵌套的属性变更时触发
  // 注意:`newValue` 此处和 `oldValue` 是相等的
  // 因为它们是同一个对象!
})

obj.count++

相比之下,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调:

watch(
  () => state.someObject,
  () => {
    // 仅当 state.someObject 被替换时触发
  }
)

你也可以给上面这个例子显式地加上 deep 选项,强制转成深层侦听器:

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true }
)

在 Vue 3.5+ 中,deep 选项还可以是一个数字,表示最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数。

即时回调的侦听器

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。

watch(
  source,
  (newValue, oldValue) => {
    // 立即执行,且当 `source` 改变时再次执行
  },
  { immediate: true }
)
一次性侦听器
  • 仅支持 3.4 及以上版本

每当被侦听源发生变化时,侦听器的回调就会执行。如果希望回调只在源变化时触发一次,请使用 once: true 选项。

watch(
  source,
  (newValue, oldValue) => {
    // 当 `source` 变化时,仅触发一次
  },
  { once: true }
)
watchEffect
const todoId = ref(1)
const data = ref(null)

watch(
  todoId,
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    )
    data.value = await response.json()
  },
  { immediate: true }
)

特别是注意侦听器是如何两次使用 todoId 的,一次是作为源,另一次是在回调中。

我们可以用 watchEffect 函数 来简化上面的代码。watchEffect() 允许我们自动跟踪回调的响应式依赖。上面的侦听器可以重写为:

watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})
副作用清理

有时我们可能会在侦听器中执行副作用,例如异步请求:

watch(id, (newId) => {
  fetch(`/api/${newId}`).then(() => {
    // 回调逻辑
  })
})

但是如果在请求完成之前 id 发生了变化怎么办?当上一个请求完成时,它仍会使用已经过时的 ID 值触发回调。理想情况下,我们希望能够在 id 变为新值时取消过时的请求。

我们可以使用 onWatcherCleanup() API 来注册一个清理函数,当侦听器失效并准备重新运行时会被调用:

import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
  const controller = new AbortController()

  fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
    // 回调逻辑
  })

  onWatcherCleanup(() => {
    // 终止过期请求
    controller.abort()
  })
})
watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})
同步侦听器
import { watchSyncEffect } from 'vue'

watchSyncEffect(() => {
  /* 在响应式数据变化时同步执行 */
})
停止侦听器

setup()<script setup> 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。因此,在大多数情况下,你无需关心怎么停止一个侦听器。

一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。如下方这个例子:

<script setup>
import { watchEffect } from 'vue'

// 它会自动停止
watchEffect(() => {})

// ...这个则不会!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>
const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()

注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:

// 需要异步请求得到的数据
const data = ref(null)

watchEffect(() => {
  if (data.value) {
    // 数据加载后执行某些操作...
  }
})

9. 模板引用

访问模板引用

useTemplateRef() 3.5+

<script setup>
import { useTemplateRef, onMounted } from 'vue'

// 第一个参数必须与模板中的 ref 值匹配
const input = useTemplateRef('my-input')

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="my-input" />
</template>

v-for 中的模板引用

<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'

const list = ref([
  /* ... */
])

const itemRefs = useTemplateRef('items')

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

函数模板引用

<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

组件上的 ref

<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'

const childRef = useTemplateRef('child')

onMounted(() => {
  // childRef.value 将持有 <Child /> 的实例
})
</script>

<template>
  <Child ref="child" />
</template>

如果一个子组件使用的是选项式 API 或没有使用 <script setup>,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。

使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
  a,
  b
})
</script>

请注意,defineExpose 必须在任何 await 操作之前调用。否则,在 await 操作后暴露的属性和方法将无法访问。

10.组件基础

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成一个层层嵌套的树状结构:

在这里插入图片描述

定义一个组件

当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC):

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义:

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
  // 也可以针对一个 DOM 内联模板:
  // template: '#my-template-element'
}

要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue 的文件中,这个组件将会以默认导出的形式被暴露给外部。

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

通过 <script setup>,导入的组件都在模板中直接可用。

如果你是直接在 DOM 中书写模板 (例如原生 <template> 元素的内容),模板的编译需要遵从浏览器中 HTML 的解析行为。在这种情况下,你应该需要使用 kebab-case 形式并显式地关闭这些组件的标签。

<!-- 如果是在 DOM 中书写该模板 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
传递 props

Props 是一种特别的 attributes,你可以在组件上声明注册。要传递给博客文章组件一个标题,我们必须在组件的 props 列表上声明它。这里要用到 defineProps 宏:

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

defineProps 是一个仅 <script setup> 中可用的编译宏命令,并不需要显式地导入。声明的 props 会自动暴露给模板。defineProps 会返回一个对象,其中包含了可以传递给组件的所有 props

const props = defineProps(['title'])
console.log(props.title)

如果你没有使用 <script setup>,props 必须以 props 选项的方式声明,props 对象会作为 setup() 函数的第一个参数被传入:

export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

一个组件可以有任意多的 props,默认情况下,所有 prop 都接受任意类型的值。

当一个 prop 被注册后,可以像这样以自定义 attribute 的形式传递数据给它:

<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

在实际应用中,我们可能在父组件中会有如下的一个博客文章数组:

const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

这种情况下,我们可以使用 v-for 来渲染它们:

<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

使用 v-bind 语法 (:title="post.title") 来传递动态 prop 值的。当事先不知道要渲染的确切内容时,这一点特别有用。

监听事件

组件实例提供了一个自定义事件系统。父组件可以通过 v-on@ 来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样:

<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件:

<!-- BlogPost.vue, 省略了 <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

因为有了 @enlarge-text="postFontSize += 0.1" 的监听,父组件会接收这一事件,从而更新 postFontSize 的值。

我们可以通过 defineEmits 宏来声明需要抛出的事件:

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
const emit = defineEmits(['enlarge-text'])
//emit 函数
emit('enlarge-text')
</script>

如果你没有在使用 <script setup>,你可以通过 emits 选项定义组件会抛出的事件。你可以从 setup() 函数的第二个参数,即 setup 上下文对象上访问到 emit 函数:

export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}
插槽
<AlertBox>
  Something bad happened.
</AlertBox>

可以通过 Vue 的自定义 <slot> 元素来实现:

Something bad happened.会插入到strong标签后

<!-- AlertBox.vue -->
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>
动态组件
<script setup>
import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'
import { ref } from 'vue'
 
const currentTab = ref('Home')

const tabs = {
  Home,
  Posts,
  Archive
}
</script>

<template>
  <div class="demo">
    <button
       v-for="(_, tab) in tabs"
       :key="tab"
       :class="['tab-button', { active: currentTab === tab }]"
       @click="currentTab = tab"
     >
      {{ tab }}
    </button>
	  <component :is="tabs[currentTab]" class="tab"></component>
  </div>
</template>
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

在上面的例子中,被传给 :is 的值可以是以下几种:

  • 被注册的组件名
  • 导入的组件对象

你也可以使用 is attribute 来创建一般的 HTML 元素。

当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 `` 组件强制被切换掉的组件仍然保持“存活”的状态。

DOM 内模板解析注意事项

大小写区分:HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式

闭合标签:在 DOM 内模板中,我们必须显式地写出关闭标签。由于 HTML 只允许一小部分特殊的元素省略其关闭标签,最常见的就是 <input><img>。对于其他的元素来说,如果你省略了关闭标签,原生的 HTML 解析器会认为开启的标签永远没有结束。

元素位置限制:某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul><ol><table><select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li><tr><option>

这将导致在使用带有此类限制元素的组件时出现问题。例如:

<table>
  <blog-post-row></blog-post-row>
</table>

自定义的组件 <blog-post-row> 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute 作为一种解决方案:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

一些小例子:示例

11.生命周期钩子

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。

注册周期钩子

举例来说,onMounted 钩子可以用来在组件完成初始渲染并创建 DOM 节点后运行代码:

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

还有其他一些钩子,会在实例生命周期的不同阶段被调用,最常用的是 onMountedonUpdatedonUnmounted。所有生命周期钩子的完整参考及其用法请参考 API 索引。

当调用 onMounted 时,Vue 会自动将回调函数注册到当前正被初始化的组件实例上。这意味着这些钩子应当在组件初始化时被同步注册。例如,请不要这样做:

js

setTimeout(() => {
  onMounted(() => {
    // 异步注册时当前组件实例已丢失
    // 这将不会正常工作
  })
}, 100)

注意这并不意味着对 onMounted 的调用必须放在 setup()<script setup> 内的词法上下文中。onMounted() 也可以在一个外部函数中调用,只要调用栈是同步的,且最终起源自 setup() 就可以。

生命周期图示

在这里插入图片描述

有关所有生命周期钩子及其各自用例的详细信息,请参考生命周期钩子 API 索引。

3.深入组件

4.逻辑复用

5.内置组件

6.应用规模化

7.最佳实践

8.进阶主题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/980574.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

eMMC安全简介

1. 引言 术语“信息安全”涵盖多种不同的设计特性。一般而言&#xff0c; 信息安全是指通过实践防止信息遭受未经授权的访问、使用、披露、中断、篡改、检查、记录或销毁。 信息安全的三大核心目标为 机密性&#xff08;Confidentiality&#xff09;、完整性&#xff08;Integr…

SpringBoot新闻推荐系统设计与实现

随着信息时代的快速发展&#xff0c;新闻推荐系统成为用户获取个性化内容的重要工具。本文将介绍一个幽络源的基于SpringBoot开发的新闻推荐系统&#xff0c;该系统功能全面&#xff0c;操作简便&#xff0c;能够满足管理员和用户的多种需求。 管理员模块 管理员模块为系统管…

zookeeper-docker版

Zookeeper-docker版 1 zookeeper概述 1.1 什么是zookeeper Zookeeper是一个分布式的、高性能的、开源的分布式系统的协调&#xff08;Coordination&#xff09;服务&#xff0c;它是一个为分布式应用提供一致性服务的软件。 1.2 zookeeper应用场景 zookeeper是一个经典的分…

Java 调试模式下 Redisson 看门狗失效

一、场景分析 前几天在做分布式锁测试&#xff1a; 在调试模式下&#xff0c;lock.lock() 之后打上断点&#xff0c;想测试一下在当前线程放弃锁之前&#xff0c;别的线程能否获取得到锁。 发现调试模式下&#xff0c;看门狗机制失效了&#xff0c;Redis 上 30 秒后&#xff0…

FFmpeg入门:最简单的音频播放器

FFmpeg入门&#xff1a;最简单的音频播放器 欢迎大家来到FFmpeg入门的第二章&#xff0c;今天只做一个最简单的FFmpeg音频播放器&#xff1b;同样&#xff0c;话不多说&#xff0c;先上流程图 流程图 以上流程和视频播放器的解码过程基本上是一致的&#xff1b; 不同点在于 S…

基于Python Django的人脸识别上课考勤系统(附源码,部署)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【小羊肖恩】小羊杯 Round 2 C+K

题目链接&#xff1a;https://ac.nowcoder.com/acm/contest/100672#question C.是毛毛虫吗&#xff1f; 思路&#xff1a; 其实很简单&#xff0c;假设我们要满足题目所给条件&#xff0c;那么这个毛毛虫最坏情况下肯定是一条如下图所示的无向图 右端省略号为对称图形 &…

PyCharm怎么集成DeepSeek

PyCharm怎么集成DeepSeek 在PyCharm中集成DeepSeek等大语言模型(LLM)可以借助一些插件或通过代码调用API的方式实现,以下为你详细介绍两种方法: 方法一:使用JetBrains AI插件(若支持DeepSeek) JetBrains推出了AI插件来集成大语言模型,不过截至2024年7月,官方插件主要…

安装 Open WebUI

2025.03.01 早上 我已经安装了ollama 和deeseek模型 &#xff08;本地部署流水账之ollama安装Deepseek安装-CSDN博客&#xff09;&#xff0c;然后需要个与模型沟通的工具&#xff08;这么说不知道对不对&#xff09;。 刚开始用的chatbox&#xff0c;安装很方便&#xff0c;…

java后端开发day25--阶段项目(二)

&#xff08;以下内容全部来自上述课程&#xff09; 1.美化界面 private void initImage() {//路径分两种&#xff1a;//1.绝对路径&#xff1a;从盘符开始写的路径 D:\\aaa\\bbb\\ccc.jpg//2.相对路径&#xff1a;从当前项目开始写的路径 aaa\\bbb\\ccc.jpg//添加图片的时…

基于Spring Boot + Vue的常规应急物资管理系统设计与实现

大家好&#xff0c;今天要和大家聊的是一款基于Spring Boot Vue的“常规应急物资管理系统”的设计与实现。项目源码以及部署相关事宜请联系我&#xff0c;文末附上联系方式。 项目简介 基于Spring Boot Vue的“常规应急物资管理系统”设计与实现的主要使用者分为管理员、员工…

SpringCloud + Spring AI Alibaba 整合阿里云百炼大模型

一、前言 记录一次自己使用微服务整合阿里云的百炼大模型&#xff0c;需要用到Redis来记录最近五条信息&#xff0c;已能够保证上下文的连通性&#xff0c;Ai和用户之间的对话是使用的MongoDB来进行存储。然后我这篇文章是介绍了两种请求方式&#xff0c;一种是通过Http请求&a…

(贪心 合并区间)leetcode 56

思路来源&#xff1a;代码随想录--代码随想录_合并区间题解 首先用lambda 按照左界值升序排序 建立答案的二维数组&#xff0c;将第一个行区间放入&#xff0c;判断从第二行开始 第i行的左区间一定大于第i-1行的左区间&#xff08;排序过了&#xff09;&#xff0c;所以只判断…

CAN总线通信协议学习4——数据链路层之仲裁规则

CAN总线只有一对差分信号线&#xff0c;同一时间只能有一个设备操作总线发送数据若多个设备同时有发送需求&#xff0c;该如何分配总线资源? 解决问题的思路&#xff1a;制定资源分配规则&#xff0c;依次满足多个设备的发送需求&#xff0c;确保同一时间只有一个设备操作总线…

Kubespray部署企业级高可用K8S指南

目录 前言1 K8S集群节点准备1.1 主机列表1.2 kubespray节点python3及pip3准备1.2.1. 更新系统1.2.2. 安装依赖1.2.3. 下载Python 3.12源码1.2.4. 解压源码包1.2.5. 编译和安装Python1.2.6. 验证安装1.2.7. 设置Python 3.12为默认版本&#xff08;可选&#xff09;1.2.8. 安装pi…

互推机制在开源AI智能名片2+1链动模式S2B2C商城小程序源码推广中的应用探索

摘要&#xff1a; 在数字化营销时代&#xff0c;开源AI智能名片21链动模式S2B2C商城小程序源码作为一种创新的技术解决方案&#xff0c;正逐步成为企业数字化转型的重要工具。然而&#xff0c;面对激烈的市场竞争&#xff0c;如何高效推广这一前沿技术产品&#xff0c;成为开发…

波导阵列天线 学习笔记11双极化全金属垂直公共馈电平板波导槽阵列天线

摘要&#xff1a; 本communicaition提出了一种双极化全金属垂直公共馈电平板波导槽阵列天线。最初提出了一种公共馈电的单层槽平板波导来实现双极化阵列。此设计消除了传统背腔公共馈电的复杂腔体边缘的必要性&#xff0c;提供了一种更简单的天线结构。在2x2子阵列种发展了宽十…

腾讯游戏完成架构调整 IEG新设五大产品事业部

易采游戏网2月28日独家消息&#xff1a;继1月份腾讯天美工作室群完成内部组织架构调整后&#xff0c;腾讯旗下互动娱乐事业群&#xff08;IEG&#xff09;再次宣布对组织架构进行优化调整。此次调整的核心在于新设立了五大产品事业部&#xff0c;包括体育产品部、音舞产品部、V…

Vue3国际化开发实战:i18n-Ally + vue-i18n@next高效配置教程,项目中文显示

本文详解 Vue3 国际化开发全流程&#xff1a;从安装 vue-i18nnext 到配置多语言文件&#xff08;JSON/YAML&#xff09;&#xff0c;结合 i18n-Ally 插件实现高效翻译管理。重点涵盖&#xff1a; 工程配置&#xff1a;创建 i18n 实例、模块化语言文件结构&#xff08;支持命名…

【愚公系列】《Python网络爬虫从入门到精通》036-DataFrame日期数据处理

标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…