Vue 3 学习笔记

Vue 基础

Vue 中应用和组件的概念

  • createApp 表示创建一个 Vue 应用, 存储到 app 变量中
  • 传入的参数表示,这个应用最外层的组件,应该如何展示
  • MVVM 设计模式:M -> Model 数据, V -> View 视图, VM -> ViewModel 视图数据连接层
DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>lesson 5title>
  <script src="https://unpkg.com/vue@next">script>
head>
<body>
  <div id="root">div>
body>
<script>
  const app = Vue.createApp({
    data() {
      return {
        message: 'hello world'
      }
    },
    template: "
{{message}}
"
}); // 注册组件 app.component('cmp', { props: ['title'], template: '{{ title }}' }); // vm 代表的就是 Vue 应用的根组件 const vm = app.mount('#root');
script> html>

Vue 生命周期与生命周期函数

生命周期函数:某一时刻会自动执行的函数

  1. beforeCrate:在实例生成之前会自动执行的函数
  2. created:在实例生成之后会自动执行的函数
  3. beforeMount:在组件内容被渲染到页面之前自动执行的函数
  4. mounted:在组件内容被渲染到页面之后自动执行的函数
  5. beforeUpdate:当数据发生变化时会自动执行的函数
  6. updated:当数据发生变化,页面重新渲染后,会自动执行的函数
  7. beforeUnmount:当 Vue 应用失效时,自动执行的函数
  8. unmounted:当 Vue 应用失效时,且 dom 完全销毁后,自动执行的函数

常用模板语法

  • {{}}:插值表达式

  • v-html:将数据作为 HTML 代码进行渲染

  • v-bind(:):数据绑定

  • v-on(@):事件绑定

    • :[propertyName]@[eventName]:动态属性/事件,属性/事件名从 data 中获取(见源码)
    • 修饰符:控制事件触发方式,例:@click.prevent:阻止自带的事件(如源码,点击事件后会跳转到指定 url,使用 prevent 修饰符消除此默认操作)
  • v-slot(#):具名

  • v-once:仅在第一次使用 data 中的数据(后续使用的属性值仍会改变,但节点仅渲染一次)

  • v-ifv-if-elsev-else:分支逻辑,仅在条件符合的情况下渲染节点

  • v-show:控制节点显示

    • v-if / v-show 区别:v-if 不显示时会将节点直接移除,v-show 不显示则仅是将节点的 display 属性设置为 none,若需要频繁控制节点显示隐藏,使用 v-show 对性能会更加友好
DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>lesson 7title>
  <script src="https://unpkg.com/vue@next">script>
head>
<body>
  <div id="root">
      <form v-if='show' action="https://www.baidu.com" @click.prevent="handleClick">
        <button type="submit">提交button>
      form>
      
      <span v-else :[bindName]="message">
            {{ message }}
      span>
  div>
body>
<script>
  const app = Vue.createApp({
    data() {
      return {
        message: "hello world",
        show: false,
        bindName: 'title'
      }
    },
    methods: {
      handleClick() {
        alert('click')
      }
    },
    template: ``
  });
  const vm = app.mount('#root');
script>
html>
  • v-for:列表循环渲染
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 11title>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    const app = Vue.createApp({
        data() {
            return {
                listArray: ['dell', 'lee', 'teacher'],
                listObject: {
                    firstName: 'dell',
                    lastName: 'lee',
                    job: 'teacher'
                }
            }
        },
        methods: {
            handleAddBtnClick() {
                // 1. 使用数组的变更函数 push, pop, shift, unshift, splice, sort, reverse
                // this.listArray.push('hello');
                // this.listArray.pop();
                // this.listArray.shift();
                // this.listArray.unshift('hello');
                // this.listArray.reverse();

                // 2. 直接替换数组
                // this.listArray = ['bye', 'world']
                // this.listArray = ['bye', 'wolrd'].filter(item => item === 'bye');

                // 3. 直接更新数组的内容
                // this.listArray[1] = 'hello'

                // 直接添加对象的内容,也可以自动的展示出来
                // this.listObject.age = 100;
                // this.listObject.sex = 'male';
            }
        },
        template: `
          
{{ item }}
`
}); const vm = app.mount('#root');
script> html>
  • v-forv-if 存在于同一个节点时,v-for 拥有更高的优先级,此时 v-if 失效

数据,方法,计算属性和侦听器

  • computedmethod 都能实现的一个功能,建议使用 computed,因为有缓存
  • computedwatched 都能实现的功能,建议使用 computed 因为更加简洁
DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>lesson 8title>
  <script src="https://unpkg.com/vue@next">script>
head>
<body>
  <div id="root">div>
body>
<script>
  // data & methods & computed & watcher
  const app = Vue.createApp({
    data() {
      return {
        message: "hello world",
        count: 2,
        price: 5,
        newTotal: 10,
      }
    },
    watch: {
      // price 发生变化时,函数会执行
      price(current, prev) {
        this.newTotal = current * this.count;
      }
    },
    computed: {
      // 当计算属性依赖的内容发生变更时,才会重新执行计算
      total() {
        return Date.now() + this.count;
        // return this.count * this.price
      }
    },
    methods: {
      formatString(string) {
        return string.toUpperCase();
      },
      // 只要页面重新渲染,才会重新计算
      getTotal() {
        return Date.now();
        // return this.count * this.price;
      },
    },
    template: `
     
{{message}} {{newTotal}}
`
}); const vm = app.mount('#root');
script> html>

样式绑定语法

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 9title>
    <style>
        .red {
            color: red;
        }

        .green {
            color: green;
        }
    style>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    const app = Vue.createApp({
        data() {
            return {
                // 直接传入字符串进行样式类配置
                classString: 'red',
                // 通过对象控制样式类是否生效
                classObject: {red: false, green: true},
                // 通过数组配置样式类,可接受对象形式元素
                classArray: ['red', 'green', {brown: false}],
                // 字符串形式配置内联样式
                styleString: 'color: yellow;background: orange',
                // 对象形式配置内联样式
                styleObject: {
                    color: 'orange',
                    background: 'yellow'
                }
            }
        },
        template: `
          
Hello World
`
}); app.component('demo', { // $attr.class:获取组件的指定(class)属性 template: `
one
two
`
}) const vm = app.mount('#root');
script> html>

事件绑定

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 12title>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    // event, $event
    // 事件修饰符:
          // stop(阻止事件冒泡)
          // prevent(阻止默认事件)
          // capture
          // self(仅自身触发,不适用于子节点)
          // once(仅生效一次)
          // passive
    // 按键修饰符(@keydown):enter, tab, delete, esc, up, down, left, right
    // 鼠标修饰符:left, right, middle
    // 精确修饰符:exact
    const app = Vue.createApp({
        methods: {
            // 若方法未传入参数,则第一位为原生事件对象
            handleClick(event) {
            // 方法有参数时接收原生事件对象
            handleClick(val, event) {
                console.log('click')
            },
            handleClick1(val, event) {
                console.log('click')
            },
        },
        template: `
          
123
">123
` // 方法存在自定义参数情况下传入原生事件对象 //
// 要一次性执行多个方法,可直接将方法调用传入并以 ‘,’ 隔开,不支持传入方法本身 //
}); const vm = app.mount('#root'); script> html>

双向绑定

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 13title>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    // input, textarea, checkbox, radio, select
    // 修饰符 lazy(控件失焦时更新), number(转化输入数据类型), trim(去除头尾空格)
    const app = Vue.createApp({
        data() {
            return {
                message: 'hello',
            }
        },
        template: `
          
{{ message }}
`
}); const vm = app.mount('#root');
script> html>

Vue 组件

概念定义

组件的定义:

  • 组件具备复用性
  • 全局组件,只要定义了,处处可以使用,性能不高,但是使用起来简单,名字建议 小写字母单词,中间用横线间隔
  • 局部组件,定义了,要注册之后才能使用,性能比较高,使用起来有些麻烦,建议大些字母开头,驼峰命名
  • 局部组件使用时,要做一个名字和组件间的映射对象,你不写映射,Vue 底层也会自动尝试帮你做映射
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 14title>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    const Counter = {
        data() {
            return {
                count: 1
            }
        },
        template: `
          
{{ count }}
`
} const HelloWorld = { template: `
hello world
`
} const app = Vue.createApp({ components: { // counter: Counter, // 'hello-world': HelloWorld, Counter, HelloWorld, }, template: `
`
}); // 通过 component 创建的组件可全局使用,但会永久占用空间 app.component('counter-parent', { template: `` }) app.component('counter', { data() { return { count: 1 } }, template: `
{{count}}
`
}) const vm = app.mount('#root');
script> html>

组件间传值及传值校验

  • 使用 provideinject 进行多层组件传值,祖组件使用 provide 向后代组件提供数据,后代组件使用 inject 从祖组件注入数据。provideinject 绑定并不是可响应的,除非传递一个数据对象(例如组件自身对象)。
  • 若想要给后代组件传递自身可变属性,可传递一个方法,后代组件接收到并运行即可获取当前数据。
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 15title>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    const app = Vue.createApp({
        data() {
            return {num: 1234}
        },
        // 第一种
        provide(){
          return{
            foo:'halo'
          }
        },
        // 第二种
        provide:{
          foo:'halo~~~~',
          // 返回自身的属性
          num: () => this.num
        },
        template: `
          
`
}); // type(数据类型):String, Boolean, Array, Object, Function, Symbol // required 必填 // default 默认值 app.component('test', { props: { content: { type: Number, // 对从父组件接受的属性值进行校验 validator: function (value) { return value < 1000; }, default: function () { return 456; } } }, // 从祖节点注入 inject:['foo'], template: `
{{ content }}
`
}); const vm = app.mount('#root');
script> html>

单向数据流的理解

  • v-bind="params" 等价于 :content="params.content" :a="params.a" :b="params.b" :c="params.c"
  • 属性传的时候,使用 content-abc 这种命名,接的时候,使用 contentAbc 命名
  • 单项数据流的概念: 子组件可以使用父组件传递过来的数据,但是绝对不能修改传递过来的数据

Non-props

  1. 若父组件向子组件传值时子组件并没有接收(使用 props),则 Vue 会将父组件传递的内容作为属性放置在子组件最外层的 dom 节点上。
  2. 要想规避 “1” 中规则,可使用 inheritAttrs: false 特性,阻止子组件继承父组件传递的属性。
  3. this.$attrs:父组件传递的属性对象。
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 17title>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    const app = Vue.createApp({
        template: `
      
`
}); app.component('counter', { // inheritAttrs: false, mounted() { console.log(this.$attrs.msg); }, template: `
Counter
Counter
Counter
`
}); const vm = app.mount('#root');
script> html>

组件间事件通信

  • this.$emit():触发父组件事件,由父组件调用子组件时设置监听器。
  • $emit() 中可传值,传递的值会自动传入父组件监听器对应的方法中。
  • $emit() 可实现类似 v-modal 的双向绑定效果,当使用默认 v-modal 时,子组件内接收的属性名必须为 “modalValue",此时 $emit 触发的事件为 "update:modalValue",传递过去的值也会赋值给父组件中 v-modal 中对应的属性。若想自定义属性名,可在父组件中使用 v-modal:[属性名] 的形式监听对应变量的变化。
  • 使用 modeModifiers 定义属性修改器。
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 18title>
    <script src="https://unpkg.com/vue@next">script>
head>
<body>
<div id="root">div>
body>
<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        template: `
          
        `
    });

    app.component('counter', {
        props: {
          'modelValue': String,
          'modeModifiers': {
            tostring: (val) => val.toString();
          }
        },
        methods: {
            handleClick() {
                this.$emit('update:modelValue', this.modelValue + 3);
            }
        },
        template: `
          
{{ modelValue }}
`
}); const vm = app.mount('#root');
script> html>

使用插槽、具名插槽和作用域插槽解决组件内容传递问题

  1. slot 中使用的数据,作用域的问题
    • 父模版里调用的数据属性,使用的都是父模版里的数据
    • 子模版里调用的数据属性,使用的都是子模版里的数据
  2. 可直接在子组件的 slot 标签内定义默认节点,当父组件未传入插槽内容时,默认显示该节点内容。
  3. 具名插槽与作用域插槽
    • 子组件中用 name 属性来表示插槽的名字,不传为默认插槽,父组件传入时指定插槽名字可使节点内容显示在指定区域。
    • 在 vue2.6 中,父组件中插槽的 “slot” 写法被软废弃(3.0正式废弃),取而代之的是内置指令 v-slot,可以缩写为【#】,子组件中用法不变
    • 默认插槽名为 default,可以省略 default 直接写 v-slot
      缩写为 # 时不能不写参数,写成 #default(这点所有指令都一样,v-bindv-on
    • 多个插槽混用时,v-slot不能省略default
    • v-slot 属性只能在