Vue 3 学习笔记

6 篇文章 0 订阅
3 篇文章 0 订阅

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 5</title>
  <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: "<div>{{message}}</div>"
  });
  // 注册组件
  app.component('cmp', {
    props: ['title'],
    template: '<span>{{ title }}</span>'
  });
  // 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 7</title>
  <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 11</title>
    <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: `
          <div>
          <!-- 使用 key 属性区分新旧数据,避免列表发生变化时旧数据重复渲染影响性能 -->
          <template
              v-for="(value, key, index) in listObject"
              :key="index">
            <div v-if="key !== 'lastName'">
              {{ value }} -- {{ key }}
            </div>
          </template>
          <div v-for="item in 10">{{ item }}</div>
          <button @click="handleAddBtnClick">新增</button>
          </div>
        `
    });

    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 8</title>
  <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: `
     <div> {{message}} {{newTotal}} </div>
    `
  });
  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 9</title>
    <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: `
          <div :style="styleObject">
          Hello World
          </div>
        `
    });

    app.component('demo', {
        // $attr.class:获取组件的指定(class)属性
        template: `
      <div :class="$attrs.class">one</div>
      <div :class="$attrs.class">two</div>
    `
    })

    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 12</title>
    <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: `
          <div>
          <div @click.ctrl.exact="handleClick">123</div>">123</div>
          </div>
        `
          // 方法存在自定义参数情况下传入原生事件对象
          // <div @click.ctrl.exact="handleClick(val, $event)
          // 要一次性执行多个方法,可直接将方法调用传入并以 ‘,’ 隔开,不支持传入方法本身
          // <div @click.ctrl.exact="handleClick(val, $event), handleClick1(val, $event)"></div>
    });

    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 13</title>
    <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: `
          <div>
          {{ message }}
          <input v-model.trim="message"/>
          </div>
        `
    });

    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 14</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const Counter = {
        data() {
            return {
                count: 1
            }
        },
        template: `
          <div @click="count += 1">{{ count }}</div>`
    }
    const HelloWorld = {
        template: `<div>hello world</div>`
    }
    const app = Vue.createApp({
        components: {
            // counter: Counter,
            // 'hello-world': HelloWorld,
            Counter, HelloWorld,
        },
        template: `
          <div>
          <hello-world/>
          <counter/>
          </div>
        `
    });

    // 通过 component 创建的组件可全局使用,但会永久占用空间
    app.component('counter-parent', {
      template: `<counter />`
    })
    app.component('counter', {
      data() {
        return {
          count: 1
        }
      },
      template: `<div @click="count += 1">{{count}}</div>`
    })
    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 15</title>
    <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: `
          <div>
          <test :content="num"/>
          </div>
        `
    });

    // 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: `
          <div>{{ content }}</div>`
    });

    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 17</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        template: `
      <div>
        <counter msg="hello" msg1="hello1" />
      </div>
    `
    });

    app.component('counter', {
        // inheritAttrs: false,
        mounted() {
            console.log(this.$attrs.msg);
        },
        template: `
      <div :msg="$attrs.msg">Counter</div>
      <div v-bind="$attrs">Counter</div>
      <div :msg1="$attrs.msg1">Counter</div>
    `
    });

    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 18</title>
    <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: `
          <counter v-model.tostring="count"/>
        `
    });

    app.component('counter', {
        props: {
          'modelValue': String,
          'modeModifiers': {
            tostring: (val) => val.toString();
          }
        },
        methods: {
            handleClick() {
                this.$emit('update:modelValue', this.modelValue + 3);
            }
        },
        template: `
          <div @click="handleClick">{{ modelValue }}</div>
        `
    });

    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 属性只能在 <template> 上使用,但在【只有默认插槽时】可以在组件标签上使用
    • 作用域插槽则是通过 slot-scope 获取子组件的信息,在内容中使用。这里可以用解构语法去直接获取想要的属性
    • slot-scope 属性弃用,作用域插槽通过 v-slot:xxx="slotProps"slotProps 来获取子组件传出的属性
    • 可以通过解构获取 v-slot={item},还可以重命名 v-slot="{item: newItem}" 和定义默认值 v-slot="{item = '默认值'}"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 20</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        template: `
      // 具名插槽
      <layout>
        <template #header>
          <div>header</div>
        </template>
        <template v-slot:footer>
          <div>footer</div>
        </template>
      </layout>

      // 作用域插槽
      // 可使用解构语法直接获取指定属性名
      <list v-slot="{item}">
        <div>{{item}}</div>
      </list>
    `
    });

    app.component('layout', {
        template: `
      <div>
        <slot name="header"></slot>
        <div>content</div>
        <slot name="footer"></slot>
      </div>
    `
    });

    app.component('list', {
        data() {
            return {list: [1, 2, 3]}
        },
        template: `
          <div>
          <slot v-for="item in list" :item="item"/>
          </div>
        `
    });

    const vm = app.mount('#root');
</script>
</html>

动态组件与异步组件

  • 动态组件: 根据数据的变化,结合 compoent 这个标签,来随时动态切换组件的现实
    • 要想切换组件时保存数据,可使用 keep-alive 标签来缓存第一次渲染后的输入状态以及变更情况。
  • 异步组件: 是异步执行某些组件的逻辑,这叫做异步组件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 21</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        template: `
      <div>
        // 动态组件
        <keep-alive>
          <compoent :is="compoentFlag">
        </keep-alive>
        <common-item />
        <async-common-item />
      </div>
    `
    });

    app.component('common-item', {
        template: `<div>hello world</div>`
    });

    app.component('async-common-item', Vue.defineAsyncComponent(() => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({
                    template: `<div>this is an async component</div>`
                })
            }, 4000)
        })
    }))

    const vm = app.mount('#root');
</script>
</html>

Vue 动画

基础 CSS 过渡与动画效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 23</title>
    <style>
        /*  动画
        @keyframes leftToRight {
          0% {
            transform: translateX(-100px);
          }
          50% {
            transform: translateX(-50px);
          }
          0% {
            transform: translateX(0px);
          }
        }
        .animation {
          animation: leftToRight 3s;
        } */

        /* 过渡
        .transition {
          transition: 3s background-color ease;
        }
        .blue {
          background: blue;
        }
        .green {
          background: green;
        } */

        .transition {
            transition: 3s background-color ease;
        }

    </style>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        data() {
            return {
                styleObj: {
                    background: 'blue'
                }
            }
        },
        methods: {
            handleClick() {
                if (this.styleObj.background === 'blue') {
                    this.styleObj.background = 'green';
                } else {
                    this.styleObj.background = 'blue'
                }
            }
        },
        template: `
          <div>
          <div class="transition" :style="styleObj">hello world</div>
          <button @click="handleClick">切换</button>
          </div>
        `
    });

    const vm = app.mount('#root');
</script>
</html>

使用 transition 标签实现单元素组件的过渡和动画效果

  • 使用 transition 标签包裹节点可为节点添加过渡或动画效果。
  • 默认情况下使用 v-[动作(enter-from、enter-to、leave-from、deave-to)] 命名 css 类即可触发对应效果,同时可使用 name 指定效果前缀。
  • 可使用 enter-from-classenter-to-class 之类的属性自定义效果类,此时直接传入类名即可生效。
  • 可使用 animate.css 之类的第三方动画库
  • 使用 transitiontype 属性指定基准效果(animation / translation),效果的持续时间以基准效果为准。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 24</title>
    <link
        rel="stylesheet"
        href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
    />
    <style>
        @keyframes shake {
            0% {
                transform: translateX(-100px)
            }
            50% {
                transform: translateX(-50px)
            }
            100% {
                transform: translateX(50px)
            }
        }

        .hello-leave-active {
            animation: shake 3s;
        }

        .hello-enter-active {
            animation: shake 3s;
        }
    </style>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // 单元素,单组件的入场出场动画
    const app = Vue.createApp({
        data() {
            return {
                show: false
            }
        },
        methods: {
            handleClick() {
                this.show = !this.show;
            }
        },
        template: `
          <div>
          <transition name="hello">
            <div v-if="show">hello world</div>
          </transition>
          // 使用第三方动画库
          <transition
            enter-active-class="animate__animated animate__bounce"
            leave-active-class="animate__animated animate__bounce">
            <div v-if="show">hello world</div>
          </transition>
          <button @click="handleClick">切换</button>
          </div>
        `
    });

    const app = Vue.createApp({
        data() {
            return {
                show: false
            }
        },
        methods: {
            handleClick() {
                this.show = !this.show;
            },
            handleBeforeEnter(el) {
                el.style.color = "red";
            },
            handleEnterActive(el, done) {
                const animation = setInterval(() => {
                    const color = el.style.color;
                    if (color === 'red') {
                        el.style.color = 'green';
                    } else {
                        el.style.color = 'red';
                    }
                }, 1000)
                setTimeout(() => {
                    clearInterval(animation);
                    done();
                }, 3000)
            },
            handleEnterEnd(el) {
                alert(123);
            }
        },
        template: `
          <div>
          <transition
              :css="false"
              @before-enter="handleBeforeEnter"
              @enter="handleEnterActive"
              @after-enter="handleEnterEnd"
          >
            <div v-show="show">hello world</div>
          </transition>
          <button @click="handleClick">切换</button>
          </div>
        `
    });

    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">
    <link
            rel="stylesheet"
            href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
    />
    <title>lesson 25</title>
    <style>
        .v-leave-to {
            opacity: 0;
        }

        .v-enter-from {
            opacity: 0;
        }

        .v-enter-active,
        .v-leave-active {
            transition: opacity 1s ease-in;
        }

        .v-leave-from,
        .v-enter-to {
            opacity: 1;
        }
    </style>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // 多个单元素标签之间的切换
    // 多个单组件之间的切换
    const ComponentA = {
        template: '<div>hello world</div>'
    }

    const ComponentB = {
        template: '<div>bye world</div>'
    }

    const app = Vue.createApp({
        data() {
            return {component: 'component-a'}
        },
        methods: {
            handleClick() {
                if (this.component === 'component-a') {
                    this.component = 'component-b';
                } else {
                    this.component = 'component-a';
                }
            },
        },
        components: {
            'component-a': ComponentA,
            'component-b': ComponentB,
        },
        template: `
          <div>
          <transition mode="out-in" appear>
            <component :is="component"/>
          </transition>
          <button @click="handleClick">切换</button>
          </div>
        `
    });

    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">
    <link
            rel="stylesheet"
            href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
    />
    <title>lesson 26</title>
    <style>
        .v-enter-from {
            opacity: 0;
            transform: translateY(30px);
        }

        .v-enter-active {
            transition: all .5s ease-in;
        }

        .v-enter-to {
            opacity: 1;
            transform: translateY(0);
        }

        .v-leave-active {
            transition: all .5s ease-in;
        }

        .v-leave-to {
            opacity: 1;
            transform: translateY(-30px);
        }

        .v-move {
            transition: all .5s ease-in;
        }

        .list-item {
            display: inline-block;
            margin-right: 10px;
        }
    </style>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // 列表动画的实现
    const app = Vue.createApp({
        data() {
            return {list: [1, 2, 3]}
        },
        methods: {
            handleClick() {
                this.list.unshift(this.list.length + 1);
            },
            handleDelete() {
                this.list.pop();
            },
        },
        template: `
          <div>
          <button @click="handleClick">增加</button>
          <button @click="handleDelete">删除</button>
          <transition-group>
            <span class="list-item" v-for="item in list" :key="item">{{ item }}</span>
          </transition-group>
          </div>
        `
    });

    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 27</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // 状态动画
    const app = Vue.createApp({
        data() {
            return {
                number: 1,
                animateNumber: 1
            }
        },
        methods: {
            handleClick() {
                this.number = 100 + this.number;
                if (this.animateNumber < this.number) {
                    const animation = setInterval(() => {
                        this.animateNumber += 1;
                        if (this.animateNumber === this.number) {
                            clearInterval(animation);
                        }
                    }, 10);
                }
            },
        },
        template: `
          <div>
          <div>{{ animateNumber }}</div>
          <button @click="handleClick">增加</button>
          </div>
        `
    });

    const vm = app.mount('#root');
</script>
</html>

Vue 高级语法

Mixin 混入基础

  • 组件 data, methods 优先级高于 mixin data, methods 优先级
  • 生命周期函数,先执行 mixin 里面的,再执行组件里面的
  • 自定义的属性,组件种的属性优先级高于 mixin 属性的优先级
  • 混入会造成代码维护困难,一般情况下可使用 Composition API(Vue 3.0)或者插件替代。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 28</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const myMixin = {
        number: 1
    }

    const app = Vue.createApp({
        mixins: [myMixin],
        number: 2,
        template: `
          <div>
          <div>{{ this.$options.number }}</div>
          </div>
        `
    });

    // 修改自定义属性的使用优先级
    app.config.optionMergeStrategies.number = (mixinVal, appValue) => {
        return mixinVal || appValue;
    }

    const vm = app.mount('#root');
</script>
</html>

开发实现 Vue 中的自定义指令

  • 使用 Vue 核心对象的 directive 方法自定义全局指令
    • 可直接传入一个由生命周期函数组成的对象,若对象内仅存在 mountedupdated 生命周期函数,则可以是直接使用函数替代。
    • 定义全局指令时,会自动传入两个参数,第一个 el 是 Dom 节点本身,第二个 binding 为绑定的参数,其中 arg 为绑定的参数别名,value 为绑定的值。
  • 使用组价的 directives 属性临时导入自定义指令
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 29</title>
    <style>
        .header {
            position: absolute
        }
    </style>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    临时导入自定义指令 directives
    const directives = {
      focus: {
        mounted(el) {
          el.focus();
        }
      }
    }
    
    const app = Vue.createApp({
        directives: directives,
        data() {
            return {
                distance: 110
            }
        },
        template: `
          <div>
          <div v-pos:right="distance" class="header">
            <input/>
          </div>
          </div>
        `
    });

    // 自定义全局指令 directive
    app.directive('focus', {
        mounted(el) {
          el.focus();
        }
    })


    app.directive('pos', (el, binding) => {
        el.style[binding.arg] = (binding.value + 'px');
    })

    const vm = app.mount('#root');
</script>
</html>

Teleport 传送门功能

使用 Teleport 传送门可将节点或组件传送到指定标签上,teleport 标签中使用 to 属性指定传送的目标标签,类似 Dom 节点定位方式。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 30</title>
    <style>
        .area {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            width: 200px;
            height: 300px;
            background: green;
        }

        .mask {
            position: absolute;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            background: #000;
            opacity: 0.5;
            color: #fff;
            font-size: 100px;
        }
    </style>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
<div id="hello"></div>
</body>
<script>
    // teleport 传送门
    const app = Vue.createApp({
        data() {
            return {
                show: false,
                message: 'hello'
            }
        },
        methods: {
            handleBtnClick() {
                this.show = !this.show;
            }
        },
        template: `
          <div class="area">
          <button @click="handleBtnClick">按钮</button>
          <teleport to="#hello">
            <div class="mask" v-show="show">{{ message }}</div>
          </teleport>
          </div>
        `
    });

    const vm = app.mount('#root');
</script>
</html>

render 函数

template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 31</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // render function
    const app = Vue.createApp({
        template: `
      <my-title :level="2">
        hello dell
      </my-title>
    `
    });

    app.component('my-title', {
        props: ['level'],
        render() {
            const {h} = Vue;
            return h('h' + this.level, {}, [
                this.$slots.default(),
                h('h4', {}, 'dell')
            ])
        }
    })

    const vm = app.mount('#root');
</script>
</html>

插件的定义和使用

  • plugin 插件, 是把通用性的功能封装起来
  • 使用 Vue 核心对象的 use 方法载入插件。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 32</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const myPlugin = {
        install(app, options) {
            app.provide('name', 'Dell Lee');
            app.directive('focus', {
                mounted(el) {
                    el.focus();
                }
            })
            app.mixin({
                mounted() {
                    console.log('mixin')
                }
            })

            // 扩展全局变量
            app.config.globalProperties.$sayHello = 'hello world';
        }
    }

    const app = Vue.createApp({
        template: `
      <my-title />
    `
    });

    app.component('my-title', {
        inject: ['name'],
        mounted() {
          // 使用全局变量
          console.log(this.$sayHello);
        },
        template: `<div>{{name}}<input v-focus /></div>`
    })

    app.use(myPlugin, {name: 'dell'});

    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 33</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // 对数据做校验的插件
    const app = Vue.createApp({
        data() {
            return {name: 'dell', age: 23}
        },
        rules: {
            age: {
                validate: age => age > 25,
                message: 'too young, to simple'
            },
            name: {
                validate: name => name.length >= 4,
                message: 'name too short'
            }
        },
        template: `
          <div>name:{{ name }}, age:{{ age }}</div>
        `
    });

    const validatorPlugin = (app, options) => {
        app.mixin({
            created() {
                for (let key in this.$options.rules) {
                    const item = this.$options.rules[key];
                    this.$watch(key, (value) => {
                        const result = item.validate(value);
                        if (!result) console.log(item.message);
                    })
                }
            }
        })
    }

    app.use(validatorPlugin);
    const vm = app.mount('#root');
</script>
</html>

Composition API

Setup 函数的使用

  • setup 函数执行于 created 之前,需要两个参数:props(外部传入属性),context(上下文)
  • setup 函数中无法使用 this(处于 created 之前,组件未创建成功。)
  • setup 中返回的属性 / 方法会暴露在组件中供组件使用。
  • 组件创建挂载完成后可直接调用 setup 函数,
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 34</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        template: `
          <div @click="handleClick">{{ name }}</div>
        `,
        methods: {
            test() {
                console.log(this.$options.setup());
            }
        },
        mounted() {
            this.test();
        },
        // created 实例被完全初始化之前
        setup(props, context) {
            return {
                name: 'dell',
                handleClick: () => {
                    alert(123)
                }
            }
        }
    });
    const vm = app.mount('#root');
</script>
</html>

ref,reactive 响应式引用的用法和原理

  • 原理:通过 proxy 对数据进行封装,当数据变化时,触发模版等内容的更新
  • ref 处理基础类型的数据
    • proxy , 'dell' 变成 proxy({value: 'dell'}) 这样的一个响应式引用
    • 调用 ref 生成变量时,使用 变量名.value ,但 Vue 3 底层会自动调用 value ,因此使用时与一般情况相同即可
  • reactive 处理非基础类型的数据
    • proxy , { name: 'dell' } 变成 proxy({ name: 'dell'}) 这样的一个响应式引用
    • setup 中直接将 reactive 引用的属性导出(let { name } = reactive({ name: val });),此时仅导出一个普通变量,若要使此变量可响应,可使用 toRefs 方法。
  • Vue 3 中 refreactive 可代替 data 函数,因此使用此语法时可省略 data 函数。
  • readonly 生成一个只读数据
  • toRefs 可将一个 reactive 引用的值变为 ref 引用。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 35</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        template: `
          <div>{{ name }}</div>
        `,
        setup(props, context) {
            // const { ref } = Vue;
            // 
            // let name = ref('dell');
            // setTimeout(() => {
            //   name.value = 'lee'
            // }, 2000)
            // return { name }

            const {reactive, readonly, toRefs} = Vue;
            // 
            const nameObj = reactive({name: 'dell', age: 28});
            setTimeout(() => {
                nameObj.name = 'lee'
            }, 2000)
            // toRefs proxy({ name: 'dell', age: 28}), { 
            //  name: proxy({ value: 'dell'}),
            //  age: proxy({value: 28})
            // }
            const {name, age} = toRefs(nameObj);
            return {name}
        }
    });
    const vm = app.mount('#root');
</script>
</html>

toRef 以及 context 参数

  • toRef 旨在当响应式引用中没有属性的时候给予一个默认值,此时变量无需导出:let age = toRef({ name = 'liu' }, 'default')
  • context:包含上下文环境,常用属性:
    • attrs:包含所有 Non-props (父组件中传入但未在自组价中使用 props 接收的参数)
    • slots:包含所有插槽,等价于 this.$slots
    • emit:等价于 this.$emit()
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 36</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // toRef, context
    const app = Vue.createApp({
        methods: {
            handleChange() {
                alert('change');
            }
        },
        template: `
          <child @change="handleChange">parent</child>`,
    });

    app.component('child', {
        template: '<div @click="handleClick">123123</div>',
        setup(props, context) {
            const {h} = Vue;
            const {attrs, slots, emit} = context;

            function handleClick() {
                emit('change');
            }

            return {handleClick}
        }
    })
    const vm = app.mount('#root');
</script>
</html>

使用 Composition API 开发 TodoList

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 37</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // 关于 list 操作的内容进行了封装
    const listRelativeEffect = () => {
        const {reactive} = Vue;
        const list = reactive([]);
        const addItemToList = (item) => {
            list.push(item);
        }
        return {list, addItemToList}
    }

    // 关于 inputValue 操作的内容进行了封装
    const inputRelativeEffect = () => {
        const {ref} = Vue;
        const inputValue = ref('');
        const handleInputValueChange = (e) => {
            inputValue.value = e.target.value
        }
        return {inputValue, handleInputValueChange}
    }

    const app = Vue.createApp({
        setup() {
            // 流程调度中转
            const {list, addItemToList} = listRelativeEffect();
            const {inputValue, handleInputValueChange} = inputRelativeEffect();
            return {
                list, addItemToList,
                inputValue, handleInputValueChange
            }
        },
        template: `
          <div>
          <div>
            <input :value="inputValue" @input="handleInputValueChange"/>
            <button @click="() => addItemToList(inputValue)">提交</button>
          </div>
          <ul>
            <li v-for="(item, index) in list" :key="index">{{ item }}</li>
          </ul>
          </div>
        `,
    });

    const vm = app.mount('#root');
</script>
</html>

computed方法生成计算属性

  • 使用 Vue 的 computed 方法,传入一个回调方法进行属性计算
  • 可传入带有 getset 方法的对象来设置计算属性的取值赋值方法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 38</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    // computed 计算属性
    const app = Vue.createApp({
        setup() {
            const {reactive, computed} = Vue;
            const countObj = reactive({count: 0});
            const handleClick = () => {
                countObj.count += 1;
            }
            let countAddFive = computed({
                get: () => {
                    return countObj.count + 5;
                },
                set: (param) => {
                    countObj.count = param - 5;
                }
            })

            setTimeout(() => {
                countAddFive.value = 100;
            }, 3000)

            return {countObj, countAddFive, handleClick}
        },
        template: `
          <div>
          <span @click="handleClick">{{ countObj.count }}</span> -- {{ countAddFive }}
          </div>
        `,
    });

    const vm = app.mount('#root');
</script>
</html>

watch 和 watchEffect 的使用和差异性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 39</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        setup() {
            const {reactive, watch, watchEffect, toRefs} = Vue;
            const nameObj = reactive({
                name: 'dell', englishName: 'lee'
            })
            // watch 侦听器
            // 具备一定的惰性 lazy
            // 参数可以拿到原始和当前值
            // 侦听 reactive 的值时,需要传入一个侦听函数,返回需要侦听的值。
            // 可以侦听多个数据的变化,用一个侦听器承载
            watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, preEng]) => {
                console.log('watch', curName, prevName, '---', curEng, preEng);
            }, {immediate: true})

            // watchEffect 侦听器,偏向于 effect
            // 立即执行,没有惰性 immediate
            // 不需要传递你要侦听的内容,自动会感知代码依赖,不需要传递很多参数,只要传递一个回调函数
            // 不能获取之前数据的值
            const stop = watchEffect(() => {
              console.log(nameObj.name);
              console.log(nameObj.englishName);
              setTimeout(() => {
                stop();
              }, 5000)
            })

            const {name, englishName} = toRefs(nameObj);

            return {name, englishName}
        },
        template: `
          <div>
          <div>
            Name: <input v-model="name">
          </div>
          <div>
            Name is {{ name }}
          </div>
          <div>
            EnglishName: <input v-model="englishName">
          </div>
          <div>
            EnglishName is {{ englishName }}
          </div>
          </div>
        `,
    });

    const vm = app.mount('#root');
</script>
</html>

生命周期函数的新写法

  • Composition API 的 setup 函数执行时间介于旧生命周期的 beforeCreatedcreated 之间,因此 Composition API 未提供这两个生命周期函数,相关的操作直接在 setup 函数内进行即可。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lesson 40</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
    const app = Vue.createApp({
        // beforeMount => onBeforeMount
        // mounted => onMounted
        // beforeUpdate => onBeforeUpdate
        // beforeUnmount => onBeforeUnmount
        // unmouted => onUnmounted
        setup() {
            const {
                ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
                onRenderTracked, onRenderTriggered
            } = Vue;
            const name = ref('dell')
            onBeforeMount(() => {
                console.log('onBeforeMount')
            })
            onMounted(() => {
                console.log('onMounted')
            })
            onBeforeUpdate(() => {
                console.log('onBeforeUpdate')
            })
            onUpdated(() => {
                console.log('onUpdated')
            })
            // 每次渲染后重新收集响应式依赖
            onRenderTracked(() => {
                console.log('onRenderTracked')
            })
            // 每次触发页面重新渲染时自动执行
            onRenderTriggered(() => {
                console.log('onRenderTriggered')
            })
            const handleClick = () => {
                name.value = 'lee'
            }
            return {name, handleClick}
        },
        template: `
      <div @click="handleClick">
        {{name}}
      </div>
    `,
    });

    const vm = app.mount('#root');
</script>
</html>

Provide,Inject,模版 Ref 的用法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>lesson 41</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="root"></div>
</body>
<script>
  provide, inject
  dom ref
  const app = Vue.createApp({
    setup() {
      const { provide, ref, readonly } = Vue;
      const name = ref('dell');
      // 限制后代组件对 name 属性的修改
      provide('name', readonly(name));
      // 为后代组件开放 name 属性修改方法
      provide('changeName', (value) => {
        name.value = value;
      });
      return { }
    },
    template: `
      <div>
        <child />
      </div>
    `,
  });

  app.component('child', {
    setup() {
      const { inject } = Vue;
      const name = inject('name');
      const changeName = inject('changeName');
      const handleClick = () => {
        changeName('lee');
      }
      return { name, handleClick }
    },
    template: '<div @click="handleClick">{{name}}</div>'
  })

  // Composition API 的语法下,获取真实的 DOM 元素节点
  // 使用 ref(null) 定义一个 ref 节点,接收的变量名需与目标节点的 ref 属性相同
  const app = Vue.createApp({
    setup() {
      const { ref, onMounted } = Vue;
      const hello = ref(null);
      onMounted(() => {
        console.log(hello.value);
      })
      return { hello }
    },
    template: `
      <div>
        <div ref="hello">hello world</div>
      </div>
    `,
  });
  
  const vm = app.mount('#root');
</script>
</html>

Vue 项目开发配套工具讲解

VueCLI 的使用和单文件组件

  • nrm 工具:包含国内常用镜像源
    • nrm ls:查看镜像列表
    • nrm use <镜像名>:使用对应镜像
  • npm install -g @vue/cli 安装最新脚手架工具
    • vue create <项目名>:创建新项目
  • 单文件组件:一个文件对应一个组件,由 <template>(模板区域)、<script>(逻辑渲染区域)、<style>(样式区域)三个区域组成
  • 简单实例
<!-- index.html - 入口 HTML -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
// main.js

import { createApp } from 'vue'
import App from './App.vue'

// 将创建的核心对象绑定在 ID 为 "app" 的节点上
createApp(App).mount('#app')
<!-- App.vue - 根组件 -->

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
// 单文件组件
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
}
</script>

<style>
</style>
<!-- HelloWorld.vue - 子组件 -->

<template>
  <h1>{{ msg }}</h1>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<style>
</style>

使用单文件组件编写 TodoList

<!-- App.vue - 根组件 -->

<template>
  <div>
    <input v-model="inputValue"/>
    <button class="button" @click="handleAddItem">提交</button>
  </div>
  <ul>
    <list-item v-for="(item, index) in list" :key="index" :msg="item"/>
  </ul>
</template>

<script>
import {reactive, ref} from 'vue';
import ListItem from './components/ListItem';

export default {
  name: 'App',
  components: {ListItem},
  setup() {
    const inputValue = ref('');
    const list = reactive([]);

    const handleAddItem = () => {
      list.push(inputValue.value);
      inputValue.value = '';
    };
    return {handleAddItem, inputValue, list}
  }
}
</script>

<style>
.button {
  margin-left: 20px;
  color: red;
}
</style>
<!-- ListItem.vue - 子组件 -->

<template>
  <li class="button">{{ msg }}</li>
</template>

<script>
export default {
  name: 'ListItem',
  props: {
    msg: String
  }
}
</script>

<style>
</style>

Vue-Router 路由的理解和使用

  • 路由是指根据U川的不同,展示不同的内容
    • # 为hash路由
    • 异步加载路由:用到的时候再加载,减少首页的加载,但是
      会造成页面跳转的时候加载较慢
  • <router-link>:是跳转路由的标签
  • <router-view>:负责展示当前路由对应的组件路由
  • route 实例
// route.js - 路由

import {createRouter, createWebHashHistory} from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'

const routes = [
    {
        path: '/',
        name: 'Home',
        // 页面加载完成即导入
        component: Home
    },
    {
        path: '/login',
        name: 'Login',
        component: Login
    },
    {
        path: '/about',
        name: 'About',
        // 异步加载路由,仅在触发后请求加载
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes
})

export default router
<!-- App.vue - 入口文件 -->

<template>
  <div id="nav">
    <!-- router-link 是跳转路由的标签 -->
    <router-link to="/">Home</router-link>
    |
    <router-link to="/about">About</router-link>
    |
    <router-link to="/login">Login</router-link>
  </div>
  <!-- router-view 负责展示当前路由对应的组件内容 -->
  <router-view/>
</template>

<style></style>

VueX 的语法详解

  • VueX:数据管理框架
    • VueX创建了一个全局唯一的仓库 store,用来存放全局的数据
    • 数据定义由 store 中的 state 完成
  • VueX 全局数据修改流程(异步)
    1. dispatch 方法,派发一个 action,方法名为 change
      • this.$store.dispatch('change', str)
      • dispatchactions 做关联,changeactions 中的方法
    2. 感知到 change 这个 action,执行 storeactions 下面的 change 方法
    3. commit 提交一个 mutation,方法名为 change
      • store.commit('change', str)
      • commit 和 mutation 做关联,changemutations 中的方法
    4. 感知到提交的 change 改变,执行 storemutations 下面的 change 方法改变数据
      • mutations 里面只允许写同步代码,不允许写异步代码(非强制,仅为代码设计要求)
      • 异步代码交由 actions 处理
  • 若仅需同步修改数据,可跳过 dispatch 流程,直接使用 commit 触发 mutation
  • actions 中的方法第一个参数为 store 对象;mutations 中的方法第一个参数为 store 中的 state 对象
  • 简单实例
// 全局store的定义
// store/index.js

import {createStore} from 'vuex'
// VueX 数据管理框架
// VueX 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
    // 定义全局数据
    state: {
      name: 'dell'
    },
    // mutation 里面只允许写同步代码,不允许写异步代码
    // commit 和 mutation 做关联
    mutations: {
        change(state, str) {
            state.name = str;
        }
    },
    // dispatch 和 actions 做关联
    actions: {
        change(store, str) {
            setTimeout(() => {
                store.commit('change', str)
            }, 2000)
        }
    }
})
<!-- 组件使用 -->
<!-- views/Home.vue -->

<template>
  <div class="home">
    <h1 @click="handleClick">This is a home page</h1>
    <h1>{{ myName }}</h1>
  </div>
</template>

<script>
export default {
  name: 'Home',
  computed: {
    myName() {
      return this.$store.state.name;
    }
  },
  methods: {
    handleClick() {
      // 异步修改全局数据
      this.$store.dispatch('change', 'hello world');
      // 同步修改全局数据
      this.$store.commit('change', 'hello world');
    }
  }
}
</script>

CompositionAPI 中如何使用 VueX

  • CompositionAPI 使用的是 useStore 获取 store 对象
  • 简单实例
// store/index.js
import {createStore} from 'vuex'

export default createStore({
    state: {name: 'dell'},
    mutations: {
        changeName(state, str) {
            state.name = str;
        }
    },
    actions: {
        getData(store) {
            setTimeout(() => {
                store.commit('changeName', 'hello')
            }, 2000)
        }
    }
})
<!-- views/Home.vue -->

<template>
  <div class="home">
    <h1 @click="handleClick">This is a home page</h1>
    <h1>{{ name }}</h1>
  </div>
</template>

<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';

export default {
  name: 'Home',
  setup() {
    const store = useStore();
    const {name} = toRefs(store.state);
    const handleClick = () => {
      store.dispatch('getData')
    }
    return {name, handleClick}
  }
}
</script>
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃芒果的芬里尔狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值