【Vue】Vue 快速教程

Vue tutorial

参考:教程 | Vue.js (vuejs.org)

该教程需要前置知识:HTML, CSS, JavaScript

学习前置知识,你可以去 MDN

Vue framework 是一个 JavaScript framework,以下简称 Vue,下面是它的特点

  • 声明式渲染(Declarative Rendering):即声明 JavaScript 对象,改变对象状态来更改 HTML,这个过程由 Vue 完成
  • 响应式(Reactivity):JavaScript 的对象状态改变会马上反映到 DOM(不知道 DOM 的去查 MDN 文档)

Declarative Rendering and Reactivity

Vue 实现了:

JavaScript Obejct <-> Vue <-> DOM

但是 Vue 显然是利用 JavaScript 机制,那就是 Proxy,Proxy 可以实现

JavaScript Object <-> Proxy <-> DOM

所以 Vue 把 Proxy 改造后封装成了 reactive(),调用这个 API 会返回一个特殊的对象,称之为响应式对象(reactive object)

  • reactive()
import { reactive } from 'vue'

const counter = reactive({
  count: 0
})

console.log(counter.count) // 0
counter.count++

但是 reactive() 参数只能是对象(还有数组和内置类型),Vue 又把 reactive() 改造封装成了 ref(),它也会返回一个响应式对象,并且带一个 .value property,只不过它的参数可以填写值。

  • ref()
import { ref } from 'vue'

const message = ref('Hello World!')

console.log(message.value) // "Hello World!"
message.value = 'Changed'

以上给 ref()reactive() 填写参数得到响应式对象的过程,就被成为数据绑定(data binding)。

Template syntax

在这里复习一下 HTML element 和 attribute 概念

Anatomy of an HTML element

这是 HTML element

在这里插入图片描述

这是 HTML attribute

在这里插入图片描述

在此之中,Class 为 attribute name,editor-note 为 attribute value

Vue 自己创造了一套 template language。最基本的数据绑定是文本插值(Text Interpolation),它可以改变 element 的 content,像这样

<span>{{ message }}</span>

这种语法被成为 “Mustache”语法 (即双大括号)。再结合上节讲到的 reactive object,我们可以这样写

import { ref } from 'vue'

const message = ref('Hello World!')

效果是这样的

在这里插入图片描述

可能你会有些疑问,为什么不是写 {{message.value}},因为它是 top-level property,会自动解包(unwrapping)

const object = { id: ref(1) }

比如这个里面,id 就不是 top-level property,如果你这么写

{{ object.id + 1 }}

渲染的结果将是 [object Object]1

在这里插入图片描述

你需要手动解包,才会渲染出 2

{{ object.id.value + 1 }}

在这里插入图片描述

Directive

v-xxx 就是一种 attribute,在它的 template language 中被称为 directive

v-bind

Attribute bind

在 Vue 中,Mustache 语法只能用于文本插值来改变 element content,没法儿操作 element attribute。而且 element attribute 是静态的,为了给 element attribute 绑定一个动态值,需要使用 Vue 的 v-bind directive

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

冒号后面的 id 被称为 directive 的参数(argument),dynamicId 则为参数值,它会和响应式对象的 property 同步。它可以简写为

<div :id="dynamicId"></div>

例子:

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

const titleClass = ref('title')
</script>

<template>
  <h1 :class="titleClass">Make me red</h1>
</template>

<style>
.title {
  color: red;
}
</style>

v-on

Event Listen

可以通过 v-on 来监听 DOM event

<button v-on:click="increment">{{ count }}</button>

简写

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

点击 button 会触发 increment() 这个函数。

例子:

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

const count = ref(0)

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

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

v-model

Form bind

同时使用 v-bindv-on 对表单(form)进行绑定和监听

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

const text = ref('')

function onInput(e) {
  text.value = e.target.value
}
</script>

<template>
  <input :value="text" @input="onInput" placeholder="Type here">
  <p>{{ text }}</p>
</template>

啊,这么写实在太麻烦,所以 vue 提供了 v-model。当然,最好看一下它支持哪些 element。

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

const text = ref('')
</script>

<template>
  <input v-model="text" placeholder="Type here">
  <p>{{ text }}</p>
</template>

v-if and v-else

Conditional Rendering

v-ifv-else 可以根据条件来决定 element 是否在 DOM 中。

例子:

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

const awesome = ref(true)

function toggle() {
  awesome.value = !awesome.value
}
</script>

<template>
  <button @click="toggle">toggle</button>
  <h1 v-if="awesome">Vue is awesome!</h1>
  <h1 v-else>Oh no 😢</h1>
</template>

改变 awesome 的值来显示 “Vue is awesome!” 和 “Oh no 😢”。

v-for

当你想写一个列表时,一个个写列表的 element 实在太累了,如果有 1000 个那不就完蛋了,所以 v-for 可以通过循环,直接渲染出列表(当然,你得给相应的数据)

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

// 给每个 todo 对象一个唯一的 id
let id = 0

const newTodo = ref('')
const todos = ref([
  { id: id++, text: 'Learn HTML' },
  { id: id++, text: 'Learn JavaScript' },
  { id: id++, text: 'Learn Vue' }
])

function addTodo() {
  todos.value.push({ id: id++, text: newTodo.value })
  newTodo.value = ''
}

function removeTodo(todo) {
  todos.value = todos.value.filter((t) => t !== todo)
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo" required placeholder="new todo">
    <button>Add Todo</button>
  </form>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.text }}
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
</template>

computed()

template 里面可以写这种计算表达式

<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

但是这种计算写在 template 里是真的不好理解,个人感觉在结构上 View 里不能有逻辑,所以尽量不要这么写。

所以我们可以用 computed(),这样

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

它和 method() 最大区别在于,它是只有在响应式对象更新时才会重新被调用和计算,否则就会直接返回缓存值,即 books 不变,重渲染(比如页面刷新,更新)时 computed() 不会被调用,而 method() 则会。插嘴一句 method(),重渲染时总会被调用。

Lifecycle and Template Refs

手动用 JavaScript 操作 DOM 是一件苦差事,所以我们用 vue 来帮忙,但是有时我们不得不操作 DOM,这个时候我们就得使用模板引用(template ref)

这个时候就要使用 ref attribute

<p ref="pElementRef">hello</p>

如果要访问这个 ref,我们需要声明(declare)ref 并初始化

const pElementRef = ref(null)

注意我们使用给 ref 的 argument 为 null,这是因为<script setup> 执行时,DOM 还没有初始化,template ref 只能在挂在(mount)后访问,所以我们可以使用生命周期钩子(lifecycle hook)比如 onMounted(),关于 lifecycle 请看生命周期图示

例子:

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

const pElementRef = ref(null)

onMounted(() => {
  pElementRef.value.textContent = 'mounted!'
})
</script>

<template>
  <p ref="pElementRef">hello</p>
</template>

watch()

watch() 可以监察一个 ref,并触发一个 callback function,比如下面的例子就是监察 todoId,触发 fetchData

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

const todoId = ref(1)
const todoData = ref(null)

async function fetchData() {
  todoData.value = null
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  todoData.value = await res.json()
}

fetchData()

watch(todoId, fetchData)
</script>

<template>
  <p>Todo id: {{ todoId }}</p>
  <button @click="todoId++" :disabled="!todoData">Fetch next todo</button>
  <p v-if="!todoData">Loading...</p>
  <pre v-else>{{ todoData }}</pre>
</template>

Component

Vue application 常常是多个 component 嵌套创建,所以就有 parent component 包含 child component

如果要使用 child component,就需要导入它

import ChildComp from './ChildComp.vue'

使用 child component

<ChildComp />

例子:

<!--App.vue-->

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

<template>
  <ChildComp />
</template>

Props

child component 可以通过 props 从 parent component 获取动态数据

<!--ChildComp.vue-->
<script setup>
const props = defineProps({
  msg: String
})
</script>

注意,defineProps() 是一个 runtime marcro,不需要导入。

这样,msg 就可以在 child component 的 <template> 中使用

<template>
  <h2>{{ msg || 'No props passed yet' }}</h2>
</template>

而 parent component 则可以用 v-bind 传递数据

<!--App.vue-->
<ChildComp :msg="greeting" />

例子:

<!--App.vue-->
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const greeting = ref('Hello from parent')
</script>

<template>
  <ChildComp :msg="greeting" />
</template>
<!--ChildComp.vue-->
<script setup>
const props = defineProps({
  msg: String
})
</script>

<template>
  <h2>{{ msg || 'No props passed yet' }}</h2>
</template>

child component 中采用的 “runtime declaration”,还有一种是如果你用 typescript,需要采用 “type-based declaration”,具体看官方文档。

Emits

child component 可以向 parent component 传 event,emit() 中,第一个 argument 是 event name,其他的会传给 event listener。

parent component 可以通过 v-on 监听 child-emitted event,并且可以将额外的 argument 赋值给 local state

<!--App.vue-->
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const childMsg = ref('No child msg yet')
</script>

<template>
  <ChildComp @response="(msg) => childMsg = msg" />
  <p>{{ childMsg }}</p>
</template>
<!--ChildComp.vue-->
<script setup>
const emit = defineEmits(['response'])

emit('response', 'hello from child')
</script>

<template>
  <h2>Child component</h2>
</template>

Solots

除了 props,parent component 可以将 template 片段传给 child component,而在 child component,则可以使用 <slot> 来显示片段的内容。

<!--App.vue-->
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const msg = ref('from parent')
</script>

<template>
  <ChildComp>Message: {{ msg }}</ChildComp>
</template>
<template>
  <slot>Fallback content</slot>
</template>

以上 parent component 中,<ChildComp> 的内容会传给 child component 中的 <slot>,最后渲染在 parent component 上。

在这里插入图片描述

Essential

Create application

application instance and root component

Vue application 通常是通过 createApp 来创建一个 application instance

createApp 的参数则被称为 root component,而且其他 component 则作为 root 的 child component,所以 vue application 是由 root component 和 child component 组成的。

一般这种创建代码都在 <project-name>/src/main.js

<!--main.js-->
import { createApp } from 'vue'
// 导入一个单组件
import App from './App.vue'
// 将这个单组件作为根组件
const app = createApp(App)

一个 Todo application 的例子

App (root component)
├─ TodoList
│  └─ TodoItem
│     ├─ TodoDeleteButton
│     └─ TodoEditButton
└─ TodoFooter
   ├─ TodoClearButton
   └─ TodoStatistics

mount application

application instance 必须调用 mount() 才能渲染,而必须的 argument 则为 DOM 的 element 或者 CSS selector

在 vue project 中一般在 <project-name>/index.html

<div id="app"></div>

注意 mount() 应该始终在应用配置完成后调用,简单点儿说就是最后调用。

Applicaiton configuration

application instance 会提供一个 config object,这样就可以配置 vue app,比如 app-level option,capture error

app.config.errorHandler = (err) => {
  /* 处理错误 */
}

或者 component registration

比如 global registration

app.component('TodoDeleteButton', TodoDeleteButton)

其他细节清参考 vue 文档。

Component registration

如果要使用 component,则必须是 registered

Global registration

使用 .component() method:

import { createApp } from 'vue'

const app = createApp({})

app.component(
  // the registered name
  'MyComponent',
  // the implementation
  {
    /* ... */
  }
)

如果使用 SFC,则

import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

Local registration

使用 component option

import ComponentA from './ComponentA.js'

export default {
  components: {
    ComponentA
  },
  setup() {
    // ...
  }
}

使用 SFC

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

<template>
  <ComponentA />
</template>

Toolchain

当你创建一个 vue project 时,手动新建目录和文件是很麻烦的事情,所有我们有项目脚手架(Project Scaffolding)来自动创建 project 基本的目录和文件。

Vite

尤雨溪开发的 build tool,支持 SFC,通过 Vite 创建项目:

npm create vue@latest

Vue CLI

基于 webpack 的 build tool,但现在是维护状态,建议使用 Vite。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值