## 入门

### 介绍

Vue 是一套用于构建用户界面的渐进式框架

- Vue 3.x 官方文档 (cn.vuejs.org)
- Vue Router 4.x 官方文档 (router.vuejs.org)
- Vue 2 备忘清单

注意：Vue 3.x 版本对应 Vue Router 4.x 路由版本

### 创建应用

已安装 `16.0` 或更高版本的 Node.js

    $ npm init vue@latest

指令将会安装并执行 create-vue，它是 Vue 官方的项目脚手架工具

    ✔ Project name: … <your-project-name>
    ✔ Add TypeScript? … No/Yes
    ✔ Add JSX Support? … No/Yes
    ✔ Add Vue Router for Single Page Application development? … No/Yes
    ✔ Add Pinia for state management? … No/Yes
    ✔ Add Vitest for Unit testing? … No/Yes
    ✔ Add Cypress for both Unit and End-to-End testing? … No/Yes
    ✔ Add ESLint for code quality? … No/Yes
    ✔ Add Prettier for code formatting? … No/Yes

    Scaffolding project in ./<your-project-name>...
    Done.

安装依赖并启动开发服务器

    $ cd <your-project-name>
    $ npm install
    $ npm run dev

当你准备将应用发布到生产环境时，请运行：

    $ npm run build

此命令会在 `./dist` 文件夹中为你的应用创建一个生产环境的构建版本

### 应用实例

    import { createApp } from 'vue'

    const app = createApp({
      data() {
        return { count: 0 }
      }
    })
    app.mount('#app')

挂载应用

    <div id="app">
      <button @click="count++">
        {{ count }}
      </button>
    </div>

### 通过 CDN 使用 Vue

    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <div id="app">{{ message }}</div>
    <script>
      const { createApp } = Vue
      createApp({
        data() {
          return {
            message: 'Hello Vue!'
          }
        }
      }).mount('#app')
    </script>

### 使用 ES 模块构建版本

    <div id="app">{{ message }}</div>
    <script type="module">
      import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
      createApp({
        data() {
          return {
            message: 'Hello Vue!'
          }
        }
      }).mount('#app')
    </script>

## 模板语法

### 文本插值

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

使用的是 `Mustache` 语法 (即双大括号)，每次 `msg` 属性更改时它也会同步更新

### 原始 HTML

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

双大括号`{{}}`会将数据解释为纯文本，使用 `v-html` 指令，将插入 HTML

### Attribute 绑定

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

简写

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

### 布尔型 Attribute

    <button :disabled="isButtonDisabled">
      Button
    </button>

### 动态绑定多个值

    data() {
      return {
        objectOfAttrs: {
          id: 'container',
          class: 'wrapper'
        }
      }
    }

通过不带参数的 `v-bind`，你可以将它们绑定到单个元素上

    <div v-bind="objectOfAttrs"></div>

### 使用 JavaScript 表达式

    {{ number + 1 }}
    {{ ok ? 'YES' : 'NO' }}
    {{ message.split('').reverse().join('') }}

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

### 仅支持表达式(例子都是无效)

    <!-- 这是一个语句，而非表达式 -->
    {{ var a = 1 }}
    <!-- 条件控制也不支持，请使用三元表达式 -->
    {{ if (ok) { return message } }}

### 调用函数

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

### 指令 Directives

    <p v-if="seen">Now you see me</p>

### 参数 Arguments

    <a v-bind:href="url"> ... </a>
    <!-- 简写 -->
    <a :href="url"> ... </a>

### 绑定事件

    <a v-on:click="doSomething"> ... </a>
    <!-- 简写 -->
    <a @click="doSomething"> ... </a>

### 动态参数

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

这里的 `attributeName` 会作为一个 JS 表达式被动态执行

### 动态的事件名称

    <a v-on:[eventName]="doSomething"> ... </a>
    <!-- 简写 -->
    <a @[eventName]="doSomething">

### 修饰符 Modifiers

    <form @submit.prevent="onSubmit">
      ...
    </form>

`.prevent` 修饰符会告知 `v-on` 指令对触发的事件调用 `event.preventDefault()`

### 指令语法

    v-on:submit.prevent="onSubmit"
    ──┬─ ─┬──── ─┬─────  ─┬──────
      ┆   ┆      ┆        ╰─ Value 解释为JS表达式
      ┆   ┆      ╰─ Modifiers 由前导点表示
      ┆   ╰─ Argument 跟随冒号或速记符号
      ╰─ Name 以 v- 开头使用速记时可以省略

## 响应式基础

### 声明状态

    <div>{{ state.count }}</div>

    import { defineComponent, reactive } from 'vue';

    // `defineComponent`用于IDE推导类型
    export default defineComponent({
      // `setup` 是一个专门用于组合式 API 的特殊钩子函数
      setup() {
        const state = reactive({ count: 0 });

        // 暴露 state 到模板
        return {
          state
        };
      },
    });

### 声明方法

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

    import { defineComponent, reactive } from 'vue';

    export default defineComponent({
      setup() {
        const state = reactive({ count: 0 });

        function increment() {
          state.count++;
        }

        // 不要忘记同时暴露 increment 函数
        return {
          state,
          increment
        };
      },
    })

### `<script setup>` setup语法糖

    <script setup>
    import { reactive } from 'vue';

    const state = reactive({ count: 0 })

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

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

`setup` 语法糖用于简化代码，尤其是当需要暴露的状态和方法越来越多时

### 用 `ref()` 定义响应式变量

    // `reactive`只能用于对象、数组和 Map、Set 这样的集合类型，对 string、number 和 boolean 这样的原始类型则需要使用`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

    const objectRef = ref({ count: 0 });

    // 这是响应式的替换
    objectRef.value = { count: 1 };

    const obj = {
      foo: ref(1),
      bar: ref(2)
    };

    // 该函数接收一个 ref
    // 需要通过 .value 取值
    // 但它会保持响应性
    callSomeFunction(obj.foo);

    // 仍然是响应式的
    const { foo, bar } = obj;

    <!-- PS: 在html模板中不需要带.value就可以使用 -->
    <script setup>
    import { ref } from 'vue';

    const count = ref(0);
    </script>

    <template>
      <div>
        {{ count }}
      </div>
    </template>

### 有状态方法

    import { reactive, defineComponent, onUnmounted } from 'vue';
    import { debounce } from 'lodash-es';

    export default defineComponent({
      setup() {
        // 每个实例都有了自己的预置防抖的处理函数
        const debouncedClick = debounce(click, 500);

        function click() {
          // ... 对点击的响应 ...
        }

        // 最好是在组件卸载时
        // 清除掉防抖计时器
        onUnmounted(() => {
          debouncedClick.cancel();
        });
      },
    });

### 响应式样式

    <script setup>
    import { ref } from 'vue'
    const open = ref(false);
    </script>

    <template>
      <button @click="open = !open">Toggle</button>
      <div>Hello Vue!</div>
    </template>

    <style scope>
      div{
        transition: height 0.1s linear;
        overflow: hidden;
        height: v-bind(open ? '30px' : '0px');
      }
    </style>

## 响应式进阶 —— watch和computed

### 监听状态

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

    const count = ref(0)
    const isEvent = ref(false)

    function increment() {
      state.count++
    }

    watch(count, function() {
      isEvent.value = count.value % 2 === 0
    })
    </script>

    <template>
      <button @click="increment">
        {{ count }}
      </button>
      <p>is event: {{ isEvent ? 'yes' : 'no' }}</p>
    </template>

### 立即监听状态

    watch(count, function() {
      isEvent.value = count.value % 2 === 0
    }, {
      // 上例中的 watch 不会立即执行，导致 isEvent 状态的初始值不准确。配置立即执行，会在一开始的时候立即执行一次
      immediate: true
    })

### 计算状态

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

    const text = ref('')

    // computed 的回调函数里，会根据已有并用到的状态计算出新的状态
    const capital = computed(function(){
      return text.value.toUpperCase();
    })
    </script>

    <template>
      <input v-model="text" />
      <p>to capital: {{ capital }}</p>
    </template>

## 组件通信

### defineProps

    <script setup>
    import { defineProps } from 'vue';

    // 这里可以将 `username` 解构出来，但是一旦解构出来再使用，就不具备响应式能力
    defineProps({
      username: String
    })
    </script>

    <template>
      <p>username: {{ username }}</p>
    </template>

子组件定义需要的参数

    <script setup>
    const username = 'vue'
    </script>

    <template>
      <children :username="username" />
    </template>

父组件参入参数

### defineEmits

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

    const emit = defineEmits(['search'])

    const keyword = ref('')

    const onSearch = function() {
      emit('search', keyword.value)
    }
    </script>

    <template>
      <input v-model="keyword" />
      <button @click="onSearch">search</button>
    </template>

子组件定义支持 `emit` 的函数

    <script setup>
    const onSearch = function(keyword){
      console.log(keyword)
    }
    </script>

    <template>
      <children @search="onSearch" />
    </template>

父组件绑定子组件定义的事件

### defineExpose

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

    const keyword = ref('')

    const onSearch = function() {
      console.log(keyword.value)
    }

    defineExpose({ onSearch })
    </script>

    <template>
      <input v-model="keyword" />
    </template>

子组件对父组件暴露方法

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

    const childrenRef = ref(null)

    const onSearch = function(){
      childrenRef.value.onSearch()
    }
    </script>

    <template>
      <children ref='childrenRef' />
      <button @click="onSearch">search</button>
    </template>

父组件调用子组件的方法

## API 参考

### 全局 API - 应用实例

:- | :-
--- | ---
`createApp()` | 创建一个应用实例 #
`createSSRApp()` | 以 SSR 激活模式创建一个应用实例 #
`app.mount()` | 将应用实例挂载在一个容器元素中 #
`app.unmount()` | 卸载一个已挂载的应用实例 #
`app.provide()` | 提供一个可以在应用中的所有后代组件中注入使用的值 #
`app.component()` | 注册或获取全局组件 #
`app.directive()` | 注册或获取全局指令 #
`app.use()` | 安装一个插件 #
`app.mixin()` | 全局注册一个混入 #
`app.version` | 当前应用所使用的 Vue 版本号 #
`app.config` | 获得应用实例的配置设定 #
`app.config.errorHandler` | 为应用内抛出的未捕获错误指定一个全局处理函数 #
`app.config.warnHandler` | 为 Vue 的运行时警告指定一个自定义处理函数 #
`app.config.performance` | 在浏览器开发工具中追踪性能表现 #
`app.config.compilerOptions` | 配置运行时编译器的选项 #
`app.config.globalProperties` | 注册全局属性对象 #
`app.config.optionMergeStrategies` | 定义自定义组件选项的合并策略的对象 #

### 全局 API - 通用

:- | :-
--- | ---
`version` | Vue 版本号 #
`nextTick()` | 等待下一次 DOM 更新后执行回调 #
`defineComponent()` | 在定义 Vue 组件时提供类型推导的辅助函数 #
`defineAsyncComponent()` | 定义一个异步组件 #
`defineCustomElement()` | #

### 组合式 API - setup()

:- | :-
--- | ---
`基本使用` | #
`访问 Props` | #
`Setup 上下文` | #
`与渲染函数一起使用` | #

### 组合式 API - 响应式: 工具

:- | :-
--- | ---
`isRef()` | 判断是否为 ref #
`unref()` | 是 ref，返回内部值，否则返回参数本身 #
`toRef()` | 创建一个属性对应的 ref #
`toRefs()` | 将对象上的每一个可枚举属性转换为 ref #
`isProxy()` | 检查一个对象是否是由 `reactive()`、`readonly()`、`shallowReactive()` 或 `shallowReadonly()` 创建的代理 #
`isReactive()` | 检查一个对象是否是由 `reactive()` 或 `shallowReactive()` 创建的代理。 #
`isReadonly()` | 检查传入的值是否为只读对象 #

### 组合式 API - 生命周期钩子

:- | :-
--- | ---
`onMounted()` | 组件挂载完成后执行 #
`onUpdated()` | 状态变更而更新其 DOM 树之后调用 #
`onUnmounted()` | 组件实例被卸载之后调用 #
`onBeforeMount()` | 组件被挂载之前被调用 #
`onBeforeUpdate()` | 状态变更而更新其 DOM 树之前调用 #
`onBeforeUnmount()` | 组件实例被卸载之前调用 #
`onErrorCaptured()` | 捕获了后代组件传递的错误时调用 #
`onRenderTracked()` | 组件渲染过程中追踪到响应式依赖时调用 #
`onRenderTriggered()` | 响应式依赖的变更触发了组件渲染时调用 #
`onActivated()` | 若组件实例是 <KeepAlive> 缓存树的一部分，当组件被插入到 DOM 中时调用 #
`onDeactivated()` | 若组件实例是 <KeepAlive> 缓存树的一部分，当组件从 DOM 中被移除时调用 #
`onServerPrefetch()` | 组件实例在服务器上被渲染之前调用 #

### 组合式 API - 依赖注入

:- | :-
--- | ---
`provide()` | 提供一个可以被后代组件中注入使用的值 #
`inject()` | 注入一个由祖先组件提供的值 #

### 组合式 API - 响应式: 核心

:- | :-
--- | ---
`ref()` | 返回一个 ref 对象 #
`computed ()` | 定义一个计算属性 #
`reactive()` | 返回一个对象的响应式代理 #
`readonly()` | 返回一个原值的只读代理 #
`watchEffect()` | 立即运行一个函数，同时监听 #
`watchPostEffect()` | `watchEffect()` 使用 `flush: 'post'` 选项时的别名。 #
`watchSyncEffect()` | `watchEffect()` 使用 `flush: 'sync'` 选项时的别名。 #
`watch()` | 侦听一个或多个响应式数据源 #

### 选项式 API - 状态选项

:- | :-
--- | ---
`data` | 声明组件初始响应式状态 #
`props` | 声明一个组件的 props #
`computed` | 声明要在组件实例上暴露的计算属性 #
`methods` | 声明要混入到组件实例中的方法 #
`watch` | 声明在数据更改时调用的侦听回调 #
`emits` | 声明由组件触发的自定义事件 #
`expose` | 声明当组件实例被父组件通过模板引用访问时暴露的公共属性 #

### 选项式 API - 生命周期选项

:- | :-
--- | ---
`beforeCreate` | 组件实例初始化完成之后立即调用 #
`created` | 组件实例处理完所有与状态相关的选项后调用 #
`beforeMount` | 组件被挂载之前调用 #
`mounted` | 组件被挂载之后调用 #
`beforeUpdate` | 状态变更而更新其 DOM 树之前调用 #
`updated` | 状态变更而更新其 DOM 树之后调用 #
`beforeUnmount` | 组件实例被卸载之前调用 #
`unmounted` | 组件实例被卸载之后调用 #
`errorCaptured` | 捕获了后代组件传递的错误时调用 #
`renderTracked` Dev only | 组件渲染过程中追踪到响应式依赖时调用 #
`renderTriggered` Dev only | 响应式依赖的变更触发了组件渲染时调用 #
`activated` | 若组件实例是 <keepalive> 缓存树的一部分，当组件被插入到 DOM 中时调用 #</keepalive>
`deactivated` | 若组件实例是 <keepalive> 缓存树的一部分，当组件从 DOM 中被移除时调用 #</keepalive>
`serverPrefetch` SSR only | 组件实例在服务器上被渲染之前调用 #

### 选项式 API - 其他杂项

:- | :-
--- | ---
`name` | 显式声明组件展示时的名称 #
`inheritAttrs` | 是否启用默认的组件 `attribute` 透传行为 #
`components` | 注册对当前组件实例可用的组件 #
`directives` | 注册对当前组件实例可用的指令 #

### 选项式 API - 渲染选项

:- | :-
--- | ---
`template` | 声明组件的字符串模板 #
`render` | 编程式地创建组件虚拟 DOM 树的函数 #
`compilerOptions` | 配置组件模板的运行时编译器选项 #

### 选项式 API - 组件实例

:- | :-
--- | ---
`$data` | 观察的数据对象 #
`$props` | 组件已解析的 props 对象 #
`$el` | 实例管理的 DOM 根节点 #
`$options` | 实例的初始化选项 #
`$parent` | 父实例 #
`$root` | 当前组件树的根实例 #
`$slots` | 访问被插槽分发的内容 #
`$refs` | DOM 元素和组件实例 #
`$attrs` | 包含了组件所有透传 attributes #
`$watch()` | 观察 Vue 实例上的一个表达式或者一个函数计算结果的变化 #
`$emit()` | 触发一个自定义事件 #
`$forceUpdate()` | 强制该组件重新渲染 #
`$nextTick()` | 回调延迟执行 #

### 选项式 API - 组合选项

:- | :-
--- | ---
`provide` | 提供可以被后代组件注入的值 #
`inject` | 注入一个由祖先组件提供的值 #
`mixins` | 接收一个混入对象的数组 #
`extends` | 要继承的“基类”组件 #

### 内置内容 - 指令

:- | :-
--- | ---
`v-text` | 更新元素的 `textContent` #
`v-html` | 更新元素的 `innerHTML` #
`v-show` | 切换元素的 `display` css 属性 #
`v-if` | 有条件地渲染元素 #
`v-else` | #
`v-else-if` | #
`v-for` | 多次渲染元素或模板块 #
`v-on` | 绑定事件监听器 #
`v-bind` | 动态地绑定一个或多个属性 #
`v-model` | 创建双向绑定 #
`v-slot` | 提供插槽或接收 props 的插槽 #
`v-pre` | 跳过元素和它的子元素编译过程 #
`v-once` | 只渲染元素和组件一次 #
`v-memo` (3.2+) | 缓存一个模板的子树 #
`v-cloak` | 保持在元素上直到实例结束编译 #

### 内置内容 - 组件

:- | :-
--- | ---
`<Transition>` | 单个元素/组件的过渡效果 #
`<TransitionGroup>` | 多个元素/组件的过渡效果 #
`<KeepAlive>` | 缓存包裹在其中的动态切换组件 #
`<Teleport>` | 将其插槽内容渲染到 DOM 中的另一个位置 #
`<Suspense>` (Experimental) | 协调对组件树中嵌套的异步依赖的处理 #

### 内置内容 - 特殊 Attributes

:- | :-
--- | ---
`key` | 用在 Vue 的虚拟 DOM 算法 #
`ref` | 元素或子组件注册引用信息 #
`is` | 绑定动态组件 #

### 内置内容 - 特殊元素

:- | :-
--- | ---
`<component>` | 渲染一个“元组件”用于动态组件或元素 #
`<slot>` | 组件模板中的插槽内容出口 #

### 单文件组件 - 语法定义

:- | :-
--- | ---
`总览` | #
`相应语言块` | #
`自动名称推导` | #
`预处理器` | #
`Src 导入` | #
`注释` | #

### 单文件组件 - <script setup>

:- | :-
--- | ---
`基本语法` | #
`响应式` | #
`使用组件` | #
`使用自定义指令` | #
`defineProps() 和 defineEmits()` | #
`defineExpose` | #
`useSlots() 和 useAttrs()` | #
`与普通的 &lt;script&gt; 一起使用` | #
`顶层 await` | #
`针对 TypeScript 的功能` | #
`限制` | #

### 单文件组件 - CSS 功能

:- | :-
--- | ---
`组件作用域 CSS` | #
`CSS Modules` | #
`CSS 中的 v-bind()` | #

### 进阶 API - 渲染函数

:- | :-
--- | ---
`h()` | 创建虚拟 DOM 节点 #
`mergeProps()` | 合并多个 props 对象 #
`cloneVNode()` | 克隆一个 vnode #
`isVNode()` | 判断一个值是否为 vnode 类型 #
`resolveComponent()` | 按名称手动解析已注册的组件 #
`resolveDirective()` | 按名称手动解析已注册的指令 #
`withDirectives()` | 用于给 vnode 增加自定义指令 #
`withModifiers()` | 用于向事件处理函数添加内置 `v-on 修饰符` #

### 进阶 API - 服务端渲染

:- | :-
--- | ---
`renderToString()` | #
`renderToNodeStream()` | #
`pipeToNodeWritable()` | #
`renderToWebStream()` | #
`pipeToWebWritable()` | #
`renderToSimpleStream()` | #
`useSSRContext()` | #

### 进阶 API - TypeScript 工具类型

:- | :-
--- | ---
`PropType<T>` | 在用运行时 props 声明时给一个 prop 标注更复杂的类型定义 #
`ComponentCustomProperties` | 增强组件实例类型以支持自定义全局属性 #
`ComponentCustomOptions` | 扩展组件选项类型以支持自定义选项 #
`ComponentCustomProps` | 扩展全局可用的 TSX props #
`CSSProperties` | 扩展在样式属性绑定上允许的值的类型 #

### 进阶 API - 自定义渲染

:- | :-
--- | ---
`createRenderer()` | 创建一个自定义渲染器 #

## 另见

- Vue 3.x 官方文档
- Vue Router 4.x 官方文档