You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

16 KiB

基础

npm create vue@latest

创建应用

应用配置

确保在挂载应用实例之前完成所有应用配置!

  • 应用级的错误处理器,用来捕获所有子组件上的错误
app.config.errorHandler = (err) => {
  /* 处理错误 */
}
  • 注册全局资源,注册一个组件
app.component('TodoDeleteButton', TodoDeleteButton)

多实例

const app1 = createApp({
  /* ... */
})
app1.mount('#container-1')

const app2 = createApp({
  /* ... */
})
app2.mount('#container-2')

模板语法

如果偏好使用JavaScript,JSX 支持直接手写渲染函数而不采用模板。但JSX不会享受到和模板同等级别的编译时优化

文本插值

<span>Message: {{ msg }}</span>

使用原始HTML,不被vue编译

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

原始HTML

安全警告

Attribute 绑定

<div v-bind:id="dynamicId"></div>
<!--简写-->
<div :id="dynamicId"></div>

<!--同名简写 版本>=3.4-->
<!--  :id="id" 相同 -->
<div :id></div>

<!-- 这也同样有效 -->
<div v-bind:id></div>

布尔型 Attribute ???

当 isButtonDisabled 为真值或一个空字符串 (即 <button disabled="">) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。

动态绑定多个值

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}
<div v-bind="objectOfAttrs"></div>

使用 JavaScript 表达式

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

调用函数

<time :title="toTitleDate(date)" :datetime="date">
  {{ formatDate(date) }}
</time>

受限的全局访问

  • Math 和 Date可以在标签中使用,其他的需要在配置中注册,app.config.globalProperties
app.config.globalProperties.msg = 'hello'

export default {
    mounted() {
        console.log(this.msg) // 'hello'
    }
}

动态参数

<!--
注意参数表达式有一些约束
参见下面动态参数值的限制动态参数语法的限制章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

动态事件

<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>
  • 不要这样写
    • 动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。
<!-- 这会触发一个编译器警告 -->
<template>
<!--    <a :['foo' + bar]="value"> ... </a>-->
</template>

修饰符 Modifiers ?


响应式基础

声明响应式状态

ref的实际样子

const count = ref(0)

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

count.value++
console.log(count.value) // 1

使用ref

import { ref } from 'vue'

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

    function increment() {
      // 在 JavaScript 中需要 .value
      count.value++
    }

    // 不要忘记同时暴露 increment 函数
    return {
      count,
      increment
    }
  }
}
<div>{{ count }}</div>
<button @click="count++">
    {{ count }}
</button>

<script setup>

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

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>

DOM 更新时机

import { nextTick } from 'vue'

async function increment() {
  count.value++
  await nextTick()
  // 现在 DOM 已经更新了
}

reactive()

  • reactive() 将使对象本身具有响应性

<script setup>
    import { reactive } from 'vue'
    const state = reactive({ count: 0 })
</script>

<template>
    <button @click="state.count++">
        {{ state.count }}
    </button>
</template>
  • reactive() 返回的是一个原始对象的 Proxy
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({})

const raw = {}
proxy.nested = raw

console.log(proxy.nested === raw) // false

reactive() 的局限性

  • 有限的值类型:不能持有如 string、number 或 boolean 这样的原始类型。

  • 不能替换整个对象:

let state = reactive({ count: 0 })

// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
  • 对解构操作不友好
const state = reactive({ count: 0 })

// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

计算属性

基础示例

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

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>
  • 计算属性有缓存,当响应值没变化时,计算属性不会执行,如果用函数的话,函数每次都会执行
// now永远不会更新,因为他没有用到响应值
const now = computed(() => Date.now())

可写计算属性

<script setup>
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(' ')
  }
})
</script>

类与样式绑定

<script setup>
    const isActive = ref(true)
    const hasError = ref(false)
</script>
<template>
    <div
        class="static"
        :class="{ active: isActive, 'text-danger': hasError }"
    ></div>
</template>

渲染结果:

<div class="static active"></div>

组件的类会叠加

<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>

<!-- 在使用组件时 -->
<MyComponent class="baz boo" />

<!--渲染出的 HTML -->
<p class="foo bar baz boo">Hi!</p>

条件渲染基本没变

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

列表渲染

v-for可以遍历对象,顺序基于Object.keys()

使用范围值 【整数值】

<!--n从1开始-->
<span v-for="n in 10">{{ n }}</span>

<template> 上也可以用 v-for

事件处理

  • v-on 指令 (简写为 @)

内联事件处理器

<script setup>
    const count = ref(0)
</script>
<template>
    <button @click="count++">Add 1</button>
    <p>Count is: {{ count }}</p>
</template>

方法事件处理器

<script setup>
    const name = ref('Vue.js')

    function greet(event) {
        alert(`Hello ${name.value}!`)
        // `event` 是 DOM 原生事件
        if (event) {
            alert(event.target.tagName)
        }
    }
</script>
<template>
    <!-- `greet` 是上面定义过的方法名 -->
    <button @click="greet">Greet</button>
</template>

在内联事件处理器中访问事件参数

  • $event
<script setup>
    function warn(message, event) {
        // 这里可以访问原生事件
        if (event) {
            event.preventDefault()
        }
        alert(message)
    }
</script>
<template>
    <!-- 使用特殊的 $event 变量 -->
    <button @click="warn('Form cannot be submitted yet.', $event)">
        Submit
    </button>

    <!-- 使用内联箭头函数 -->
    <button @click="(event) => warn('Form cannot be submitted yet.', event)">
        Submit
    </button>
</template>

事件修饰符

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>

<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>

<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>

<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>

<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>

.capture.once.passive 修饰符与原生 addEventListener 事件相对应:

<!-- 添加事件监听器时使用 `capture` 捕获模式 -->
<!-- 例如指向内部元素的事件在被内部元素处理前先被外部处理 -->
<div @click.capture="doThis">...</div>

<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>

<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>

.passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。

按键修饰符

  • $event.key

  • 按键别名

    • .enter
    • .tab
    • .delete (捕获“Delete”和“Backspace”两个按键)
    • .esc
    • .space
    • .up
    • .down
    • .left
    • .right
  • 系统按键修饰符

    • .ctrl
    • .alt
    • .shift
    • .meta
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>

.exact 修饰符允许精确控制触发事件所需的系统修饰符的组合。

<!-- 当按下 Ctrl 即使同时按下 Alt  Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

鼠标按键修饰符

  • .left
  • .right
  • .middle

生命周期

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

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

生命周期

侦听器

<script setup>
  import { ref, watch } from 'vue'
  const x = ref(0)
  const y = ref(0)

  // 单个 ref
  watch(x, (newX, oldX) => {
    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}`)
  })
</script>
  • 不能直接侦听响应式对象的属性值
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}`)
        }
)

深层侦听器 { deep: true }

const obj = reactive({ count: 0 })

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

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

obj.count++

即时回调的侦听器,在创建侦听器时,立即执行一遍回调 { immediate: true }

watch(
  source,
  (newValue, oldValue) => {
    // 立即执行,且当 `source` 改变时再次执行
  },
  { immediate: true }
)

一次性侦听器 (版本>=3.4)

watch(
  source,
  (newValue, oldValue) => {
    // 当 `source` 变化时,仅触发一次
  },
  { once: true }
)

watchEffect(), 如果函数体内有响应式参数被调用,则会自动执行,且会立即执行一次

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

回调的触发时机 ?

停止侦听器

const unwatch = watchEffect(() => {})

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

模板引用

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

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

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

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

v-for 中的模板引用

  • 应该注意的是,ref 数组并不保证与源数组相同的顺序。
<script setup>
import { ref, onMounted } from 'vue'

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

const itemRefs = ref([])

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

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

函数模板引用

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

组件上的 ref

  • setup组合式组件,需要手动暴露子组件的变量
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

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

组件基础

单文件组件 (简称 SFC) .vue

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

const count = ref(0)
</script>

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

.js

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'
}

传递 props

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

<template>
  <h4>{{ title }}</h4>
</template>
  • 如果你没有使用