[TOC]

Vue3新特性

组件

script 标签中使用 setup 方法, 使用 import 进行引入,可直接使用,无需使用 components 方法注册组件

<script setup>
  import Header from './components/Header.vue'
</script>

<template>
  <Header/>
</template>

多个根标签

<template>                                                                   
  <div>根标签1</div>
  <h1>根标签2</h1>
</template>

组合式API

Setup 方法

setup方法是Composition API(组合式API)的入口

语法糖

<script setup></script>

引入或使用方法

Vue3中使用 watchcomputed等方法需要使用import进行导入

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

ref 和 reactive(递归监听,每层数据都使用proxy进行监听)

ref

用来定义基本数据类型
通过Object.definePrototype()getset来实现响应式(数据劫持)
操作数据需要使用.value,读取数据时模板中直接读取不需要.value

<script setup>
  let count = ref(0)
</script>

reactive

用来定义对象或数组类型数据
通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源代码内部的数据
操作数据与读取数据:均不需要.value

<script setup>
  let count = reactive({
    value: 0
  })
</script>

注意:项目中常用reactive进行数据操作

shallowRef 和 shallowReactive(非递归监听)

需要监听的数据量比较大的时候,使用shallowReactiveshallowRef

shallowReactive

只监听第一层(最外层)的数据变化,牵一发而动全身
第一层数据不发生改变,其内部的数据便不会发生改变

<script setup>
import { shallowReactive } from 'vue'
  let count = shallowReactive({
    a: 'a',
    f: {
      b: 'b',
      s: {
        c: 'c'
      }
    }
  })
  function myFn() {
    count.a = '1';
    count.f.b = '2';
    count.f.s.c = '3';

    console.log(state)
    console.log(state.f)
    console.log(state.f.s)
  }
</script>

shallowRef

只监听.value的变化,并不是监听第一层(最外层)的变化

<script setup>
import { shallowRef } from 'vue'
  let count = shallowRef({
    a: 'a',
    f: {
      b: 'b',
      s: {
        c: 'c'
      }
    }
  })
  function myFn() {
    count.value = {
      a: '1',
      f: {
        b: '2',
        s: {
          c: '3'
        }
      }
    }

    console.log(state)
    console.log(state.f)
    console.log(state.f.s)
  }
</script>

triggerRef

根据传入的数据,进行局部更新

<script setup>
import { shallowRef, triggerRef } from 'vue'
  let count = shallowRef({
    a: 'a',
    f: {
      b: 'b',
      s: {
        c: 'c'
      }
    }
  })
  function myFn() {
    count.value.f.s.c = '3'
    triggerRef(count)

    console.log(state)
    console.log(state.f)
    console.log(state.f.s)
  }
</script>

toRaw

避免不必要的更新和追踪,使用toRaw进行修改原数据,不会被追踪和更新UI,优化性能

<script setup>
// reactive
  import {reactive, toRaw} from 'vue'
  let obj = {name: 'zs', age: 18}
  let state = reactive(obj)
  let obj2 = toRaw(state)
  console.log(obj)
  console.log(state)
  console.log(obj2)
</script>

<script setup>
// ref
  import {ref, toRaw} from 'vue'
  let obj = {name: 'zs', age: 18}
  let state = ref(obj)
  let obj2 = toRaw(state.value)
  console.log(obj)
  console.log(state)
  console.log(obj2)
</script>

markRaw

为了让一些数据永远不被更新和追踪

<script setup>
  import {reactive, markRaw} from 'vue'
  let obj = {name: 'zs', age: 18}
  obj.markRaw(obj)
  let state = reactive(obj)
  function myFn() {
    state.name = 'ls'
  }
</script>

Provide/Inject

组件深度传值

<template>
  <Child/>
</template>
<script setup>
  import Child from './Child.vue'
  import { provide, ref } from 'vue'
  let count = ref(0)
  provide('count', count)
</script>
<template>
  <h2>{{count}}</h2>
</template>
<script setup>
  import { inject } from 'vue'
  let count = inject('count')
</script>

安装配置vue扩展

vue-router

# npm 安装
npm install vue-router@4

# 在 src 目录下新建 router 和 views 目录

# 在 router 目录下新建 index.js 文件,并进行配置
```js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('@/views/props.vue')  // .vue不能省略
    }
  ]
})
export default router

在 main.js 中调用

import { createApp } 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router).mount('#app')

在app.vue 中使用 进行使用即可

<a name="TRADP"></a>
### vuex
```markdown
# npm 安装
npm install vuex@next --save

# 在 src 目录下新建 store 目录

# 在 store 目录下新建 index.js 文件,并进行配置
```js
import { createStore } from 'vuex'
const store = createStore({
  state: {
    test: 'test'
  },
  mutations: {},
  actions: {},
  getters: {}
})
export default store

在 main.js 中调用

import { createApp } 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store).mount('#app')

在任意组件中调用,查询结果是否正确

<template>
  <h1>{{$store.state.test}}</h1>
</template>

<script>
  export default {}
</script>

<style>
</style>
<a name="rNzxR"></a>
### Scss&Sass
```markdown
# npm 安装
npm i node-sass sass-loader style-loader --save

# 在 package.json 文件中配置

Vue3响应式原理

  • 通过Proxy(代理):拦截对象中任意属性的变化, 包括属性的读写属性的添加属性的删除
  • 通过Reflect(反射):对源对象的属性进行操作

    const p = new Proxy(data, {
    // 读取属性时调用
    get(target, propName) {
      return Reflect.get(target, propName)
    },
    // 修改或添加属性时调用
    set(target, propName, value) {
      return Reflect.set(target, propName, value)
    },
    // 删除属性时调用
    deleteProperty(target, propName) {
      return Reflect.deleteProperty(target, propName)
    }
    })
    

    Vue3配置路径别名

  • 在项目根目录下创建vite.config.js文件,有则无需创建

    // 引入 path 模块
    import * as path from 'path'
    // 配置别名
    export default defineConfig({
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src')
      }
    }
    })
    

    vue3.js 安装

    CDN 安装

    <script src="https://unpkg.com/vue@next"></script>
    

    NPM 安装

    npm install vue@next
    
  • 如果需要使用单文件组件,需安装@vue/compiler-sfc

    npm install @vue/compiler-sfc
    

    如果你是从 Vue 2 过渡而来的,请注意 @vue/compiler-sfc 替换掉了 vue-template-compiler

Vue3 渲染模板

  • 下面模板中的 num 为一个变量

    <div id="counter">
    <h1>counter: {{ num }}</h1>
    </div>
    
  • Counter 为一个对象,Vue.createApp(Counter) 创建了一个应用并将 Counter 对象注册到该应用中,.mount("#counter") 是获取到 id 值为 counterhtml 元素,并将 Counter 对象中的 num 属性渲染到 html 模板中

    <script>
      const Counter = {
        data() {
          return {
            num: 0
          }
        }
      }
     Vue.createApp(Counter).mount("#counter")
    </script>
    
  • 结果:counter: 0

    使用 Vite 构建工具创建 Vue3 项目

    方法1:

    ```bash $ npm init vite-app

$ cd $ npm install & cnpm install $ npm run dev

<a name="HePoV"></a>
## 方法2:
```bash
# npm 6.x
$ npm init vite@latest <project-name> --template vue

# npm 7+, extra double-dash is needed:
$ npm init vite@latest <project-name> -- --template vue

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

方法3:

$ npm init @vitejs/app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev

Vue3生命周期 & 实例生命周期

  • Vue3 生命周期图

Vue3.x - 图1

不在setup函数内的写法

  • beforeCreate():数据初始化之前执行的方法
  • created():数据初始化之后执行的方法
  • beforeMount():数据挂载渲染之前执行的函数
  • mounted():数据挂载渲染之后执行的函数
  • beforeUpdate():数据更新之前执行的函数
  • updated():数据更新之后执行的函数
  • beforeUnmount():实例销毁之前执行的函数
  • unmounted():实例销毁之后执行的函数

    setup函数内的写法:

  • beforeCreate ===> Not needed不需要写

  • created ===> Not needed不需要写
  • beforeMount ===> onBeforeMount
  • mounted ===> onMounted
  • beforeUpdate ===> onBerforeUpdate
  • updated ===> onUpdated
  • beforeUnmount ===> onBerforeUnmount
  • unmounted===> onUnmounted

    在Vue3中,beforeCreatecreated并没有组合式API,setup就相当于这两个生命周期函数

基础语法

组件基础

通过插槽分发内容

// 父组件模板内容
<template>
    <div>
      <!-- 子组件 -->
    <alert-box>
          Something bad happened.
      </alert-box>
  </div>
</template>

<script setup>
    import alertBox from '子组件路径'
</script>
// 子组件模板内容
<template>
    <div>
    <strong>Error!</strong>
    <!-- 通过插槽标签来接收父组件中子组件标签内的内容 -->
    <slot></slot>
  </div>
</template>

解析 DOM 模板时注意的事项

元素位置受限

  • 一些 HTML 元素内部是有严格限制的,意思是这些元素内部只能出现特定的元素
  • 下面模板内的自定义组件会被作为无效的内容提升到外部,并导致最终渲染结果出错

    <template>
    <div>
    <table>
      <!-- blog-post-row 为自定义组件 -->
        <blog-post-row></blog-post-row>
    </table>
    </div>
    </template>
    
  • 可以通过 **is** attribute 方法,作为一个变通的办法:

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

    **is** 用于原生 HTML 元素时,**is** 的值必须以 **vue:** 开头,才可以被解释为 Vue 组件 这是为了避免和原生自定义元素混淆

大小写不敏感

  • HTML arribute 名不区分大小写,因此浏览器将所有大写字符解释为小写。
  • 这意味着当你在 DOM 模板中使用 **驼峰 prop** 名称和 **event 处理器参数** 需要使用它们的 kebab-cased (横线字符分割) 等效值: ```vue // 方法1:

方法2:

``` ```vue ``` # 组件注册 > 组件注册注意点:1、全部小写 2、包含连字符(有多个单词与连字符符号连接) ## 组件注册方法 - 组件注册方法1:注册在字符串模板 ```javascript const app = Vue.createApp({...}) app.component('my-component-name', { /* ... */ }) ``` - 组件注册方法2:单文件组件(使用 `.vue` 结尾的文件) ```vue // JavaScript模板 // HTML 模板 // CSS 模板 ``` ## 组件大小写方法 - 方法1:kebab-case(短横线隔开) ```javascript app.component('my-component-name', { /* ... */ }) ``` 当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 `` - 方法2:PascalCase(驼峰命名法、帕斯卡命名法) ```javascript app.component('MyComponentName', { /* ... */ }) ``` 当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 `` 和 `` 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的 ## 全局注册 - 以下模板中的组件是**全局注册**的,它们注册之后可以用在任何新创建的组件实例的模板中: ```javascript const app = Vue.createApp({}) app.component('component-a', { /* ... */ }) app.component('component-b', { /* ... */ }) app.component('component-c', { /* ... */ }) app.mount('#app') ``` ```html
``` > 在所有子组件中也是如此,这三个组件内部之间也可以互相使用 ## 局部组件 - 注册局部组件:通过一个普通的 JavaScript 对象来定义组件: ```javascript const ComponentA = { /* ... */ } const ComponentB = { /* ... */ } const app = Vue.createApp({ components: { 'component-a': ComponentA, 'component-b': ComponentB } }) ``` > 注册**局部注册的组件在其子组件中不可用** - 如果你希望 `ComponentA` 在 `ComponentB` 中可用,则需要通过以下这种写法: ```javascript const ComponentA = { /* ... */ } const ComponentB = { components: { 'component-a': ComponentA } // ... } ``` - 或者通过 Babel 和 webpack 使用 ES2015 模块化的方式: ```javascript import ComponentA from './ComponentA.vue' export default { conponents; { ComponentA } } ``` > 在 ES2015+ 中,在对象中放一个类似 `ComponentA` 的变量名其实是 `ComponentA: ComponentA` 的缩写,即这个变量名同时是:1、用在模板中的自定义元素的名称 2、包含了这个组件选项的变量名 ## 在模块中注册局部组件 - 在局部注册之前导入每个你想使用的组件,可以在 `.js` 或者 `.vue` 结尾的文件中这样写: ```javascript import ComponentA from './ComponentA' import ComponentC from './ComponentC' export default { components: { ComponentA, ComponentC } // ... } ``` > 现在 `ComponentA` 和 `ComponentB` 都可以在当前模板文件中使用了 # Mixin - 将mixin定义的代码和组件代码进行混用 ## 基本使用 ```javascript export const myMixin = { data() { return { msg: 'hello', foo: '123' } } } ``` ```vue ``` > 注意:若是`mixin.js`中的变量或方法与 `App.vue`中的变量或方法冲突时,`App.vue`中的变量或方法会覆盖`mixin.js`中的变量和方法 # 自定义指令 ## 全局 ```javascript // directives/demo.js function demo(el, binding) { console.log(binding.value.color); // white console.log(binding.value.text); // small_fish } export default { mounted(el, binding) { demo(el, binding) }, } // directives/index.js import demo from './demo' export default app => { app.directive('demo', demo) } // main.js import { createApp } from 'vue' import App from './App.vue' import directive from './directives/index.js' const app = createApp(App) directive(app) app.mount('#app') // 任意.vue组件 ```template ``` ``` ## 局部 ```vue // 任意.vue组件内 <a name="wg2S2"></a> ## 可用的钩子函数 - `created`:绑定元素的属性或i事件监听器被应用之前调用 - `beforeMount`:当指令第一次绑定到元素并且挂在父组件之前调用 - `mounted`:在绑定元素的父组件被挂载后调用 - `beforeUpdate`:在更新包含组件的 VNode 之前调用 - `updated`:在包含组件的 VNode **及其子组件的 VNode** 更新后调用 - `beforeUnmount`:在卸载(销毁)绑定元素的父组件之前调用 - `unmounted`:当指令与元素解除绑定且父组件已经卸载(销毁)时,只调用一次 <a name="G1uEJ"></a> ## 注意点 - 如果想在`mounted`和`updated`时触发相同的行为,而不关心其他的钩子函数,可以去掉这两个钩子函数 <a name="dQAr6"></a> # Teleport - 传送门:将`teleport`标签内的内容传送到指定的元素内部进行渲染 <a name="hhYjP"></a> ## teleport实现传送vue

```vue
<script setup>
</script>

<template>
  <h2>This is a parent component</h2>
  <teleport to='body'> // 将该标签中的内容传送至body标签下
    <h2>传送至body标签下</h2>
  </teleport>
</template>

<style lang="">

</style>

注意:<teleport to="">中的to属性表示传送的位置,to的值是传送至标签的类名(标签名或id)

在同一传送目标上使用多个 teleport

<teleport to="body">
  <div>teleport1</div>
</teleport>
<teleport to="body">
  <div>teleport2</div>
</teleport>

<!-- result -->
<body>
  <div>teleport1</div>
  <div>teleport2</div>
</body>

渲染函数(render)