日常生活的交流与学习

首页 新随笔 联系 管理

简介

组件化

组件化开发最最重要的一点,就是复用.

image-20210720190301214

类型检测

为什么一定要有类型检测呢?

简而言之,就是错误发现越早越好.

image-20210720190611546


  • JavaScript的类型错误只有在运行阶段才能发现

image-20210720190813935

技术栈

vue项目需要掌握的技术栈

image-20210720191004043

学习方法

image-20210720191046562

什么是渐进式框架

一点点引入和使用

image-20210720191150539

vue的本质

本质就是一个JavaScript库,就当做一个JS文件引入就好了

image-20210720191258814

基本思路

image-20210720192510061


传入一个对象,返回一个对象,将返回的对象挂在到dom元素上面

image-20210720192833514

链式调用

链式调用更简单,更常用.

image-20210720193019418

调试工具

[shell-chrome.rar - 快捷方式.lnk](..\ae_文本文件\shell-chrome.rar - 快捷方式.lnk)

环境搭建

image-20210720191429242

CDN方式引入

什么是CDN?

个人理解,有点像P2P的下载模式,有点就近转发的意思.

image-20210720191546762

引入

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

image-20210720191811252

通过vue.js文件引入

下载

登录网址, https://unpkg.com/browse/vue@3.1.5/dist/ ,如下图所示下载vue.global.js文件,这个文件并不是源码文件,而是经过打包之后的文件.

image-20210720193418728

引入

如下图引入:

image-20210720194006466

计数器案例

原生JS实现

原生JS实现计数器-增加事件监听.html

==增加事件监听=

image-20210720202214407

<!DOCTYPE html>
<html lang="en">
<head>
    
    
    
    <title>Document</title>
</head>
<body>
    <div id="counter"></div>
    <button id="increase" >+</button>
    <button id="decrease">-</button>
    <script>
        //aa-get the counter and button elements
        let counter = document.querySelector('#counter')
        let increase = document.querySelector('#increase')
        let decrease = document.querySelector('#decrease')
        //bb-display number in the counter div
        let num = 100
        counter.innerHTML = num
        //cc-bind the button click event 
        increase.addEventListener('click',()=>{
            num++
            counter.innerHTML = num
        })
        decrease.addEventListener('click',()=>{
            num--
            counter.innerHTML = num
        })
    </script>
</body>
</html>

原生JS实现计数器-在元素中绑定onclick属性和script标签中增加onclick属性.html

方式一

方式二

image-20210720203035899

<!DOCTYPE html>
<html lang="en">
<head>
    
    
    
    <title>Document</title>
</head>
<body>
    <div id="counter"></div>
    <button id="increase" onclick="increase()" >+</button>
    <button id="decrease">-</button>
    <script>
        //aa-get the counter and button elements
        let counter = document.querySelector('#counter')
        let increase = document.querySelector('#increase')
        let decrease = document.querySelector('#decrease')
        //bb-display number in the counter div
        let num = 100
        counter.innerHTML = num
        //cc-bind the button click event 
        //ca-bind onclick in the button div
        function increase(){
            num++
            counter.innerHTML = num
        }
        //cb-bind onclick in the script query
        decrease.onclick = function(){
            num --
            counter.innerHTML = num
        }
    </script>
</body>
</html>

VUE实现计数器

vue实现计数器.html

基本结构

image-20210720224607723

数据绑定mustache语句

image-20210720224659089

事件绑定

image-20210720224738318

data的value为啥是个函数

<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
    <div id="app"></div>
    <script src="./vue/vue.js"></script>
    <script>
        Vue.createApp({
            template:`
                <h2>{{counter}}</h2>
                <button @click='increase'>+1</button>
                <button @click='decrease'>-1</button>
            `,
            data:function(){
                return{
                    counter: 10
                }
            },
            methods: {
                increase(){
                    this.counter++
                },
                decrease(){
                    this.counter--
                }
            },
        }).mount('#app')
    </script>
</body>
</html>

vue实现计数器 and es5的写法 and 箭头函数的写法.html

es5和es6的写法的对比

image-20210720225907205

箭头函数的写法

image-20210720230007375

<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
    <div id="app"></div>
    <script src="./vue/vue.js"></script>
    <script>
        Vue.createApp({
            template:`
                <h2>{{counter}}</h2>
                <button @click='increase'>+1</button>
                <button @click='decrease'>-1</button>
            `,
            data:function(){
                return{
                    counter: 10
                }
            },
            methods: {
                //es6的写法
                //increase(){
                //    this.counter++
                //},
                //es5的写法--OK
                increase:function(){
                    this.counter++
                }
                ,
                //箭头函数--Not OK!!!
                decrease:()=>{
                    this.counter--
                }
            },
        }).mount('#app')
    </script>
</body>
</html>

命令式和声明式编程

声明式编程

理解

image-20210720230637295

命令式编程和声明式编程的区别

不恰当的比喻:一个是手把手的教,一个是发个命令就好了.

image-20210721064900640

MVVM模型

MVC模型

image-20210721065538134

image-20210721065144627

MVVM模型

vue的模式类似MVVM模型

image-20210721065457700

template属性

挂载

template中的内容会被挂载到对应的元素下面,并且其中的内容会被覆盖掉

image-20210721065749579

分离式写法_script标签

image-20210721070213332

分离式写法.html

关键代码

image-20210721070838288

<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
    <div id="app"></div>
    <script type='x-template' id='main'>
        <h2>{{counter}}</h2>
        <button @click='increase'>+1</button>
        <button @click='decrease'>-1</button>
    </script>
    <script src="./vue/vue.js"></script>
    <script>
        Vue.createApp({
            template:`#main`,
            data:function(){
                return{
                    counter: 10
                }
            },
            methods: {
                increase(){
                    this.counter++
                },
                decrease(){
                    this.counter--
                }
            },
        }).mount('#app')
    </script>
</body>
</html>

分离式写法_template标签

template中的内容分离式写法_使用template标签.html

核心代码

image-20210721071236294

<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
    <div id="app"></div>
    <template id='main'>
        <div>
            <h2>{{counter}}</h2>
            <button @click='increase'>+1</button>
            <button @click='decrease'>-1</button>
        </div>
    </template>
   
    <script src="./vue/vue.js"></script>
    <script>
        Vue.createApp({
            template:`#main`,
            data:function(){
                return{
                    counter: 10
                }
            },
            methods: {
                increase(){
                    this.counter++
                },
                decrease(){
                    this.counter--
                }
            },
        }).mount('#app')
    </script>
</body>
</html>

template标签的特点

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/template

image-20210721071624380

普通div其实也是可以实现挂载的,vue也会将其挂载上去,只不过div会被浏览器解析器渲染上去,显示出来,从而导致内容重复多出来.

image-20210721072239210

template中的根元素

vue2和vue3中template中根元素个数的区别.html

vue3中可以有多个根元素

image-20210722092351744

vue2中只能有一个根元素

image-20210722092424104

  <!-- vue3 是允许template中有多个根元素 -->
  <template id="my-app">
    <a v-bind:href="link">百度一下</a>
    <a :href="link">百度一下</a>
  </template>
  
  <!-- vue2 template模板中只能有一个根元素 -->
  <template id="my-app">
    <div>
      <a v-bind:href="link">百度一下</a>
      <a :href="link">百度一下</a>
    </div>
  </template>

data属性

vue2和vue3中的区别

image-20210721072402349

methods属性

image-20210721072653548

this的指向

普通函数

普通函数的this指向.html

this的指向

image-20210721075050193

this永远指向的是调用他的那个对象

fun7()其实是window.fun7()的省略写法

image-20210721075459603

<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
    <script>
       /**
         * this的指向
         * 1-函数调用的时候,this就是指向这个window对象
         * 2-对象进行调用的时候,就是指向这个对象。
         */
        function fun7(){
            console.log(this);
            console.log(this.name);
            console.log("I'm fun7");
        }
        fun7();  
        
        console.log('----------------');
        
        var obj2 = {
            name:"Bruce",
            age:12,
            say:function(){
                console.log(this);
                console.log(this.name);
                console.log("I'm fun8"); 
            }
        }  
        obj2.say();
    </script>
</body>
</html>

强制改变普通函数函数定义时候的this.html

强制改变普通函数定义时候的this

image-20210721100954533

<!DOCTYPE html>
<html lang="zh">

<head>
</head>

<body>
    <script>

        function Timer() {
            this.s1 = 0;
            this.s2 = 0;
            // 箭头函数
            setInterval(() => this.s1++, 1000);
            // 普通函数
            _this = this
            setInterval(function () {
                _this.s2++;
            }, 1000);
        }

        var timer = new Timer();

        setTimeout(() => console.log('s1: ', timer.s1), 3100);//=>3
        setTimeout(() => console.log('s2: ', timer.s2), 3100);//=>3


    </script>
</body>

</html>

箭头函数

箭头函数中this的指向.html

普通函数this永远指向调用他的对象

箭头函数this的指向定义时候的this

箭头函数没有自己的this,他会从里头向外头寻找,直到找到有this为止,这里的this是windows对象.

image-20210721093417251

<!DOCTYPE html>
<html lang="zh">

<head>
</head>

<body>
    <script>
    var obj2 = {
            name:"Bruce",
            age:12,
            say:function(){
                console.log(this);
                //=>{name: "Bruce", age: 12, say: ƒ, run: ƒ}
            },
            run:()=>{
                console.log(this);
                //=>Window {window: Window, self: Window, document: document, name: "", location: Location, …}
            },
        }  
     obj2.say();
     obj2.run()
    </script>
</body>

</html>

为什么vue的methods不要使用箭头函数?

计数器.html

我们想要操作的对象是什么?

我想要操作的对象是什么,我们想要通过这个this.counter对象拿到这个data中的counter,但是如果传入的this是Windows对象,我们通过这个windows对象是拿不到这个counter的.所以不推荐使用箭头函数.

箭头函数没有自己this,一般在定义的时候,会到自己的上级作用域寻找this,这里的上级作用域就是windows所在的作用域,一般来说都是这个windows作用域.

image-20210721170554857

普通函数和箭头函数中的this

image-20210721170903992

<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
    <div id="app"></div>
    <script src="./vue/vue.js"></script>
    <script>
        Vue.createApp({
            template:`
                <h2>{{counter}}</h2>
                <button @click='increase'>+1</button>
                <button @click='decrease'>-1</button>
            `,
            data:function(){
                return{
                    counter: 10
                }
            },
            methods: {
                increase(){
                    console.log(this);
                    //=>Proxy {increase: ƒ, decrease: ƒ, …}
                    this.counter++
                },
                decrease:()=>{
                    console.log(this);
                    //=>Window {window: Window, self: Window, document: document, name: "", location: Location, …}
                    this.counter--
                }
            }
        }).mount('#app')
    </script>
</body>
</html>

总结

使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。

使用function定义的函数中this指向是随着调用环境的变化而变化的

//使用function定义的函数
function foo(){
	console.log(this);
}
var obj = { aa: foo };
foo(); //Window
obj.aa() //obj { aa: foo }

明显使用箭头函数的时候,this的指向是没有发生变化的。

//使用箭头函数定义函数
var foo = () => { console.log(this) };
var obj = { aa:foo };
foo(); //Window
obj.aa(); //Window

VSCode代码片段

  1. 赋值自己需要的代码
<!DOCTYPE html>
<html lang="zh">
<head>
    
    
    
    <title>Document</title>
</head>
<body>
    <div id="app">
        {{msg}}
    </div>
    <script src="vue/vue.js"></script>
    <script>
        Vue.createApp({
            data:function(){
                return{
                    msg: 'hello vue'
                }
            }
        }).mount('#app')
    </script>
</body>
</html>
  1. 登录这个网站

https://snippet-generator.app/

image-20210721184734917

  1. 将生成的代码片段拷贝下来
"create vue app": {
  "prefix": "vueapp",
  "body": [
    "<!DOCTYPE html>",
    "<html lang=\"zh\">",
    "<head>",
    "    ",
    "    ",
    "    ",
    "    <title>Document</title>",
    "</head>",
    "<body>",
    "    <div id=\"app\">",
    "        {{msg}}",
    "    </div>",
    "    <script src=\"vue/vue.js\"></script>",
    "    <script>",
    "        Vue.createApp({",
    "            data:function(){",
    "                return{",
    "                    msg: 'hello vue'",
    "                }",
    "            }",
    "        }).mount('#app')",
    "    </script>",
    "</body>",
    "</html>"
  ],
  "description": "create vue app"
}
  1. 打开vscode

image-20210721185000847

image-20210721185012387

image-20210721185149459

image-20210721185215489

Honeycam 2021-07-21 18-54-06

Musache语法

正确用法

mustache语法.html

基本使用

image-20210721191047349

简单的表达式

image-20210721191130009

image-20210721191255879

也可以是methods中的函数

image-20210721191709464

既然可以是表达式,当然也可以是三元表达式了

<!DOCTYPE html>
<html lang="zh">
<body>
  <div id="app"></div>
  <template id="my-app">
    <!-- 1.mustache的基本使用 -->
    <h2>{{message}}</h2>
    <!-- 2.是一个表达式 -->
    <h2>{{counter * 10}}</h2>
    <h2>{{ message.split(" ").reverse().join(" ") }}</h2>
    <!-- 3.也可以调用函数 -->
    <!-- 可以使用computed(计算属性) -->
    <h2>{{getReverseMessage()}}</h2>
    <!-- 4.三元运算符 -->
    <h2>{{ isShow ? "哈哈哈": "" }}</h2>
    <button @click="toggle">切换</button>
  </template>
  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World",
          counter: 100,
          isShow: true
        }
      },
      methods: {
        getReverseMessage() {
          return this.message.split(" ").reverse().join(" ");
        },
        toggle() {
          this.isShow = !this.isShow;
        }
      }
    }
    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

错误用法

musache错误用法.html

两者都是赋值语句,不是表达式

image-20210722074511774

<!-- 错误用法 -->
<!-- var name = "abc" -> 赋值语句 -->

<h2>{{var name = "abc"}}</h2>
<h2>{{ if(isShow) {  return "哈哈哈" } }}</h2>

基本指令

v-once

v-once修饰的html元素,只渲染一次,以后都是不变,相当于一个原始的参照系.

v-once.html

代码理解

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <div v-once>
      <h2>原始数值:{{counter}}</h2>
    </div>
    <h2>当前数值{{counter}}</h2>
    <button @click="increment">+1</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          counter: 100,
        }
      },
      methods: {
        increment() {
          this.counter++;
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出

Honeycam 2021-07-22 08-11-24

v-text

v-text就是其修饰的html元素中添加内容,作用和mustache类似,不过没有mustache语法灵活.

v-text.html

关键代码

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2 v-text="message"></h2>
    <h2>{{message}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

v-html

v-html.html

关键代码

image-20210722081414361

<!DOCTYPE html>
<html lang="en">
<body>
  <div id="app"></div>
  <template id="my-app">
    <div>{{msg}}</div>
    <div v-html="msg"></div>
  </template>
  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          msg: '<span style="color:red; background: blue;">哈哈哈</span>'
        }
      }
    }
    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出

image-20210722081422869

v-pre

v-pre.html

关键代码

image-20210722085858915

<!DOCTYPE html>
<html lang="zh">
<body>
  <div id="app"></div>
  <template id="my-app">
    <h2 v-pre>{{message}}</h2>
  </template>
  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      }
    }
    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出

image-20210722085943860

v-cloak

cloak就是斗篷,遮盖的的意思,这个指令的作用是什么,主要为了显示效果,比如说网络很卡,浏览器很卡,这个{{message}}中的内容还没有渲染进来,网页页面会显示mustache语法的原始内容,我们加上这个遮盖之后,就是什么都不显示,然后等到这个里面的message内容渲染完成之后,才将其显示,目的是为了更好的用户体验.

v-cloak.html

<!DOCTYPE html>
<html lang="en">
<body>
  <div id="app"></div>

  <template id="my-app">
    <h2 v-cloak>{{message}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

cloak单词的意思

重要指令

v-bind

属性绑定

v-bind是用来绑定属性,实现动态属性.mustache是用来绑定内容,实现动态内容.

v-bind的基本使用.html

v-bind的基本使用

image-20210722091926655

v-bind的语法糖

<!DOCTYPE html>
<html lang="zh">
<body>
  <div id="app"></div>
    
  <template id="my-app">
    <!-- 1.v-bind的基本使用 -->
    <a v-bind:href="link">百度一下</a>
    <!-- 2.v-bind提供一个语法糖 : -->
    <a :href="link">百度一下</a>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          link: "https://www.baidu.com"
        }
      }
    }
    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

属性绑定_对象形式

属性绑定_对象形式.html

key-value结构

image-20210722105528267

错误写法

key可以加上引号,也可以不加引号.但是value一定不能加引号,因为value加上引号,就变成了字符串.

image-20210722105708876

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      .active {
        color: red;
      }
    </style>
  </head>
  <body>
    <div id="app"></div>

    <template id="my-app">
      <!-- 正确写法:key-value结构 -->
      <!-- 对象语法: {key: value} -->
      <h2 :class="{'active': isActive}">呵呵呵呵</h2>
      <h2 :class="{active: isActive}">呵呵呵呵</h2>
      <!-- 错误写法 -->
      <h2 :class="{active: 'isActive'}">呵呵呵呵</h2>
      <button @click="toggle">切换</button>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            isActive: true,
          };
        },
        methods: {
          toggle() {
            this.isActive = !this.isActive;
          },
        },
      };

      Vue.createApp(App).mount("#app");
    </script>
  </body>
</html>

输出

Honeycam 2021-07-22 10-58-26

多值情况

属性绑定_多值情况.html

多个键值对

image-20210722110501400

默认class和动态的class结合

image-20210722110706098

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      .active {
        color: red;
      }
      .title {
        background-color: yellowgreen;
      }
    </style>
  </head>
  <body>
    <div id="app"></div>

    <template id="my-app">
      <button @click="toggle">切换</button>

      <!-- 也可以有多个键值对 -->
      <div :class="{active: isActive, title: true}">多个键值对</div>

      <!-- 默认的class和动态的class结合 -->
      <div class="abc cba" :class="{active: isActive, title: true}">
        默认的class和动态的class结合
      </div>

    </template>

    <script src="../js/vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            isActive: true,
          };
        },
        methods: {
          toggle() {
            this.isActive = !this.isActive;
          }
        },
      };

      Vue.createApp(App).mount("#app");
    </script>
  </body>
</html>

对象放到一个单独的属性中

将对象放到一个单独的属性中.html

将对象放到一个单独的属性中

image-20210722111658561

<!DOCTYPE html>
<html lang="zh">

<head>
  <style>
    .active {
      color: red;
    }
  </style>
</head>

<body>
  <div id="app"></div>

  <template id="my-app">
    <!-- 将对象放到一个单独的属性中 -->
    <h2 class="abc cba" :class="classObj">将对象放到一个单独的属性中</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: "#my-app",
      data() {
        return {
          classObj: {
            active: true,
            title: true
          }
        };
      }
    };

    Vue.createApp(App).mount("#app");
  </script>
</body>

</html>
将对象放到methods中返回

将返回的对象放到一个methods(computed)方法中.html

将对象放到一个methods返回

image-20210722112014022

<!DOCTYPE html>
<html lang="zh">

<head>
  <style>
    .active {
      color: red;
    }
  </style>
</head>

<body>
  <div id="app"></div>

  <template id="my-app">
    <!-- 将返回的对象放到一个methods(computed)方法中 -->
    <h2 class="abc cba" :class="getClassObj()">将对象放到一个methods(computed)方法中返回</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: "#my-app",
      methods: {
        getClassObj() {
          return {
            active: true,
            title: true
          }
        }
      },
    };

    Vue.createApp(App).mount("#app");
  </script>
</body>

</html>

属性绑定_数组形式

数组形式绑定属性.html

基本用法

image-20210722114741177

image-20210722114916250

嵌入三元表达式

image-20210722114752033

嵌入对象

image-20210722114801586

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- 基本用法 -->
    <div :class="['abc', title]">哈哈哈哈</div>
    <!-- 数组中可以嵌入三元表达式 -->
    <div :class="['abc', title, isActive ? 'active': '']">哈哈哈哈</div>
    <!-- 数组中可以嵌入对象 -->
    <div :class="['abc', title, {active: isActive}]">哈哈哈哈</div>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World",
          title: "cba",
          isActive: true
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

样式绑定_对象形式

v-bind绑定样式.html

基本使用

image-20210722115639670

基本使用_简单拼接

image-20210722115805707

绑定data属性中的object对象

image-20210722115908808

方法中返回的一个对象

image-20210722120030176

短横线需要加引号,驼峰不需要加引号

image-20210722121914360

<!DOCTYPE html>
<html lang="zh">
<head>
</head>
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- :style="{cssPropertyName: cssPropertyValue}" -->
    <div :style="{color: finalColor, 'font-size': '30px'}">'font-size'加了引号</div>
    <div :style="{color: finalColor, fontSize: '30px'}">fontSize不加引号</div>
    <div :style="{color: finalColor, fontSize: finalFontSize + 'px'}">finalFontSize是data中的值,和后面的'px'拼起来</div>

    <!-- 绑定一个data中的属性值, 并且是一个对象 -->
    <div :style="finalStyleObj">绑定一个data中的属性</div>
    <!-- 方法中返回的一个对象 -->
    <div :style="getFinalStyleObj()">methods中返回的一个对象</div>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World",
          finalColor: 'red',
          finalFontSize: 50,
          finalStyleObj: {
            'font-size': '50px',
            fontWeight: 700,
            backgroundColor: 'red'
          }
        }
      },
      methods: {
        getFinalStyleObj() {
          return {
            'font-size': '50px',
            fontWeight: 700,
            backgroundColor: 'red'
          }
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

样式绑定_数组形式

样式绑定_数组形式.html

数组中嵌套对象

image-20210722120457109

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <div :style="[style1Obj, style2Obj]">哈哈哈</div>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          style1Obj: {
            color: 'red',
            fontSize: '30px'
          },
          style2Obj: {
            textDecoration: "underline"
          }
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

属性名称绑定

属性名称绑定.html

属性名称的绑定

image-20210722122405532

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <div :[name]="value">哈哈哈</div>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          name: "classs",
          value: "content"
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

属性名和属性值绑定

属性名和属性值绑定.html

和属性值绑定的区别

没有冒号:

image-20210722123405766

多个属性名和属性值的键值对的绑定

image-20210722123153573

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2 v-bind="info">同时绑定多个属性名和属性值</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: {
            name: "zhuo",
            age: 18,
            height: 1.88
          }
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

v-bind(same)

  • 缩写:

  • 预期any (with argument) | Object (without argument)

  • 参数attrOrProp (optional)

  • 修饰符

    • .camel - 将 kebab-case attribute 名转换为 camelCase。
  • 用法

    动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

    在绑定 classstyle attribute 时,支持其它类型的值,如数组或对象。可以通过下面的教程链接查看详情。

    在绑定 prop 时,prop 必须在子组件中声明。可以用修饰符指定不同的绑定类型。

    没有参数时,可以绑定到一个包含键值对的对象。注意此时 classstyle 绑定不支持数组和对象。

  • 示例

    <!-- 绑定 attribute -->
    <img v-bind:src="imageSrc" />
    
    <!-- 动态 attribute 名 -->
    <button v-bind:[key]="value"></button>
    
    <!-- 缩写 -->
    <img :src="imageSrc" />
    
    <!-- 动态 attribute 名缩写 -->
    <button :[key]="value"></button>
    
    <!-- 内联字符串拼接 -->
    <img :src="'/path/to/images/' + fileName" />
    
    <!-- class 绑定 -->
    <div :class="{ red: isRed }"></div>
    <div :class="[classA, classB]"></div>
    <div :class="[classA, { classB: isB, classC: isC }]">
      <!-- style 绑定 -->
      <div :style="{ fontSize: size + 'px' }"></div>
      <div :style="[styleObjectA, styleObjectB]"></div>
    
      <!-- 绑定一个全是 attribute 的对象 -->
      <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
    
      <!-- prop 绑定。"prop" 必须在 my-component 声明 -->
      <my-component :prop="someThing"></my-component>
    
      <!-- 通过 $props 将父组件的 props 一起传给子组件 -->
      <child-component v-bind="$props"></child-component>
    
      <!-- XLink -->
      <svg><a :xlink:special="foo"></a></svg>
    </div>
    

    .camel 修饰符允许在使用 DOM 模板时将 v-bind property 名称驼峰化,

    例如 SVG 的 viewBox property:

    <svg :view-box.camel="viewBox"></svg>
    

    在使用字符串模板或通过 vue-loader / vueify 编译时,无需使用 .camel

v-on

基本使用

v-on的基本使用.html

click事件

mousemove事件

image-20210722124014612

<!DOCTYPE html>
<html lang="zh">
<head>
  <style>
    .area {
      width: 200px;
      height: 200px;
      background: red;
    }
  </style>
</head>
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- 完整写法: v-on:监听的事件="methods中方法" -->
    <button v-on:click="btn1Click">按钮1</button>
    <div class="area" v-on:mousemove="mouseMove">div</div>
    <!-- 语法糖 -->
    <button @click="btn1Click">按钮1</button>
    
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      methods: {
        btn1Click() {
          console.log("按钮1发生了点击");
        },
        mouseMove() {
          console.log("鼠标移动");
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>   

绑定一个对象

v-on通过绑定一个对象从而实现绑定多个事件.html

v-on绑定一个对象实现绑定多个事件

image-20210722124432040

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    .area {
      width: 200px;
      height: 200px;
      background: red;
    }
  </style>
</head>
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- 绑定一个对象 -->
    <div class="area" v-on="{click: btn1Click, mousemove: mouseMove}"></div>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      methods: {
        btn1Click() {
          console.log("按钮1发生了点击");
        },
        mouseMove() {
          console.log("鼠标移动");
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>   

传递参数

v-on如何向vue的methods中传递参数.html

默认传入event事件

传入其他参数的同时传入event参数

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- 默认传入event对象, 可以在方法中获取 -->
    <button @click="btn1Click">按钮1</button>
    <!-- $event可以获取到事件发生时的事件对象 -->
    <button @click="btn2Click($event, 'zhuo', 18)">按钮2</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      },
      methods: {
        btn1Click(event) {
          console.log(event);
        },
        btn2Click(event, name, age) {
          console.log(name, age, event);
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

修饰符

image-20210722130338774

stop修饰符

阻止事件冒泡的按钮.html

关键代码

image-20210722125641019

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <div @click="divClick">
      <button @click='btnClick'>没有阻止冒泡的按钮</button><br>
      <button @click.stop="btnClick">阻止冒泡按钮</button>
    </div>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      methods: {
        divClick() {
          console.log("divClick");
        },
        btnClick() {
          console.log('btnClick');
        },
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

Honeycam 2021-07-22 12-57-11

[按键]修饰符

输入框实现enter键上屏的效果.html

关键代码

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <input type="text" @keyup.enter="enterKeyup">
    <h2>{{content}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          content:''
        }
      },
      methods: {
        enterKeyup(event) {
          this.content = event.target.value
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

Honeycam 2021-07-22 13-02-02

v-if

渲染原理

怎么理解这个惰性?当条件为false时,在dom的元素就会完全的删除掉,而不是display:none.

image-20210722200738970

基本使用

v-if的基本使用.html

基本使用

<!DOCTYPE html>
<html lang="zn">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2 v-if="isShow">哈哈哈哈</h2>
    <button @click="toggle">切换</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          isShow: true
        }
      },
      methods: {
        toggle() {
          this.isShow = !this.isShow;
        }
      }
    }
    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出

Honeycam 2021-07-22 20-11-57

多个条件

v-if多个条件的使用.html

关键代码

image-20210722201319872

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <input type="text" v-model="score">
    <h2 v-if="score > 90">优秀</h2>
    <h2 v-else-if="score > 60">良好</h2>
    <h2 v-else>不及格</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          score: 95
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出

Honeycam 2021-07-22 20-14-40

template和v-if的结合使用

为什么需要和v-if结合使用

v-if不结合使用.html

关键代码和输出效果

多了一个外层的div,现在就是不想要这个div

image-20210722202534782

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- 和div结合使用 -->
    <div v-if="isShowHa">
      <h2>哈哈哈哈</h2>
      <h2>哈哈哈哈</h2>
      <h2>哈哈哈哈</h2>
    </div>
    <!-- 和template结合使用 -->
    <template v-else>
      <h2>呵呵呵呵</h2>
      <h2>呵呵呵呵</h2>
      <h2>呵呵呵呵</h2>
    </template>
    <button @click='toggle'>toggle</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          isShowHa: true
        }
      },
      methods: {
        toggle(){
          this.isShowHa = !this.isShowHa
        }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

v-if结合使用例子

template和v-if的结合使用.html

关键代码

image-20210722202011296

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <template v-if="isShowHa">
      <h2>哈哈哈哈</h2>
      <h2>哈哈哈哈</h2>
      <h2>哈哈哈哈</h2>
    </template>

    <template v-else>
      <h2>呵呵呵呵</h2>
      <h2>呵呵呵呵</h2>
      <h2>呵呵呵呵</h2>
    </template>
    <button @click='toggle'>toggle</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          isShowHa: true
        }
      },
      methods: {
        toggle(){
          this.isShowHa = !this.isShowHa
        }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

Honeycam 2021-07-22 20-18-42

v-show

基本使用

v-show的基本使用.html

关键代码

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2 v-show="isShow">哈哈哈哈</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          isShow: true
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

v-show和v-if的区别

2,3,4都是很好理解,第1个怎么理解?首先要理解一点就是v-show是通过display:none来控制显示不显示的,而template这个标签一旦被渲染,这个标签就是不存在了,我在对这个标签使用css修饰已经没有任何意义了.

v-show和v-if的区别.html

关键代码

image-20210722203016325

重要区别

v-show通过修改css属性来实现隐藏显示,而v-if直接就是干掉整个元素.

Honeycam 2021-07-22 20-32-44

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2 v-if="isShow">哈哈哈哈</h2>
    <h2 v-show="isShow">呵呵呵呵</h2>
    <button @click='toggle'>toggle</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          isShow: true
        }
      },methods: {
        toggle(){
          this.isShow = !this.isShow
        }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

v-show和v-if如何选择

image-20210722233700515

v-for

基本使用

v-for的使用.html

遍历数组

括号的中两个参数分别是value和index

image-20210722222732217

image-20210722223051804

v-for中传递参数的括号可以不加,但是建议加上去

image-20210723223851209

v-for的in也可以使用of

image-20210723224004367

遍历对象

遍历对象的时候,括号中的参数分别是value,key和index

image-20210722222743792

image-20210722223058417

遍历数字

遍历数字的时候,括号中的参数分别是num和index

image-20210722222756507

image-20210722223104508

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>遍历数组</h2>
    <ul>
      <!-- 遍历数组 -->
      <li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li>
    </ul>
    <h2>遍历对象</h2>
    <ul>
      <!-- 遍历对象 -->
      <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
    </ul>
    <h2>遍历数字</h2>
    <ul>
       <!-- 遍历数字 -->
      <li v-for="(num, index) in 10">{{num}}-{{index}}</li>
    </ul>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          movies: [
            "星际穿越",
            "盗梦空间",
            "大话西游",
            "教父",
            "少年派"
          ],
          info: {
            name: "zhuo",
            age: 18,
            height: 1.88
          }
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

v-for和template搭配使用

v-for和template搭配使用.html

v-for和template搭配使用

image-20210722223903277

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <ul>
      <template v-for="(value, key) in info">
        <li>{{key}}</li>
        <li>{{value}}</li>
        <li class="divider"></li>
      </template>
    </ul>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: {
            name: "why",
            age: 18,
            height: 1.88
          }
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

搭配绑定key

image-20210722234232631

数组更新检测

vue已经自动帮我们侦听了数组,所以数组的改变就会触发新的视图.实际上可以这样理解,vue已经自动帮我们把这个数组进行了数据的双向绑定.

image-20210722230657490

push()方法

数组的更新检测.html

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>电影列表</h2>
    <ul>
      <li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li>
    </ul>
    <input type="text" v-model="newMovie">
    <button @click="addMovie">添加电影</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          newMovie: "",
          movies: [
            "星际穿越",
            "盗梦空间",
            "大话西游",
            "教父",
            "少年派"
          ]
        }
      },
      methods: {
        addMovie() {
          this.movies.push(this.newMovie);
          this.newMovie = "";
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

Honeycam 2021-07-22 23-12-19

VNode

image-20210722233935367

计算属性-computed

为什么会有计算属性?

有些数据通过mustache语法显示在界面上面,但是在mustache中又进行了简单的表达式计算.这个用来写单独的项目是可以的.但是现在我们想要写出通用的组件,就必须让这个mustache中的数据更加纯粹,于是就必须对这些数据进行解耦,那么感觉我就是对原来的数据进行一下二次封装,然后就这些二次封装后的数据在渲染到界面上面.这个二次封装的数据就是计算属性.

基本使用

计算属性.html

计算属性的来源和目的地

  • 来源:计算属性数据的来源肯定是来自data中的元数据,
  • 目的地:mustache展示界面.

image-20210723164251032

<!DOCTYPE html>
<html lang="en">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{fullName}}</h2>
    <h2>{{result}}</h2>
    <h2>{{reverseMessage}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant",
          score: 80,
          message: "Hello World"
        }
      },
      computed: {
        fullName() {
          return this.firstName + " " + this.lastName;
        },
        result() {
          return this.score >= 60 ? "及格": "不及格";
        },
        reverseMessage() {
          return this.message.split(" ").reverse().join(" ");
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

使用methods实现的数据二次封装和计算属性的区别

methods和计算属性的区别.html

计算属性

image-20210723194828601

methods实现类似计算属性的功能

image-20210723195005203

区别

image-20210723194653255

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h1>计算属性</h1>
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>

    <h1>使用methods实现</h1>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant"
        }
      },
      computed: {
        // 计算属性是有缓存的, 当我们多次使用计算属性时, 计算属性中的运算只会执行一次.
        // 计算属性会随着依赖的数据(firstName)的改变, 而进行重新计算.
        fullName() {
          console.log("computed的fullName中的计算");
          return this.firstName + " " + this.lastName;
        }
      },
      methods: {
        getFullName() {
          console.log("methods的getFullName中的计算");
          return this.firstName + " " + this.lastName;
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

methods和计算属性的区别_修改元数据.html

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <button @click="changeFirstName">修改firstName</button>

    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>

    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant"
        }
      },
      computed: {
        fullName() {
          console.log("computed的fullName中的计算");
          return this.firstName + " " + this.lastName;
        }
      },
      methods: {
        getFullName() {
          console.log("methods的getFullName中的计算");
          return this.firstName + " " + this.lastName;
        },
        changeFirstName() {
          this.firstName = "Coder"
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

描述

methods修改了三次,计算属性只是修改了一次.所以计算属性的更省.

Honeycam 2021-07-23 19-58-29

修改计算属性

修改计算属性_没有效果的例子.html

修改流程

按钮绑定click事件去修改,通过methods方法去修改计算属性中的计算属性.

image-20210723201311169

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <button @click="changeFullName">修改fullName</button>
    <h2>{{fullName}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant"
        }
      },
      computed: {
        // fullName 的 getter方法
        fullName() {
          return this.firstName + " " + this.lastName;
        },
        
      },
      methods: {
        changeFullName() {
          this.fullName = "Coder Why";
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

描述

没有丝毫效果

Honeycam 2021-07-23 20-14-51

修改计算属性_成功例子.html

计算属性的全写形式

image-20210723203810598

语法糖和全写形式

image-20210723204016900

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <button @click="changeFullName">修改fullName</button>
    <h2>{{fullName}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          firstName: "Kobe",
          lastName: "Bryant"
        }
      },
      computed: {
        // fullName的getter和setter方法
        fullName: {
          get: function() {
            console.log('计算属性被__获取了');
            return this.firstName + " " + this.lastName;
          },
          set: function(newValue) {
            console.log('计算属性被__修改了');
            let names = newValue.split(" ");
            this.firstName = names[0];
            this.lastName = names[1];
          }
        }
      },
      methods: {
        changeFullName() {
          this.fullName = "Coder Why";
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

描述

getter方法用上展示,没有getter方法,无法在mustache中渲染显示出来.

setter方法用于修改,没有setter方法,无法修改计算属性

Honeycam 2021-07-23 20-36-17

Watch

基本使用

监听输入的内容.html

监听器的内容

image-20210723225252213

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>监听输入的内容</h2>
    <input type="text" v-model="question">
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          // 侦听question的变化时, 去进行一些逻辑的处理(JavaScript, 网络请求)
          question: "Hello World",
        }
      },
      watch: {
        // question侦听的data中的属性的名称
        // newValue变化后的新值
        // oldValue变化前的旧值
        question: function(newValue, oldValue) {
          console.log("新值: ", newValue, "旧值", oldValue);
          this.displayInput();
        }
      },
      methods: {
        displayInput() {
          console.log(`你输入的内容是:${this.question}`);
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

Honeycam 2021-07-23 22-55-28

深度监听

反例_语法糖形式

无法深度监听的一个例子.html

info对象

image-20210724065031093

监听的部分代码

image-20210724070458977

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{info.name}}</h2>
    <button @click="changeInfo">改变info</button>
    <button @click="changeInfoName">改变info.name</button>
    <button @click="changeInfoNbaName">改变info.nba.name</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: { name: "why", age: 18, nba: {name: 'kobe'} }
        }
      },
      watch: {
        // 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听)
        info(newInfo, oldInfo) {
          console.log("newValue=", newInfo);
          console.log("oldValue=", oldInfo);
        }
      },
      methods: {
        changeInfo() {
          this.info = {name: "kobe"};
        },
        changeInfoName() {
          this.info.name = "kobe";
        },
        changeInfoNbaName() {
          this.info.nba.name = "james";
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

描述

改变整个info对象是能够监听到的,

改变info.name无法监听到,

改变info.nba.name无法监听到.

Honeycam 2021-07-24 06-54-22

反例_全写形式

无法深度监听的例子2.html

监听部分的代码

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{info.name}}</h2>
    <button @click="changeInfo">改变info</button>
    <button @click="changeInfoName">改变info.name</button>
    <button @click="changeInfoNbaName">改变info.nba.name</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: { name: "why", age: 18, nba: {age: 18} }
        }
      },
      watch: {
        info: {
          handler: function(newInfo, oldInfo) {
            console.log(newInfo);
            console.log(oldInfo);
          },
        }
      },
      methods: {
        changeInfo() {
          this.info = {name: "kobe"};
        },
        changeInfoName() {
          this.info.name = "kobe";
        },
        changeInfoNbaName() {
          this.info.nba.age = 20;
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

正例

能够深度监听的例子.html

关键代码:开启深度监听

image-20210724071049053

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{info.name}}</h2>
    <button @click="changeInfo">改变info</button>
    <button @click="changeInfoName">改变info.name</button>
    <button @click="changeInfoNbaName">改变info.nba.name</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: { name: "why", age: 18, nba: {age: 18} }
        }
      },
      watch: {
        info: {
          handler: function(newInfo, oldInfo) {
            console.log('newInfo VVV');
            console.log(newInfo);
            console.log('oldInfor VVV');
            console.log(oldInfo);
          },
          deep:true
        }
      },
      methods: {
        changeInfo() {
          this.info = {name: "kobe"};
        },
        changeInfoName() {
          this.info.name = "kobe";
        },
        changeInfoNbaName() {
          this.info.nba.age = 20;
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

改变info.nba.Age的效果

现在的情况是可以监听到这个info里面对象的变化,但是监听不到里面具体内容的变化,只能检测到改变之后的内容.

Honeycam 2021-07-24 09-12-32

改变info.name

虽然info.name被改变了,mustache中的内容也改变了,但是这个watch只能检测到info.name的变化,无法检测到内容的变化,无法知道以前的info.name的值

Honeycam 2021-07-24 09-17-12

改吗info

改变info可以检测到变化,也可以检测其新旧的内容,只有当整个对象发生变化的时候,才能够检测到其内容.

立即执行

有时候我们希望这个watch在启动的时候就是自动监听一次.

立即监听的例子.html

核心代码

image-20210724093622584

<!DOCTYPE html>
<html lang="zh">
<body>
  <div id="app"></div>
  <template id="my-app">
    <h2>{{info.name}}</h2>
    <button @click="changeInfo">改变info</button>
  </template>
  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: { name: "why", age: 18, nba: {name: 'kobe'} }
        }
      },
      watch: {
        // 深度侦听/立即执行(一定会执行一次)
        info: {
          handler: function(newInfo, oldInfo) {
            console.log('newInfo VVV');
            console.log(newInfo);
            console.log('oldInfo VVV');
            console.log(oldInfo);
          },
          deep: true, // 深度侦听
          immediate: true // 立即执行
        }
      },
      methods: {
        changeInfo() {
          this.info = {name: "kobe"};
        }
      }
    }
    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

描述

刷新一下立马执行一次,也就是相当于有个初始化执行.

Honeycam 2021-07-24 09-35-14

针对对象中某个属性的监听

针对info.name的监听.html

针对info.name的监听

image-20210724203123785

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{info}}</h2>
    <button @click="changeInfo">改变info</button>
    <button @click="changeInfoName">改变info.name</button>
    <button @click="changeInfoNbaAge">改变info.age</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: { name: "why", age: 18 }
        }
      },
      watch: {
        "info.name": function(newName, oldName) {
          console.log('newName='+newName,' oldName='+oldName);
        },
      },
      methods: {
        changeInfo() {
          this.info = {name: "kobe"};
        },
        changeInfoName() {
          this.info.name = "kobe";
        },
        changeInfoNbaAge() {
          this.info.age = 100
        },
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

描述

  • 改变info: 有效果,因为整个对象都发生了变化,所以里面的name属性当然也发生了变化,所以能够检测到
  • 改变info.name:很显然有效果
  • 改变info.age: 发现虽然年龄由18-->100发生了变化,但是没有检测到

Honeycam 2021-07-24 20-34-41

create()创建监听器

在create声明周期函数里面创建监听器.html

关键代码

image-20210724204926369

this.$watch()的三个参数

image-20210724205113255

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>{{info.name}}</h2>
    <button @click="changeInfo">改变info</button>
    <button @click="changeInfoName">改变info.name</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: { name: "why", age: 18 },
        }
      },
      methods: {
        changeInfo() {
          this.info = {name: "kobe" , age:100};
        },
        changeInfoName() {
          this.info.name = "james";
        },
      },
      created() {
        this.$watch("info", function(newInfo, oldInfo) {
          console.log('newInfo VVV');
          console.log(newInfo);
          console.log('oldInfo VVV');
          console.log(oldInfo);
        }, {
          deep: true,
          immediate: true
        })
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

综合案例_书籍购物车

image-20210727080433004

按钮禁用

按钮禁用

<button v-bind:disabled='book.count<=1'>-</button>

问题_多个tbody标签

问题

多个tbody标签

image-20210727095014904

没有tr标签

image-20210727095119407

v-for直接写在tbody里面了

image-20210727095201051

解决

增加tr标签,v-for写在tr标签里面

image-20210727095337065

问题_有时能完全删除,有时不能

问题

问题_有时能够完全删除,有时不能删除

Honeycam 2021-07-27 10-19-00

问题_代码部分问题

__因为我传递的是数组中的book的id,而当我删除这个id的时候,这个id是固定不变的,比如说当我删除最后一个的时候,id=4,然后现在数组中只有一个元素,我传递过来的id等于4,即使id-1=3,也没有下标从三开始的元素,所以无法删除.

image-20210727102401670

答案

解决_传递变化的index来解决问题

image-20210727102703509

输出效果

Honeycam 2021-07-27 10-28-26

最终

index.html

增加数量

image-20210727120924874

减少数量

image-20210727121000685

移出书籍

image-20210727120952984

总价格

image-20210727121020786

<!DOCTYPE html>
<html lang="zh">
<head>
    
    
    
    <link rel="stylesheet" href="style.css">
    <title>书籍购物车</title>
</head>
<body>
    <div id="app"></div>
    <template id="my-app">
        <table>
            <thead>
                <th></th>
                <th>书籍名称</th>
                <th>出版日期</th>
                <th>价格</th>
                <th>购买数量</th>
                <th>操作</th>
            </thead>
            <tbody>
                <tr v-for="(book, index) in books" :key="book.id">
                    <td>{{index+1}}</td>
                <td>{{book.name}}</td>
                <td>{{book.date}}</td>
                <td>{{formatPrice(book.price)}}</td>
                <td>
                    <button 
                        @click='decrease($event,book.id)' 
                        v-bind:disabled='book.count<=1'>-</button>
                    {{book.count}}
                    <button @click='increase($event,book.id)'>+</button>
                </td>
                <td>
                    <button @click='remove($event,index)'>移除</button>
                </td>
                </tr>
            </tbody>
        </table>
        <h2>总价为:{{formatPrice(totalPrice)}}</h2>
    </template>
    <script src="../js/vue.js"></script>
    <script src="./index.js"></script>

</body>
</html>

index.js

Vue.createApp({
    template:'#my-app',
    data() {
        return {
            books: [
                {
                  id: 1,
                  name: '《算法导论》',
                  date: '2006-9',
                  price: 85.00,
                  count: 1
                },
                {
                  id: 2,
                  name: '《UNIX编程艺术》',
                  date: '2006-2',
                  price: 59.00,
                  count: 1
                },
                {
                  id: 3,
                  name: '《编程珠玑》',
                  date: '2008-10',
                  price: 39.00,
                  count: 1
                },
                {
                  id: 4,
                  name: '《代码大全》',
                  date: '2006-3',
                  price: 128.00,
                  count: 1
                }
            ]
        }
    },
    computed:{
      //总价格使用计算属性
      totalPrice(){
        let totalPrice = 0
        for(let book of this.books){
          totalPrice += book.count*book.price
        }
        return totalPrice
      }
    },
    methods: {
        //增加数量
        increase(event,id){
            this.books[id-1].count++
        },
        //减少数量
        decrease(event,id){
            this.books[id-1].count--
        },
        //移出书籍
        remove(event,id){
            console.log(id);
            this.books.splice(id,1)
        },
        //用来给代码加上rmb符号
        formatPrice(price) {
          return "¥" + price;
        }
    },
}).mount('#app')

index.css

table {
    border: 1px solid #e9e9e9;
    border-collapse: collapse;
    border-spacing: 0;
  }
  
  th, td {
    padding: 8px 16px;
    border: 1px solid #e9e9e9;
    text-align: left;
  }
  
  th {
    background-color: #f7f7f7;
    color: #5c6b77;
    font-weight: 600;
  }
  
  .counter {
    margin: 0 5px;
  }
  

输出效果

Honeycam 2021-07-27 12-05-16

v-model

原始方法实现v-model

原始方法实现v-model.html

h2中的数据和input显示的数据绑定

image-20210727190956938

监听input输入事件

image-20210727191053331

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- 1.v-bind value的绑定 2.监听input事件, 更新message的值 -->
    <input type="text" :value="message" @input="inputChange">
    <h2>{{message}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      },
      methods: {
        inputChange(event) {
          this.message = event.target.value;
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

Honeycam 2021-07-27 19-11-44

v-model语法糖实现

v-model本质上是上面方法的语法糖.

v-model语法糖实现数据双向绑定.html

message数据的双向绑定

image-20210727191804574

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <input type="text" v-model="message">
    <h2>{{message}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: "Hello World"
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

绑定基本组件

绑定textarea

v-model绑定textarea.html

关键代码

image-20210728162431843

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- 1.绑定textarea -->
    <label for="intro">
      自我介绍<br>
      <textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea>
    </label>
    <h2>intro: {{intro}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          intro: "Hello World",
        }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

image-20210728162625923

绑定单选框

绑定单选框.html

关键代码

image-20210728165508968

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">

    <!-- 2.checkbox -->
    <!-- 2.1.单选框 -->
    <label for="agree">
      <input id="agree" type="checkbox" v-model="isAgree"> 同意协议
    </label>
    <h2>isAgree: {{isAgree}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          isAgree: false,
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

image-20210728165616426

绑定多选框

绑定多选框.html

多选框

image-20210728165935194

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>
  <template id="my-app">
    <!-- 多选框 -->
    <span>你的爱好: </span>
    <label for="basketball">
      <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
    </label>
    <label for="football">
      <input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
    </label>
    <label for="tennis">
      <input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
    </label>
    <h2>hobbies: {{hobbies}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          hobbies: ["basketball"],
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

image-20210728170035510

绑定单选按钮

绑定单选按钮.html

绑定单选按钮

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- radio -->
    <span>你的爱好: </span>
    <label for="male">
      <input id="male" type="radio" v-model="gender" value="male">男
    </label>
    <label for="female">
      <input id="female" type="radio" v-model="gender" value="female">女
    </label>
    <h2>gender: {{gender}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          gender: "",
        }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

image-20210728170440417

绑定下拉框

绑定下拉框.html

下拉框

image-20210728170717286

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <!-- select -->
    <span>喜欢的水果: </span>
    <select v-model="fruit" multiple size="2">
      <option value="apple">苹果</option>
      <option value="orange">橘子</option>
      <option value="banana">香蕉</option>
    </select>
    <h2>fruit: {{fruit}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          fruit: "orange"
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

image-20210728170808607

修饰符

lazy修饰符

lazy修饰符修饰的输入框.html

lazy模式

image-20210728172107593

普通模式和lazy模式的对比

image-20210728172143464

<!DOCTYPE html>
<html lang="zh">
<body>
  <div id="app"></div>
  <template id="my-app">
    普通模式<input type="text" v-model="message1">
    <h2>{{message1}}</h2>
    <hr>
    lazy模式<input type="text" v-model.lazy="message2">
    <h2>{{message2}}</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message1: "Hello World",
          message2: "Hello World"
        }
      },
    }
    Vue.createApp(App).mount('#app');
  </script>
  
</body>
</html>

输出效果

普通模式

实时输出.

lazy模式

先输入,只有按下enter键后,输入的内容才会显示其上.

Honeycam 2021-07-28 17-22-42

number修饰符

number修饰符.html

对比

image-20210728173641910

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
     <h2>普通的没有number修饰符</h2>
     <input type="text" v-model="message1">
     <h2>{{message1}}</h2>
     <button @click="showType1">查看类型</button>
<hr>
    <h2>有number修饰符</h2>
    <input type="text" v-model.number="message2">
    <h2>{{message2}}</h2>
    <button @click="showType2">查看类型</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message1: "",
          message2: ""
        }
      },
      methods: {
        showType1() {
          console.log(this.message1, typeof this.message1);
        },
        showType2() {
          console.log(this.message2, typeof this.message2);
        },
      }
    }
    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出效果

Honeycam 2021-07-28 17-35-25

trim修饰符

trim修饰符能够去掉输入字符串前面和后面的空格.

trim修饰符.html

有trim和没有trim的对比

image-20210728175335627

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <h2>普通模式,没有trim修饰符</h2>
    <input type="text" v-model="message1">
    <button @click="showResult1">查看结果</button>
    <hr>
    <h2>trim模式,有trim修饰符</h2>
    <input type="text" v-model.trim="message2">
    <button @click="showResult2">查看结果</button>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message1: "",
          message2: ""
        }
      },
      methods: {
        showResult1() {
          console.log(this.message1);
        },
        showResult2() {
          console.log(this.message2);
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>
</body>
</html>

输出

Honeycam 2021-07-28 17-51-47

组件化开发

全局组件

注册一个全局组件.html

组件的注册逻辑

从template中拿到id=component-a的组件内容,然后注册名为component-a的组件

image-20210728180523214

自定义组件的使用

image-20210728180952783

<!DOCTYPE html>
<html lang="zh">
  <body>
    <div id="app"></div>

    <template id="my-app">
      <component-a></component-a>
    </template>

    <template id="component-a">
      <h2>{{title}}</h2>
      <button @click="btnClick">按钮点击</button>
    </template>


    <script src="../js/vue.js"></script>
    <script>
      const app = Vue.createApp({
        template: "#my-app",
      });

      // 使用app注册一个全局组件app.component()
      // 全局组件: 意味着注册的这个组件可以在任何的组件模板中使用
      app.component("component-a", {
        template: "#component-a",
        data() {
          return {
            title: "我是标题",
          }
        },
        methods: {
          btnClick() {
            alert('clicked!')
          },
        },
      });

      app.mount("#app");
    </script>
  </body>
</html>

多个组件

多个全局组件注册.html

两个组件的注册逻辑

image-20210729080010613

<!DOCTYPE html>
<html lang="zh">
  <body>
    <div id="app"></div>

    <template id="my-app">
      <component-a></component-a>
      <hr>
      <component-b></component-b>
    </template>

    <template id="component-a">
      <h1>组件一号</h1>
      <h2>{{title}}</h2>
      <p>{{desc}}</p>
      <button @click="btnClick">按钮点击</button>
    </template>

    <template id="component-b">
      <h1>组件二号</h1>
      <input type="text" v-model="message"/>
      <h2>{{message}}</h2>
    </template>


    <script src="../js/vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
      };

      const app = Vue.createApp(App);

      // 使用app注册一个全局组件app.component()
      app.component("component-a", {
        template: "#component-a",
        data() {
          return {
            title: "我是标题",
            desc: "我是内容, 哈哈哈哈哈",
          };
        },
        methods: {
          btnClick() {
            console.log("按钮的点击");
          },
        },
      });

      app.component("component-b", {
        template: "#component-b",
        data() {
          return {
            message: "Hello World",
          };
        },
      });

      app.mount("#app");
    </script>
  </body>
</html>

输出

image-20210729080240755

组件的命名

组件命名方法.html

注册组件大驼峰,引用组件下划线

image-20210729074806222

<!DOCTYPE html>
<html lang="zh">
  <body>
    <div id="app"></div>

    <template id="my-app">
      <component-name></component-name>
    </template>

    <template id="component-c">
      <h2>ComponentC</h2>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
      };
      const app = Vue.createApp(App);

      // 使用app注册一个全局组件app.component()
      app.component('ComponentName', {
        template: "#component-c"
      })

      app.mount("#app");
    </script>
  </body>
</html>

局部组件

局部组件.html

局部组件的注册逻辑

image-20210729081121846

全局组件和局部组件对比

image-20210729081307849

<!DOCTYPE html>
<html lang="zh">
<body>
  
  <div id="app"></div>

  <template id="my-app">
    <component-a></component-a>
  </template>

  <template id="component-a">
    <h2>我是组件A</h2>
  </template>

  <script src="../js/vue.js"></script>
  <script>
    //组件A的对象内容引入
    const ComponentA = {
      template: "#component-a"
    }

    const App = {
      template: '#my-app',
      components: {
        // key: 组件名称   value: 组件对象
        ComponentA: ComponentA
      },
      data() {
        return {
          message: "Hello World"
        }
      }
    }

    const app = Vue.createApp(App);
    app.mount('#app');
  </script>
</body>
</html>

基于Vue CLI组件化开发

关于Vue CLI的使用,在webpack的学习笔记中有详细的创建过程.

使用Vue CLI创建项目

命令行

vue create 03_learn_component_2

输出

自此一个使用vue脚手架的创建的就是已经创建好了

image-20210831160117984

image-20210831160535588

image-20210831160607981

创建一个总的组件

为了便于学习,不需要每次都是创建一个新的项目,我们现在把这个src文件夹的文件除了main.js,其他的文件都是删除,然后新建一个文件夹,用来存放第一个组件.

image-20210831161305804

安装下面这款插件:

image-20210831161820422

在App.vue中输入vbase之后,会自动生成如下代码:

image-20210831162124359

App.vue

<template>
<div id="app">
    <div class="myheader">
        <h2>Header</h2>
        <h2>Navebar</h2>
    </div>

    <div class="main">
        <h2>Banner</h2>
        <ul>
            <li>product info 1</li>
            <li>product info 2</li>
            <li>product info 3</li>
            <li>product info 4</li>
            <li>product info 5</li>
        </ul>
    </div>

    <div class="footer">
        <h2>Footer</h2>
    </div>
</div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

main.js

import { createApp } from 'vue'
import App from './01_组件的拆分和嵌套/App.vue'

createApp(App).mount('#app')

输出

image-20210831163800019

组件拆分

在分别创建另外三个组件MyHeader.vue MyMain.vue MyFooter.vue ,然后将App.vue中组件分别拆分到三个组件当中.

image-20210831164320101

拆分过后需要在App.vue中引入其他组件:

App.vue中引入其他组件

三个步骤

导入,注册和使用

image-20210831164711924

<template>
<div id="app">
    <my-header></my-header>
    <my-main></my-main>
    <my-footer></my-footer>
</div>
</template>

<script>
    import MyHeader from './MyHeader.vue'
    import MyMain from './MyMain.vue'
    import MyFooter from './MyFooter.vue'


    export default {
        components:{
            MyHeader,
            MyMain,
            MyFooter
        }
        
    }
</script>

<style scoped>

</style>

输出

image-20210831164753155

进一步组件拆分

MyMain.vue进一步拆分成两个组件

image-20210831165738577

MyMain.vue

组件注册错误

image-20210831165915903

<template>
  <div class="main">
    <my-main-banner></my-main-banner>
    <my-main-product-list></my-main-product-list>
  </div>
</template>

<script>
import MyMainBanner from "./MyMainBanner.vue";
import MyMainProductList from "./MyMainProductList.vue";

export default {
  components: {
    MyMainBanner,
    MyMainProductList,
  },
};
</script>

<style lang="scss" scoped>
</style>

输出OK了

image-20210831170005248

组件的CSS作用域

App.vue

<template>
  <div>
    <h2>this is App.vue</h2>
    <hello-vue></hello-vue>
  </div>
</template>

<script>
import HelloVue from "./HelloVue.vue";
export default {
  components: {
    HelloVue,
  },
};
</script>

<style scoped>
 h2{
     color: green;
 }
</style>

HelloVue.vue

<template>
    <h2>this HelloVue.vue</h2>
</template>

<script>
    export default {
        
    }
</script>

<style  scoped>
    /* h2{
        color:red
    } */
</style>

输出

image-20210831172113241

去掉HelloVue.vue的注释

<template>
    <h2>this HelloVue.vue</h2>
</template>

<script>
    export default {
        
    }
</script>

<style  scoped>
    h2{
        color:red
    }
</style>

输出

紫色被注释掉了

image-20210831172516980

去掉去掉App.vue的style标签的scoped

image-20210831172753191

<template>
  <div>
    <h2>this is App.vue</h2>
    <hello-vue></hello-vue>
  </div>
</template>

<script>
import HelloVue from "./HelloVue.vue";
export default {
  components: {
    HelloVue,
  },
};
</script>

<style >
 h2{
     color: green;
 }
</style>

输出

达到了期望的效果

image-20210831172815880

总结

上面的实验说命令,这个当这个子组件没有自己的样式时候,父组件的样式会作用于子组件.我们希望这个父组件的样式就是仅仅作用域父组件,而作用域子组件,一般就是:尽量不适用html标签来作用样式,使用类名,实际开发就是使用的类

组件通信

组件通信中使用最为广泛的就是父子组件间的通信:

image-20210831173838262

父传子

image-20210831173929528

简而言之

子组件注册属性,然后父组件使用属性,也就是给这些属性赋值.

image-20210831174027406

属性形式_字符串数组

逻辑图

image-20210831175335122

子组件注册属性:ShowMsg.vue

<template>
    <div>
        <h2>{{name}} : {{age}}</h2>
    </div>
</template>

<script>
    export default {
        props:['name','age']
    }
</script>

<style lang="scss" scoped>

</style>

父组件给属性赋值:App.vue

<template>
    <div>
        <show-msg name="zhuo" age=10></show-msg>
    </div>
</template>

<script>
    import ShowMsg from './ShowMsg.vue'
    export default {
        components:{
            ShowMsg
        }
    }
</script>

<style scoped>

</style>

输出

image-20210831174715945


除了直接赋值的方式也可以使用v-bind动态绑定属性:

image-20210831175924024

属性形式_对象

ShowMsg.vue

<template>
    <div>
        <h2>{{name}} : {{age}}</h2>
    </div>
</template>

<script>
    export default {
        props:{
            name:String,
            age:Number
        }
    }
</script>

<style lang="scss" scoped>

</style>

App.vue

<template>
    <div>
        <show-msg name="zhuo" age=10></show-msg>
        <show-msg :name="name" :age=age></show-msg>
    </div>
</template>
<script>
    import ShowMsg from './ShowMsg.vue'
    export default {
        components:{
            ShowMsg
        },
        data() {
            return {
                name:'bing',
                age:12
            }
        },
    }
</script>

<style scoped>

</style>

输出

image-20210831215627449


required属性

image-20210831220155283

default属性

发现没传给这个age属性赋值,于是就使用默认值.

image-20210831220555287

其他写法

image-20210831221007262

image-20210831221131682

为什么对象的默认值必须通过一个工厂函数获取?

image-20210831221618979

非Prop的Attribute

image-20210831221938712

单根结点

传递一个没有定义的属性怎么样

image-20210831222629174

如何把属性绑定到目标的标签上面

image-20210831223536314

image-20210831222930778

如何去掉这个子组件根元素的属性

image-20210831223600634

image-20210831223428096

多根结点

image-20210831223745491

子传父

image-20210901103646154

注册事件_数组形式

计数器案例_无参数传递

代码逻辑

image-20210901105621090

父组件App.vue

<template>
    <div>
        <h2>current number:{{counter}}</h2>
        <counter-operation @add="addOne" @sub="subOne"></counter-operation>
    </div>
</template>

<script>
    import CounterOperation from './CounterOperation.vue'
    export default {
        components:{
            CounterOperation
        },
        data() {
            return {
                counter:0
            }
        },
        methods: {
            addOne(){
                this.counter++
            },
            subOne(){
                this.counter--
            }
        },
    }
</script>

<style scoped>

</style>

子组件:CounterOperation.vue

<template>
    <div>
        <button @click="addOne">+1</button>
        <button @click="subOne">-1</button>
    </div>
</template>

<script>
    export default {
        emits:['add','sub'],
        methods: {
            addOne(){
                console.log("+1");
                this.$emit('add')
            },
            subOne(){
                console.log("-1");
                this.$emit('sub')
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>

输出

image-20210901105129239

计数器案例_传递参数

代码逻辑

image-20210901113015891

父组件App.vue

<template>
    <div>
        <h2>current number:{{counter}}</h2>
        <counter-operation 
                            @add="addOne" 
                            @sub="subOne"
                            @addN="addN">
        </counter-operation>

    </div>
</template>

<script>
    import CounterOperation from './CounterOperation.vue'
    export default {
        components:{
            CounterOperation
        },
        data() {
            return {
                counter:0
            }
        },
        methods: {
            addOne(){
                this.counter++
            },
            subOne(){
                this.counter--
            },
            addN(num){
                console.log(num);
                this.counter += num
            }
        },
    }
</script>

<style scoped>

</style>

子组件CounterOperation.vue

<template>
    <div>
        <h2>current number:{{counter}}</h2>
        <counter-operation 
                            @add="addOne" 
                            @sub="subOne"
                            @addN="addN">
        </counter-operation>

    </div>
</template>

<script>
    import CounterOperation from './CounterOperation.vue'
    export default {
        components:{
            CounterOperation
        },
        data() {
            return {
                counter:0
            }
        },
        methods: {
            addOne(){
                this.counter++
            },
            subOne(){
                this.counter--
            },
            addN(num){
                console.log(num);
                this.counter += num
            }
        },
    }
</script>

<style scoped>

</style>

输出效果

image-20210901113112689

注册事件_对象形式

上面的例子中都是使用数组形式>image-20210901113653680,对象的形式常用来进行参数检查.

子组件CounterOperation.vue

对象形式注册事件

主要是为了检查参数

image-20210901114756881

<template>
    <div>
        <button @click="addOne">+1</button>
        <button @click="subOne">-1</button>

        <input type="number" v-model.number="num">
        <button @click="emitAddN">+N</button>
    </div>
</template>

<script>
    export default {
        // emits:['add','sub','addN'],
        emits:{
            add:null,
            sum:null,
            addN:num =>{
                console.log(num);
                if(num > 10){
                    return true
                }else{
                    return false
                }
            }
        },
        data() {
            return {
                num:0
            }
        },
        methods: {
            addOne(){
                console.log("+1");
                this.$emit('add')
            },
            subOne(){
                console.log("-1");
                this.$emit('sub')
            },
            emitAddN(){
                console.log("+n");
                this.$emit('addN',this.num)
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>

输出效果

当我们传递过去的参数小于10的时候就会出现警告,只有大于10的时候,才能够正常运行

Honeycam 2021-09-01 11-43-26

组件通信案例

输出效果

Honeycam 2021-09-01 13-09-10

代码逻辑

image-20210901131807945

父组件:App.vue

<template>
    <div>
        <tab-control :titles="titles"></tab-control>
    </div>
</template>

<script>
 import TabControl from './TabControl.vue'
    export default {
        
        components:{
            TabControl
        },
        data() {
            return {
                titles:['衣服','鞋子','帽子']
            }
        }
    }
</script>

<style  scoped>

</style>

子组件:TabControl.vue

<template>
    <div class="tab-control">
        <div class="tab-control-item"
             :class="{active:currentIndex === index}"
             v-for='(title,index) in titles' 
             :key="title"
             @click="itemClick(index)">
            <span>{{title}}</span>
        </div>
    </div>
</template>

<script>
   
    export default {
        data(){
            return{
                currentIndex:0
            }
        },
        props:{
            titles:{
                type:Array,
                default(){
                    return ['title1','title2','title3']
                }
            }
        },
        methods: {
            itemClick(index){
                this.currentIndex = index
            }
        },
        
    }
</script>

<style scoped>
  .tab-control {
    display: flex;
  }
  .tab-control-item {
      flex: 1;
      text-align: center;
  }
  .tab-control-item.active {
      color: red;
  }
  .tab-control-item.active span {
      border-bottom:5px red solid;
      padding: 5px 10px;
  }

</style>

非父子组件之间的通信

Provide/Inject

image-20210901151152804

背景

代码逻辑:爷爷和孙子

image-20210901152434689

通信

父组件:App.vue

<template>
    <div>
        <home></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    export default {
        components:{
            Home
        },
        provide:{
            name:'bing',
            age:100
        }
    }
</script>

<style lang="scss" scoped>

</style>

儿子组件:Home.vue

<template>
    <div>
        <home-content></home-content>
    </div>
</template>

<script>
    import HomeContent from "./HomeContent.vue"
    export default {
        components:{
            HomeContent
        }
    }
</script>

<style lang="scss" scoped>

</style>

孙子组件:HomeContent.vue

<template>
    <div>
        this is homeContent--{{name}}--{{age}}
    </div>
</template>

<script>
    export default {
        inject:['name','age']
    }
</script>

<style lang="scss" scoped>

</style>

长辈组件和子孙组件间的通信逻辑图

image-20210901153236655

App.vue

image-20210901153305880

<template>
    <div>
        <home></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    export default {
        components:{
            Home
        },
        provide:{
            name:'bing',
            age:100
        }
    }
</script>

<style lang="scss" scoped>

</style>

HomeContent.vue

image-20210901153337898

<template>
    <div>
        this is homeContent--{{name}}--{{age}}
    </div>
</template>

<script>
    export default {
        inject:['name','age']
    }
</script>

<style lang="scss" scoped>

</style>

问题_长辈组件在自己中是否使用provide中的数据

很显然是不能使用

image-20210901154018148

问题_长辈组件如何拿到自己data中的数据信息然后传给子孙组件

问题背景

image-20210901154728762

很显然,没有拿到.

原因分析

this的指向很是关键,我们知道在一个函数中,this的指向就是这个函数作用域,image-20210901154906107作用域里面的this.

image-20210901155113416,所以报错

解决方案

把provide写成函数形式,就是把这个作用域现在这个函数里面.

App.vue

<template>
    <div>
        <home></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    console.log(this);
    export default {
        components:{
            Home
        },
        // provide:{
        //     name:'bing',
        //     age:100,
        //     namesLength:this.names.length
        // },
        provide(){
            return {
                name:'bing',
                age:100,
                namesLength:this.names.length
            }
        },
        data(){
            return{
                names:['Alice','Bruce','Celina']
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>

子孙组件:HomeContent.vue

<template>
    <div>
        this is homeContent--{{name}}--{{age}}--{{namesLength}}
    </div>
</template>

<script>
    export default {
        inject:['name','age','namesLength']
    }
</script>

<style lang="scss" scoped>

</style>

输出

image-20210901160151645

问题_长辈组件传给子孙组件的是动态数据吗

上面的例子中,尽管我们给这个数组增加了内容,数组的长度发生了变化,但是这个传递被子孙组件的数据依然没有改变,其实很好理解,这个就是在第一次就是当成一个普通的值赋了过去.

image-20210901160748129

解决方案_动态传递数据

使用计算属性.

输出效果

数组的长度增加,这个传递给子孙组件的数据长度也是增加

Honeycam 2021-09-01 16-14-40

长辈组件:App.vue

关键代码

image-20210901161605116

<template>
    <div>
        <home></home>
        <button @click="addName">array push</button>
    </div>
</template>

<script>
    import Home from './Home.vue'
    import {computed} from 'vue'
    export default {
        components:{
            Home
        },
        // provide:{
        //     name:'bing',
        //     age:100,
        //     namesLength:this.names.length
        // },
        provide(){
            return {
                name:'bing',
                age:100,
                namesLength:computed(()=>this.names.length)
            }
        },
        data(){
            return{
                names:['Alice','Bruce','Celina']
            }
        },
        methods: {
            addName(){
                console.log(this.names);
                this.names.push('bing')
            }
        },
    }
</script>

<style lang="scss" scoped>

</style>

警告

injected property "namesLength" is a ref 
and will be auto-unwrapped and no longer needs `.value` in the next minor release. 

To opt-in to the new behavior now, set `app.config.unwrapInjectedRef = true` 
(this config is temporary and will not be needed in the future.)

image-20210901161652837

警告的原因:

image-20210901162037542

子孙组件:HomeContent.vue

<template>
    <div>
        this is homeContent--{{name}}--{{age}}--{{namesLength}} //警告出现,提示会自动解包
      	this is homeContent--{{name}}--{{age}}--{{namesLength.value}}//手动解包
    </div>
</template>

<script>
    export default {
        inject:['name','age','namesLength']
    }
</script>

<style lang="scss" scoped>

</style>

Mitt全局事件总线

image-20210901193721449

https://github.com/developit/mitt

https://github.com/scottcorgan/tiny-emitter

安装mitt库

npm install mitt -D

背景

现在我想要在[image-20210901195441833]的事件

image-20210901195401987

代码逻辑

image-20210901200813961

输出效果

image-20210901200840207

About.vue

image-20210901200936233

<template>
    <div>
        this is about.vue
        <button @click="btnClick">about click</button>
    </div>
</template>

<script>
    import emitter from './utils/eventBus'
    export default {
        methods: {
            btnClick(){
                console.log('About.vue is clicked')
                emitter.emit('aboutClicked',{name:'bing',age:20})
            }
        },
    }
</script>

<style lang="scss" scoped>

</style>

HomeContent.vue

image-20210901200949971

<template>
    <div>
        this is home-content
        <hr>
    </div>
</template>

<script>
    import emitter from './utils/eventBus'
    export default {
        created() {
            emitter.on('aboutClicked',(info)=>{
                console.log(info);
            })
        },
    }
</script>

<style lang="scss" scoped>

</style>

多事件监听

About.vue

发送两个事件

image-20210901201744299

<template>
    <div>
        this is about.vue
        <button @click="btnClick">about click</button>
        <button @click="btnClick1">about click</button>
    </div>
</template>

<script>
    import emitter from './utils/eventBus'
    export default {
        methods: {
            btnClick(){
                emitter.emit('aboutClicked',{name:'bing',age:20})
            },
            btnClick1(){
                emitter.emit('aboutClicked1',{name:'bing1',age:201})
            }
        },
    }
</script>

<style lang="scss" scoped>

</style>

HomeContent.vue

监听所有事件

打印事件名称和传递过来的参数

<template>
    <div>
        this is home-content
        <hr>
    </div>
</template>

<script>
    import emitter from './utils/eventBus'
    export default {
        created() {
            // emitter.on('aboutClicked',(info)=>{
            //     console.log(info);
            // })
            emitter.on('*',(eventName,info) => {
                console.log(eventName);
                console.log(info);
            })
        },
    }
</script>

<style lang="scss" scoped>

</style>

输出效果

Honeycam 2021-09-01 20-16-40

事件取消

image-20210901202032989

插槽

image-20210902080325326

image-20210902080334501

比如说上面的NavBar的共性就是都是具有三个部分,左中右,不同就是左中右三个区域可以显示三个不同的内容.

image-20210902080400107

image-20210902080406632

插槽的基本使用

代码逻辑

有插入的内容显示插入的内容,没有插入的内容显示默认的内容

image-20210902083354620

App.vue

<template>
    <div>
        <my-slot-cpn>
            <h4>我是插入插槽中的内容</h4>
        </my-slot-cpn>

        <my-slot-cpn>
            <button>我是插入插槽中的按钮</button>
        </my-slot-cpn>
        
        <my-slot-cpn></my-slot-cpn>
    </div>
</template>

<script>
    import MySlotCpn from './MySlotCpn.vue'
    export default {
        components:{
            MySlotCpn
        }
    }
</script>

<style lang="scss" scoped>

</style>

MySlotCpn.vue

<template>
    <div>
        <h3>组件开始VVVVVV</h3>
        <slot>我是插槽中的默认内容</slot>
        <h3>组件结束YYYYYY</h3>
        <hr>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style lang="scss" scoped>

</style>

多个插槽

一个内容插入多个插槽

我把一个元素插入多个插槽,发现是一个元素每个插槽都会被插入一次.

image-20210902084125039

三个元素插入三个插槽

我们三个元素插入三个插槽,是这个三个元素作为一个整体插入三个插槽,因此显示了9个,我们其实有点希望三个分别插入三个插槽,这个需要下面的具名插槽来实现.

image-20210902084723531

具名插槽

代码逻辑

通过名字来达到分别填入的效果

image-20210902090232359

App.vue

<template>
    <div>
        <my-slot-cpn>
            <template v-slot:slot1>
                <h6>我是插入 第一个 插槽中的内容</h6>
            </template>
            <template v-slot:slot2>
                <button>我是想插入 第二个 插槽中的按钮</button>
            </template> 
            <template v-slot:slot3>
                <h5>我是想插入 第三个 插槽的内容</h5>
            </template>
        </my-slot-cpn>
        <my-slot-cpn></my-slot-cpn>
    </div>
</template>

<script>
    import MySlotCpn from './MySlotCpn.vue'
    export default {
        components:{
            MySlotCpn
        }
    }
</script>

<style lang="scss" scoped>

</style>

MySlotCpn.vue

<template>
    <div>
        <h3>组件开始VVVVVV</h3>
        <slot name="slot1"><h5>我是插槽中的默认内容1</h5></slot>
        <slot name="slot2"><h5>我是插槽中的默认内容2</h5></slot>
        <slot name="slot3"><h5>我是插槽中的默认内容3</h5></slot>
        <h3>组件结束YYYYYY</h3>
        <hr>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style lang="scss" scoped>

</style>

动态具名插槽

代码逻辑

先把三个插槽的名字通过父传子的形式传递过去,然后再使用时候使用其对应的名字就是可以了.

image-20210902093931596

具名插槽的缩写

渲染作用域

image-20210902103947213

image-20210902103958919

作用域插槽

image-20210902104216458

代码逻辑

image-20210902113727678

App.vue

image-20210902113935569

image-20210902114006724

image-20210902114023685

<template>
    <div>
        <show-name :names="names"></show-name>
        <hr>

        **v-slot="slotProps" and 默认插槽的省略写法,省略template**
        <show-name-slot :names="names" v-slot="slotProps">
               <button>{{slotProps.index}}-{{slotProps.item}}</button>
        </show-name-slot>

        <hr>
        **v-slot="slotProps 默认插槽的省略写法"**
        <show-name-slot :names="names" >
            <template v-slot="slotProps">
                <button>{{slotProps.index}}-{{slotProps.item}}</button>
            </template>
        </show-name-slot>
        <hr>
        **v-slot:default="slotProps" 默认插槽的使用**
        <show-name-slot :names="names" >
            <template v-slot:default="slotProps">
                <button>{{slotProps.index}}-{{slotProps.item}}</button>
            </template>
        </show-name-slot>
        <hr>
        **v-slot:juming="slotProps" 具名插槽的使用**
        <show-name-slot :names="names" >
            <template v-slot:juming="slotProps">
                <button>{{slotProps.index}}-{{slotProps.item}}</button>
            </template>
        </show-name-slot>

    </div>
</template>

<script>
    import ShowName from './ShowName.vue'
    import ShowNameSlot from './ShowNameSlot.vue'
    export default {
        components:{
            ShowName,
            ShowNameSlot
        },
        data() {
            return {
                names:['Alice','Bruce','Celina','Dora']
            }
        },
    }
</script>

<style scoped>

</style>

ShowName.vue

<template>
    <div>
        <ul>
            <li  
                v-for="(name,index) in names" 
                :key="index">
            {{index}}--{{name}}</li>
        </ul>
    </div>
</template>

<script>
    export default {
        props:{
            names:{
                type:Array,
                default:()=>[]
            }
        }
    }
</script>

<style scoped>

</style>

ShowNameSlot.vue

image-20210902114053256

<template>
    <div>
        <template v-for="(item,index) in names">
            <slot :item='item' :index='index'>
            </slot>

            <slot name='juming' :item='item' :index='index'></slot>
        </template>
    </div>
</template>

<script>
    export default {
        props:{
            names:{
                type:Array,
                default:()=>[]
            },
        }
    }
</script>

<style scoped>

</style>

总结

image-20210902114310638

image-20210902114323633

动态组件

按钮切换案例

输出效果

Honeycam 2021-09-02 17-53-01

App.vue

<template>
    <div>
        <button 
                v-for="tab in tabs" 
                :key="tab"
                :class="{active:currentTab === tab}"
                @click="btnClick(tab)">
                {{tab}}
        </button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                tabs:['home','about','category'],
                currentTab:'home'
            }
        },
        methods: {
            btnClick(tab){
                this.currentTab = tab
            }
        },
    }
</script>

<style scoped>
    .active {
        color: red;
    }
</style>

按钮切换案例-动态组件

输出效果

Honeycam 2021-09-02 18-02-30

代码逻辑图

image-20210902181613344

App.vue

关键代码

image-20210902181947529

<template>
    <div>
        <button 
                v-for="tab in tabs" 
                :key="tab"
                :class="{active:currentTab === tab}"
                @click="btnClick(tab)">
                {{tab}}
        </button>
        <component :is="currentTab"></component>
    </div>
</template>

<script>
    import Home from './Home.vue'
    import About from './About.vue'
    import Category from './Category.vue'
    export default {
        components:{
            Home,
            About,
            Category
        },
        data() {
            return {
                tabs:['home','about','category'],
                currentTab:'home'
            }
        },
        methods: {
            btnClick(tab){
                this.currentTab = tab
            }
        },
    }
</script>

<style scoped>
    .active {
        color: red;
    }
</style>

Home.vue

<template>
    <div>
        <h3>Home.vue</h3>
    </div>
</template>

<script>
    export default {
        name:'home'
    }
</script>

<style scoped>

</style>

About.vue

<template>
    <div>
        <h3>About.vue</h3>
    </div>
</template>

<script>
    export default {
        name:'about'
    }
</script>

<style scoped>

</style>

Category.vue

<template>
    <div>
        <h3>Category.vue</h3>
    </div>
</template>

<script>
    export default {
        name:'category'
    }
</script>

<style scoped>

</style>

动态组件传递参数&发送事件

参数传递

输出效果

Honeycam 2021-09-02 19-17-44

代码逻辑图

image-20210902192346066

App.vue

<template>
    <div>
        <button 
                v-for="tab in tabs" 
                :key="tab"
                :class="{active:currentTab === tab}"
                @click="btnClick(tab)">
                {{tab}}
        </button>
        <component :is="currentTab"
                    name='bing'
                    :age='13'>
        </component>
        <hr>
        <home name='bingbing' :age='12'></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    import About from './About.vue'
    import Category from './Category.vue'
    export default {
        components:{
            Home,
            About,
            Category
        },
        data() {
            return {
                tabs:['home','about','category'],
                currentTab:'home'
            }
        },
        methods: {
            btnClick(tab){
                this.currentTab = tab
            }
        },
    }
</script>

<style scoped>
    .active {
        color: red;
    }
</style>

Home.vue

<template>
    <div>
        <h3>Home.vue</h3>
        <h4>{{name}}--{{age}}</h4>
    </div>
</template>

<script>
    export default {
        name:'home',
        props:{
            name:{
                type:String,
                default:''
            },
            age:{
                type:Number,
                default:0
            }
        }
    }
</script>

<style scoped>

</style>

如何传递的参数由默认的字符串类型转换成数字类型

image-20210902191643082

监听事件

和上面的参数传递一样,都是写在component的属性里面.

App.vue

image-20210902192937747

<template>
    <div>
        <button 
                v-for="tab in tabs" 
                :key="tab"
                :class="{active:currentTab === tab}"
                @click="btnClick(tab)">
                {{tab}}
        </button>
        <component :is="currentTab"
                    name='bing'
                    :age='13'
                    @homeClick='homeClick'>
        </component>
        <hr>
        <home name='bingbing' :age='12'></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    import About from './About.vue'
    import Category from './Category.vue'
    export default {
        components:{
            Home,
            About,
            Category
        },
        data() {
            return {
                tabs:['home','about','category'],
                currentTab:'home'
            }
        },
        methods: {
            btnClick(tab){
                this.currentTab = tab
            },
            homeClick(){
                console.log('home.vue is clicked');
            }
        },
    }
</script>

<style scoped>
    .active {
        color: red;
    }
</style>

Home.vue

<template>
    <div @click="divClick">
        <h3>Home.vue</h3>
        <h4>{{name}}--{{age}}</h4>
    </div>
</template>

<script>
    export default {
        name:'home',
        props:{
            name:{
                type:String,
                default:''
            },
            age:{
                type:Number,
                default:0
            }
        },
        emits:['homeClick'],
        methods: {
            divClick(){
                this.$emit('homeClick')
            }
        },
    }
</script>

<style scoped>

</style>

keep-alive(缓存组件)

背景案例

在About页面增加一个按钮计数器.

输出效果

现在我们在About页面添加按钮计数器,然后切换页面我们发现这个计数的数值就是清零了.清零的原因是这个当我们切换界面的时候,这个界面实际上被销毁了,也就是生命周期结束了.

我们其实有点希望这个数值就是保存下来.

Honeycam 2021-09-02 19-36-06

App.vue

<template>
    <div>
        <button 
                v-for="tab in tabs" 
                :key="tab"
                :class="{active:currentTab === tab}"
                @click="btnClick(tab)">
                {{tab}}
        </button>
        <component :is="currentTab"
                    name='bing'
                    :age='13'
                    @homeClick='homeClick'>
        </component>
    </div>
</template>

<script>
    import Home from './Home.vue'
    import About from './About.vue'
    import Category from './Category.vue'
    export default {
        components:{
            Home,
            About,
            Category
        },
        data() {
            return {
                tabs:['home','about','category'],
                currentTab:'home'
            }
        },
        methods: {
            btnClick(tab){
                this.currentTab = tab
            },
            homeClick(){
                console.log('home.vue is clicked');
            }
        },
    }
</script>

<style scoped>
    .active {
        color: red;
    }
</style>

About.vue

<template>
    <div>
        <h3>About.vue</h3>
        <button @click="increase">{{counter}}</button>
    </div>
</template>

<script>
    export default {
        name:'about',
        data() {
            return {
                counter:0
            }
        },
        methods: {
            increase(){
                this.counter ++
            }
        },
    }
</script>

<style scoped>

</style>

基本使用案例

输出效果

现在即使是切换界面,这个计数器按钮的数值仍然能够保持不变

Honeycam 2021-09-02 19-42-05

仅仅是增加了一行代码

image-20210902194412552

App.vue

image-20210902194306147

<template>
    <div>
        <button 
                v-for="tab in tabs" 
                :key="tab"
                :class="{active:currentTab === tab}"
                @click="btnClick(tab)">
                {{tab}}
        </button>
        <keep-alive>
            <component :is="currentTab"
                    name='bing'
                    :age='13'
                    @homeClick='homeClick'>
            </component>
        </keep-alive>
    </div>
</template>

<script>
    import Home from './Home.vue'
    import About from './About.vue'
    import Category from './Category.vue'
    export default {
        components:{
            Home,
            About,
            Category
        },
        data() {
            return {
                tabs:['home','about','category'],
                currentTab:'home'
            }
        },
        methods: {
            btnClick(tab){
                this.currentTab = tab
            },
            homeClick(){
                console.log('home.vue is clicked');
            }
        },
    }
</script>

<style scoped>
    .active {
        color: red;
    }
</style>

keep-alive的属性

image-20210902194617101

include

输出效果

about界面的计数器是能够保持不变的,category界面的计数器不能

Honeycam 2021-09-02 19-51-57

App.vue

关键代码

image-20210902195336274

<template>
    <div>
        <button 
                v-for="tab in tabs" 
                :key="tab"
                :class="{active:currentTab === tab}"
                @click="btnClick(tab)">
                {{tab}}
        </button>
        <keep-alive include="about">
            <component :is="currentTab"
                    name='bing'
                    :age='13'
                    @homeClick='homeClick'>
            </component>
        </keep-alive>
    </div>
</template>

<script>
    import Home from './Home.vue'
    import About from './About.vue'
    import Category from './Category.vue'
    export default {
        components:{
            Home,
            About,
            Category
        },
        data() {
            return {
                tabs:['home','about','category'],
                currentTab:'home'
            }
        },
        methods: {
            btnClick(tab){
                this.currentTab = tab
            },
            homeClick(){
                console.log('home.vue is clicked');
            }
        },
    }
</script>

<style scoped>
    .active {
        color: red;
    }
</style>

Webpack的代码分包&异步组件

为什么需要分包

image-20210902215301849

如何分包

image-20210902215345581

默认的打包情况

image-20210902220448375


现在我们想要把自己写的某些代码也是单独打包,怎么办呢?

JS中的代码分包

代码逻辑图

代码的引用逻辑

image-20210902222212366

image-20210902221725155

main.js

image-20210902222432042

import { createApp } from 'vue'
import App from './12_异步组件的使用/App.vue'

//以前的引入方式和使用方法
// import {sum} from './12_异步组件的使用/utils/math'
// console.log(sum(100,200));

//现在的引入方式和使用方法
import('./12_异步组件的使用/utils/math').then(res => {
    console.log(res.sum(200,300));
})

createApp(App).mount('#app')

Vue组件中实现异步组件(代码分包)

工厂函数形式

新旧导入组件的对比

image-20210902223453793

App.vue

关键代码

image-20210902223542237

<template>
    <div>
        <home></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    //以前的导入方式
    // import AsyncCategory from './AsyncCategory.vue'

    //现在的导入方式
    ////首先导入一个vue中的函数
    import {defineAsyncComponent} from 'vue'
    const AsyncCategory = defineAsyncComponent(() => import('./AsyncCategory.vue'))
    
    export default {
        components:{
            Home,
            AsyncCategory
        }
    }
</script>

<style scoped>

</style>

对象形式

其实使用对象形式主要是为了更多的属性.

导入型的对比

image-20210902224129100

对象形式中常用的属性

image-20210902224236801

异步组件和suspense

image-20210902224537657

输出效果

Honeycam 2021-09-02 23-07-11

App.vue

关键代码

image-20210902230950477

<template>
    <div>
        <suspense>
            
            <template #default>
               <async-category></async-category>
            </template>

            <template #fallback>
               <home></home>
            </template>

        </suspense>
        
    </div>
</template>

<script>
    import Home from './Home.vue'

    //现在的导入方式-工厂函数形式
    ////首先导入一个vue中的函数
    import {defineAsyncComponent} from 'vue'
    const AsyncCategory = defineAsyncComponent(() => import('./AsyncCategory.vue'))

    export default {
        components:{
            Home,
            AsyncCategory,
        }
    }
</script>

<style scoped>

</style>

$refs的使用

vue中的DOM操作

不推荐使用document.getElement...方法或jQuery等等来操作Vue中的dom元素,因为vue中已经帮我们封装好了操作dom元素的函数,我在使用以前的方法,就是显得很笨拙.

image-20210902231209507

获取元素

输出效果

Honeycam 2021-09-02 23-20-37

App.vue

关键代码

image-20210902232153303

<template>
    <div>
        <h2 ref="titleH2">我是被获取的元素</h2>
        <button @click="getElement">获取自己中的元素</button>
    </div>
</template>

<script>
    export default {
        methods: {
            getElement(){
                console.log(this.$refs.titleH2);
            }
        },
    }
</script>

<style scoped>

</style>

获取组件

输出效果

Honeycam 2021-09-02 23-43-49

代码的数据流

也就是说通过$ref不仅仅可以或这个组件,还可以获取这个组件里面的东西.

image-20210902234139722

App.vue

<template>
    <div>
        <h2 ref="titleH2">我是被获取的元素</h2>
        <button @click="getElement">获取自己中的元素</button>
        <hr>
        <home ref="homeCpn"></home>
        <button @click="getCpn">获取自己中的元素</button>
    </div>
</template>

<script>
    import Home from './Home.vue'
    export default {
        components:{
            Home
        },
        methods: {
            getElement(){
                console.log(this.$refs.titleH2);
            },
            getCpn(){
                console.log(this.$refs.homeCpn);
                console.log(this.$refs.homeCpn.$el);
                console.log(this.$refs.homeCpn.name);
                this.$refs.homeCpn.printName()
            }
        },
    }
</script>

<style scoped>

</style>

Home.vue

<template>
    <div>
        <h2>我是被获取的组件</h2>
    </div>
</template>

<script>
    export default {
        data(){
            return{
                name:'我是home组件中的数据'
            }
        },
        methods: {
            printName(){
                console.log('执行了home组件中的函数')
            }
        },
    }
</script>

<style scoped>

</style>

$parent$root

image-20210902234757801

生命周期

image-20210902234909428

image-20210902234917264

生命周期流程

img

缓存组件的生命周期

image-20210903083613534

首先看看缓存组件和非缓存组件的区别

这里的About是缓存组件,Category是非缓存组件,

缓存组件的只会一行一次created(),然后就什么都不执行了,

而非非缓存组件created()和unmounted()只要切换就是会执行.

我们现在希望这个非缓存组件且能够频繁执行某些生命周期函数.

Honeycam 2021-09-03 08-18-27

期望的输出效果

Honeycam 2021-09-03 08-33-10

About.vue

关键代码

image-20210903083407953

<template>
    <div>
        <h3>About.vue</h3>
        <button @click="increase">{{counter}}</button>
    </div>
</template>

<script>
    export default {
        name:'about',
        data() {
            return {
                counter:0
            }
        },
        methods: {
            increase(){
                this.counter ++
            }
        },
        created() {
            console.log('about is created')
        },
        unmounted() {
            console.log('about is unmounted')
        },
        activated() {
            console.log('about is actived')
        },
        deactivated() {
            console.log('about is deactived')
        },
    }
</script>
 
<style scoped>

</style>

组件的v-model

基本使用

输出效果

Honeycam 2021-09-03 09-11-32

代码逻辑图

image-20210903092525155

App.vue

<template>
    <div>
        <h2>普通元素的v-model</h2>
        <input  v-model="msg">
        <!-- <input v-bind:value="msg" @input="msg = $event.target.value"> -->
        <h3>{{msg}}</h3>
<!-- ------------------------------------------------------------------------------------------ -->
        <hr>
        <h2>组件的v-model</h2>
        <my-input v-model="msg"></my-input>
        <!-- 等价于 -->
        <!-- <my-input :modelValue="msg" @update:modelValue="msg = $event"></my-input> -->
    </div>
</template>

<script>
    import MyInput from './MyInput.vue'

    export default {
        components:{
            MyInput
        },
        data() {
            return {
                msg:''
            }
        },
    }
</script>

<style scoped>

</style>

MyInput.vue

<template>
    <div>
        <input type="text" :value="modelValue" @input="sendInputEvent">
        <h3>{{modelValue}}</h3>
    </div>
</template>

<script>
    export default {
        props:{
            modelValue:String
        },
        emits:['update:modelValue'],
        methods: {
            sendInputEvent(event){
               this.$emit("update:modelValue", event.target.value);
            }
        },
    }
</script>

<style scoped>

</style>

优化

上面的例子,虽然实现了组件v-model的基本使用,但是还存在一些小的问题,比如'image-20210903093008466'我们这里的双向绑定,竟然绑定的是属性,这显然是不合逻辑的,因为这里面一般都是存放从父组件传递过来的数据,我们希望这些数据保持原样,子组件最好不要修改.这个就是时候我们就希望把这些数据copy一份,然后再次基础上进行操作,从而也降低了组件间的耦合性,于是就想到了计算属性.

代码逻辑

image-20210903143226532

MyInput.vue

双向绑定

image-20210903143314487

计算属性间接属性的值

image-20210903143345027

给父组件发送触发事件

image-20210903143416246

<template>
    <div>
        <input type="text" v-model="valueFromPar" >
        <h3>{{valueFromPar}}</h3>
    </div>
</template>

<script>
    export default {
        props:{
            modelValue:String
        },
        emits:['update:modelValue'],
        computed:{
            valueFromPar:{
                set(value){
                    this.$emit("update:modelValue", value);
                },
                get(){
                    return this.modelValue
                }
            }
        }
    }
</script>

<style scoped>

</style>

绑定多个v-model

代码逻辑图

image-20210903180040823

image-20210903181007534

普通标签上面不支持绑定多个v-model

Honeycam 2021-09-03 18-01-57

App.vue

<template>
    <div>
        <span>msg: </span><input  v-model="msg"  >
        <h3>{{msg}}</h3>

        <span>msg2: </span><input  v-model="msg2" >
        <h3>{{msg2}}</h3>

        <hr>
        <h2>组件的v-model</h2>
        <my-input v-model="msg" v-model:modelValue2="msg2"></my-input>
    </div>
</template>

<script>
    import MyInput from './MyInput.vue'

    export default {
        components:{
            MyInput
        },
        data() {
            return {
                msg:'msg',
                msg2:'msg2'
            }
        },
    }
</script>

<style scoped>

</style>

MyInput.vue

<template>
    <div>
        <span>msg: </span><input type="text" v-model="valueFromPar" >
        <h3>{{valueFromPar}}</h3>
        <span>msg2: </span><input type="text" v-model="valueFromPar2" >
        <h5>{{valueFromPar2}}</h5>
    </div>
</template>

<script>
    export default {
        props:{
            modelValue:String,
            modelValue2:String
        },
        emits:['update:modelValue','update:modelValue2'],
        computed:{
            valueFromPar:{
                set(value){
                    this.$emit("update:modelValue", value);
                },
                get(){
                    return this.modelValue
                }
            },
            valueFromPar2:{
                set(value){
                    this.$emit("update:modelValue2", value);
                },
                get(){
                    return this.modelValue2
                }
            }
        }
    }
</script>

<style scoped>

</style>

Vue3过渡&动画实现

认识动画

image-20210903205400418

案例_hello world的显示和隐藏

没有动画效果

输出效果

Honeycam 2021-09-03 21-09-26

App.vue

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <h2 v-if="isShow">hello world</h2>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>

</style>

加入动画效果

输出效果

Honeycam 2021-09-03 21-14-40

App.vue

transition标签将要显示或隐藏的内容包裹起来

image-20210903211638053

根据其的名字来设置css样式

image-20210903211729110

可以省略的代码

将这个元素完全显示出来他的opacity默认就是1,所以可以省略不写,也是可以的.

image-20210903212458998

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <transition name="hw">
            <h2 v-if="isShow">hello world</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .hw-enter-from,
    .hw-leave-to{
        opacity:0
    }

    .hw-enter-to,
    .hw-leave-from{
        opacity:1
    }

    .hw-enter-active,
    .hw-leave-active{
        transition:opacity 2s ease;
    }

</style>

transition组件的原理

过渡动画class

image-20210903212012433

class的添加或删除的时机

class的name命名规则

image-20210903212157266

animation动画

输出效果

Honeycam 2021-09-03 21-31-16

App.vue

image-20210903213249418

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw">
            <h2 class='hello' v-if="isShow">hello world</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .hello {
        display: inline-block;
    }
    .hw-enter-active {
        animation:bounce 2s ease
    }
    .hw-leave-active {
        animation:bounce 2s ease reverse
    }


    @keyframes bounce {
    0% {
      transform: scale(0)
    }

    50% {
      transform: scale(1.2);
    }

    100% {
      transform: scale(1);
    }
  }

</style>

transition的属性

type属性

image-20210903214128610

duration属性

image-20210903214201456

mode属性(常用多个元素切换)

image-20210903214259553

存在的问题

两个元素进行切换的时候,发现前面一个元素还没有离开,后面的元素就是进来了,然后前面的元素完全离开,后面的元素才会过来占位,有种一卡一卡的感觉.

Honeycam 2021-09-03 21-45-06

App.vue

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw">
            <h2 class='hello' v-if="isShow">hello world</h2>
            <h2 class='hello' v-else>I'am computer</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .hello {
        display: inline-block;
    }

    .hw-enter-from,
    .hw-leave-to{
        opacity:0
    }

    .hw-enter-to,
    .hw-leave-from{
        opacity:1
    }

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


</style>

改进后的输出效果

Honeycam 2021-09-03 21-47-28

App.vue

mode的模式

先让前面的元素离开,然后再进来.就是这种丝滑的感觉.

我们由此可知前面的卡卡的感觉,先让后面的元素进来,等待前面的元素离去,就是in-out了

image-20210903214809239

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw" mode="out-in">
            <h2 class='hello' v-if="isShow">hello world</h2>
            <h2 class='hello' v-else>I'am computer</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .hello {
        display: inline-block;
    }

    .hw-enter-from,
    .hw-leave-to{
        opacity:0
    }

    .hw-enter-to,
    .hw-leave-from{
        opacity:1
    }

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


</style>

appear属性

image-20210903215839918

下面的例子是一个组件的例子和普通元素差不多.

输出效果

Honeycam 2021-09-03 21-57-11

App.vue


<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw" mode="out-in" appear="true">
            <component :is="isShow?'home':'about'"></component>
        </transition>
    </div>
</template>

<script>
    import About from './pages/About.vue'
    import Home from './pages/Home.vue'
    export default {
        components:{
            About,
            Home
        },
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .hello {
        display: inline-block;
    }

    .hw-enter-from,
    .hw-leave-to{
        opacity:0
    }

    .hw-enter-to,
    .hw-leave-from{
        opacity:1
    }

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


</style>

animate.css动画

https://animate.style/

http://www.animate.net.cn/

https://www.dowebok.com/demo/2014/98/

介绍

image-20210904101921222

如何使用

image-20210904101931910

安装animate.css

命令行输入

 npm install animate.css

输出效果

image-20210904102853520

导入animate.css

image-20210904103021235

基本使用

输出效果

Honeycam 2021-09-04 10-54-42

App.vue

关键代码

image-20210904110110664

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw">
            <h2 class='title' v-if="isShow">hello world</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .title {
        text-align: center;
    }
    .hw-enter-active {
        animation: bounceInDown 2s ease
    }
    .hw-leave-active {
        animation: bounceOutDown 2s ease
    }
</style>

自定义过渡class_使用animate中的类

image-20210904110356246

输出效果

和上面的例子中输出效果一样,只不过这里使用了不同的方式

Honeycam 2021-09-04 10-54-42

代码逻辑

image-20210904111251676

App.vue

关键代码

image-20210904111441998

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw" 
        enter-active-class="animate__animated animate__bounceInDown" 
        leave-active-class="animate__animated animate__bounceOutDown">
            <h2 class='title' v-if="isShow">hello world</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .title {
        text-align: center;
    }
</style>

小修改

有时候我们对第三方库的动画效果进行小小的修改,比如说添加个reverse.

输出效果

Honeycam 2021-09-04 11-51-29

App.vue

关键代码

image-20210904115644050

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw" 
        enter-active-class="animate__animated animate__bounceInDown" 
        leave-active-class="animate__animated animate__bounceOutDown">
            <h2 class='title' v-if="isShow">hello world</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .title {
        text-align: center;
    }
    .animate__bounceOutDown {
        animation-direction: reverse;
    }
</style>

自定义过渡class_使用自己定义的类

输出效果

Honeycam 2021-09-04 11-31-27

关键代码

image-20210904113304476

App.vue

<template>
    <div>
        <button @click="isShow = !isShow">toggle</button>
        <hr>
        <transition name="hw" 
        enter-active-class="animate__animated animate__bounceInDown" 
        leave-active-class="animate__animated my__animate__drop">
            <h2 class='title' v-if="isShow">hello world</h2>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                isShow:true
            }
        },
    }
</script>

<style scoped>
    .title {
        text-align: center;
    }
    .my__animate__drop {
        animation:hinge 2s ease;
    }
</style>

gsap库

https://www.npmjs.com/package/gsap

https://greensock.com/

介绍

image-20210904115745446

如何使用

image-20210904115759948

安装gsap库

命令行

npm install gsap

JavaScript钩子

image-20210904120648513

输出效果

Honeycam 2021-09-04 12-09-38

App.vue

<template>
  <div>
    <button @click="isShow = !isShow">toggle</button>
    <hr />
    <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @after-enter="afterEnter"
      @before-leave="beforeLeave"
      @leave="leave"
      @afterLeave="afterLeave"
    >
      <h2 class="title" v-if="isShow">hello world</h2>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: true,
    };
  },
  methods: {
    beforeEnter() {
      console.log("beforeEnter");
    },
    enter() {
      console.log("enter");
    },
    afterEnter() {
      console.log("afterEnter");
    },
    beforeLeave() {
      console.log("beforeLeave");
    },
    leave() {
      console.log("leave");
    },
    afterLeave() {
      console.log("afterLeave");
    },
  },
};
</script>

<style scoped>
.title {
  text-align: center;
}
</style>

基本使用gsap

参考网址: https://greensock.com/get-started/

输出

Honeycam 2021-09-04 12-30-31

代码逻辑

image-20210904122931969

App.vue

<template>
  <div>
    <button @click="isShow = !isShow">toggle</button>
    <hr />
    <transition
      @enter="enter"
      @leave="leave"
    >
      <h2 class="title" v-if="isShow">hello world</h2>
    </transition>
  </div>
</template>

<script>
    import gsap from 'gsap'
    export default {
    data() {
        return {
        isShow: true,
        };
    },
    methods: {
        enter(el,done) {
          console.log("enter");
          gsap.from(el,{
              scale:0,
              x:200,
              onComplete:done
          })
        },
        leave(el,done) {
          console.log("leave");
          gsap.to(el,{
              scale:0,
              x:200,
              onComplete:done
          })
        },

    },
    };
</script>

<style scoped>
  .title {
    text-align: center;
 }
</style>

gsap实现数字增长效果

输出

Honeycam 2021-09-04 12-43-58

App.vue

关键代码

image-20210904124615598

<template>
  <div>
    <input type="number" step=100 v-model="counter">
    <hr>
    <h2>{{showCounter.toFixed(0)}}</h2>
  </div>
</template>

<script>
    import gsap from 'gsap'
    export default {
      data() {
          return {
            counter:0,
            showCounter:0
          };
      },
      watch:{
        counter(newValue){
          gsap.to(this,{
            duration:1,
            showCounter:newValue
          })
        }
      }
    };
</script>

<style scoped>
  .title {
    text-align: center;
 }
</style>

列表的过渡

image-20210904140347108

image-20210904140409165

App.vue

image-20210904143325296

<template>
    <div>
        <button @click="addNum">添加数字</button>
        <button @click="removeNum">删除数字</button>

        <transition-group tag="p" name="why">
          <span v-for="item in numbers" :key="item" class="item">
              {{item}}
          </span>
        </transition-group>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                numbers:[0,1,2,3,4,5,6,7,8,9],
            }
        },
        methods: {
            addNum() {
                this.numbers.splice(this.randomIndex(), 0, this.numCounter)
            },
            removeNum() {
                this.numbers.splice(this.randomIndex(), 1)
            },
            randomIndex() {
                return Math.floor(Math.random() * this.numbers.length)
            },
        },
        computed:{
            numCounter(){
                return this.numbers.length
            }
        }
    }
</script>

<style scoped>
    .item {
    margin-right: 10px;
    display: inline-block;
  }

  .why-enter-from,
  .why-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }

  .why-enter-active,
  .why-leave-active {
    transition: all 1s ease;
  }

</style>

输出效果与不足

现在的情况是添加数字和删除数字都是具有动画的,而剩余在数组中的其他数字占位却显得很生硬,

Honeycam 2021-09-04 14-20-39

尝试在App.vue添加如下代码:

App.vue添加的代码

<style scoped>
  .why-move {
    transition: transform 1s ease;
  }
</style>

输出效果

现在的情况是添加数字时,无论是添加的数字还是移动的数字都是具有动画的,而删除数字时,删除的数字有动画,但是移动的数字很生硬.原因是因为当一个数字被删除时,这个动画还没有执行完成,这个元素仍然是标准流中的元素,所以会占位.我们现在希望这个删除的时候就是不要占位,因此可以将其删除时候的属性设置为absolute,脱离标准流.

Honeycam 2021-09-04 14-26-39

在App.vue继续增加如下代码

<style scoped>
  .why-leave-active {
    position: absolute;
  } 
</style>

输出效果

输出效果还可以,无论删除或增加数字,以及由此引起的数字的移动,都是具有动画的.

Honeycam 2021-09-04 14-31-32

完整App.vue代码

<template>
    <div>
        <button @click="addNum">添加数字</button>
        <button @click="removeNum">删除数字</button>

        <transition-group tag="p" name="why">
          <span v-for="item in numbers" :key="item" class="item">
              {{item}}
          </span>
        </transition-group>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                numbers:[0,1,2,3,4,5,6,7,8,9],
            }
        },
        methods: {
            addNum() {
                this.numbers.splice(this.randomIndex(), 0, this.numCounter)
            },
            removeNum() {
                this.numbers.splice(this.randomIndex(), 1)
            },
            randomIndex() {
                return Math.floor(Math.random() * this.numbers.length)
            },
        },
        computed:{
            numCounter(){
                return this.numbers.length
            }
        }
    }
</script>

<style scoped>
    .item {
    margin-right: 10px;
    display: inline-block;
  }

  .why-enter-from,
  .why-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }

  .why-enter-active,
  .why-leave-active {
    transition: all 1s ease;
  }

  .why-leave-active {
    position: absolute;
  }

  .why-move {
    transition: transform 1s ease;
  }
</style>

列表的交错过渡

代码逻辑图

image-20210904152410102

App.vue

HTML5中数据传递的方式

image-20210904152535698

数组的过滤

image-20210904152619618

<template>
  <div>
    <input v-model="keyword">
    <transition-group tag="ul" name="why" :css="false"
                      @before-enter="beforeEnter"
                      @enter="enter"
                      @leave="leave">
      <li v-for="(item, index) in showNames" :key="item" :data-index="index">
        {{item}}
      </li>
    </transition-group>
  </div>
</template>

<script>
  import gsap from 'gsap';

  export default {
    data() {
      return {
        names: ["abc", "cba", "nba", "why", "lilei", "hmm", "kobe", "james"],
        keyword: ""
      }
    },
    computed: {
      showNames() {
        return this.names.filter(item => item.indexOf(this.keyword) !== -1)
      }
    },
    methods: {
      beforeEnter(el) {
        el.style.opacity = 0;
        el.style.height = 0;
      },
      enter(el, done) {
        gsap.to(el, {
          opacity: 1,
          height: "1.5em",
          delay: el.dataset.index * 0.5,
          onComplete: done
        })
      },
      leave(el, done) {
        gsap.to(el, {
          opacity: 0,
          height: 0,
          delay: el.dataset.index * 0.5,
          onComplete: done
        })
      }
    }
  }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-04 15-27-32

数字洗牌

用到了了一个第三方库,首先安装这个第三方库,在命令行输入:

npm install lodash

然后再App.vue中使用:

App.vue

image-20210904153155982

<template>
  <div>
    <button @click="addNum">添加数字</button>
    <button @click="removeNum">删除数字</button>
    <button @click="shuffleNum">数字洗牌</button>

    <transition-group tag="p" name="why">
      <span v-for="item in numbers" :key="item" class="item">
        {{item}}
      </span>
    </transition-group>
  </div>
</template>

<script>
  import _ from 'lodash';

  export default {
    data() {
      return {
        numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        numCounter: 10
      }
    },
    methods: {
      addNum() {
        this.numbers.splice(this.randomIndex(), 0, this.numCounter++)
      },
      removeNum() {
        this.numbers.splice(this.randomIndex(), 1)
      },
      shuffleNum() {
        this.numbers = _.shuffle(this.numbers);
      },
      randomIndex() {
        return Math.floor(Math.random() * this.numbers.length)
      }
    },
  }
</script>

<style scoped>
  .item {
    margin-right: 10px;
    display: inline-block;
  }

  .why-enter-from,
  .why-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }

  .why-enter-active,
  .why-leave-active {
    transition: all 1s ease;
  }

  .why-leave-active {
    position: absolute;
  }

  .why-move {
    transition: transform 1s ease;
  }
</style>

输出效果

Honeycam 2021-09-04 15-32-27

Mixin

认识Mixin

关键就是把组件中相同的代码逻辑进行抽取.

image-20210905090253135

image-20210905090308874

Mixin的基本使用

export default和export两种不同的导出方式

代码逻辑图

image-20210905121512469

mixins>demoMixin.js

export const demoMixin = {
    data() {
        return {
            msg:'this is mixin message',
            msg2:'this is mixin message2'
        }
    },
    methods: {
        mixinFunc(){
            console.log('this is mixin function')
        },
        mixinAndApp(){
            console.log('mixinAndApp function in the demoMixin.js');
        }
    },
    created() {
        console.log('mixin created')
    },
}

App.vue

<template>
    <div>
        <h4>demoMixin中的msg--{{msg}}</h4>
        <button @click="mixinFunc">mixFunc</button>
        <hr>
        <h4>demoMixin和App.vue中都有的msg2--{{msg2}}</h4>
        <button @click="mixinAndApp">mixinAndApp</button>
        <hr>
        <h4>App.vue中的appData--{{appData}}</h4>
        <button @click="appFunc">appFunc</button>
    </div>
</template>

<script>
    import {demoMixin} from './mixins/demoMixin'
    export default {
        mixins:[demoMixin],
        data() {
            return {
                msg2:'this is App.vue message2',
                appData:"this is App.vue appData"
            }
        },
        methods: {
            mixinAndApp(){
                console.log('mixinAndApp function but in the App.vue');
            },
            appFunc(){
                console.log('appFunc function in the App.vue');
            }
        },
        created() {
            console.log('App.vue is created');
        },
    }
</script>

<style scoped>

</style>

输出效果

Mixin的合并规则

image-20210905121749751

全局混入Mixin

image-20210905121949721

代码逻辑

image-20210905125848529

main.js

import { createApp } from 'vue'
import App from './02_mixin的全局混入/App.vue'

const app = createApp(App)
app.mixin({
    data() {
        return {
            msgInMain:'msg data in main.js'
        }
    },
})

app.mount('#app')

extends

image-20210905144751658

Options API的弊端

image-20210905144954599

image-20210905145005765

Composition API

image-20210905145115972

setup函数的参数

image-20210905145521157

props

image-20210905145534732

通过props拿到父组件传递过来的数据

父组件:App.vue

<template>
    <div>
        App
        <hr>
        <home paraFromParent="hello my son"></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    export default {
        components:{
            Home
        }
    }
</script>

<style scoped>

</style>

子组件:Home.vue

<template>
    <div>
        Home
    </div>
</template>

<script>
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props) {
            console.log(props.paraFromParent);  //=>hello my son
            console.log(this);                  //=>undefined
            // console.log(this.paraFromParent);//=>报错
        }
    }
</script>

<style scoped>

</style>

输出

  1. setup()不能使用this
  2. 父组件传递过来的数据通过props拿到.

image-20210905150812015

context

image-20210905145546333

非prop的attribute

代码的逻辑

image-20210905152107245

父组件:App.vue

<template>
    <div>
        App
        <hr>
        <home 
            paraFromParent="hello my son"
            class="helloHome"
            id="helloHome_1"
        ></home>
    </div>
</template>

<script>
    import Home from './Home.vue'
    export default {
        components:{
            Home
        }
    }
</script>

<style scoped>

</style>

子组件:Home.vue

<template>
    <div>
        Home
    </div>
</template>

<script>
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props,context) {
            console.log(props.paraFromParent);  //=>hello my son
            console.log(context.attrs)Proxy //=>{class: "helloHome", id: "helloHome_1", __vInternal: 1}
            console.log(context.attrs.class)//=>helloHome
            console.log(context.attrs.id)//=>helloHome_1
        }
    }
</script>

<style scoped>

</style>

子组件:Home.vue

解构赋值

image-20210905163010312

<template>
    <div>
        Home
    </div>
</template>

<script>
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props,{attrs,slots,emit}) {
            console.log(props.paraFromParent)
            console.log(attrs)
            console.log(slots)
            console.log(emit)
        }
    }
</script>

<style scoped>

</style>

setup函数的返回值

代码逻辑图

setup的返回值

setup的返回值可以在template中使用.

存在的问题

虽然数字能够绑定到mustache语法中,但是数据不是动态绑定的.

image-20210905165224971

Home.vue

<template>
    <div>
        Home
        <h3>msg: {{msg}}</h3>
        <h4>counter: {{counter}}</h4>
        <button @click="increment">+</button>
    </div>
</template>

<script>
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props,{attrs,slots,emit}) {

            let counter = 100

            const increment = () => {
                counter++;
                console.log(counter);
            };

            return {
                msg:'I am Home data msg',
                counter,
                increment
            }
        }
    }
</script>

<style scoped>

</style>

补充

在setup中的函数也要返回

Honeycam 2021-09-05 17-00-34

Reactive API_动态绑定数据

代码逻辑图

image-20210905171757726

Home.vue

关键代码

image-20210905171852390

<template>
    <div>
        Home
        <h4>counter: {{dyCounter.counter}}</h4>
        <button @click="increment">+</button>
    </div>
</template>

<script>
    import {reactive} from 'vue'
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props,{attrs,slots,emit}) {

            const dyCounter = reactive({
                counter:100
            })
            
            const increment = () => {
                dyCounter.counter++;
            };

            return {
                dyCounter,
                increment
            }
        }
    }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-05 17-20-06

Ref API

image-20210905172258235

代码逻辑图

image-20210905173353290

Home.vue

关键代码

image-20210905173553886

自动解包功能

自动解包功能,本来这里应该填写counter.value,vue帮我们做了自动解析,所以这里填写counter也是可以的.

image-20210905173709717

<template>
    <div>
        Home
        <h4>counter: {{counter}}</h4>
        <button @click="increment">+</button>
    </div>
</template>

<script>
    import {ref} from 'vue'
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props,{attrs,slots,emit}) {
            let counter = ref(100)

            const increment = () => {
                counter.value ++
            }
            return {
                counter,
                increment
            }
        }
    }
</script>

<style scoped>

</style>

自动解包的层数

image-20210905172329836

代码的逻辑图

image-20210905174821639

Home.vue

<template>
    <div>
        Home
        <h4>counter: {{info}}</h4>
        <h4>info.counter: {{info.counter}}</h4>
        <h4>info.counter.value: {{info.counter.value}}</h4>
        <button @click="increment">+</button>
    </div>
</template>

<script>
    import {ref} from 'vue'
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props,{attrs,slots,emit}) {
            let counter = ref(100)

            const info = {
                counter
            }

            const increment = () => {
                info.counter.value ++
            }
            return {
                info,
                increment
            }
        }
    }
</script>

<style scoped>

</style>

readonly

image-20210905175141055

image-20210905175154899

关键代码

image-20210905175953303

Home.vue

<template>
    <div>
        Home
        <h4>rCounter: {{rCounter}}</h4>
        <button @click="increment">+</button>
    </div>
</template>

<script>
    import {ref, readonly} from 'vue'
    export default {
        props:{
            paraFromParent:{
                type:String,
                required:true
            }
        },
        setup(props,{attrs,slots,emit}) {
            let counter = ref(100)
            let rCounter = readonly(counter)

            const increment = () => {
              	//正确使用方式
                //counter.value ++
                rCounter.value ++//=>Set operation on key "value" failed: target is readonly.
            }
            return {
                rCounter,
                increment
            }
        }
    }
</script>

<style scoped>

</style>

输出

image-20210905175703453

Reactive判断的API

image-20210906212647226

toRefs

image-20210906212808568

使用结构赋值存在的问题

App.vue

<template>
    <div>
        <h3>{{name}}--{{age}}</h3>
        <button @click="changAge">changName</button>
    </div>
</template>

<script>
    import {reactive} from 'vue'
    export default {
        setup() {
            const {name,age} = reactive({name:'Alice',age:18})

            const changAge = ()=>{
               age ++
            }

            return {
                name,
                age,
                changAge
            }
        }
    }
</script>

<style scoped>

</style>

输出

  1. 发现使用解构赋值之后,这个age就不是动态数据了.

image-20210906214522484

使用toRefs解决解构赋值的问题

App.vue

关键代码

image-20210906215622322

toRefs一般和reactive搭配使用

toRefs()里面传入的一定是reactive类型的对象.

image-20210906215722537

<template>
    <div>
        <h3>{{name}}--{{age}}</h3>
        <button @click="changAge">changName</button>
    </div>
</template>

<script>
    import {reactive,toRefs} from 'vue'
    export default {
        setup() {
            const info = reactive({name:'Alice',age:18})
            
            let {name,age} = toRefs(info)
            

            const changAge = ()=>{
                // info.age ++  //有作用
                // age ++       //没有作用
                age.value ++     //有作用
               console.log(info.age);
            }

            return {
                name,
                age,
                changAge
            }
        }
    }
</script>

<style scoped>

</style>

输出

  1. toRefs在image-20210906220135287这两者之间建立了联系,两者之中只要有一个改变,另外一个也会改变.

Honeycam 2021-09-06 21-54-49

toRef

App.vue

解构赋值的单独使用

image-20210906221751425

<template>
    <div>
        <h3>{{name}}--{{age}}</h3>
        <button @click="changAge">changName</button>
    </div>
</template>

<script>
    import {reactive,toRef} from 'vue'
    export default {
        setup() {
            const info = reactive({name:'Alice',age:18})
            
            let {name} = info
            // let {age} = toRef(info,age) //没有作用
            // let age = toRef(info,age)//没有作用
            // let {age} = toRef(info,'age')//没有作用
            let age = toRef(info,'age')//有作用
            

            const changAge = ()=>{
                info.age ++
                console.log(info.age);
            }

            return {
                name,
                age,
                changAge
            }
        }
    }
</script>

<style scoped>

</style>

输出

image-20210906221657079

ref其他的API

image-20210906222307383

shallowRef&triggerRef

问题背景

App.vue

<template>
    <div>
        <h3>{{info.name}}--{{info.age}}</h3>
        <button @click="changAge">changAge</button>
        <hr>
        <h4>{{info.friend.name}}--{{info.friend.age}}</h4>
        <button @click="changFriendAge">changFriendAge</button>
    </div>
</template>

<script>
    import {ref} from 'vue'
    export default {
        setup() {

            const info = ref({name:'Alice',age:18,friend:{name:'Celina',age:20}})
            
            
            const changAge = ()=>{
                info.value.age ++
                console.log('changAge--'+info.value.age);
            }

            const changFriendAge = ()=>{
                info.value.friend.age ++
                console.log('changFriendAge--'+info.value.friend.age);
            }

            return {
                info,
                changAge,
                changFriendAge
            }
        }
    }
</script>

<style scoped>

</style>

输出

  1. 无论是第一层'image-20210907092209688'的age都是能够被修改的.

image-20210907092038192

目标需求

  1. 我们现在希望这个第一层'image-20210907092209688'的age是不能够被修改的.

  2. 这个时候我们就可以使用shallowRef'image-20210907092452285'了.

  3. shallRef一般搭配image-20210907094627829使用

使用shallowRef后没有反应的代码

App.vue

<template>
    <div>
        <h3>{{info.name}}--{{info.age}}</h3>
        <button @click="changAge">changAge</button>
        <hr>
        <h4>{{info.friend.name}}--{{info.friend.age}}</h4>
        <button @click="changFriendAge">changFriendAge</button>
    </div>
</template>

<script>
    import {ref,shallowRef} from 'vue'
    export default {
        setup() {

            const info = shallowRef({name:'Alice',age:18,friend:{name:'Celina',age:20}})
            
            
            const changAge = ()=>{
                info.value.age ++
                console.log('changAge--'+info.value.age);
            }

            const changFriendAge = ()=>{
                info.value.friend.age ++
                console.log('changFriendAge--'+info.value.friend.age);
            }

            return {
                info,
                changAge,
                changFriendAge
            }
        }
    }
</script>

<style scoped>

</style>

输出

  1. 虽然info.value.age的值和info.value.friend.age的值都是发生改变了,但是在mustache语法中的值没有改变.

image-20210907093518738

shallowRef需要搭配triggerRef

搭配triggerRef的代码:App.vue

<template>
    <div>
        <h3>{{info.name}}--{{info.age}}</h3>
        <button @click="changAge">changAge</button>
        <hr>
        <h4>{{info.friend.name}}--{{info.friend.age}}</h4>
        <button @click="changFriendAge">changFriendAge</button>
    </div>
</template>

<script>
    import {ref,shallowRef, triggerRef} from 'vue'
    export default {
        setup() {

            const info = shallowRef({name:'Alice',age:18,friend:{name:'Celina',age:20}})
            
            
            const changAge = ()=>{
                info.value.age ++
                triggerRef(info)
                console.log('changAge--'+info.value.age);
            }

            const changFriendAge = ()=>{
                info.value.friend.age ++
                // triggerRef(info)
                console.log('changFriendAge--'+info.value.friend.age);
            }

            return {
                info,
                changAge,
                changFriendAge
            }
        }
    }
</script>

<style scoped>

</style>

输出

  1. 在第一层'image-20210907094417260',所以数据是动态绑定的
  2. 在第二层'image-20210907094424817',所以数据无法动态更新
  3. 当我们再次点击第一个changeAge时,我们又再一次触发这个trigger,所以不仅第一层的age更新了,第二层的age也更新了.

Honeycam 2021-09-07 09-39-31

customRef

image-20210907094737237

App.vue

<template>
    <div>
        <input type="text"  v-model="msg">
        <h2>{{msg}}</h2>
    </div>
</template>

<script>
    import debounceRef from './hook/useDebounceRef'
    export default {
        setup(props) {

            const msg = debounceRef('hello world')

            return{
                msg
            }
        }
    }
</script>

<style scoped>

</style>

hook>useDebounceRef.js

import { customRef } from 'vue';

// 自定义ref
export default function(value, delay = 300) {
  let timer = null;

  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return value;
      },
      
      set(newValue) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      }
    }
  })
}

输出

  1. 输入延时的效果
  2. 防抖

Honeycam 2021-09-07 12-33-48

computed

基本使用_传入一个getter函数

App.vue

image-20210907124254648

<template>
  <div>
    <h2>{{fullName}}</h2>
  </div>
</template>

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

  export default {
    setup() {
      const firstName = ref("Kobe");
      const lastName = ref("Bryant");

      // 1.用法一: 传入一个getter函数
      // computed的返回值是一个ref对象
      const fullName = computed(() => firstName.value + " " + lastName.value);

      return {
        fullName,
      }
    }
  }
</script>

<style scoped>

</style>

输出

image-20210907124306066

传入一个对象,包含getter和setter

image-20210907131612578

代码逻辑

image-20210907130157644

App.vue

<template>
  <div>
    <h2>{{fullName}}</h2>
    <button @click="changName">changName</button>
  </div>
</template>

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

  export default {
    setup() {
      const firstName = ref("Kobe");
      const lastName = ref("Bryant");

      const fullName = computed({
          get:() => firstName.value + " " + lastName.value,
          set(newValue){
              let names = newValue.split(' ')
              firstName.value  = names[0]
              lastName.value = names[1]
          }
      })
      const changName = ()=>{
          fullName.value = 'haha hehe'
      }

      return {
        fullName,
        changName
      }
    }
  }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-07 12-56-06

侦听数据的变化_watchEffect

image-20210907133015242

watchEffect

image-20210907133049527

App.vue

image-20210907133536390

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changName</button>
    <button @click="changeAge">changAge</button>
  </div>
</template>

<script>
  import {ref,watchEffect} from 'vue'

  export default {
    setup() {
      const info = ref({name:'Alice',age:20})


      const changeName = ()=>{
        info.value.name = 'Celina'
      }
      const changeAge = () => {
        info.value.age ++
      }

      watchEffect(
        () => {
        	console.log('name: ',info.value.name+'    age: ',info.value.age);
        }
      )
      return {
        info,
        changeName,
        changeAge
      }
    }
  }
</script>

<style scoped>

</style>

输出

  1. watchEffect会监听所有的数据,当其中只要有一个数据发生变化时,就是输出
  2. 刚开始的时候会立即执行一次.

Honeycam 2021-09-07 13-33-07

watchEffect的停止侦听

image-20210907134942652

代码逻辑图

image-20210907135123997

App.vue

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changName</button>
    <button @click="changeAge">changAge</button>
  </div>
</template>

<script>
  import {ref,watchEffect} from 'vue'

  export default {
    setup() {
      const info = ref({name:'Alice',age:20})

      const stop = watchEffect(() => {
        console.log('name: ',info.value.name+'    age: ',info.value.age);
      })

      const changeName = ()=>{
        info.value.name = 'Celina'
      }

      const changeAge = () => {
        info.value.age ++
        if(info.value.age > 25){
          stop()
        }
      }

      return {
        info,
        changeName,
        changeAge
      }
    }
  }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-07 13-46-34

watchEffect清除副作用

image-20210907135253007

App.vue

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changName</button>
    <button @click="changeAge">changAge</button>
  </div>
</template>

<script>
  import {ref,watchEffect} from 'vue'

  export default {
    setup() {
      const info = ref({name:'Alice',age:20})

      const stop = watchEffect((onInvalidate) => {
        onInvalidate(() => {
          //在这里面清除额外的副作用
          console.log('onInvaliddata');
        })

        console.log('name: ',info.value.name+'    age: ',info.value.age);
      })

      const changeName = ()=>{
        info.value.name = 'Celina'
      }

      const changeAge = () => {
        info.value.age ++
      }

      return {
        info,
        changeName,
        changeAge
      }
    }
  }
</script>

<style scoped>

</style>

输出

  1. 每次输出都会执行onInvaliddata中的程序,
  2. 相当于清除的作用

Honeycam 2021-09-07 13-58-52

App.vue

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changName</button>
    <button @click="changeAge">changAge</button>
  </div>
</template>

<script>
  import {ref,watchEffect} from 'vue'

  export default {
    setup() {
      const info = ref({name:'Alice',age:20})
      const timer = setTimeout(() =>{
        console.log('网络请求成功');
      },2000)

      const stop = watchEffect((onInvalidate) => {
        onInvalidate(() => {
          //在这里面清除额外的副作用
          clearTimeout(timer)
          console.log('清除上次的网络请求');
        })

        console.log('name: ',info.value.name+'    age: ',info.value.age);
      })

      const changeName = ()=>{
        info.value.name = 'Celina'
      }

      const changeAge = () => {
        info.value.age ++
      }

      return {
        info,
        changeName,
        changeAge
      }
    }
  }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-07 14-04-21


代码逻辑图

image-20210907141127795

模拟网络请求的例子:App.vue

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changName</button>
    <button @click="changeAge">changAge</button>
  </div>
</template>

<script>
  import {ref,watchEffect} from 'vue'

  export default {
    setup() {
      const info = ref({name:'Alice',age:20})
      const timer = setTimeout(() =>{
        console.log('网络请求成功');
      },3000)

      const stop = watchEffect((onInvalidate) => {
        onInvalidate(() => {
          //在这里面清除额外的副作用
          clearTimeout(timer)
          console.log('清除上次的网络请求');
          console.log('准备下次的网络请求...');
          setTimeout(() =>{
            console.log('网络再次请求成功');
          },3000)
          console.log('----');
        })

        console.log('name: ',info.value.name+'    age: ',info.value.age);
      })

      const changeName = ()=>{
        info.value.name = 'Celina'
      }

      const changeAge = () => {
        info.value.age ++
      }

      return {
        info,
        changeName,
        changeAge
      }
    }
  }
</script>

<style scoped>

</style>

输出:模拟网络请求

Honeycam 2021-09-07 14-08-50

setup中使用ref

App.vue

<template>
  <div>
    <h2 ref="titleRef">hello world</h2>
  </div>
</template>

<script>
  import {ref,watchEffect} from 'vue'

  export default {
    setup() {
      const titleRef = ref(null)

      watchEffect(() => {
        console.log(titleRef.value);
      },{
        flush:'post'
      })
      
      return {
        titleRef
      }
    }
  }
</script>

<style scoped>

</style>

输出

image-20210907145216546


几种输出比较

  1. 为什么打印了两次
  2. image-20210907145733258
  3. 其他补充:image-20210907145800653

image-20210907144956924

侦听数据的变化_watch

侦听reactive对象

第一种传入方式'image-20210907151337906'

App.vue

image-20210907151154741

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changeName</button>
  </div>
</template>

<script>
  import {reactive,watch} from 'vue'

  export default {
    setup() {
      const info = reactive({name:'Alice',age:19})
      
      const changeName = () => {
        info.name = 'Celina'
      }

      watch(() => info.name,(newValue,oldValue) =>{
        console.log(oldValue,'--',newValue);
      })

      return {
        info,
        changeName
      }
    }
  }
</script>

<style scoped>

</style>

输出

image-20210907150959744

第二种传入方式'image-20210907151510505'

App.vue

image-20210907151458978

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changeName</button>
  </div>
</template>

<script>
  import {reactive,watch} from 'vue'

  export default {
    setup() {
      const info = reactive({name:'Alice',age:19})
      
      const changeName = () => {
        info.name = 'Celina'
      }

      watch(info,(newValue,oldValue) =>{
        console.log(oldValue,'--',newValue);
      })

      return {
        info,
        changeName
      }
    }
  }
</script>

<style scoped>

</style>

输出

image-20210907151350130

结构赋值

App.vue

关键代码

image-20210907154629406

和上面的对比

image-20210907154822192

<template>
  <div>
    <h2>{{info.name}}--{{info.age}}</h2>
    <button @click="changeName">changeName</button>
  </div>
</template>

<script>
  import {reactive,watch} from 'vue'

  export default {
    setup() {
      const info = reactive({name:'Alice',age:19})
      
      const changeName = () => {
        info.name = 'Celina'
      }

      watch(() => {
        return {...info}
      }
      ,(newValue,oldValue) =>{
        console.log(oldValue,newValue);
      })

      return {
        info,
        changeName
      }
    }
  }
</script>

<style scoped>

</style>

输出

image-20210907154602785

侦听ref对象

App.vue

<template>
  <div>
    <h2>{{name}}</h2>
    <button @click="changeName">changeName</button>
  </div>
</template>

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

  export default {
    setup() {
      const name = ref('Alice')
      
      const changeName = () => {
        name.value = 'Celina'
      }

      watch(name,(newValue,oldValue) =>{
        console.log(oldValue,'--',newValue);
      })

      return {
        name,
        changeName
      }
    }
  }
</script>

<style scoped>

</style>

输出

image-20210907152347122

侦听多个数据

image-20210907161029892

App.vue

传入的值是一个数组

传入的值是一个数组,返回的新旧的值也是一对数组.

image-20210907155618873

<template>
  <div>
    <h2 ref="title">{{info.name}}</h2>
    <button @click="changeData">修改数据</button>
  </div>
</template>

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

  export default {
    setup() {
      // 1.定义可响应式的对象
      const info = reactive({name: "why", age: 18});
      const name = ref("why");

      // 2.侦听器watch
      watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
        console.log(newInfo, newName,'---', oldInfo, oldName);
      })

      const changeData = () => {
        info.name = "kobe";
      }

      return {
        changeData,
        info
      }
    }
  }
</script>

<style scoped>

</style>

侦听数组

image-20210907161057463

深层侦听

image-20210907161332201

生命周期钩子

image-20210907211323627

App.vue

<template>
    <div>
        <h2>{{name}}</h2>
        <button @click="changeName">changName</button>
    </div>
</template>

<script>
    import {ref,onMounted, onUpdated} from 'vue'
    export default {
        setup(props) {
            onMounted(() => {
                console.log('App onMounted_1');
            })
            onMounted(() => {
                console.log('App onMounted_2');
            })

            onUpdated(() =>{
                console.log('App onUpdated');
            })

            const name = ref("Alice")
            const changeName = () => {
                name.value = 'Celina'
            }
            return {
                name,
                changeName
            }
        }
    }
</script>

<style scoped>

</style>

输出

  1. 生命周期函数可以重复定义,其中执行的代码会叠加.
  2. 改变用户名称时,会执行onUpdated()

Honeycam 2021-09-07 21-21-18

Provide和Inject

基本使用_父组件向子组件传递数据

数据传递过程图

image-20210907214226137

App.vue

<template>
    <div>
        App
        <hr>
        <home></home>
    </div>
</template>

<script>
    import {ref,provide} from 'vue'
    import Home from './Home.vue'
    export default {
        components:{
            Home
        },
        setup() {
            const msg = ref('I am msg form Father')
            provide('msg',msg)
        }
    }
</script>

<style scoped>

</style>

Home.vue

<template>
    <div>
        Home
        <h2>{{msg}}</h2>
    </div>
</template>

<script>
    import {inject} from 'vue'
    export default {
        setup() {
            const msg = inject('msg')
            
            return{
                msg
            }
        }
    }
</script>

<style scoped>

</style>

基本使用_动态数据传递

父组件:App.vue

<template>
    <div>
        <h1>App.vue</h1>
        <input type="text" v-model="msg">
        <hr>
        <home></home>
    </div>
</template>

<script>
    import {ref,provide} from 'vue'
    import Home from './Home.vue'
    export default {
        components:{
            Home
        },
        setup() {
            const msg = ref('I am msg form Father')
            provide('msg',msg)
            return{
                msg
            }
        }
    }
</script>

<style scoped>

</style>

Home.vue

<template>
    <div>
        <h1>Home.vue</h1>
        <h2>{{msg}}</h2>
        <input type="text" v-model="msg">
    </div>
</template>

<script>
    import {inject} from 'vue'
    export default {
        setup() {
            const msg = inject('msg')

            return{
                msg
            }
        }
    }
</script>

<style scoped>
    

</style>

输出

  1. 父组件能够传递数据到子组件
  2. 父组件的数据能够动态绑定到子组件中,也就修改父组件中的数据,子组件中的数据能够动态变化.
  3. 存在的问题,就是子组件修改数据,父组件中的数据也遭受到修改,违反了数据的单向流原则,不太友好

Honeycam 2021-09-07 21-50-51

改进_数据单向流

针对上面出现的问题,修改子组件中的数据,父组件中的数据也会遭到修改.

父组件:App.vue

和上面代码对比

image-20210907215849692

<template>
    <div>
        <h1>App.vue</h1>
        <input type="text" v-model="msg">
        <hr>
        <home></home>
    </div>
</template>

<script>
    import {ref,provide,readonly} from 'vue'
    import Home from './Home.vue'
    export default {
        components:{
            Home
        },
        setup() {
            const msg = ref('I am msg form Father')
            provide('msg',readonly(msg))
            return{
                msg
            }
        }
    }
</script>

<style scoped>

</style>

输出效果

Honeycam 2021-09-07 21-57-11

Composition API练习

计数器案例

代码提取图

image-20210907222108728

App.vue

<template>
    <div>
        <h2>{{counter}}</h2>
        <button @click="increment">+1</button>
        <button @click="decrement">-1</button>
    </div>
</template>

<script>
    import useCounter from './hooks/useCounter'
    export default {
        setup(){
           const {counter,increment,decrement} = useCounter()
            return {
                counter,
                increment,
                decrement
            }
        }
    }
</script>

<style scoped>

</style>

hooks>useCounter.js

import {ref} from 'vue'
export default function(){
    const counter = ref(0)
    const increment = () => {
        counter.value ++
    }
    const decrement = () => {
        counter.value --
    }
    return {
        counter,
        increment,
        decrement
    }
}

useTitle案例

App.vue

<template>
    <div>
    </div>
</template>

<script>
   
    import useTitle from './hooks/useTitle'
    export default {
        setup(){
          
           const titleRef = useTitle('myTitle')
           
           setTimeout(() => {
               titleRef.value = 'youTitle'
           },2000)
          
        }
    }
</script>

<style scoped>

</style>

hooks>useTitle


import {ref,watch} from 'vue'
export default function(title = 'default title'){
    const titleRef = ref(title)
    document.title = title
    watch(titleRef,(newTitle) => {
        document.title = newTitle
    })
    return titleRef
}

输出

  1. 刷新的时候显示默认的title,即default title
  2. 然后迅速变换成myTitle
  3. 最后经过两秒过后,这个变成youTitle

Honeycam 2021-09-07 22-36-31

综合案例

关键代码逻辑

App.vue

<template>
  <div>
    <h2>{{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <hr />
    <p class="content"></p>

    <div class="scroll">
      <div class="scroll-x">scrollX: {{ scrollX }}</div>
      <div class="scroll-y">scrollY: {{ scrollY }}</div>
    </div>
    <div class="mouse">
      <div class="mouse-x">mouseX: {{ mouseX }}</div>
      <div class="mouse-y">mouseY: {{ mouseY }}</div>
    </div>
  </div>
</template>

<script>
import useCounter from "./hooks/useCounter";
import useTitle from "./hooks/useTitle";
import useScrollPosition from './hooks/useScrollPosition'
import useMousePosition from './hooks/useMousePosition'
export default {
  setup() {

    const { counter, increment, decrement } = useCounter();

    useTitle("myTitle");

    const {scrollX, scrollY} = useScrollPosition()
    const {mouseX,mouseY} = useMousePosition()


    return {
      counter,
      increment,
      decrement,

      scrollX,
      scrollY,

      mouseX,
      mouseY
    };
  },
};
</script>

<style scoped>
  .content {
    width: 3000px;
    height: 5000px;
  }

  .scroll {
    position: fixed;
    right: 30px;
    bottom: 30px;
  }
  .mouse {
    position: fixed;
    right: 30px;
    bottom: 80px;
  }
</style>

hooks>useScrollPosition

import { ref } from 'vue';

export default function() {
  const scrollX = ref(0);
  const scrollY = ref(0);

  document.addEventListener("scroll", () => {
    scrollX.value = window.scrollX;
    scrollY.value = window.scrollY;
  });

  return {
    scrollX,
    scrollY
  }
}

hooks>useMousePosition

import { ref } from 'vue';

export default function() {
  const mouseX = ref(0);
  const mouseY = ref(0);

  window.addEventListener("mousemove", (event) => {
    mouseX.value = event.pageX;
    mouseY.value = event.pageY;
  });

  return {
    mouseX,
    mouseY
  }
}

输出

Honeycam 2021-09-07 22-49-14

代码整合优化

所有导入合并成一个单独的文件

image-20210907231129915

jsx

image-20210908130110379

基本使用

App.vue

  1. 非常简单,就是把template标签扔掉了,然后直接 在render()的返回值里面写html代码
<script>
    export default {
        render() {
            return <h2>hello, I am render</h2>
        }
    }
</script>

<style scoped>

</style

输出

image-20210908131126066

计数器案例

App.vue

<script>
    export default {
        data() {
            return {
                counter:0
            }
        },
        render() {
            const increment = () =>this.counter ++
            const decrement = () =>this.counter --

            return (    
                <div>
                    <h2>当前计数:{this.counter}</h2>
                    <button onClick={increment}>+1</button>
                    <button onClick={decrement}>-1</button>
                </div>
            )
        }
    }
</script>

<style scoped>

</style>

输出

image-20210908131941895

引入子组件

基本使用

App.vue

<script>
    import Home from './Home.vue'
    export default {
        render() {

            return (    
                <div>
                    <h3>App.vue</h3>
                    <hr></hr>
                    <Home></Home>
                </div>
            )
        }
    }
</script>

<style scoped>

</style>

Home.vue


<template>
    <div>
        <h3>Home</h3>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

输出

image-20210908132320174

使用插槽

App.vue

<script>
    import Home from './Home.vue'
    export default {
        render() {

            return (    
                <div>
                    <h3>App.vue</h3>
                    <hr></hr>
                    <Home>
                        {{default:props => <button>Button</button>}}
                    </Home>
                </div>
            )
        }
    }
</script>

<style scoped>

</style>

Home.vue



<script>
    export default {
        render(h) {
            return(
                <div>
                    <h3>Home.vue</h3>
                    {this.$slots.default ? this.$slots.default():<span>hahaha</span>}
                </div>
            )
        },
    }
</script>

<style scoped>

</style>

输出

  1. 当父组件传递插槽过来时,就是使用父组件的插槽
  2. 当父组件没有传递插槽过来时,就是使用默认的组件.

Honeycam 2021-09-08 13-29-32

VueRouter

概念

路由器

image-20210909085056766

前后端分离阶段

image-20210909085544380

URL的hash

image-20210909085718494

代码理解1

image-20210909090630124

代码理解2

image-20210909092325937

代码理解3

image-20210909093022026

HTML5的History

image-20210909093215351

代码理解1

image-20210909094229698

e.preventDefault()

代码理解:history.html

image-20210909110725784

<!DOCTYPE html>
<html lang="en">
<head>
  
  
  
  <title>Document</title>
</head>
<body>
  <div id="app">
    <a href="/home">home</a>
    <a href="/about">about</a>

    <div class="content">Default</div>
  </div>

  <script>

    const aEls = document.getElementsByTagName("a");
    for (let aEl of aEls) {
      aEl.addEventListener("click", e => {
        e.preventDefault();
        console.log("clicked");
      })
    }
  </script>
</body>
</html>

输出

  1. 没有用e.preventDefault();时,浏览器地址会进行跳转
  2. 使用了e.preventDefault();,浏览器地址不会跳转,不会变化,
  3. 问-为什么要阻止浏览器地址的跳转?答-因为浏览器地址跳转会引发网络请求,而每次引发网络请求,频繁的网络请求也是不好的.

Honeycam 2021-09-09 11-06-18


history.pushState({}, "", href);

使用history方式跳转页面:history.html

阻止网络请求

image-20210909111950076

使用新的路径

image-20210909112006909

<!DOCTYPE html>
<html lang="en">
<head>
  
  
  
  <title>Document</title>
</head>
<body>
  <div id="app">
    <a href="/home">home</a>
    <a href="/about">about</a>

    <div class="content">Default</div>
  </div>

  <script>
    const contentEl = document.querySelector('.content');
    const changeContent = () => {
      switch(location.pathname) {
        case "/home":
          contentEl.innerHTML = "Home";
          break;
        case "/about":
          contentEl.innerHTML = "About";
          break;
        default: 
          contentEl.innerHTML = "Default";
      }
    }
    
    const aEls = document.getElementsByTagName("a");
    for (let aEl of aEls) {
      aEl.addEventListener("click", e => {
        e.preventDefault();
        const href = aEl.getAttribute("href");
        history.pushState({}, "", href);
        changeContent();
      })
    }
  </script>
</body>
</html>

代码理解

image-20210909111902192

输出

  1. 实现了即使改变url的部分内容,不用网络请求

Honeycam 2021-09-09 11-15-35

  1. history.pushState({}, "", href);这个过程类似入栈,出栈的过程,所以可以前进后退.

Honeycam 2021-09-09 11-21-36


history.replaceState({},'',href)

使用history中的history.replaceState({},'',href):history.html

关键代码

image-20210909112746163

<!DOCTYPE html>
<html lang="en">
<head>
  
  
  
  <title>Document</title>
</head>
<body>
  <div id="app">
    <a href="/home">home</a>
    <a href="/about">about</a>

    <div class="content">Default</div>
  </div>

  <script>
    const contentEl = document.querySelector('.content');
    const changeContent = () => {
      switch(location.pathname) {
        case "/home":
          contentEl.innerHTML = "Home";
          break;
        case "/about":
          contentEl.innerHTML = "About";
          break;
        default: 
          contentEl.innerHTML = "Default";
      }
    }
    
    const aEls = document.getElementsByTagName("a");
    for (let aEl of aEls) {
      aEl.addEventListener("click", e => {
        e.preventDefault();
        const href = aEl.getAttribute("href");
        // history.pushState({}, "", href);
        history.replaceState({},'',href)
        changeContent();
      })
    }
  </script>
</body>
</html>

输出

  1. 这个 history.replaceState({},'',href)就不是history.pushState({}, "", href);的出栈入栈了,而是直接替换,所以无法前进和后退'image-20210909112911717'.

Honeycam 2021-09-09 11-26-29

认识vue-router

image-20210909113743180

安装vue-router

image-20210909113803223

路由的使用步骤

image-20210909114123562

image-20210909114147601


代码结构图

image-20210909124436941

App.vue

<template>
  <div>
    <h3>App</h3>
    <hr>
    <!-- vue-router中的超链接,类似a标签 -->
    <router-link to="/home">home</router-link>&nbsp;&nbsp;&nbsp;
    <router-link to="/about">about</router-link>
    <hr>
    <!-- 子组件显示的区域 -->
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    
  }
</script>

<style scoped>

</style>

router>index.js

import {createRouter,createWebHashHistory, createWebHistory} from 'vue-router'

import Home from '../pages/Home.vue'
import About from '../pages/About.vue'

//配置映射关系
const routes = [
    {path:'/home',component:Home},
    {path:'/about',component:About}
]

//创建一个路由对象router
const router = createRouter({
    routes,
    history:createWebHashHistory()
})
export default router

Home.vue

<template>
    <div>
        Home
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

About.vue

<template>
    <div>
        About
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

代码逻辑图

image-20210909130003311

输出

Honeycam 2021-09-09 12-51-20

存在的问题

  1. 刚进来的时候没有默认的页面,会有一个警告

Honeycam 2021-09-09 13-10-32

路由的默认路径

image-20210909131303548

router>index.js

image-20210909131607872

import {createRouter,createWebHashHistory} from 'vue-router'

import Home from '../pages/Home.vue'
import About from '../pages/About.vue'

//配置映射关系
const routes = [
    {path:'/',redirect:'/home'},
    {path:'/home',component:Home},
    {path:'/about',component:About}
]

//创建一个路由对象router
const router = createRouter({
    routes,
    history:createWebHashHistory()
})
export default router

输出

  1. 以前的默认打开地址栏'image-20210909131711806'
  2. 现在的默认打开地址栏image-20210909131732020

Honeycam 2021-09-09 13-15-15

router-linker

image-20210909141025614

active-class&exact-active-class属性

输出

Honeycam 2021-09-09 14-19-40

两者的区别

  1. 当点击Home时,两个属性image-20210909201036607都会被激活
  2. 当点击Home中的消息商品时,只会激活其中一个属性image-20210909201055333

Honeycam 2021-09-09 20-06-44

路由懒加载

image-20210909142524601

分包_魔法注释

懒加载,肯定要分包

index.js

import {createRouter,createWebHistory} from 'vue-router'

// import Home from '../pages/Home.vue'
// import About from '../pages/About.vue'

//配置映射关系
const routes = [
    {path:'/',redirect:'/home'},
    // {path:'/home',component:Home},
    // {path:'/about',component:About}
    {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
    {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')}
]

//创建一个路由对象router
const router = createRouter({
    routes,
    history:createWebHistory()
})
export default router

代码逻辑图

image-20210909152035604

路由的其他属性

image-20210909152533985

动态路由_参数传递

发送参数

image-20210909153535541

接受参数

image-20210909153602994

index.js

传递参数的关键代码

image-20210909161558433

import {createRouter,createWebHistory} from 'vue-router'


//配置映射关系
const routes = [
    {path:'/',redirect:'/home'},
    {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
    {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
    {
        path:'/user/:userName',
        component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')}
]

//创建一个路由对象router
const router = createRouter({
    routes,
    history:createWebHistory()
})
export default router

App.vue

发送参数的关键代码

image-20210909161946401

<template>
  <div>
    <h3>App</h3>
    <hr>
    <!-- vue-router中的超链接,类似a标签 -->
    <router-link to="/home" >Home</router-link>&nbsp;&nbsp;&nbsp;
    <router-link to="/about" >About</router-link>&nbsp;&nbsp;&nbsp;
    <router-link to="/user/Bruce/Celina" >User</router-link>
    <hr>
    <!-- 子组件显示的区域 -->
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    
  }
</script>

<style scoped>

</style>

输出

  1. 通过在/user后面添加/Alice,可以传递参数

Honeycam 2021-09-09 16-13-48

子组件接受参数:User.vue

接受参数的关键代码

image-20210909162242734

<template>
    <div>
        User
    </div>
</template>

<script>
    export default {
        created() {
            console.log(this.$route)
        },
    }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-09 16-23-29

子组件接受指定的参数:User.vue

关键代码

image-20210909162720071

<template>
    <div>
        User
    </div>
</template>

<script>
    export default {
        created() {
            console.log(this.$route)
            console.log(this.$route.params.userName)
        },
    }
</script>

<style scoped>

</style>

输出

image-20210909162703265

User.vue

关键代码

image-20210909163656133

<template>
    <div>
        User : {{$route.params.userName}}
    </div>
</template>

<script>
    export default {
        created() {
            console.log(this.$route)
            console.log(this.$route.params.userName)
        },
    }
</script>

<style scoped>

</style>

输出

image-20210909163520486

如何在setup()中的拿到动态路由传递的参数

代码逻辑图

image-20210909164334949

User.vue

关键代码

image-20210909164430862

<template>
    <div>
        User : {{$route.params.userName}}
    </div>
</template>

<script>
import { useRoute } from 'vue-router'
    export default {
        created() {
            console.log(this.$route)
        },
        setup() {
            const route = useRoute();
            console.log(route.params.userName);
        }
    }
</script>

<style scoped>

</style>

输出

image-20210909164531295

多参数传递

image-20210909153710245

路由配置文件 : index.js

传递多个参数的关键配置

image-20210909165121308

import {createRouter,createWebHistory} from 'vue-router'


//配置映射关系
const routes = [
    {path:'/',redirect:'/home'},
    {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
    {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
    {
        path:'/user/:userName/:age',
        component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')}
]

//创建一个路由对象router
const router = createRouter({
    routes,
    history:createWebHistory()
})
export default router

父组件 : App.vue

传递多个参数

image-20210909165232615

<template>
  <div>
    <h3>App</h3>
    <hr>
    <!-- vue-router中的超链接,类似a标签 -->
    <router-link to="/home" >Home</router-link>&nbsp;&nbsp;&nbsp;
    <router-link to="/about" >About</router-link>&nbsp;&nbsp;&nbsp;
    <router-link to="/user/Alice/19" >User</router-link>
    <hr>
    <!-- 子组件显示的区域 -->
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    
  }
</script>

<style scoped>

</style>

子组件 : User.vue

<template>
    <div>
        User : {{$route.params.userName}} &nbsp;&nbsp;&nbsp;
        Age : {{$route.params.age}}
    </div>
</template>

<script>
import { useRoute } from 'vue-router'
    export default {
        created() {
            console.log(this.$route)
        },
        // setup() {
        //     const route = useRoute();
        //     console.log(route.params.userName);
        // }
    }
</script>

<style scoped>

</style>

输出

image-20210909165938209

NotFound

image-20210909170131364

映射表配置文件: route>index.js

关键代码

image-20210909170916122

import {createRouter,createWebHistory} from 'vue-router'


//配置映射关系
const routes = [
    {path:'/',redirect:'/home'},
    {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')},
    {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
    {
        path:'/user/:userName/:age',
        component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')
    },
    {
        path:'/:pathMatch(.*)',
        component:() => import(/* webpackChunkName:"chunk-notFound" */ '../pages/NotFound.vue')
    }
]

//创建一个路由对象router
const router = createRouter({
    routes,
    history:createWebHistory()
})
export default router

输出

image-20210909171038700

$route.params.pathMatch

NotFound.vue

<template>
    <div>
        Not Found
        <h4>{{$route.params.pathMatch}}</h4>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

输出

image-20210909171648518

匹配规则加*

image-20210909171807933

路由的嵌套

image-20210909171846620

路由配置文件: index.js

关键代码

image-20210909195407033

import {createRouter,createWebHistory} from 'vue-router'


//配置映射关系
const routes = [
    {path:'/',redirect:'/home'},
    {
        path:'/home',
        component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue'),
        children:[
            {
                path:'msg',
                component:() => import('../pages/HomeMsg.vue')
            },
            {
                path:'pro',
                component:() => import('../pages/HomeShops.vue')
            }
            
        ]
    },
    {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')},
    {
        path:'/user/:userName/:age',
        component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')
    },
    {
        path:'/:pathMatch(.*)',
        component:() => import(/* webpackChunkName:"chunk-notFound" */ '../pages/NotFound.vue')
    }
]

//创建一个路由对象router
const router = createRouter({
    routes,
    history:createWebHistory()
})
export default router

Home.vue

关键代码

image-20210909195720053

<template>
    <div>
        <table border="1">
            <div>Home</div>
            <router-link to="/home/msg">消息</router-link>&nbsp;&nbsp;&nbsp;
            <router-link to="/home/pro">商品</router-link>
            <router-view></router-view>
        </table>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

HomeMsg.vue

<template>
    <div>
        <table border="1">
            HomeMsg
        </table>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

HomeShops.vue

<template>
    <div>
        <table border="1">
            HomeShops
        </table>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-09 19-57-56

编程式控制路由

App.vue

关键代码

image-20210909203053159

<template>
  <div>
    <table border="1">
      <h3>App</h3>
      <!-- vue-router中的超链接,类似a标签 -->
      <router-link to="/home" >Home</router-link>&nbsp;&nbsp;&nbsp;
      <router-link to="/about" >About</router-link>&nbsp;&nbsp;&nbsp;
      <router-link to="/user/Alice/19" >User</router-link><br>
      
      <button @click="jumpToHome">Home</button>&nbsp;&nbsp;&nbsp;
      <button @click="jumpToAbout">About</button>&nbsp;&nbsp;&nbsp;
      <button @click="jumpToUser">User</button>
      <!-- 子组件显示的区域 -->
      <router-view></router-view>
    </table>
    
  </div>
</template>

<script>
  import {useRouter} from 'vue-router'
  export default {
    setup() {
      const router = useRouter()
      const jumpToHome = () => {
        router.push('/home')
      }
      const jumpToAbout = () => {
        router.push('/about')
      }
      const jumpToUser = () => {
        router.push('/user/Alice/19')
      }
      return {
        jumpToHome,
        jumpToAbout,
        jumpToUser
      }
    }
  }
</script>

<style scoped>

</style>

输出

Honeycam 2021-09-09 20-31-29

router-link的v-slot

image-20210910164405858

image-20210910164448962

v-slot的基本使用

image-20210910165227182

custom的作用和如何通过插槽给内部传值

image-20210910170838114

Honeycam 2021-09-10 17-14-47

isActive

Honeycam 2021-09-10 17-16-56

router-view的v-slot

image-20210910171926805

动态添加路由

添加一级路由

image-20210910172257280

添加二级路由

image-20210910172340527

image-20210910172348606

动态删除路由

image-20210910172425569

image-20210910172436180

路由导航守卫

https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html

image-20210910172645397

登录守卫功能

image-20210910172731797

Vuex的状态管理

https://next.vuex.vuejs.org/

批量引入Vuex中的数据_mapState

image-20210916112650878

setup中如何使用mapState

image-20210916125210631

Home.vue

<template>
  <div class="home">
    <table border="1">
      <h5>{{ name }}</h5>
      <h5>{{ age }}</h5>
      <h5>{{ counter }}</h5>
    </table>
  </div>
</template>

<script>
import { useStore, mapState } from "vuex";
import { computed } from "vue";
export default {
  setup() {
    const store = useStore();
    const storeStateFns = mapState(["name", "age", "counter"]);
    // console.log(storeStateFns);//=>{name: ƒ, age: ƒ, counter: ƒ}
    const storeState = {};

    Object.keys(storeStateFns).forEach((fnKey) => {

      const fn = storeStateFns[fnKey].bind({ $store: store });
      storeState[fnKey] = computed(fn);
      
    });

    return {
      ...storeState,
    };
  },
};
</script>

<style></style>

setup中使用mapState的封装

image-20210916130410713

hooks>useState.js

import { useStore, mapState } from "vuex";
import { computed } from "vue";

export function useState(mapper) {
  const store = useStore();
  const storeStateFns = mapState(mapper);
  const storeState = {};

  Object.keys(storeStateFns).forEach((fnKey) => {
    const fn = storeStateFns[fnKey].bind({ $store: store });
    storeState[fnKey] = computed(fn);
  });
  return storeState;
}

Home.vue

<template>
  <div class="home">
    <table border="1">
      <h5>{{ name }}</h5>
      <h5>{{ age }}</h5>
      <h5>{{ counter }}</h5>
    </table>
  </div>
</template>

<script>
import { useState } from "../hooks/useState";

export default {
  setup() {
    const storeState = useState(["name", "age", "counter"]);
    return {
      ...storeState,
    };
  },
};
</script>

<style></style>

Vuex的计算属性_getters

使用state中的数据

image-20210916141156885

使用getters中的数据

image-20210916141733165

getters返回一个函数

image-20210916142513906

index.js

import { createStore } from "vuex";

export default createStore({
  state() {
    return {
      counter: 10,
      name: "Alice",
      age: 19,
      height: 180,
      books: [
        { name: "Alice", price: 10, count: 1 },
        { name: "Bruce", price: 9, count: 1 },
        { name: "Celina", price: 11, count: 1 },
      ],
    };
  },
  getters: {
    totalBooksPrice(state) {

      return function (disCount) {

        let totalPrice = 0;
        for (const book of state.books) {
          totalPrice += book.price * book.count * disCount;
        }
        return totalPrice;
        
      };

    },
  },
  mutations: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
  },
  actions: {},
  modules: {},
});

Home.vue

<template>
  <div class="home">
    <table border="1">
      <h2>{{ $store.getters.totalBooksPrice(0.8) }}</h2>
    </table>
  </div>
</template>

<script>
export default {
  setup() {},
};
</script>

<style></style>

批量拿到getters中的数据

image-20210916144839393

Home.vue

<template>
  <div class="home">
    <table border="1">
      <h2>{{ $store.getters.totalBooksPrice }}</h2>
      <h2>{{ $store.getters.info }}</h2>
      <hr />
      <h2>{{ totalBooksPrice }}</h2>
      <h2>{{ info }}</h2>
    </table>
  </div>
</template>

<script>
import { mapGetters, useStore } from "vuex";
import { computed } from "vue";
export default {
  setup() {
    const store = useStore();
    const gettersFns = mapGetters(["totalBooksPrice", "info"]);
    const getters = {};
    Object.keys(gettersFns).forEach((key) => {
      const fn = gettersFns[key].bind({ $store: store });
      getters[key] = computed(fn);
    });

    return {
      ...getters,
    };
  },
};
</script>

<style></style>

封装批量拿到getters中的数据

image-20210916145541975

hooks>useGetters.js

import { mapGetters, useStore } from "vuex";
import { computed } from "vue";

export function useGetters(mapper) {
  const store = useStore();
  const gettersFns = mapGetters(mapper);
  const getters = {};
  Object.keys(gettersFns).forEach((key) => {
    const fn = gettersFns[key].bind({ $store: store });
    getters[key] = computed(fn);
  });
  return getters;
}

mapState 和mapGetters的综合封装

图解

image-20210916163049125

hooks>useMapper.js

import { useStore } from "vuex";
import { computed } from "vue";

export function useMapper(mapper, mapFunc) {
  const store = useStore();
  const gettersFns = mapFunc(mapper);
  const getters = {};
  Object.keys(gettersFns).forEach((key) => {
    const fn = gettersFns[key].bind({ $store: store });
    getters[key] = computed(fn);
  });
  return getters;
}

hooks>useState.js

import { mapState } from "vuex";
import { useMapper } from "./useMapper";

export function useState(mapper) {
  return useMapper(mapper, mapState);
}

hooks>useGetters.js

import { useMapper } from "./useMapper";
import { mapGetters } from "vuex";

export function useGetters(mapper) {
  return useMapper(mapper, mapGetters);
}

hooks>index.js

import { useGetters } from "./useGetters";
import { useState } from "./useState";

export { useGetters, useState };

引用useGetters的文件:Home.vue

<template>
  <div class="home">
    <table border="1">
      <h2>{{ $store.getters.totalBooksPrice }}</h2>
      <h2>{{ $store.getters.info }}</h2>
      <hr />
      <h2>{{ totalBooksPrice }}</h2>
      <h2>{{ info }}</h2>
    </table>
  </div>
</template>

<script>
// import { useGetters } from "../hooks/useGetters";

import { useGetters } from "../hooks/index";

export default {
  setup() {
    return {
      ...useGetters(["totalBooksPrice", "info"]),
    };
  },
};
</script>

<style></style>

Mutation

无参数传递_计数器

image-20210916095130858

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
      <button @click="decrement">-1</button>
    </table>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
  methods: {
    increment() {
      this.$store.commit("increment");
    },
    decrement() {
      this.$store.commit("decrement");
    },
  },
};
</script>

<style></style>

有参数传递_计数器

图解

传递普通参数

image-20210916164844046

传递对象类型参数

image-20210916165159173

两种提交风格

image-20210916165533732

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
      <button @click="decrement">-1</button>
      <hr />
      <button @click="increment_10">+10</button>
    </table>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
  methods: {
    increment() {
      this.$store.commit("increment");
    },
    decrement() {
      this.$store.commit("decrement");
    },
    increment_10() {
      this.$store.commit("incrementN", { step: 10 });
    },
  },
};
</script>

<style></style>

store>index.js

import { createStore } from "vuex";

export default createStore({
  state() {
    return {
      counter: 10,
      name: "Alice",
      age: 19,
      height: 180,
      books: [
        { name: "Alice", price: 10, count: 1 },
        { name: "Bruce", price: 9, count: 1 },
        { name: "Celina", price: 11, count: 1 },
      ],
    };
  },
  getters: {
    totalBooksPrice(state) {
      let totalPrice = 0;
      for (const book of state.books) {
        totalPrice += book.price * book.count;
      }
      return totalPrice;
    },
    info(state) {
      return state.name + state.age;
    },
  },
  mutations: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    incrementN(state, payload) {
      state.counter += payload.step;
    },
  },
  actions: {},
  modules: {},
});

常量类型

图解

image-20210916170610731

store>index.js

import { createStore } from "vuex";
import { INCREMENT_N } from "./mutation-types";
export default createStore({
  state() {
    return {
      counter: 10,
      name: "Alice",
      age: 19,
      height: 180,
      books: [
        { name: "Alice", price: 10, count: 1 },
        { name: "Bruce", price: 9, count: 1 },
        { name: "Celina", price: 11, count: 1 },
      ],
    };
  },
  getters: {
    totalBooksPrice(state) {
      let totalPrice = 0;
      for (const book of state.books) {
        totalPrice += book.price * book.count;
      }
      return totalPrice;
    },
    info(state) {
      return state.name + state.age;
    },
  },
  mutations: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    // incrementN(state, payload) {
    //   state.counter += payload.step;
    // },

    [INCREMENT_N](state, payload) {
      state.counter += payload.step;
    },
  },
  actions: {},
  modules: {},
});

store>mutation-types.js

export const INCREMENT_N = "incrementN";

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
      <button @click="decrement">-1</button>
      <hr />
      <button @click="increment_10">+10</button>
    </table>
  </div>
</template>

<script>
import { INCREMENT_N } from "./store/mutation-types";
export default {
  name: "App",
  components: {},
  methods: {
    increment() {
      this.$store.commit("increment");
    },
    decrement() {
      this.$store.commit("decrement");
    },
    // 第一种提交风格
    increment_10() {
      this.$store.commit(INCREMENT_N, { step: 10 });
    },

    // 第二种提交风格
    // increment_10() {
    //   this.$store.commit({
    //     type: "incrementN",
    //     step: 10,
    //   });
    // },
  },
};
</script>

<style></style>

mapMutation

图解

image-20210916171838605

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
      <button @click="decrement">-1</button>
      <hr />
      <button @click="incrementN(10)">+10</button>
    </table>
  </div>
</template>

<script>
import { INCREMENT_N } from "./store/mutation-types";

import { mapMutations } from "vuex";
export default {
  name: "App",
  components: {},
  setup() {
    const mutations = mapMutations(["increment", "decrement", INCREMENT_N]);
    return {
      ...mutations,
    };
  },
};
</script>

<style></style>

actions

image-20210916211113056

基本使用

  1. 在组件中调用store中actions的函数.
  2. 在actions中调用mutations中的方法.
  3. 真正的数据修改发生在mutations的方法里面.

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
    </table>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
  methods: {
    increment() {
      console.log("1.在App.vue中调用store中的actions的incrementAction()");
      this.$store.dispatch("incrementAction");
    },
  },
};
</script>

<style></style>

index.js

import { createStore } from "vuex";
export default createStore({
  state() {
    return {
      counter: 10,
      name: "Alice",
      age: 19,
      height: 180,
      books: [
        { name: "Alice", price: 10, count: 1 },
        { name: "Bruce", price: 9, count: 1 },
        { name: "Celina", price: 11, count: 1 },
      ],
    };
  },
  getters: {},
  mutations: {
    increment(state) {
      console.log("3.真正调用mutations中的increment");
      state.counter++;
    },
  },
  actions: {
    incrementAction(context) {
      console.log("2.在actions中调用mutations中的increment");
      context.commit("increment");
    },
  },
  modules: {},
});

基本使用-异步请求-一秒后加一的计数器

图解

image-20210916231233308

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
    </table>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
  methods: {
    increment() {
      this.$store.dispatch("incrementAction");
    },
  },
  mounted() {
    this.$store.dispatch("getBannerDataAction");
  },
};
</script>

<style></style>

store>index.js

import { createStore } from "vuex";
import axios from "axios";
export default createStore({
  state() {
    return {
      counter: 10,
      banners: [],
    };
  },
  getters: {},
  mutations: {
    increment(state) {
      state.counter++;
    },
    addBannerData(state, payload) {
      state.banners = payload;
    },
  },
  actions: {
    incrementAction(context) {
      setTimeout(() => {
        context.commit("increment");
      }, 1000);
    },
    getBannerDataAction(context) {
      axios.get("http://123.207.32.32:8000/home/multidata").then((res) => {
        context.commit("addBannerData", res.data.data.banner.list);
      });
    },
  },
  modules: {},
});

axios异步请求

图解

image-20210916230259203

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
    </table>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
  methods: {
    increment() {
      this.$store.dispatch("incrementAction");
    },
  },
  mounted() {
    this.$store.dispatch("getBannerDataAction");
  },
};
</script>

<style></style>

store>index.js

import { createStore } from "vuex";
import axios from "axios";
export default createStore({
  state() {
    return {
      counter: 10,
      banners: [],
    };
  },
  getters: {},
  mutations: {
    increment(state) {
      state.counter++;
    },
    addBannerData(state, payload) {
      state.banners = payload;
    },
  },
  actions: {
    incrementAction(context) {
      context.commit("increment");
    },
    getBannerDataAction(context) {
      axios.get("http://123.207.32.32:8000/home/multidata").then((res) => {
        context.commit("addBannerData", res.data.data.banner.list);
      });
    },
  },
  modules: {},
});

context的类型-context的解构传参

图解

image-20210917093043344

参数传递

image-20210917122102173

actions的分发操作的中风格

图解

image-20210917122908418

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="increment">+1</button>
    </table>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
  methods: {
    increment() {
      // 风格一
      // this.$store.dispatch("incrementAction", { msg: "我是被传递的参数" });

      // 风格二
      this.$store.dispatch({
        type: "incrementAction",
        msg: "我是被传递的参数",
      });
    },
  },

  mounted() {
    this.$store.dispatch("getBannerDataAction");
  },
};
</script>

<style></style>

actions的辅助函数

methods中使用

  1. 使用方法和mapMutation一样

setup中使用

图解

image-20210917144104774

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>{{ $store.state.counter }}</h2>
      <button @click="incrementAction">+1</button>
      <button @click="add">+1</button>
    </table>
    <table border="1">
      <button @click="getBannerDataAction">获取网络数据</button>
      <button @click="getData">获取网络数据</button>
    </table>
  </div>
</template>

<script>
import { mapActions } from "vuex";

export default {
  name: "App",
  components: {},
  setup() {

    const actions = mapActions(["incrementAction", "getBannerDataAction"]);

    const actions2 = mapActions({
      add: "incrementAction",
      getData: "getBannerDataAction",
    });

    return {
      ...actions,
      
      ...actions2,
    };
  },
};
</script>

<style></style>

actions中的函数的返回值

为什么会出现这样的需求?

比如说我们在axios请求之后要将数据返回给原来的组件,这个时候就要return

图解

image-20210917154312628

store>index.js
import { createStore } from "vuex";
import axios from "axios";
export default createStore({
  state() {
    return {
      counter: 10,
      banners: [],
    };
  },
  getters: {},
  mutations: {
    increment(state) {
      state.counter++;
    },
    addBannerData(state, payload) {
      state.banners = payload;
      console.log(payload);
    },
  },
  actions: {
    incrementAction(context) {
      context.commit("increment");
    },
    getBannerDataAction(context) {
      return new Promise((resolve, reject) => {
        axios
          .get("http://123.207.32.32:8000/home/multidata")
          .then((res) => {
            context.commit("addBannerData", res.data.data.banner.list);
            resolve("我是从store>index.js发来的标志位");
          })
          .catch((err) => {
            reject(err);
          });
      });
    },
  },
  modules: {},
});

App.vue
<template>
  <div class="app">
    <table border="1"></table>
  </div>
</template>

<script>
import { useStore } from "vuex";
import { onMounted } from "vue";
export default {
  name: "App",
  components: {},
  setup() {
    const store = useStore();
    onMounted(() => {
      const promise = store.dispatch("getBannerDataAction");
      promise
        .then((res) => {
          console.log(res);
        })
        .catch((err) => {
          console.log(err);
        });
    });
    return {};
  },
};
</script>

<style></style>

module的基本使用

图示

image-20210917165612773

module的命名空间

背景问题

图解

image-20210917194733727

输出

  1. 当我点击按钮式,根模块和子模块中的数据都会被修改,
  2. 这是因为根模块和子模块中的increment都会被调用

Honeycam 2021-09-17 19-41-04

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>rootStore: {{ $store.state.counter }}</h2>
      <h2>homeStore: {{ $store.state.home.counter }}</h2>
      <button @click="increment">+1</button>
    </table>
  </div>
</template>

<script>
import { mapMutations } from "vuex";
export default {
  name: "App",
  components: {},
  setup() {
    const mutations = mapMutations(["increment"]);
    return {
      ...mutations,
    };
  },
};
</script>

<style></style>

store>index.js

import { createStore } from "vuex";
import homeModule from "./modules/home";
import userModule from "./modules/user";

const store = createStore({
  state: {
    counter: 0,
  },
  getters: {},
  mutations: {
    increment(state) {
      state.counter++;
    },
  },
  actions: {},
  modules: {
    home: homeModule,
    user: userModule,
  },
});
export default store;

store>modules>home.js

const homeModule = {
  state: {
    counter: 0,
  },
  getters: {},
  mutations: {
    increment(state) {
      state.counter++;
    },
  },
  actions: {},
};
export default homeModule;

解决方案-加上命名空间namespaced

图解

  1. 仅仅只要加上namespaced:true这一行代码就可以了

image-20210917203655932

输出

Honeycam 2021-09-17 20-39-29

调用子模块的中的函数

图解

  1. mapMutations中传入两个参数,第一个参数传入模块,第二个参数传入需要的函数

image-20210917205128692

输出

  1. 这次就是单单修改home模块中的数据,root模块中的数据没有修改

Honeycam 2021-09-17 20-54-26

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>rootStore: {{ $store.state.counter }}</h2>
      <h2>homeStore: {{ $store.state.home.counter }}</h2>
      <button @click="increment">+1</button>
    </table>
  </div>
</template>

<script>
import { mapMutations } from "vuex";
export default {
  name: "App",
  components: {},
  setup() {
    const mutationsHome = mapMutations("home", ["increment"]);
    return {
      ...mutationsHome,
    };
  },
};
</script>

<style></style>

module修改或派发根组件

image-20210917205814130

module中getters和actions中的函数参数列表

image-20210917205917449

module的辅助函数

image-20210917210117406

image-20210917210132687

createNamespacedHelpers

image-20210917230823683

App.vue

<template>
  <div class="app">
    <table border="1">
      <h2>rootStore: {{ $store.state.counter }}</h2>
      <h2>homeStore: {{ $store.state.home.counter }}</h2>
      <button @click="increment">+1</button>
    </table>
  </div>
</template>

<script>
import { createNamespacedHelpers } from "vuex";
const homeMapMutations = createNamespacedHelpers("home").mapMutations;

export default {
  name: "App",
  components: {},
  setup() {
    // 原来的方式
    // const mutationsHome = mapMutations("home", ["increment"]);
    
    // 现在的方式
    const mutationsHome = homeMapMutations(["increment"]);
    return {
      ...mutationsHome,
    };
  },
};
</script>

<style></style>

mapState的关于模块的重新封装

图解

image-20210917232235356

useState.js

image-20210917232421477

image-20210917232440329

image-20210917232459435

import { createNamespacedHelpers } from "vuex";
import { useMapper } from "./useMapper";

export function useState(moduleName, mapper) {
  let mapperFn = null;
  if (typeof moduleName === "string" && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapState;
  }

  return useMapper(mapper, mapperFn);
}

useGetters.js

import { useMapper, createNamespacedHelpers } from "./useMapper";

export function useGetters(mapper) {
  let mapperFn = null;
  if (typeof moduleName === "string" && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapGetters;
  }
  return useMapper(mapper, mapperFn);
}

nexttick

官方解释:将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。

背景问题

输出

  1. 现在我们点击一下按钮,希望获取这个文字的高度,但是我们发现,打印的是0,也就是初始的高度
  2. 第二次才打印上次按钮的高度,
  3. 我们现在希望,这个获取文字高度的执行,能够和点击按钮同步执行,也就是当这个dom元素变化之后在执行,
  4. 某种程度上来看,就是延迟到下一个周期执行,也就是nexttick的字面意思.

![Honeycam 2021-09-18 14-34-40](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-18 14-34-40.gif)

App_nextTick.vue

<template>
  <div class="app">
    <div class="msgStyle" ref="msgRef">
      <p>{{ msg }}</p>
    </div>
    <button @click="addMsgContent">add</button>
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  name: "App",
  components: {},
  setup() {
    const msg = ref("");
    const msgRef = ref(null);
    const addMsgContent = () => {
      msg.value += "Hello World";
      console.log(msgRef.value.offsetHeight);
    };

    return {
      msg,
      addMsgContent,
      msgRef,
    };
  },
};
</script>

<style>
.msgStyle p {
  width: 100px;
}
</style>

nextTick的基本使用

输出

  1. 现在就是做到了这个显示高度和DOM更新是同步的

![Honeycam 2021-09-18 14-39-52](../ad_images/vu_vue3.0LearnNote.assets/Honeycam 2021-09-18 14-39-52.gif)

和上面的对比

image-20210918144457233

App_nextTick.vue

<template>
  <div class="app">
    <div class="msgStyle" ref="msgRef">
      <p>{{ msg }}</p>
    </div>
    <button @click="addMsgContent">add</button>
  </div>
</template>

<script>
import { ref, nextTick } from "vue";
export default {
  name: "App",
  components: {},
  setup() {
    const msg = ref("");
    const msgRef = ref(null);
    const addMsgContent = () => {
      msg.value += "Hello World";
      nextTick(() => {
        console.log(msgRef.value.offsetHeight);
      });
    };

    return {
      msg,
      addMsgContent,
      msgRef,
    };
  },
};
</script>

<style>
.msgStyle p {
  width: 100px;
}
</style>

posted on 2021-10-20 23:13  lazycookie  阅读(220)  评论(0编辑  收藏  举报