Skip to content

官网:Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)

V3

V3 工程可以通过 vue-cli 脚手架进行创建,但是现在流行用 create-vue 脚手架进行创建 V3 工程

create-vue:这个脚手架是基于 vite 项目构建工具来实现的(vue-cli 是通过 webpack 项目构建工具来实现的)

vite 的特点,一个字 快,服务器启动快,修改代码之后重新响应也很快(vite 也是尤雨溪团队开发的)

要使用 vue-cli 创建 V3 工程,那么 vue-cli 的版本要求最低 4.5.0,可以使用 vue --version 查看版本,如果版本较低可以通过 npm install -g @vue-cli 来进行升级

Vite 脚手架官网:Vite中文网 (vitejs.cn)

创建工程

官方指导:快速上手 | Vue.js (vuejs.org)

在工程目录下使用创建命令:npm create vue@latest(安装vue-create脚手架并创建V3工程,如果脚手架存在的话不会重新安装)

vite 与 webpack 创建的工程有何不同

入口:index 文件放在了 public 文件夹外面,以 index.html 作为入口,不在以 main.js 作为入口

配置文件:对于 vite 构建工具来说,配置文件是 vite.config.js 他与 webpack 中的 vue.config.js 类似,配置文档 配置 Vite | Vite 官方中文文档 (vitejs.dev)

setup

setup是一个函数,V3中新增的配置项,组件中所用到的data、methods、钩子等都要配置到 setup 中

vue
<template>
  <h1>{{ name }}</h1>
  <button @click="getName">输出名字</button>
</template>

<script>
export default {
  name: 'App',
  setup() {
    // 定义的变量相当于 data
    let name = '123'
    // 定义的函数相当于 methods
    function getName() { alert(name) }
    // 想要使用对应的数据必须进行返回 以 K:V 的方式,如果返回名称一致直接使用变量名也行
    return {name, getName}
  }
}
</script>

语法糖

script 标签中加入 setup 那么下面代码就不需要写了。组件导入之后不需要注册直接用

javascript
export default {
  name: "User",
  components: {xxx},
  setup(props, context) {
    return {xxx}
  }
}

props

父组件

vue
<template>
  <User :age="user.age" :name="user.name"/>
</template>

<script>
import User from "@/components/User.vue";

export default {
  name: 'App',
  // 导入组件
  components: {User},
  setup() {
    let user = {
      name: '张三',
      age: 18,
    }

    return {user}
  }
}
</script>

子组件

vue
<template>
  <h1>{{ user.name }} - {{ user.age }}</h1>
</template>

<script>
export default {
  name: "User",
  // 需要声明 props
  props: ["name", "age"],
  setup(props) {
    return {user: props}
  }
}
</script>

V3在调用 setup 函数之前会传递参数,第一个是 props。它被包装成了一个代理对象,具有响应式的 proxy 对象

生命周期

跟V2的声明周期作比较

  • 生命周期最后两个钩子由原来的 beforeDestorydestoryed 变为 beforeUnmountunmounted
  • setup 函数执行在 beforeCreate 之前

在V3中生命周期钩子函数由两种写法:选项式、组合式

选项式API:在 setup 外面写生命周期钩子(V2的写法)

组合式API:在 setup 中写生命周期钩子,并且缺少 beforeCreatebeforeed 因为 setup 代替了(如果想使用这两个钩子的话,只能采用选项式的写法)

javascript
// 从这里导入对应的钩子
import {onBeforeMount} from "vue";

export default {
  // ...
  setup() {
    onBeforeMount(() => {
      // ...
    });
    // ...
  }
}

自定义事件

父组件

vue
<template>
  <!-- 创建自定义事件 -->
  <User @event="log"/>
</template>

<script>
import User from "@/components/User.vue";

export default {
  name: 'App',
  components: {User},
  setup() {
    // 事件触发时输出内容
    function log(name, sex) {
      alert(`姓名:${name},性别:${sex}`)
    }

    return {log}
  }
}
</script>

子组件

vue
<template>
  <button @click="trigger">点击触发自定义事件</button>
</template>

<script>
export default {
  name: "User",
  setup(props, context) {
    function trigger() {
      // 触发自定义事件 并传参
      context.emit("event", "张三", "男")
    }

    return {trigger}
  }
}
</script>

setup 的第二个参数是组件的上下文对象,然后使用 emit 触发事件,第一个是事件的名称,往后所有都是传递的参数

全局事件总线

V3的全局事件总线需要借助第三方库 mitt

安装

安装:npm i mitt

然后封装一个 src/util/event-bus.js 的文件

javascript
import mitt from "mitt";
export default mitt();

使用

父组件

vue
<template>
  <User/>
</template>

<script>
import User from "@/components/User.vue";
import {onMounted} from "vue";
// 导入全局事件总线对象
import emitter from './util/event-bus'

export default {
  name: 'App',
  components: {User},
  setup() {
    // 生命周期钩子 mounted
    onMounted(() => {
      // 给全局事件总线绑定事件 | 绑定事件 - 触发的函数
      emitter.on("event", (user) => {
        console.log(user.name, user.sex)
      })
    })
  }
}
</script>

子组件

vue
<template>
  <button @click="trigger">点击触发自定义事件</button>
</template>

<script>
import emitter from '../util/event-bus'

export default {
  name: "User",
  setup(props, context) {
    function trigger() {
      // 触发自定义事件 并传参
      emitter.emit("event", {
        name: "张三",
        sex: "男"
      })
    }

    return {trigger}
  }
}
</script>

在以上的代码中通过全局事件总线实现的事件的绑定与触发。但是,与自定义事件不同的是,全局事件总线传递的参数只有一个,并且也不需要在组件上进行 @事件名

在使用是需要导入总线对象

javascript
import emitter from './util/event-bus'

绑定事件:emitter.on("事件名称", 回调函数)

触发事件:emitter.emit("事件名称", 参数)

解绑事件(全部):emitter.all.clear()

解绑事件(指定):emitter.off("事件名")

计算属性

在V3中计算属性是组合式写法,也就是得写在 setup 中

javascript
import {computed} from "vue";

export default {
  name: 'App',
  setup() {
    // 简写
    let com = computed(() => {
		return xxx;
    })

    // 完整写法
    let com = computed({
      set(value) { ... },
      get() { return xxx }
    })
    
    // 返回
    return {com}
  }
}

简写:如果只需要使用计算属性的 get 方法的情况下,就可以使用简写

完整:这种写法是包含 get/set 的方法的适合在需要直接改变计算属性的情况下使用

侦听属性

被监视的属性必须具有响应式才能够监听

侦听 ref

vue
<template>
  <input type="text" v-model="name"/>
</template>

<script>
import User from "@/components/User.vue";
import {ref, watch} from "vue";

export default {
  name: 'App',
  components: {User},
  setup() {
    let name = ref("张三")
    // 参数:被侦听的属性 回调函数 配置信息
    watch(name, (newValue, oldValue) => {
      console.log(`老数据:${oldValue} -> 新数据:${newValue}`)
    }, {
      // 立即执行
      immediate: true,
      // 深度监听
      deep: true
    })
    return {name}
  }
}
</script>

在上面的 watch 写法中,如果由多个要被侦听的属性就需要写多个 watch 但是,如果某些属性执行的逻辑是相同的,可以采用数组的形式进行简写

javascript
watch([name, age], (newValue, oldValue) => {
  console.log(`老数据:${oldValue} -> 新数据:${newValue}`)
}, {
  // 立即执行
  immediate: true,
  // 深度监听
  deep: true
})

因为采用的是数组,那么新数据和旧数据打印的也是数组,需要获取某个属性的新老数据时,采用数组的形式获取

当 ref 包裹的是对象的时候,监视属性的位置如果填写:xxx.value ,就相当于下面的 reactive 深度监视是开启的,但是如果直接填写 xxx 那就可以控制深度监视(默认关闭),想要监视单一属性就需要使用函数进行返回

javascript
// 默认不开启深度监视
watch(user, (newValue, oldValue) => {
  console.log(`老数据:${oldValue.name} -> 新数据:${newValue}`)
}, {deep: true})

watch(user.value, (newValue, oldValue) => {
  console.log(`老数据:${oldValue.name} -> 新数据:${newValue}`)
})

watch(() => user.value.name, (newValue, oldValue) => {
  console.log(`老数据:${oldValue.name} -> 新数据:${newValue}`)
})

侦听 reactive

javascript
let user = reactive({
  name: "张三",
  age: 18
})

watch(user, (newValue, oldValue) => {
  console.log(oldValue, newValue)
})

在侦听 reactive 函数获取的代理对象时,只能获取最新数据(newValue)老数据(oldValue)无法获取。并且对于代理对象来说,默认开启深度监视,而且这种深度监视是不可关闭的

如果想要进行单一的属性,不能直接 对象.属性 这种事错误的,应该采用一个函数把需要侦听的属性进行返回

javascript
watch(() => user.name, (newValue, oldValue) => {
  console.log(`老数据:${oldValue} -> 新数据:${newValue}`)
})

但是对于 对象.属性 是错误的也不是很对,在有一种场景下可以使用这种方式,那就是这个属性也是一个对象

javascript
let user = reactive({
  name: "张三",
  age: 18,
  xxx: {
  	a: 1,
  	b: 2
  }
})

在以上的情况下是可以直接 user.xxx 进行监听的,但是这种方式的老数据也是不能拿到的

在 reactive 中如果逻辑相同的话也是可以通过数组的形式来进行减少代码的,在 [] 中也是需要写函数的形式

watchEffect

这是 V3 新增的组合式API,作用就是返回监控数据变化。用法在里面跟上回调函数

javascript
setup() {
  let user = reactive({
    name: "张三",
    age: 18,
  })

  watchEffect(() => {
    const n = user.name
  })

  return {user}
}

回调函数的执行时机,按照上面的写法,每当 user.name 发生变化时,就会执行回调函数,如果有多个也是同理。但是 age 的变化不会让回调函数执行

自定义钩子函数(hook)

这个自定义钩子与V2中的 mixin 有点相同都是为了让代码得到复用

使用:在 src 目录下创建一个 hooks 的目录,然后在其中创建 js 文件(文件名一般是功能名)

javascript
// 这是一个简单的求和函数
export default function (a, b) {
    return a + b;
}

在使用的位置进行导入即可正常使用

javascript
import sum from "@/hooks/sum.js";

Fragment 组件

Fragment翻译:碎片、片段

在V2中每个组件必须有一个根标签,在组件嵌套组件的时候性能问题,因为有些时候没必要去嵌套这些根标签。然后再V3中没有根标签或者说有多个根标签,实际上再所有标签嵌套完成后会再最外层添加一个 Fragment 用这个当根标签

Teleport 组件

Teleport翻译:远距离传送、瞬间移动。用于设置组件的显示位置

在组件套组件中如果想要对最下层的某一个盒子做定位,那么定位是要有参照物的默认时body,但是如果某一层的组件添加上的某个属性作为了参照物,那么就会导致效果混乱,而 Teleport 组件就是把搬来应该再最下层的盒子移动到指定的位置,比如body的子盒子

vue
<template>
  <Teleport to="[标签名|ID选择器]">
    <User />
  </Teleport>
</template>

provide & inject 传递数据

在这种数据传递方式中,适合在祖宗组件对孙子组件传递数据的场景

javascript
// 先声明
provide("名称", 数据);

// 取值
let aaa = inject("名称");

自定义 ref

javascript
import {customRef} from "vue";

export default {
  name: 'App',
  setup() {
    // 自定义 ref
    function customizeRef(value) {
      // 返回需要是一个响应式的 ref
      return customRef((track, trigger) => {
        // 自定义 ref 的 get 和 set 并返回
        return {
          get() {
            // 追踪依赖
            track()
            return value;
          },
          set(newValue) {
            value = newValue
            // 触发依赖
            trigger()
          }
        }
      })
    }

    let hello = customizeRef("你好,世界");

    return {hello}
  }
}

通过以上的方式进行自定义一个 ref 这样做的目的是在输入和返回数据的时候进行夹带点私货