• vue整合笔记


    vue

    前言:整合了之前的笔记,加入新记的笔记信息

    目录

    一 、简介与初识

    是什么

    • 是一个专注于视图的渐进式前端框架,如果需要其他功能可以使用它的拓展库,Vue 的核心库只关注视图层。

    优缺点

    • 可以和其他语言共存,在一个app或者一个页面视图中,可以使用其他的语言开发,不会影响,因为他是组件化的

    初识

    数据绑定v-model
    • 是双向绑定数据当输入框数据变化,会让同一个使用v-model绑定的数据也变化,我理解为多个引用指向同一块内存,或者像linux的软连接,可以进行一块记忆。
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>test</title>
    </head>
    <body>
    <div id="app">
        <ul>
            <li>{{message}}</li>
        </ul>
    
        <input type="text" v-model="message" />{{message}}
    </div>
    <script src="../../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                message: "hello world"
            }
        });
    </script>
    </body>
    </html>
    
    • 效果图

    • v-model.lazy

      只有在input失去焦点的时候才会刷新数据

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
          <script src="../../js/vue.js"></script>
      </head>
      <body>
      <div id="app">
          <ul>
              <li>{{message}}</li>
          </ul>
          <!--    失去焦点或者enter才会更新值-->
          <input type="text" v-model.lazy="message">{{message}}
      </div>
      
      <script>
          const app = new Vue({
              el: "#app",
              data: {
                  message: "hello world"
              }
          });
      
      </script>
      </body>
      </html>
      

    二、命令-生命周期函数

    命令:详细

    order 语法糖 作用
    v-bind=attr :attr 将数据和标签帮顶起来
    v-on:action=actionName @action=actionName 绑定事件,事件不需要传参数可以省略()
    {{expression}} 从vue对象中取值
    v-if="boolean" if
    v-else-if="boolean" else if
    v-else else
    key 作为一种标签的身份标识
    v-show="boolean" 和v-if的区别是,它是display=none标签还在
    v-for="(item, index) in items" for循环
    :class="{className:boolean}" 也可以用数组
    v-model="entity" 双向绑定表单,实际上是两个指令结合v-bind:value和v-on:input
    v-html 输出真正的 HTML
    v-once 写在标签上只渲染一次
    $forceUpdate 强制更新数据

    生命周期函数

    1. created():当组件创建成功时
      export default {
        name: "Home",
        created() {
          console.log("Home组件被创建成功");
        }
      };
      
    2. mounted(): 当组件被挂载到dom上

      未挂载成功的元素不能被获取到

      export default {
        name: "Home",
        created() {
          console.log("Home组件被创建成功");
        },
        mounted(){
          console.log("组件被挂载成功")
        }
      };
      
    3. updated(): 当组件中发生变化时

      export default {
        name: "Home",
        created() {
          console.log("Home组件被创建成功");
        },
        mounted(){
          console.log("组件被挂载成功")
        },
        updated(){
          console.log("组件中发生改变时");
        }
      };
      
    4. 其他

      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="utf-8">
      <title></title>
      <script src="vue.js" type="text/javascript" charset="utf-8"></script>
      </head>
      <body>
      <div id="app">
      	{{msg}}
      </div>
      <script type="text/javascript">
      var vm = new Vue({
      	el : "#app",
      	data : {
      		msg : "hi vue",
      	},
      	//在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
      	beforeCreate:function(){
      		console.log('beforeCreate');
      	},
      	/* 在实例创建完成后被立即调用。
      	在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。
      	然而,挂载阶段还没开始,$el 属性目前不可见。 */
      	created	:function(){
      		console.log('created');
      	},
      	//在挂载开始之前被调用:相关的渲染函数首次被调用
      	beforeMount : function(){
      		console.log('beforeMount');
      
      	},
      	//el 被新创建的 vm.$el 替换, 挂在成功	
      	mounted : function(){
      		console.log('mounted');
      	
      	},
      	//数据更新时调用
      	beforeUpdate : function(){
      		console.log('beforeUpdate');
      			
      	},
      	//组件 DOM 已经更新, 组件更新完毕 
      	updated : function(){
      		console.log('updated');
      			
      	}
      });
      setTimeout(function(){
      	vm.msg = "change ......";
      }, 3000);
      </script>
      </body>
      </html>
      
      

    事件传参问题

    • 绑定事件时没写(),但是需要传参,参数为undefined
    • 绑定事件写了(),需要传参但是没有传,vue会默认传绑定的event对象给第一个参数,第二个就是undefined
    • 如果需要传event对象,只写event会被vue解析如果没又在vue中定义会报错,需要写成$event

    事件的冒泡、捕获、其他属性

    • 冒泡:@click="action(123)" =>@click.stop="action(123)" , 解决冒泡
    • 捕获:@click="action(123)" =>@click.prevent="action(123)"
    • 键盘:@keyUp【常用】|keyDown.enter="action",监听enter键
    • 自定义组件的根元素的事件:@click.native
    • 只监听一次: @click.once

    标签值被复用了

    需要给标签添加一个key的属性就可以了,是因为虚拟dom的问题

    v-for

    • 一般v-for中要指定:key=“一般是一个唯一的东西” 要和每一个标签对应起来,虚拟dom进行diff算法的时候会复用这个标签
     <!-- 当items是数组 -->
        <ul v-for="(item, index) in items" :key="item">
            <li></li>
        </ul>
        <!-- 当items是对象,默认是取value -->
        <ul v-for="value in obj" :key="value">
            <li></li>
        </ul>
        <!-- 当items是对象,默认是取value,key,index -->
        <ul v-for="(value,key,index) in obj" >
            <li></li>
        </ul>
    
    • 支持响应式的方法

      • pop()删除最后一个元素

      • push(a,b...)追加一【多】个元素

      • shift()删除第一个元素

      • unshift(a,b...)添加【多】元素到第一个的位置

    • sort([compare])

      • reverse()

      • splice(起始位置,删除几个【只填第一个参数就是从第一个位置删除到完】,从第一个参数位置添加【可以多个】的元素)

      • Vue.set(src,index,newValue) 修改src 中index位置值,或者对象赋值,对象直接赋值不起作用

        this.$set(a,'title','列表2');
        //或者
        Vue.set(a,'title','列表2');
        
    • 不支持响应式的方法

      • items[index]=""

    过滤器

    在vue的options中定义filters:{run :function(pram),调用 param|run

    计算属性

    • 关键字:computed

    • 更加高效因为使用了缓存

    • 计算属性的响应式是建立在计算一个响应式的数据上的,它变化才会更新计算属性,而方法是每次都计算不使用缓存

    • 计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter ,给计算属性赋值的时候会调用setter方法,取值调用getter方法

      computed: {
        fullName: {
          // getter
          get: function () {
            return this.firstName + ' ' + this.lastName
          },
          // setter
          set: function (newValue) {
            var names = newValue.split(' ')
            this.firstName = names[0]
            this.lastName = names[names.length - 1]
          }
        }
      }
      

    三、vue实例、v-model修饰符

    vue实例

    var data = { a: 1 }
    var vm = new Vue({
      el: '#example',
      data: data
    })
    
    vm.$data === data // => true
    vm.$el === document.getElementById('example') // => true
    
    // $watch 是一个实例方法
    vm.$watch('a', function (newValue, oldValue) {
      // 此回调函数将在 `vm.a` 改变后调用
    })
    

    v-model

    <input type="text" v-model="message" />{{message}}
    
    v-model.lazy
    • v-model 会和标签双向绑定,但是编辑时,时时刻刻都在同步,资源消耗没必要,v-model.lazy会监听enter 或者手标失去焦点的时候才进行数据同步
    <!--    失去焦点或者enter才会更新值--><input type="text" v-model.lazy="message">{{message}}
    
    v-model.number
    • 将输入框的值转换为number类型,默认是字符串处理所有的键盘录入
    v-model.trim
    • 将输入框中的左右两边的空格去掉

    响应式注意

    每当 data 对象发生变化,都会触发视图重新渲染。值得注意的是,如果实例已经创建,那么只有那些 data 中的原本就已经存在的属性,才是响应式的。也就是说,如果在实例创建之后,添加一个新的属性将不是响应式的

    四、组件化

    什么是组件

    借鉴了将一个大的问题拆分成一个个的小问题这种思想 , 就是"基础库"或者“基础组件",意思是把代码重复的部分提炼出一个个组件供给功能使用。

    • 将一个页面拆分成一个个的小组件,可以递归的拆分
    • 每个组件完成自己相关的功能,多个组件共同组成一个页面或者程序
    • 复用性:下次需要同样的功能就可以复用
    • 类似于项目中的一个模块,只不过更加细化了。

    组件的使用

    1. 创建组件的构造器
    2. 注册组件
    3. 使用组件

    组件必须放在vue管理的作用域内,如果是多个标签必须被一个元素包裹,就是有一个唯一的祖先元素

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <script src="../../js/vue.js"></script>
    <div id="app">
        <cpt></cpt>
        <cpt></cpt>
        <cpt></cpt>
        <cpt></cpt>
    </div>
    
    <script>
        // 1. 创建组件构造器
        const component = Vue.extend({
            template: `
                <div>
                    hello
                </div>`,
        });
        // 2. 注册组件 全局组件
        Vue.component('cpt', component);
    
        const app = new Vue({
            el: "#app",
            data: {
                message: "hello world"
            }
        });
    
    </script>
    </body>
    </html>
    

    局部组件

    <div id="app">11
        <cpt></cpt>
        <cpt></cpt>
    </div>
    
    <div id="app2">22
        <cpt></cpt>
    </div>
    <script>
        // 1. 创建组件构造器
        const component = Vue.extend({
            template: `
                <div>
                    hello
                </div>`,
        });
    
        //局部组件 只在app中的作用域有效
        const app = new Vue({
            el: "#app",
            data: {
                message: "hello world"
            },
            components: {
                cpt: component
            }
        });
    
        //未注册组件
        const app2 = new Vue({
            el: "#app2",
            data: {
                message: "hello"
            }
        });
    
    </script>
    

    父子组件

    <div id="app">11
        <pt></pt>
        <pt></pt>
        <pt></pt>
    </div>
    <script>
    
        /*第1个组件构造器*/
        const child = Vue.extend({
            template: `
                <div>
                    child
                </div>`
        });
        // 第二创建组件构造器
        const parent = Vue.extend({
            template: `
                <div>
                    parent
                    <cd></cd>
                </div>`,
            components: {
                cd: child
            }
        });
    
    
        //局部组件 只在app中的作用域有效
        const app = new Vue({
            el: "#app",
            data: {
                message: "hello world"
            },
            components: {
                pt: parent
            }
        });
    
    </script>
    

    组件的传递

    组件不会向上级作用域传递,只会向下传递,孙子没有在爷爷的作用域注册的话孙子只能在父亲的作用域使用

    组件的语法糖

    <div id="app">11
      <pt></pt>
      <pt></pt>
      <pt></pt>
    </div>
    <script>
    
      //局部组件 只在app中的作用域有效
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        },
        components: {
          pt: {
            //  语法糖直接可以放在注册的地方
            template: `
                <div>
                    hello
                </div>`
          }
        }
      });
    
    </script>
    

    模板的分离

    <script src="../../js/vue.js"></script>
    <div id="app">11
      <pt></pt>
      <pt></pt>
      <pt></pt>
    </div>
    <!--<script type="text/x-template" id="pt">
      <div>
        <div>我是标题</div>
      </div>
    </script>-->
    
    <template id="pt">
      <div>
        <div>我是tempalte</div>
      </div>
    
    </template>
    <script>
    
      //局部组件 只在app中的作用域有效
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        },
        components: {
          pt: {
            //  语法糖直接可以放在注册的地方
            template: "#pt"
          }
        }
      });
    
    </script>
    
    

    组件访问数据

    • 组件不能访问实例中的数据
    • 只能访问自己的数据
    • 在子组件中data属性是一个function不是对象,可以返回一个数据对象供它访问
    • 组件也有method属性,它的原型实际上是指向vue的实例的
    <div id="app">11
      <pt></pt>
      <pt></pt>
      <pt></pt>
    </div>
    
    <template id="pt">
      <div>
        <div>我是{{title}}</div>
      </div>
    </template>
    <script>
    
      //局部组件 只在app中的作用域有效
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        },
        components: {
          pt: {
            
            template: "#pt",
            //是一个函数,且只能访问自己的数据
            data(){
              return {title:"title"};
            }
          }
        }
      });
    
    </script>
    
    
    
    

    组件的data必须是函数

    • 如果写属性的话,很容易造成多个组件的数据引用指向同一块内存,会相互影响
    • 用function的话你只要每次返回一个匿名对象,他是没有公共引用指向的所以不会影响,如果需要的话你自己可以return 一个公用的引用就会相互影响的
    • 所以为了避免这种bug,data不是function就会报错
    • 必须return 一个对象{}

    父子组件通信

    父传子
    • props属性 : 可以写成数组或者对象,对象可以限制类型,对象更好点,也可以类型写成对象添加更多的限制、给默认值
    • 给默认值的时候如果是对象或者是数组,不能直接用{}、[] 需要用工厂(default(){})来创建
    • 自定义validator
    • 可以自定义一个类作为类型
    <div id="app">
      <pt :msg="msg" :title="title"></pt>
    </div>
    
    <template id="pt">
      <div>
        <div>{{title}}</div>
        <div>{{msg}}</div>
      </div>
    </template>
    <script>
      // 1.注册组件
      const pt = {
        template:"#pt",
        data() {
          return {};
        },
        methods: {},
        // props:["title","msg"] 可以写成数组或者对象,对象可以限制类型,对象更好点
        props:{
          // title:Array,
          title:{
            type: Array,
            default(){
              return [];
            }
          },
          //也可以写成对象的添加更多的限制、给默认值
          msg:{
            type:String,
            default:"",
            required:true,
            //自定义validator 这个待查阅
            validator: function (val) {
              return val == "hello worl";
            }
          }
        }
      }
    
      //局部组件 只在app中的作用域有效
      const app = new Vue({
        el: "#app",
        data: {
          msg: "hello world",
          title:["aaa","bbb","ccc"]
        },
        //字面量简写  pt可替换pt:pt
        components:{pt}
      });
    
    </script>
    
    
    
    子传父|自定义事件
    • v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。
    • $emit --》this.$emit('myevent')会传递给父组件的监听事件要同名
    • 推荐你始终使用 kebab-case 的事件名 my-event
    • 子组件尽量和自己的data属性去绑定
    <div id="app">
      <!--  不写参数会默认将$emit事件后传的参数【可多个】传出来,写了参数报错-->
      <pt @child-click="parentClick"></pt>
    </div>
    
    <template id="pt">
      <div>
        <button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
      </div>
    </template>
    <script>
      // 1.注册组件
      const pt = {
        template: "#pt",
        data() {
          return {
            categories: [
              {id: "aaa", name: "aaa"},
              {id: "bbb", name: "bbb"},
              {id: "ccc", name: "ccc"},
              {id: "ddd", name: "ddd"}
            ]
          };
        },
        methods: {
          btnClick(ite) {
            // js中这样写不能驼峰,vue可以
            this.$emit('child-click', ite,1);
          }
        }
      };
    
      //局部组件 只在app中的作用域有效
      const app = new Vue({
        el: "#app",
        data: {
          msg: "hello world",
          title: ["aaa", "bbb", "ccc"]
        },
        components: {pt},
        methods: {
          parentClick(obj,a) {
            console.log(obj,a);
          }
        }
      });
    
    </script>
    
    
    练习
    1. num1、num2从父组件传递过来
    2. 修改num1,dnum1也变,同时传dnum1给父组件,父组件改变num1,也改变了prop1
    3. dnum2一直是dnum1的1%
    <!--1. num1、num2从父组件传递过来
    2. 修改num1,dnum1也变,同时传dnum1给父组件,父组件改变num1,也改变了prop1
    3. dnum2一直是dnum1的1%-->
    <div id="app">
      <pt :cnum1="num1" :cnum2="num2"
          @change1="cc1"
          @change2="cc2"
      ></pt>
    </div>
    
    <template id="pt">
      <div>
        <p>props:{{cnum1}}</p>
        <p>data:{{dnum1}}</p>
        cnum1<input type="text" :value="dnum1" @input="changeProp1"><br>
        <p>props:{{cnum2}}</p>
        <p>data:{{dnum2}}</p>
        cnum2<input type="text" :value="dnum2" @input="changeProp2">
      </div>
    </template>
    <script>
    
      //局部组件 只在app中的作用域有效
      const app = new Vue({
        el: "#app",
        data: {
          num1: 1,
          num2: 2
        },
        methods: {
          cc1(eve1) {
            this.num1 = eve1;
          },
          cc2(eve2) {
            this.num2 = eve2;
          }
        },
        components: {
          pt: {
            template: "#pt",
            props: {
              cnum1: {
                type: Number,
                default: 3
              },
              cnum2: {
                type: Number,
                default: 4
              }
            },
            data() {
              return {
                dnum1: this.cnum1,
                dnum2: this.cnum2,
              };
            }, 
            methods: {
              changeProp1(event1) {
                this.dnum1 = event1.target.value;
                console.log(this.dnum1)
                if (this.dnum1) {
                  this.dnum1 = parseInt(this.dnum1)
                  this.dnum2 = this.dnum1 / 100;
                  this.$emit('change1', this.dnum1);
                } else {
                  this.dnum2 = "";
                }
    
              },
              changeProp2(event2) {
                this.dnum2 = event2.target.value;
                this.$emit('change2', parseInt(this.dnum2));
              }
    
            }
          }
        }
      });
    
    </script>
    
    
    

    watch

    • watch监听对象不能直接监听,可以用computed代替对象

    • 语法:

      watch:{
      	监听的属性名(newValue, oldValue){
      
      	}
      }
      
      
    <script src="../../js/vue.js"></script>
    <div id="app">
      {{message}}
      <input type="text" v-model="message">
      {{demo.name}}
      <input type="text" v-model="demo.name">
    </div>
    
    <template id="cd">
      <div>
        aaaaa
      </div>
    
    </template>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world",
          demo: {
            name: "nameObj"
          }
        },
        computed:{
          demoName(){
            return this.demo.name;
          }
        },
        watch: {
          message(newVal, oldVal) {
            console.log(newVal, oldVal);
          },
          //不能直接监听对象
          // demo(val) {
          //   console.log(val);
          // }
          demoName(val) {
            console.log(val);
          }
        },
        components: {
          cd: {
            template: "#cd"
          }
        }
      });
    
    </script>
    
    
    
    • 如果是键的路径需要用引号包裹

    • 也可以外部调用

    • immediate和handler

      watch有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。

      比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true。

    • **deep: **当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听

    <div id="app">
      {{demo1.name}}
      <input type="text" v-model="demo1.name">
      {{demo.name}}
      <input type="text" v-model="demo.name">
      <input type="text" v-model="demo2">
    </div>
    
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world",
          demo: {
            name: "nameObj"
          },
          demo1: {
            name: "nameObj"
          },
          demo2:"qweqw"
        },
        computed: {
          demoName() {
            return this.demo.name;
          }
        },
        watch: {
          //如果是键的路径需要用引号包裹
          "demo.name": function (val) {
            console.log(val);
          },
            
          // childrens: {  //监听的属性的名字
          //   handler:function(val){
          //     console.log(val.name);
          //   },
          //   deep: true, //可以监听到一个对象的内部属性变化
           //  immediate: true
          // },
          // "childrens.name":function (val) {
          //   console.log(val);
          // }
        }
      });
      //外部调用
      app.$watch("demo2",function (val) {
        console.log(val)
      })
    </script>
    
    
    

    访问子组件实例 $children和$refs

    • 一般不会用$children来取子组件
    • $refs.refName | $refs['refName']
      • 如果多个相同的引用会取最后一个
      • 如果绑定的是一个普通标签拿到的就是一个dom对象
    <div id="app">
      <tmp ref="a"></tmp>
      <tmp ref="a"></tmp>
      <tmp ref="b"></tmp>
      <button @click="btnClick">打印子组件</button>
    </div>
    <template id="tmp">
      <div>
        <p>哈哈哈</p>
      </div>
    </template>
    
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        },
        methods:{
          btnClick(){
            //1. 一般不会用$children来取子组件
            // console.log("第一个子组件:",this.$children[0]);
            // console.log("所有子组件:",this.$children);
    
            // 2.$refs.refName|['refName']
            console.log("所有组件有ref属性的组件:",this.$refs);
            //如果多个相同的引用会取最后一个
            console.log("取得固定的ref的元素:",this.$refs["a"]);
            console.log("取得固定的ref的元素:",this.$refs.b);
          }
        },
        components: {
          tmp: {
            template: "#tmp"
          }
        },
    
      });
    
    </script>
    
    

    访问父组件实例

    • 不建议使用this.$parent,会让组件的耦合增强不够独立
    • 祖先组件this.$root
    <div id="app">
      <tmp></tmp>
    </div>
    <template id="tmp">
      <div>
        <p>哈哈哈</p>
        <button @click="btnClick">打印父组件</button>
      </div>
    </template>
    
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        },
        components: {
          tmp: {
            template: "#tmp",
            methods: {
              btnClick() {
                //1. 不建议使用,会让组件的耦合增强不够独立
                console.log("打印直系父组件:", this.$parent);
                //祖先组件
                console.log("打印root组件:", this.$root);
              }
            }
          },
        },
    
      });
    
    
    

    插槽slot

    • 拓展组件像回调函数一样,usb接口一样
    • 插槽的基本使用
    • 插槽的默认值 默认值
    <!--1. 插槽的基本使用 <slot></slot>-->
    <!--2. 插槽的默认值 <slot>默认值</slot>-->
    <div id="app">
      <tmp></tmp><br>
      <tmp></tmp><br>
      <tmp></tmp><br>
      <tmp><div>我是插槽</div></tmp>
      <tmp><i>我是插槽i</i></tmp>
    </div>
    <template id="tmp">
      <div>
        <p>哈哈哈</p>
        <slot><p>我是默认值*******</p></slot>
        <p>娃娃</p>
      </div>
    </template>
    
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        },
        components: {
          tmp: {
            template: "#tmp"
          },
        }
    
      });
    
    </script>
    
    
    具名插槽
    <div id="app">
      <tmp ><a slot="right" href="#">我替换右边</a></tmp><br>
      <tmp ><a slot="left" href="#">我替换左边</a></tmp><br>
      <tmp><a href="#">我替换没名字的</a></tmp><br>
    </div>
    <template id="tmp">
      <div>
        <slot name="left"><p>我是默认值left</p></slot>
        <slot name="center"><p>我是默认值center</p></slot>
        <slot name="right"><p>我是默认值right</p></slot>
        <slot><p>我是默认值没有名字</p></slot>
      </div>
    </template>
    
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world"
        },
        components: {
          tmp: {
            template: "#tmp"
          },
        }
    
      });
    
    

    编译作用域

    • 始终使用自己组件中的变量
    <div id="app">
    <!--    在谁的作用域用谁的变量-->
      <cp v-show="isShow"></cp>
    </div>
    <template id="cp">
    
      <div v-show="isShow"><!-- div父元素初始化的时候不受影响 -->
        <a href="">aaa</a>
        <button v-show="isShow">按钮</button>
      </div>
    </template>
    
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world",
          isShow: true
        },
        components: {
          cp: {
            template: "#cp",
            data() {
              return {
                isShow: false
              };
            }
          }
        }
      });
    
    </script>
    
    
    作用域插槽
    • 父组件想要替换子组件的插槽的数据,数据的具体值还是由子组件来决定
    • slot-scope="slotData",类似于该组件的对象,2.5之前要用template标签
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
    <script src="../../js/vue.js"></script>
    <div id="app">
      <cp>
        <!--    slotData:类似于该组件的对象,2.5之前要用template-->
        <template slot-scope="slotData">
          <!--      取得绑定在组件中的数据-->
          <span v-for="item in slotData.datas">{{item}}-</span>
        </template>
      </cp>
    
      <cp>
        <template slot-scope="slotData">
          <!--      join方法将数组拼接成字符串-->
          <span>{{slotData.datas.join(' * ')}}</span>
        </template>
      </cp>
    </div>
    <template id="cp">
    
      <div>
        <!--    作为传递的数据-->
        <slot :datas="languages">
          <ul>
            <li v-for="item in languages">{{item}}</li>
          </ul>
        </slot>
      </div>
    </template>
    
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          message: "hello world",
        },
        components: {
          cp: {
            template: "#cp",
            data() {
              return {
                languages: ['java', 'javascript', 'css', 'html', 'vb', 'python']
              };
            }
          }
        }
      });
    
    </script>
    </body>
    </html>
    
    

    五、es6模块化

    阮一峰的ES6---Module的加载实现

    把功能进行划分,将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务

    为什么有模块化

    • js是按顺序加载的,所以一般相互依的js是具有强制性的
    • 多个js文件定义的引用会污染全局变量,多人协作开发可能会有冲突
    • 可以用闭包

    闭包解决多人协作开发

    • 只需要写好自己的模块化的命名,就可以很好的避免冲突了,相当于把所有的容错点都聚焦在一个点上,犯错的机会就少了,
    • 但是代码的复用性还是很差
    // ;是为了防止其他的导入js相互影响
    ;var xm01 = (function xiaoming01() {
      return {
        aa:"asdas",
        flag: true
      };
    }())
    
    
    //js文件2
    ;(function () {
      if (xm01.flag) {
        alert("xm01.flag:" + xm01.flag);
      }
    }());
    
    

    组件化类似模块化的更细粒度,组件充当了基本类库一样的东西目的是复用拓展性,模块主要是以功能区分类别划分尽量隔离其他业务

    模块化练习

    • xiaoming01.js

      // es6的导出,02中导入
      export let exa = "222";
      let exa1 = "333";
      let exb = "333";
      
      export {exb, exa1};
      export function fun(){
        console.log("asasddsds");
      }
      
      
      //export default :import的时候可以自定义命名,一个js中只能有一个default
      let aaa="export default";
      export default aaa;
      
      
    • xiaoming02.js

      // 导入 ,这里需要写上.js
      import {exa, exa1, exb} from "./xiaoming01.js";
      // 01
      console.log(exa1, exb);
      
      //导入default可以自定义命名
      import asd from "./xiaoming01.js";
      
      console.log('export:',asd);
      
      //导入全部的导出,并且重命名
      import * as all from "./xiaoming01.js";
      
      console.log(all);
      console.log(all.default)
      
      
      
      
    • 01-es6.html

      • script需要指定type=module
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="./xiaoming01.js" type="module"></script>
        <script src="./xiaoming02.js" type="module"></script>
      </head>
      <body>
      没有使用导入导出的话:
      Uncaught ReferenceError: flag is not defined
      at xiaoming02.js:3
          以前是可以执行的先在不知道怎么执行不了了
      </body>
      </html>
      
      

    六、npm、webpack

    npm

    • node package manager , node包管理工具

    • 查看版本:npm -v

    • 卸载安装的包 npm uninstall 包名 或 npm remove 包名

    • 查看包的详细信息, npm info 包名

    • 查看一个包存在的所有版本号 npm view 包名 versions

    • 查看指定包当前的最新版本 npm view 包名 version

    • 下载指定版本的包 npm install 包名@1.8

    • npm list 查看项目安装了哪些包 或 npm ls

    • npm install jquery --save 或 npm i jquery -S 下载生产包

    • npm install jquery --save-dev 或 npm i jquery -D 下载开发依赖包

    • npm ls jquery 查看当前安装包的版本

    • npm config set registry https://registry.npm.taobao.org 更改 npm 的下载镜像为淘宝镜像

    • npm help npm帮助文档

    • 使用和linux有的很像

    webpack起步

    *webpack* 是一个现代 JavaScript 应用程序的*静态模块打包器(module bundler)*。当 webpack 处理应用程序时,它会递归地构建一个*依赖关系图(dependency graph)*,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 *bundle*(捆,束),它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
    
    • 入口js

      //commonjs规范
      const {add} = require('./mathUtil.js');
      
      console.log(add(1,3));
      
      //es6规范
      import {result} from "./es6.js";
      
      console.log(result);
      
      
    • es6规范

    const result = 45456;
    export {result};
    
    
    • common规范
    function add(a, b) {
      return a + b;
    }
    
    module.exports = {add};
    
    

    webpack配置

    • 导出的时候es6和commonjs不能在一个模块中混用
    • 配置webpack.config.js:要使用commonjs规范
    //node的包里面的path模块,用来拼接绝对路径
    const path = require('path');
    
    //这里要用commonjs导出,不能用es6
    module.exports = {
        //打包转换的调用入口和main方法类似
      entry: './src/main.js',
      ouput: {
        //必须使用绝对路径,path.resolve(__dirname,'dist')返回绝对路径
        path: path.resolve(__dirname,'dist'),
        filename: 'bundle.js'
      }
    };
    
    
    • package.json配置:json不能有注释
    {
      "name": "meetpackage",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        //npm run build 会在这个配置文件中找webpack命令,这个使用的是本地的命令,
        //不是全局的webpack,本地是针对于你的这个开发项目
        "build":"webpack"
      },
      "author": "",
      //开发的依赖
       "devDependencies": {
        "webpack": "^3.6.0"
      },
      //开源才需要这个,json中不能注释
      "license": "ISC"
    }
    
    
    
    • 本地安装:开发时依赖 npm install webpack@3.6.0 --save-dev
    • 终端terminal里敲的命令都是全局的
    为什么使用--save-dev而不是--save?

    --save 会把依赖包名称添加到 package.json 文件 dependencies 下;

    --save-dev 则添加到 package.json 文件 devDependencies 键下;

    webpack-loader

    • 官网可以找到对应的loader安装
      • 例:npm install style-loader[@version] --save -dev[表示开发环境用]
      • npm install babel-loader@7 babel-core babel-preset-es2015 --save-dev es6转es5
    //node的包里面的path模块,用来拼接绝对路径
    const path = require('path');
    
    //这里要用commonjs导出,不能用es6
    module.exports = {
      entry: './src/main.js',
      output: {
        //必须使用绝对路径
        path: path.resolve(__dirname,'dist'),
        filename: 'bundle.js',
        //为所有的url相关的添加路径
        publicPath:'dist/'
      },
      module:{
        rules: [
          {
            test: /.css$/,
            // style-loader将模块的导出作为样式添加到 DOM 中
            // loader解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
            // 从右到左的顺序加载
            use: [ 'style-loader', 'css-loader' ]
          },
          // {
          //   test: /.(png|jpg|gif)$/,
          //   use: [
          //     {
          //       loader: 'url-loader',
          //       options: {
          //         //限制图片大小,大于limit会找file-loader
          //         limit: 9999
          //       }
          //     }
          //   ]
          // },
          // 在使用webpack进行打包时,对图片路径的处理方法常用的有两种,一种是file-loader,
          // 一种是url-loader,当我们使用其中一种是,请把另一种删掉,不然会出现图片无法正常显示的问题
          {
            test: /.(png|jpg|gif)$/,
            use: [
              {
                loader: 'file-loader',
                options: {
                  //name是文件名,hash取8位,ext是拓展名
                  name:'img/[name].[hash:8].[ext]'
                }
              }
            ]
          },
          {
            test: /.js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['es2015']
              }
            }
          }
        ]
      }
    };
    
    

    webpack-vue

    1. npm install vue -save

    2. 不写路径默认从node_modules引入 import Vue from 'vue'

    3. runtime-only:是运行的时候代码不能包含任意一个template标签

    4. runtime-compiler:代码中可以有template标签

      1. 解决3.4碰到的问题
       module:{
      
        resolve:{
          alias:{
            // vue$正则,表示导入的时候会检测vue指向的文件夹,如果这里不指定,会去找默认的runtime-only
            'vue$':'vue/dist/vue.esm.js'
          }
        }
      
      
    //使用vue
    import Vue from 'vue';
    
    
    const App = {
        template: `
        <h2>{{msg}}</h2>
        `,
        data() {
            return {
                msg: 'hello world'
    
            };
        }
    };
    
    new Vue({
        el: '#app',
        // template和el关系是,这里的template会替换el的标签
        template: `<App/>`,
        components: {
            App
        }
    });
    
    
    将组件的代码提出去
    • 新建一个.vue文件
    <template>
      <h2>{{msg}}</h2>
      <span class="title">{{tit}}</span>
    </template>
    
    <script>
      export default {
        name: "App",
        data() {
          return {
            msg: 'hello world',
            tit:'title'
          };
        }
      }
    </script>
    
    <style scoped>
    .title{
      color: red;
    }
    </style>
    
    
    • npm install vue-loader vue-template-compiler --save -dev
      • 会出现版本过高的问题 安装一个低版本的
      • 编辑package.json中的版本号,会根据你的大版本找一个合适的,必须重新npm install

    webpack-plugin

    • 安装打包静态文件:npm install --save-dev html-webpack-plugin
    • 压缩js文件替换变量为更简单的:npm install uglifyjs-webpack-plugin@1.1.1 --save -dev 指定的vueCli 2
    webpack-dev-server
    • 全局安装:可以不用

      npm install webpack-dev-server -g 
      
      
    • 开发环境:

      npm install webpack-dev-server -save -dev
      
      
    • 配置参数:

      --content-base //设定webpack-dev-server的director根目录。如果不进行设定的话,默认是在当前目录下。
      --quiet: //控制台中不输出打包的信息,开发中一般设置为false,进行 打印,这样查看错误比较方面
      --no-info: // 不显示任何信息
      --colors: //对信息进行颜色输出
      --no-colors: //对信息不进行颜色输出
      --compress:  //开启gzip压缩
      --host <hostname/ip>: //设置ip
      --port <number>: //设置端口号,默认是:8080
      --inline: //webpack-dev-server会在你的webpack.config.js的入口配置文件中再添加一个入口,
      --hot: //开发热替换
      --open: //启动命令,自动打开浏览器
      --history-api-fallback: //查看历史url
      
      
    • 两种方式:

      1. 直接scripts中使用:"dev": "webpack-dev-server --contentBase src --port 80 --hot --colors"
      2. 配置文件:
        plugins: [
          new webpack.BannerPlugin('最终版权是小明'),
          //打包静态资源,并且指定模板
          new htmlWebpackPlugin({
            template:`index.html`
          }),
          //压缩js
          new UglifyJsWebpackPlugin(),
          //热加载,不会全部加载,只加载改动的地方,配置了hot就需要配置,直接在命令中使用--hot就不需要配置这个插件
          // new webpack.HotModuleReplacementPlugin()
        ],
        // devServer: {
        //   contentBase: 'src',
        //   port: 80,
        //   hot:true
        // },
      
      
    • 报错可能是版本问题

    webpack.config.js配置文件

    //node的包里面的path模块,用来拼接绝对路径
    const path = require('path');
    const webpack = require('webpack');
    const htmlWebpackPlugin = require('html-webpack-plugin');
    const UglifyJsWebpackPlugin = require('uglifyjs-webpack-plugin');
    
    //这里要用commonjs导出,不能用es6
    module.exports = {
      entry: './src/main.js',
      output: {
        //必须使用绝对路径
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        //为所有的url相关的添加路径
        // publicPath: 'dist/'
      },
      module: {
        rules: [
          {
            test: /.css$/,
            // style-loader将模块的导出作为样式添加到 DOM 中
            // loader解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
            // 从右到左的顺序加载
            use: ['style-loader', 'css-loader']
          },
          // {
          //   test: /.(png|jpg|gif)$/,
          //   use: [
          //     {
          //       loader: 'url-loader',
          //       options: {
          //         //限制图片大小,大于limit会找file-loader
          //         limit: 9999
          //       }
          //     }
          //   ]
          // },
          // 在使用webpack进行打包时,对图片路径的处理方法常用的有两种,一种是file-loader,
          // 一种是url-loader,当我们使用其中一种是,请把另一种删掉,不然会出现图片无法正常显示的问题
          {
            test: /.(png|jpg|gif)$/,
            use: [
              {
                loader: 'file-loader',
                options: {
                //name是文件名,hash取8位,ext是拓展名
                  name: 'img/[name].[hash:8].[ext]'
                }
              }
            ]
          },
          {
            test: /.js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['es2015']
              }
            }
          },
          {
            test: /.vue$/,
            use: {
              loader: 'vue-loader'
            }
          }
        ]
      },
      resolve: {
        // 这写拓展名可以省略
        extensions: ['.css', '.js', '.vue'],
        alias: {
          // vue$正则,表示导入的时候会检测vue指向的文件夹,如果这里不指定,会去找默认的runtime-only
          'vue$': 'vue/dist/vue.esm.js'
        }
      },
      plugins: [
        new webpack.BannerPlugin('最终版权是小明'),
        //打包静态资源,并且指定模板
        new htmlWebpackPlugin({
          template:`index.html`
        }),
        //压缩js
        new UglifyJsWebpackPlugin(),
        //热加载,不会全部加载,只加载改动的地方,配置了hot就需要配置,直接在命令中使用--hot就不需要配置这个插件
        // new webpack.HotModuleReplacementPlugin()
      ],
      // devServer: {
      //   contentBase: 'src',
      //   port: 80,
      //   hot:true
      // },
    };
    
    

    抽取分离配置文件

    • 创建三个配置文件:
      1. base.config.js : 存放公共的配置
      //node的包里面的path模块,用来拼接绝对路径
      const path = require('path');
      const webpack = require('webpack');
      const htmlWebpackPlugin = require('html-webpack-plugin');
      
      //这里要用commonjs导出,不能用es6
      module.exports = {
          entry: './src/main.js',
          output: {
              //必须使用绝对路径
              path: path.resolve(__dirname, '../dist'),
              filename: 'bundle.js',
          },
          module: {
              rules: [
                  {
                      test: /.css$/,
                      // style-loader将模块的导出作为样式添加到 DOM 中
                      // loader解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
                      // 从右到左的顺序加载
                      use: ['style-loader', 'css-loader']
                  },
                  {
                      test: /.(png|jpg|gif)$/,
                      use: [
                          {
                              loader: 'file-loader',
                              options: {
                                  //name是文件名,hash取8位,ext是拓展名
                                  name: 'img/[name].[hash:8].[ext]'
                              }
                          }
                      ]
                  },
                  {
                      test: /.js$/,
                      exclude: /(node_modules|bower_components)/,
                      use: {
                          loader: 'babel-loader',
                          options: {
                              presets: ['es2015']
                          }
                      }
                  },
                  {
                      test: /.vue$/,
                      use: {
                          loader: 'vue-loader'
                      }
                  }
              ]
          },
          resolve: {
              // 这写拓展名可以省略
              extensions: ['.css', '.js', '.vue'],
              alias: {
                  // vue$正则,表示导入的时候会检测vue指向的文件夹,如果这里不指定,会去找默认的runtime-only
                  'vue$': 'vue/dist/vue.esm.js'
              }
          },
          plugins: [
              new webpack.BannerPlugin('最终版权是小明'),
              //打包静态资源,并且指定模板
              new htmlWebpackPlugin({
                  template: `index.html`
              })
          ],
      };
      
      
      1. dev.config.js : 存放开发时配置

        const WebpackMerge = require('webpack-merge');
        const baseConfig = require('./base.config');
        
        module.exports = WebpackMerge(baseConfig, {
            devServer: {
                contentBase: 'src',
                port: 80,
                inline: true
            }
        });
        
        
      2. prod.config.js : 存放生产时配置

        const UglifyJsWebpackPlugin = require('uglifyjs-webpack-plugin');
        const WebpackMerge = require('webpack-merge');
        const baseConfig = require('./base.config');
        
        module.exports = WebpackMerge(baseConfig, {
            plugins: [
                //压缩js
                new UglifyJsWebpackPlugin()
            ]
        });
        
        
    • 修改scripts
      • 可删除默认的webpack.config.js
      "scripts": {
          "test": "echo "Error: no test specified" && exit 1",
          "dev": "webpack-dev-server --config ./build/dev.config.js",
          "build": "webpack --config ./build/prod.config.js"
        },
      
      

    打包

    • 使用webpack,也可以不用配置文件自己在命令后指定参数,以下是使用配置文件

    • 使用npm,会找到package.json找到对应的script里的命令执行,实际上还是调用了webpack命令

    七、vuecli

    • 介绍:

      • vue command interface 命令行界面:使用一些命令搭建项目的基础结构
      • 都使用会规范很多配置,易维护,减少出错率
      • 依赖 webpack and npm-》npm-》依赖node
    • 安装:

      • npm install -g @vue/cli
    • 卸载之前版本

      • npm uninstall vue-cli -g or npm uninstall -g @vue/cli
    • 拉取v2的模板

      • npm install -g @vue/cli-init
        
        
    v2使用:
    • 基于webpack3
    创建project
    vue init webpack projectName
    
    

    v3使用:
    • 零配置

    • 隐藏build和config目录,可以在node-modules/@vue/cli-service

      • 要修改配置需要根目录创建一个vue.config.js

        module.exports={};
        
        
    • 基于webpack4

    • 提供vue ui命令,图形化操作

    • 移除static,新增public目录将index.html移动到下面

    • 项目名不能有大写

    创建project
    vue create projectName
    
    
    1. 会默认创建一个.git文件夹

    2. 自定义配置:

      1. 根目录新建 vue.config.js
      module.exports = {
        configureWebpack: {
          resolve: {
            // extensions:[],
            //配置别名
            alias: {
              'assets': '@/assets',
              'components': '@/components',
              'network': '@/network',
              'common': '@/commom',
              'views': '@/views',
            }
          }
        }
      };
      
      
      
      1. 配置.editorconfig : 代码格式
      root = true
      
      [*]
      charset = utf-8
      indent_style = space
      indent_size = 2
      end_of_line = lf
      insert_final_newline = true
      trim_trailing_whitespace = true
      
      
      
    vueUi
    vue ui  打开图形管理界面
    
    
    el
    • el:'#app'最后执行的还是$mount('#app')

    八、runtime-only 和 runtime-compiler

    1. runtime-only:是运行的时候代码不能包含任意一个template标签

      • render(h)- 》 virtual Dom - 》UI真实dom
    2. runtime-compiler:代码中可以有template标签

      • template加载过程:

        template - 》parse - 》ast 抽象语法树 - 》compiler - 》render(h)- 》 virtual Dom - 》UI真实dom

      1比2性能更高,代码更少(少6kb)

    //runtime-compiler
    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      components: { App },
      template: '<App/>'
    })
    
    
    
    
    import Vue from 'vue'
    import App from './App'
    
    Vue.config.productionTip = false
    
    //runtime-only,这个h是一个createElement('tagName',{attrName:'attrVal'},['innerHtml'])
    //在vue中也可以传一个template对象靠vue-template-compiler解析成render(),也可以递归创建
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      render: h => h(App)
    })
    
    
    
    

    九、路由 vue-router

    详情

    一般使用vue的插件都要用Vue.use(插件)

    • 介绍:
      • 互联的网络将信息传输到目标地址的活动
      • 路由器提供两种机制:
        1. 路由:决定数据包从源到目的地的路径
        2. 将输入端的数据转送到合适的输出端
      • 路由表:是一个映射表,决定了数据包的指向
    • 后端路由--前端路由
      • 后端处理url和页面的映射,jsp是后端渲染,到前端的时候页面就确认好了
      • 前端处理url和页面的跳转映射
    • 非嵌套路由必须包含一个前导斜杠字符

    改编url不刷新页面

    • 改location.hash='aaa';
    • history:
      • 改history.pushState({},'','aaa');类似压栈,history.back()类似弹栈
      • 改history.replaceState({},'','aaa'),不能back()
      • 改history.go(-1) = history.back(),前进或者后退
      • 改history.forword()=history.go(1)
    1. 安装路由:npm install vue-router --save 因为生产也需要路由

    2. 导入:

      • router/index.js
      import Vue from 'vue'
      //1. 导入插件
      import Router from 'vue-router'
      import HelloWorld from '@/components/HelloWorld'
      
      //2. 使用插件
      Vue.use(Router)
      
      //3. 创建路由配置
      const routes = [
        {
          path: '/',
          name: 'HelloWorld',
          component: HelloWorld
        }
      ];
      
      //4. 传入路由配置,导出路由对象
      export default new Router({
        routes
      })
      
      
      
      • main.js
      import Vue from 'vue'
      import App from './App'
      //只写目录默认会找  index.js
      import router from './router'
      
      Vue.config.productionTip = false
      
      new Vue({
        el: '#app',
        router,
        render: h => h(App)
      })
      
      
      
    • 替换a标签
    <div id="app">
        <router-link to="/home">首页</router-link>
        <!-- 相当于占位符 -->
        <router-view></router-view>
        <router-link to="/about">详情</router-link>
      </div>
    
    
    • 常用属性

      • tag 、replace

        <!-- tag设置替换成什么标签 -->
            <!-- replace表示禁用了返回前进按钮,是使用了history.replaceState() -->
            <router-link to="/home" tag='button' replace>首页</router-link>
        
        
      • 配置默认的active的样式

        .router-link-active{
          color: #f00
        }
        
        
        
      • 自定义样式:手动一个一个标签的写

        <!--active-class 自定义点击后的样式 -->
            <router-link to="/home" tag='button' replace active-class="active">首页</router-link>
        
        
      • 配置全局的active-class

      export default new Router({
        routes,
        mode:'history',
        linkActiveClass:'active'
      })
      
      

    默认重定向

    const routes = [
      {
        path:'/',
        redirect:'/home'
      },
      {
        path: '/home',
        name: 'Home',
        component: Home
      },
      {
        path:'/about',
        name:'About',
        component:About
      }
    ];
    
    

    设置router的默认方式为history

    • 本身默认hash
    • history:url不会显示#号
    //4. 传入路由配置,导出路由对象
    export default new Router({
      routes,
      mode:'history'
    })
    
    

    手动写路由跳转

    • router会给每个组件传$router
    <template>
      <div id="app">
        <button @click="homeClick">首页</button>
        <button @click="aboutClick">详细</button>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
      methods:{
        //router会给每个组件传$router
        homeClick(){
          // this.$router.push('/home');
          this.$router.replace('/home');
        },
        aboutClick(){
          // this.$router.push('/about');
          this.$router.replace('/about');
        }
      }
    }
    </script>
    
    <style>
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    
    

    动态路由

    01动态路由
    • 需要调用$route.params来获取参数
    1. 创建一个vue组件:User.vue

      <template>
        <div>
            <h2>个人信心</h2>
            <h3></h3>
        </div>
      </template>
      
      <script>
      export default {
          name:'User',
      }
      </script>
      
      <style>
      
      </style>
      
      
    2. 配置路由:index.js

      import Vue from 'vue'
      import User from '../components/User.vue'
      
      //1. 导入插件
      import Router from 'vue-router'
      
      //2. 使用插件
      Vue.use(Router)
      
      //3. 创建路由配置
      const routes = [
        {
          path:'/user',
          component:User
        }
      ];
      
      //4. 传入路由配置,导出路由对象
      export default new Router({
        routes,
        mode:'history',
        linkActiveClass:'active'
      })
      
      
      
    3. 加入路由到目标组件:Vue.vue

      <template>
        <div id="app">
          <router-link to="/user" replace>用户</router-link>
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      export default {
        name: 'App',
      }
      </script>
      
      <style>
      .active{
        color: rgb(209, 15, 25)
      }
      </style>
      
      
      
    4. 导入组件到入口 : main.js

      import Vue from 'vue'
      import App from './App'
      //只写目录默认会找  index.js
      import router from './router'
      
      Vue.config.productionTip = false
      
      /* eslint-disable no-new */
      new Vue({
        el: '#app',
        router,
        render: h => h(App)
      })
      
      
      
    5. 设置动态路由:index.js

      import Vue from 'vue'
      import User from '../components/User.vue'
      
      //1. 导入插件
      import Router from 'vue-router'
      
      //2. 使用插件
      Vue.use(Router)
      
      //3. 创建路由配置
      const routes = [
        {
          path:'/user/:userName',
          component:User
        }
      ];
      
      //4. 传入路由配置,导出路由对象
      export default new Router({
        routes,
        mode:'history',
        linkActiveClass:'active'
      })
      
      
    6. 配置页面的url: Vue.vue

      <template>
        <div id="app">
          <router-link v-bind:to="'/user/'+userName" replace>用户</router-link>
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      export default {
        name: 'App',
        data(){
            return {
                userName:'xiaoming'
            }
        }
      }
      </script>
      
      <style>
      .active{
        color: rgb(209, 15, 25)
      }
      </style>
      
      
      
    7. 获取动态路由中的参数:User.vue

      • $route是当前活跃的路由
      <template>
        <div>
            <h2>个人信心</h2>
            <h3>{{userName}}</h3>
        </div>
      </template>
      
      <script>
      export default {
          name:'User',
          computed:{
              userName(){
                  return this.$route.params.userName;
              }
          }
      }
      </script>
      
      <style>
      
      </style>
      
      
    02传参

    路由配置中props 被设置为 trueroute.params 将会被设置为组件属性。

    路由方式

    编程式路由

    // 字符串
    router.push('home')
    
    // 对象
    router.push({ path: 'home' })
    
    // 命名的路由
    router.push({ name: 'user', params: { userId: '123' }})
    
    // 带查询参数,变成 /register?plan=private
    router.push({ path: 'register', query: { plan: 'private' }})
    
    

    注意: path和params 不共存params会被忽略,path和query 可以。name和params也可以

    const userId = '123'
    
    //命名路由
    router.push({ name: 'user', params: { userId }}) // -> /user/123
    router.push({ path: `/user/${userId}` }) // -> /user/123
    
    // 这里的 params 不生效
    router.push({ path: '/user', params: { userId }}) // -> /user
    
    

    同样的规则也适用于 router-link 组件的 to 属性

    route 、router 、$router 、$route

    • $router:路由组件对象 配置的路由对象
    • $route:当前活跃路由

    匹配优先级

    有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。

    懒加载

    • 如果把所有的js都打包到app中,js将会很大,访问的时候会有等待时间,所以把不同的路由对应的组件分割成不同的代码块,然后当路由被访问的时候加载对应的资源,就更加高效了

      import Vue from 'vue'
      
      //替换成懒加载
      // import Home from '../components/Home.vue'
      // import About from '../components/About.vue'
      // import User from '../components/User.vue'
      
      //懒加载:
      const Home = ()=>import('../components/Home.vue')
      const About = ()=>import('../components/About.vue')
      const User = ()=>import('../components/User.vue')
      
      
      //1. 导入插件
      import Router from 'vue-router'
      
      //2. 使用插件
      Vue.use(Router)
      
      //3. 创建路由配置
      const routes = [
        {
          path:'/',
          redirect:'/home'
        },
        {
          path: '/home',
          name: 'Home',
          component: Home
        },
        {
          path:'/about',
          name:'About',
          component:About
        },
        {
          path:'/user/:userName',
          component:User
        }
      ];
      
      //4. 传入路由配置,导出路由对象
      export default new Router({
        routes,
        mode:'history',
        linkActiveClass:'active'
      })
      
      
      

    子路由

    • 有默认子路由的时候父漏油去掉name,转发的时候进不了子路由
    index.js
    import Vue from 'vue'
    
    //替换成懒加载
    // import Home from '../components/Home.vue'
    // import About from '../components/About.vue'
    // import User from '../components/User.vue'
    
    //懒加载:
    const Home = () => import('../components/Home.vue')
    const About = () => import('../components/About.vue')
    const User = () => import('../components/User.vue')
    const HomeChild = () => import ('../components/HomeChild.vue')
    
    
    //1. 导入插件
    import Router from 'vue-router'
    
    //2. 使用插件
    Vue.use(Router)
    
    //3. 创建路由配置
    const routes = [
      {
        path: '/',
        redirect: '/home'
      },
      {
        path: '/home',
        name: 'Home',
        component: Home,
        children: [
          {
            path: '',
            // redirect:'child'
          },
          {
            //这里不能同/开头,会自动加上
            path: 'child',
            name: 'HomeChild',
            component: HomeChild
          }]
      },
      {
        path: '/about',
        name: 'About',
        component: About
      },
      {
        path: '/user/:userName',
        component: User
      }
    ];
    
    //4. 传入路由配置,导出路由对象
    export default new Router({
      routes,
      mode: 'history',
      linkActiveClass: 'active'
    })
    
    
    
    Home.vue
    <template>
      <div>
          <h2>首页11</h2>
          <router-link to="/home/child">child</router-link>
          <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
        name:'Home'
    }
    </script>
    
    <style>
    
    </style>
    
    
    

    传参到另一个组件

    profile.vue
    <template>
      <div><span>个人档案</span>
        <span>{{$route.query}}</span><br>
        <span>query.name: {{$route.query.name}}</span>
    
      </div>
    </template>
    
    <script>
      export default {
        name: "Profile"
      }
    </script>
    
    <style scoped>
    
    </style>
    
    
    
    配置路由:index.js
    const Profile = () => import('../components/Profile') 
    
    {
        path: '/profile',
        component: Profile
      }
    
    
    显示位置的路由传参
    <template>
      <div id="app">
        <router-link to="/home" tag='button' replace >首页</router-link>
        <router-link to="/about" replace>详情</router-link>
        <router-link :to="'/user/'+userName" replace>用户</router-link>
        <router-link :to="{path:'/profile',query:{name:'lisa',age:18},fragment:'4d5as46s'}" replace>档案</router-link>
    
        <!-- <button @click="homeClick">首页</button>
        <button @click="aboutClick">详细</button> -->
        <router-view></router-view>
      </div>
    </template>
    
    

    为什么这样传参:

    • 符合url的规范:

    • 所以可以用query对象传参

    导航守卫

    注意导航守卫并没有应用在跳转(redirect)路由上,而仅仅应用在其目标上。为redirect的路由添加一个 beforeEachbeforeLeave 守卫并不会有任何效果

    全局守卫

    所有的路由都会被过滤,也可以在特定的组件内创建局部守卫

    • 主要监听页面的跳转

    • from从哪个组件来的

    • to去跳转到哪个组件

    • next()

      • next(false)中断路由
      • next(path)跳转到哪个页面,可用来做一些条件判断的跳转,比如login
      • 其他的用的时候查官网
      import Vue from 'vue'
      
      //替换成懒加载
      // import Home from '../components/Home.vue'
      // import About from '../components/About.vue'
      // import User from '../components/User.vue'
      
      //懒加载:
      const Home = () => import('../components/Home.vue')
      const About = () => import('../components/About.vue')
      const User = () => import('../components/User.vue')
      const HomeChild = () => import('../components/HomeChild.vue')
      const Profile = () => import('../components/Profile')
      
      
      //1. 导入插件
      import Router from 'vue-router'
      
      //2. 使用插件
      Vue.use(Router)
      
      //3. 创建路由配置
      const routes = [
        {
          path: '/',
          redirect: '/home'
        },
        {
          path: '/home',
          name: 'Home',
          component: Home,
          meta: {
            title: '首页'
          },
          children: [
            {
              path: '',
              // redirect:'child'
            },
            {
              //这里不能同/开头,会自动加上
              path: 'child',
              name: 'HomeChild',
              component: HomeChild,
            }]
        },
        {
          path: '/about',
          name: 'About',
          component: About,
          meta: {
            title: '详情'
          },
        },
        {
          path: '/user/:userName',
          component: User,
          meta: {
            title: '用户'
          },
        },
        {
          path: '/profile',
          component: Profile,
          meta: {
            title: '档案'
          },
        }
      ];
      const router = new Router({
        routes,
        mode: 'history',
        linkActiveClass: 'active'
      })
      
      router.beforeEach((to, from, next) => {
        next()
        //匹配path中的meta对象的title
        document.title = to.matched[0].meta.title
        console.log(to);
        // console.log(from);
        // console.log("next: "+next);
      })
      
      //4. 传入路由配置,导出路由对象
      export default router
      
      
      
    独享守卫
    import Vue from 'vue'
    
    //懒加载:
    const Home = () => import('../components/Home.vue')
    
    //1. 导入插件
    import Router from 'vue-router'
    
    //2. 使用插件
    Vue.use(Router)
    
    //3. 创建路由配置
    const routes = [
      {
        path: '/',
        redirect: '/home'
      },
      {
        path: '/home',
        name: 'Home',
        component: Home,
        meta: {
          title: '首页'
        },
        children: [
          {
            path: '',
            // redirect:'child'
          },
          {
            //这里不能同/开头,会自动加上
            path: 'child',
            name: 'HomeChild',
            component: HomeChild,
            beforeEnter: (to, from, next) => {
              console.log("独享守卫");
                next()
            }
          }]
      }
    ];
    const router = new Router({
      routes,
      mode: 'history',
      linkActiveClass: 'active'
    })
    
    //4. 传入路由配置,导出路由对象
    export default router
    
    
    
    前置钩子和后置钩子
    import Vue from 'vue'
    
    //懒加载:
    const Home = () => import('../components/Home.vue')
    
    //1. 导入插件
    import Router from 'vue-router'
    
    //2. 使用插件
    Vue.use(Router)
    
    //3. 创建路由配置
    const routes = [
      {
        path: '/',
        redirect: '/home'
      },
      {
        path: '/home',
        name: 'Home',
        component: Home,
        meta: {
          title: '首页'
        }
      },
      
    ];
    const router = new Router({
      routes,
      mode: 'history',
      linkActiveClass: 'active'
    })
    
    //前置钩子 hook,像filter一样
    router.beforeEach((to, from, next) => {
      next()
      //匹配path中的meta对象的title
      document.title = to.matched[0].meta.title
      console.log(to);
    })
    
    //后置钩子
    router.afterEach((to,from)=>{
      console.log("在跳转之后调用");
    })
    
    //4. 传入路由配置,导出路由对象
    export default router
    
    
    

    十、打包js的结构图

    • app*.js所有的业务代码
    • mainifest*.js代码转换的依赖的底层支持
    • vendor*.js第三方插件
    • .map文件:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错,可以设置:config/index.js productionSourceMap:false

    十一、keep-alive组件

    1. 是vue的一个组件:保证一些组件进入缓存不用你每次请求解析资源,提高效率,在显示的地方配置

      <template>
        <div id="app">
          <router-link to="/home" tag="button" replace>首页</router-link>
          <router-link to="/about" replace>详情</router-link>
          <router-link :to="'/user/'+userName" replace>用户</router-link>
          <router-link :to="{path:'/profile',query:{name:'lisa',age:18},fragment:'4d5as46s'}" replace>档案</router-link>
          <button @click="toProfile">档案2</button>
          <!-- <button @click="homeClick">首页</button>
          <button @click="aboutClick">详细</button>-->
          <!-- <router-view></router-view> -->
      
          <!-- 保存到缓存中 -->
          <keep-alive>
            <router-view></router-view>
          </keep-alive>
        </div>
      </template>
      
      
    2. keep-alive的组件才可以使用activated()、deactivated()

      <template>
        <div>
          <h2>首页11</h2>
          <router-link :to="{path:'/home/child',query:{content:'child1'}}">child</router-link>
          <router-link :to="toChild2">child2</router-link>
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      export default {
        name: "Home",
        data() {
          return {
            toChild2: {
              path: "/home/child2",
              query: {
                content: "child2"
              }
            },
            path: "/home/child",
            query:{
              childContent:'child1'
            }
          };
        },
        methods: {},
        created() {
          console.log("Home组件被创建成功");
        },
        mounted() {
          console.log("组件被挂载成功");
        },
        updated() {
          console.log("组件中发生改变时");
        },
        destroyed() {
          console.log("home destroyed");
        },
        activated() {
          console.log("home 激活");
          this.$router.push(this.path)
        },
        deactivated() {
          console.log("home 离开");
        },
        beforeRouteLeave(to, from, next) {
          console.log('before leave home');
          this.path = this.$route.path;
          console.log(this.path);
          next();
        }
      };
      </script>
      
      <style>
      </style>
      
      
      
    3. keep-alive 的exclude、include属性

      1. exclude=“componentName,componentName...”,被排除在缓存之外,不能加空格
      <keep-alive exclude="Profile">
            <router-view></router-view>
          </keep-alive>
      
      
      export default {
        name: "Profile",
        created() {
          console.log("profile created");
        },
        destroyed() {
          console.log("profile destroyed");
        }
      };
      
      

    十二、自定义tab-bar

    1. /*style中引用要用@import */

      准备好tabbar.vue,调好样式,预留出来一个插槽,用来放具体的tabbar的item

      <template>
        <div id="tab-bar">
          <slot></slot>
        </div>
      </template>
      
      <script>
      
        export default {
          name: "TabBar",
        }
      </script>
      
      <style scoped>
      
        #tab-bar {
          display: flex;
          background-color: #fdfdff;
      
          /*显示在最下面和屏幕等宽*/
          position: fixed;
          left: 0;
          right: 0;
          bottom: 0;
      
          /*阴影  fgba(最后是0.1表示透明度)*/
          box-shadow: 0 -1px 1px rgba(100, 100, 100, .1);
        }
      
      </style>
      
      
      
    2. 封装tab-bar-item

      <template>
        <div class="tab-bar-item" @click="itemClick">
          <div v-if="!isActive">
            <slot name="item-icon"></slot>
          </div>
          <div v-else>
            <slot name="item-icon-active"></slot>
          </div>
          <div :class="{active:isActive}">
            <slot name="item-text"></slot>
          </div>
      
        </div>
      </template>
      
      <script>
        export default {
          name: "TabBarItem",
          props:{
            path:{
              type:String
            }
          },
          data() {
            return {
              // isActive: true
            }
          },
          computed:{
            isActive(){
              return this.$route.path.indexOf(this.path) !== -1
            }
          },
          methods:{
            itemClick(e){
              this.$router.replace(this.path)
            }
          }
        }
      </script>
      
      <style scoped>
      
        .tab-bar-item {
          flex: 1;
          text-align: center;
          /*一般移动端的tabbar都是49px*/
          height: 49px;
          font-size: 14px;
        }
      
        .tab-bar-item img {
           24px;
          height: 24px;
          margin-top: 3px;
          margin-bottom: 2px;
          /*可以去掉图片下面的三个像素*/
          vertical-align: bottom;
        }
      
        .active {
          color: red;
        }
      </style>
      
      
      
    3. 注册到app.vue中

      <template>
        <div id="app">
          <router-view></router-view>
          <tab-bar>
            <tab-bar-item path="/home">
              <img slot="item-icon" src="./assets/images/tabbar/home.png" alt="首页">
              <img slot="item-icon-active" src="./assets/images/tabbar/home_active.png" alt="">
              <div slot="item-text">首页</div>
            </tab-bar-item>
            <tab-bar-item path="/category">
              <img slot="item-icon" src="./assets/images/tabbar/category.png" alt="">
              <img slot="item-icon-active" src="./assets/images/tabbar/category_active.png" alt="">
              <div slot="item-text">分类</div>
            </tab-bar-item>
            <tab-bar-item path="/cart">
              <img slot="item-icon" src="./assets/images/tabbar/cart.png" alt="">
              <img slot="item-icon-active" src="./assets/images/tabbar/cart_active.png" alt="">
              <div slot="item-text">购物车</div>
            </tab-bar-item>
            <tab-bar-item path="/profile">
              <img slot="item-icon" src="./assets/images/tabbar/profile.png" alt="">
              <img slot="item-icon-active" src="./assets/images/tabbar/profile_active.png" alt="">
              <div slot="item-text">我的</div>
            </tab-bar-item>
          </tab-bar>
        </div>
      </template>
      
      <script>
        import TabBar from "./components/tabbar/TabBar";
        import TabBarItem from "./components/tabbar/TabBarItem";
      
        export default {
          name: 'App',
          components: {
            TabBar,
            TabBarItem
          }
        }
      </script>
      
      <style>
        /*style中引用要用@*/
        @import "./assets/css/base.css";
      </style>
      
      
      

      可以优化class,颜色直接写死不合适

      还可以从父组件传过来,然后绑定style来设置

    十三、路径配置别名

    vue-cli2

    1. 配置别名:uildwebpack.base.conf.js

        resolve: {
          extensions: ['.js', '.vue', '.json'],
          alias: {
            '@': resolve('src'),
            'assets': resolve('src/assets'),
            'components': resolve('src/components'),
            'views': resolve('src/views'),
          }
        },
      
      
    2. 项目中使用

      1. html中: 前面要加 ~

        <tab-bar-item path="/home" activeColor="blue">
              <img slot="item-icon" src="~assets/images/tabbar/home.png" alt="首页" />
              <img slot="item-icon-active" src="~assets/images/tabbar/home_active.png" alt />
              <div slot="item-text">首页</div>
            </tab-bar-item>
        
        
      2. import中使用

        import TabBarItem from "components/tabbar/TabBarItem";
        
        

    vue-cli3

    1. 根目录下新建vue.config.js

    2. 在vue.config.js中的chainWebpack中配置config.resolve.alias.set('@', resolve('src')).set('components', resolve('src/components'));

    十四、promise

    是什么?

    是异步编程的一种解决方案

    什么时候使用异步呢?

    1. 网络请求
    2. 回调函数的时候

    promise用法

    1. 构造器有一个参数,是函数,这个函数有两个参数都是函数
    2. resolve:异步请求成功调的函数,被调用之后会调用then()
    3. then:来处理业务代码,参数是一个函数,可通过resolve来传入data
    4. reject:异步失败的时候调的函数,也可以传输数据到catch
      new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve('传入then 中的 data')
                }, 1500)
            }).then(data => {
                console.log(data);
    
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        // resolve('内部的resolve')
                        reject('内部的reject')
                    }, 1500)
                })
            }).catch(data => {
                console.log(data);
            })
    
    
    1. promise异步完成后会有三种状态

      1. pendding等待
      2. fullfill 完全满足
      3. reject 拒绝|次品
    2. promise的另一种写法

      1. then中也可以传两个函数,第一个是成功,第二个是失败
       new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve('传入then 中的 data')
                      // reject('失败')
                  }, 1500)
              }).then(data => {
                  console.log(data);
              },reject => {
                  console.log(reject);
              })
      
      
    3. 再简化

      1. new Promise(resolve) ==>Promise.resolve(data) ==> data
      2. throw 'msg'也会被catch()捕获
    // new Promise(resolve) ==>Promise.resolve(data) ==> data
            //throw 'msg'也会被catch()捕获
    
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve('第一层...')
                }, 1500)
            }).then(data => {
                console.log(data);
                return Promise.resolve('第二层...')
                // return Promise.reject('额鹅鹅鹅')
                throw 'dsadsa'
            }).then(data=>{
                console.log(data);
                return 'aaa'
            }).then(data=>{
                console.log(data);
            }).catch(err=>{
                console.log(err);
            })
    
    
    1. Promise.all([PromiseInstance...])
      1. 多个异步请求同时等待成功后才执行后续代码
      2. 像是java-juc的栅栏
    Promise.all([
                    new Promise((resolve, reject)=>{
                        setTimeout(()=>{
                            resolve('1111111')
                        },1000)
                    }),
                    new Promise((resolve, reject)=>{
                        setTimeout(()=>{
                            resolve('222222')
                        },2000)
                    })
            ]).then(data=>{
                //1111111,222222
                console.log(data.toString())
            })
    
    

    十五、vuex

    介绍

    1. 是什么?
      1. 是为vue程序提供一个集中状态管理模式和库
      2. 充当应用程序中所有组件的特殊共享变量的集中存储
      3. 这些共享状态都是响应式的

    1. vuex修改状态的流程

    • 通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。

      由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。

    • actions步骤可以省略,一般异步的操作放在actions中完成后放在mutations中

    • mutations只能是同步的操作,devtools监听不到异步操作

    使用步骤

    store用法

    state用法

    state中所有的已定义的属性都是响应式的,新加入的不被响应:因为属性初始化后,都被一个dep对象=【watcher,watcher..】监控,后面加入的不受监控

    1. npm install vuex --save
      
      
    2. 新建、src/store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    //1.安装,底层会调用Vuex.install
    Vue.use(Vuex)
    
    // 2.创建对象
    const store = new Vuex.Store({
      state: {
        count: 0
      }, mutations: {
        //state必须传,默认会传进来
        increment(state) {
          state.count++
        }
      }, actions: {}, getters: {}, modules: {}
    })
    
    // 3.导出store对象
    export default store
    
    
    
    1. main.js挂载插件
    import Vue from 'vue'
    import App from './App'
    import store from "./store";
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      store,
      render: h => h(App)
    })
    
    
    
    1. App.vue
    <template>
      <div id="app">
        <h2>{{$store.state.count}}</h2>
        <button @click="increment">+</button>
        <hello-vuex></hello-vuex>
      </div>
    </template>
    
    <script>
    import HelloVuex from "./components/HelloVuex";
    
    
    export default {
      name: 'App',
      methods:{
        increment(){
          this.$store.commit('increment')
        }
      },
      components: {
        HelloVuex
      }
    }
    </script>
    
    <style>
    </style>
    
    
    
    1. HelloVuex.vue
    <template>
      <div>
        <h2>{{$store.state.count}}</h2>
      </div>
    </template>
    
    <script>
      export default {
        name: "HelloVuex"
      }
    </script>
    
    <style scoped>
    
    </style>
    
    
    
    getters用法

    有点像computed的概念

    1. App.vue
    <template>
      <div id="app">
        <h2>{{$store.state.count}}</h2>
        <button @click="increment">+</button>
        <h2>年龄大于20:{{$store.getters.more20Person}}</h2>
        <h2>年龄大于20个数:{{$store.getters.more20PersonCount}}</h2>
        <h2>年龄大于age个数:{{$store.getters.moreAgePerson(13)}}</h2>
        <hello-vuex></hello-vuex>
      </div>
    </template>
    
    <script>
    import HelloVuex from "./components/HelloVuex";
    
    
    export default {
      name: 'App',
      methods:{
        increment(){
          this.$store.commit('increment')
        }
      },
      components: {
        HelloVuex
      }
    }
    </script>
    
    <style>
    </style>
    
    
    
    1. store/index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    //1.安装,底层会调用Vuex.install
    Vue.use(Vuex)
    
    // 2.创建对象
    const store = new Vuex.Store({
      state: {
        count: 0,
        persons: [
          {name: 'a', age: 12},
          {name: 'b', age: 23},
          {name: 'c', age: 32},
          {name: 'd', age: 24}
        ]
      }, mutations: {
        //state必须传,默认会传进来
        increment(state) {
          state.count++
        }
      },
      actions: {},
      //最多只能写两个参数时state,getters,默认会传进来
      getters: {
        more20Person(state) {
          return state.persons.filter(per=>per.age>20)
        },
        more20PersonCount(state,getters){
          // 这里不用写括号
          return getters.more20Person.length
        },
        //返回一个函数可以传动态的参数
        moreAgePerson(state){
          return (age)=>{
            return state.persons.filter(per=>per.age>age)
          }
        }
    
      },
      modules: {}
    })
    
    // 3.导出store对象
    export default store
    
    
    
    mutations
    1. 官方规定修改state只能用mutations
    2. 分为两部分函数名叫做字符串时间类型
    3. 代码块叫做回调函数
    4. 可以传多个参数第一个是store,后面的自定义叫做payload(负载 )
    //store/index.js 
    mutations: {
        //state必须传,默认会传进来
        increment(state) {
          state.count++
        },
        add(state,num){
          state.count +=num
        }
      }
    
    //app.vue
    methods:{
        increment(){
          this.$store.commit('increment')
        },
        add(num){
          this.$store.commit('add',num)
        }
      }
    
    
    

    第二种提交风格

    1. 提交的参数会当成一个对象来取
    inc(num){
          this.$store.commit({
            type:'inc',
            num
          })
        }
    
    
    mutations: {
        //state必须传,默认会传进来
        increment(state) {
          state.count++
        },
        add(state,num){
          state.count +=num
        },
            //当成对象处理参数
        inc(state,payLoad){
          state.count +=payLoad.num
        }
      }
    
    
    响应式操作
    update(state){
          //响应式
          // state.persons.push({name:'e',age:99})
          //响应式
          // state.person={name:'f',age:101}
    
          //新加的属性不会被监控,只有在其他任意的属性变化一次后他会刷新一次
          // state.person.add=111
          // state.person['address']=222
          //删除一个对象的属性
          // delete state.person.age
    
          //vue set value是响应式的,key必须是字符串
          // Vue.set(state.person,'asd','vue set value是响应式的')
          Vue.delete(state.person,'age')
        }
    
    
    mutations中方法的官方定义
    1. 避免写错,定义一个常量对象,在使用的文件中导入

    2. 定义

      [const](){}
      
      
    //mutation-type.js
    export const INCREMENT='increment'
    export const ADD='add'
    export const INC='inc'
    export const UPDATE='update'
    
    
    import {INCREMENT,ADD,UPDATE,INC} from "./mutation-type";
    
    //app.vue
    update(){
          this.$store.commit({
            type:UPDATE,
          })
        }
    
    
    //index.js
    mutations: {
        //state必须传,默认会传进来
        [INCREMENT](state) {
          state.count++
        },
        [ADD](state,num){
          state.count +=num
        },
        //当成对象处理参数
        [INC](state,payLoad){
          state.count +=payLoad.num
        },
        [UPDATE](state){
          Vue.delete(state.person,'age')
        }
      }
    
    
    
    
    
    actions
    1. mutations的异步方法修改的数据,插件是跟踪不到的
    2. 所有的异步操作都应该放在actions中处理,处理后的回调放在mutations中处理
    3. 修改state的唯一途径就是mutations
    4. actions中的默认参数是上下文context(context=store)

    action处理异步操作:

    //app.vue
    aUpdate(){
          // this.$store.dispatch('aUpdate',{
          //   msg:'参数信息',
          //  success:(data)=>{console.log(data)}
          // })
    
          //第二种方法,异步函数返回的promise对象
          this.$store.dispatch('aUpdate',{
            msg:'参数信息'
          }).then(res=>{
            console.log('完成异步操作');
            console.log(res);
          })
        }
    
    
    //index.js
    actions: {
        // aUpdate(context,payload) {
        //   // console.log('默认参数是上下文对象: ',context)
        //   setTimeout(function () {
        //     context.commit('aUpdate',payload)
        //   }, 1000)
        // }
    
        //第二种方式返回一个promise对象,在调用处可以使用
        aUpdate(context, payload) {
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              context.commit('aUpdate', payload)
              resolve(12312)
            }, 1000)
          })
        }
    
      }
    
    
    modules
    1. 只在一个state中存数据可能因为数据量过大而臃肿,所以在modules中分多个模块
    2. 取值$store.state. modulesName .propertise
    3. 子模块:
      1. state:需要指定模块名,可以和父模块同名
      2. getters : 和父模块同名会报错,可以直接访问不需要指定模块名
      3. actions|mutations : 和父模块名字相同都会调用,先调用父模块的,所以不要定义相同的名字

    显示:app.vue

     <h2>-------------state--modules的内容---------</h2>
        <h2>{{$store.state.a.name}}</h2>
        <h2>{{$store.getters.getModuleA}}</h2>
        <h2>{{$store.getters.getModuleA_add('age')}}</h2>
        <h2>{{$store.getters.getModuleA_add_root}}</h2>
        <button @click="moduleA">模块a修改name</button>
        <button @click="asyncUpdateModuleA">异步模块a修改name</button>
    
    
    methods:{
    	  moduleA() {
            this.$store.commit('aUpdate','模块a名字修改')
          },
          asyncUpdateModuleA(){
            this.$store.dispatch('asyncUpdateModuleA')
          }
    }
    
    

    index.js

    modules: {
        a:{
          //需要指定模块名,可以和父模块同名
          state:{name:'module_a',person:123},
          //和父模块同名会报错,可以直接访问不需要指定模块名
          getters:{
            getModuleA(state){
              return state.name+'_getModuleA'
            },
            getModuleA_add(state,getters){
              return (age) => {
                return getters.getModuleA+age
              }
    
            },
            //三个默认参数
            getModuleA_add_root(state,getters,rootState){
              return state.name+getters.getModuleA+'_add_'+rootState.count
            }
          },
          // 和mutations使用差不多
          actions:{
              //也可以使用对象的解构,详见es6
            asyncUpdateModuleA(context){
              setTimeout(()=>{
                context.commit('aUpdate','异步修改子模块')
              },1000)
            }
          },
          mutations:{
            //和父模块名字相同都会调用,先调用父模块的,所以不要定义相同的名字
            aUpdate(state,payload){
              state.name=payload
              console.log('child mutations 被调用')
            }
          },
          modules:{}
        },
            
        //模块b
        b:ModuleB
      }
    
    

    抽离index.js

    1. state一般是不抽取出来的
    2. modules是新建一个./modules/文件夹,在里面建立模块
    3. 抽离好的文件
    import Vue from 'vue'
    import Vuex from 'vuex'
    import mutations from "./mutations";
    import actions from "./actions";
    import getters from "./getters";
    import module_a from "./modules/module_a";
    
    //1.安装,底层会调用Vuex.install
    Vue.use(Vuex)
    
    // 2.创建对象
    const store = new Vuex.Store({
      state: {
        count: 0,
        persons: [
          {name: 'a', age: 12},
          {name: 'b', age: 23},
          {name: 'c', age: 32},
          {name: 'd', age: 24}
        ],
        person: {
          name: 'g',
          age: 100
        }
      },
      mutations,
      actions,
      getters,
      modules: {
        a: module_a
      }
    })
    
    // 3.导出store对象
    export default store
    
    
    

    刷新页面store丢失

    • stote/index.js
    export default new Vuex.Store({
        state:sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')): {
            enterprise: {},
            grid: [],
            iofficeUserRoleID: '',
            userVO: {},
        },
    
    
    • app.vue
    mounted() {
                window.addEventListener('unload', this.saveState)
            },
            methods: {
                saveState() {
                    sessionStorage.setItem('state', JSON.stringify(this.$store.state))
                }
            }
    
    

    十六、axios

    介绍

    1. 为什么选则它
      • vue作者不再维护vue-resource,推荐使用vxios
      • 可以在node环境中使用
      • 可以拦截请求
      • 可以拦截响应数据处理
    2. 支持的请求方式,满足resful,和jq的有点像
      1. axios(config) :默认是get,参数是一个对象
      2. axios.request(config)
      3. axios.get(url [, config])
      4. axios.delete(url [, config])
      5. axios.head(url [, config])
      6. axios.post(url [, data [, config] ])
      7. axios.put(url [, data [, config] ])
      8. axios.patch(url [, data [, config] ])
    3. 内部封装了promise
    4. 会根据提供的参数数据自动转换:content-type
    5. 会在跨域并使用content-type:application、json变成非简单请求的时候,预请求一个options方法到后台,不处理的话造成后台传参错误

    使用

    初步使用
    import axios from 'axios'
    
    axios.defaults.baseURL = 'https://httpbin.org'
    axios.defaults.timeout = 5000
    axios({
      // url:'http://123.207.32.32:8080/home/mutidata',
      url: 'post',
      method: 'post',
      // 拼接在URL后
      params: {
        name: 1
      },
      // 请求体中的参数
      data: {
        type: 'sell',
        page: 3
      },
      //拦截请求
      transformRequest:[function (query) {
    
      }],
      //拦截返回数据
      transformResponse:[function (response) {
    
      }],
    }).then(res => {
      console.log(res);
    })
    
    // 同时处理多个异步请求,最后返回一个数据数组,像java的栅栏
    axios.all([axios({url: 'post', method: 'post'}), axios({url: 'get'})]).then(res => {
      console.log(res);
    })
    
    //处理返回的结果数组,使用的是数组的解构是根据下标解构的
    axios.all([axios({url: 'post', method: 'post'}), axios({url: 'get'})])
      .then(([res1, res2]) => {
        console.log(res1);
        console.log(res2);
      })
    // 这样也可以
    axios.all([axios({url: 'post', method: 'post'}), axios({url: 'get'})])
      .then(
        axios.spread((res1, res2) => {
          console.log(res1);
          console.log(res2);
        }))
    
    
    
    进一步封装

    避免使用全局的axios,可能每个模块的请求是不一样的

    1. 使用的时候导入就可以了
    2. transformrequest和axiosInstance.interceptors.request.use 不冲突后者先调用
    3. transformResponse和axiosInstance.interceptors.response前者先调用

    新建/network/request.js

    import axios from "axios";
    
    export function request(config) {
      if (!config.baseURL) {
        config.baseURL = 'https://httpbin.org'
      }
      if (!config.timeout) {
        config.timeout = 5000;
      }
      const axiosInstance = axios.create(config);
      //req是请求参数对象
      axiosInstance.interceptors.request.use(req => {
        console.log(req);
        //1.可以修改一些请求的参数
        // 2.可以设置一个加载图片
        return req
      })
      //res是返回的对象
      axiosInstance.interceptors.response.use(res => {
        console.log(res.data);
        return res.data
      })
      return axiosInstance(config);
    }
    
    
    

    十七、细节和杂项

    this.$refs.[refName]

    只会取当前模块的引用

    style标签里的scoped

    只会作用当前的组件的css

    组件是不能直接监听原生事件的,需要:@click.native=""

    main.js里设置Vue.config.productionTip = false和true的区别

    • 是一个全局配置属性2.2.0 新增
    • 设置为 false 以阻止 vue 在启动时生成生产提示。

    vue文件可以首字母大写,如果首字母大写你使用组件的时候可以用-为分隔符,TabBar标签使用的时候可使用tab-bar

    使用':style'

    :style 后面是对象的时候里面的属性值是字符串格式

    vue表单提交:其他方式

    controller

    @RequestMapping("save")
    public ResponseModel savenew(@RequestBody @Validated SysUser user, BindingResult 		result) {
            if (result.hasErrors()) {
                return ResponseModel.FAIL()
                        .setMsg(result.getAllErrors()
                                .stream()
                                .map(err->err.getDefaultMessage())
                                        .collect(Collectors.joining(";"))
                                );
            }
            String password = user.getPassword();
            if (password.length() < 32) {
                user.setPassword(CryptUtil.shiroEncry(user));
            }
            userService.save(user);
            return ResponseModel.SUCCESS();
    }
    
    

    vue

    <template>
    	<div id="user">
    		<div>
    			姓名:
    			<input type="text" name="username" v-model="entity.username"/>
    		</div>
    		<div>
    			密码:
    			<input type="password" v-model="entity.password" />
    		</div>
    		<div>
    			电话:
    			<input type="text" v-model="entity.phone" />
    		</div>
    		<div>
    			电话:
    			<input type="text" v-model="entity.role.roleName" />
    		</div>
    		<button @click="saveUserInfo">保存</button>
    	</div>
    </template>
    
    <script>
    	import {
    		saveUser
    	} from 'network/module/user.js';
    
    	export default {
    		name: "User",
    		methods: {
    			saveUserInfo() {
    				saveUser(this.entity).then(res=>alert(res.msg));
    			}
    		},
    		data(){
    			return {
    				entity:{
    					role:{}
    				}
    			}
    		}
    	};
    </script>
    
    

    415Unsupported Media Type错误

    前端工具请求接口的类型和后端服务器定义的类型不一致造成

    vue跨域

    vue.config.js

    //vue-cli3配置这个做代理
    	devServer: {
    		proxy: {
    			'/api': {
    				target: 'http://localhost:8080/mall/api', //API服务器的地址
    				changeOrigin: true, // 是否跨域
    				pathRewrite: {
    					'^/api': ''
    				}
    			}
    		},
    	},
    	//vue-cli2使用这个
    	// dev: {
    	// 	proxyTable: {
    	// 		'/api': {
    	// 			target: 'http://localhost:8080/mall/api', //API服务器的地址
    	// 			changeOrigin: true, // 是否跨域
    	// 			pathRewrite: {
    	// 				'^/api': ''
    	// 			}
    	// 		}
    	// 	}
    	// },
    
    

    qs的嵌套对象序列化问题

    qs官网

    • 手写嵌套对象:

      {
          a:{
              'b.c':d
          }
      }
      
      
    • qs默认嵌套对象的序列化会用 ‘[]’

      //{ allowDots: true }会将【】转换成 ‘.’
      this.$qs.stringify(obj,{ allowDots: true })
      
      

    简单请求和非简单请求

    阮一峰跨域

    package.js中的^和~区别

    npm官网

    ~1.15.2 := >=1.15.2 <1.16.0 匹配到第二位,大于当前版本小于1.16.0

    ^3.3.4 := >=3.3.4 <4.0.0 匹配到第一位所有大于当前版本且小于4

    vue常用插件

    详情

    Vue.nextTick

    this.$nextTick(function)将回调延迟到下次 DOM 更新循环之后执行,vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上

    @load的时候总是不生效

    • image src有值的时候,才开始加载
    • onload事件是在src的资源加载完毕的时候,才会触发
  • 相关阅读:
    学习笔记9(必做)
    团队作业(四):描述设计
    改进ls的实现(课下作业)
    stat命令的实现-mysate(必做)
    反汇编测试
    学习笔记8(必做)
    初次使用git上传代码到码云
    团队作业(三):确定分工
    学习笔记7(必做)
    缓冲区溢出漏洞实验
  • 原文地址:https://www.cnblogs.com/zpyu521/p/12847207.html
Copyright © 2020-2023  润新知