vue基础

Posted zpyu521

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue基础相关的知识,希望对你有一定的参考价值。

vue

一 、简介

是什么

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

优缺点

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

二、命令-生命周期函数

命令

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" for循环
:class="{className:boolean}" 也可以用数组
v-model="entity" 双向绑定表单,实际上是两个指令结合v-bind:value和v-on:input

生命周期函数

技术图片

  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("组件中发生改变时");
      }
    };

事件传参问题

  • 绑定事件时没写(),但是需要传参,参数为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位置值
  • 不支持响应式的方法
    • items[index]=""

过滤器

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

三、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

  • 将输入框中的左右两边的空格去掉

四、组件化

什么是组件

借鉴了将一个大的问题拆分成一个个的小问题这种思想

  • 将一个页面拆分成一个个的小组件,可以递归的拆分
  • 每个组件完成自己相关的功能,多个组件共同组成一个页面或者程序
  • 复用性:下次需要同样的功能就可以复用

组件的使用

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

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

<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>

局部组件

<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属性 : 可以写成数组或者对象,对象可以限制类型,对象更好点,也可以类型写成对象添加更多的限制、给默认值
  • 给默认值的时候如果是对象或者是数组,不能直接用{}、[] 需要用工厂来创建
  • 自定义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"]
    },
    //字面量简写
    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

  • 监听对象不能直接监听,可以用computed代替
<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>
  • 如果是键的路径需要用引号包裹
  • 也可以外部调用
<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
      // },
      // "childrens.name":function (val) {
      //   console.log(val);
      // }
    }
  });
  //外部调用
  app.$watch("demo2",function (val) {
    console.log(val)
  })
</script>

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

  • 一般不会用$children来取子组件
  • $refs.refName|[‘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模块化

为什么有模块化

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

闭包解决多人协作开发

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


//js文件2
;(function () {
  if (xm01.flag) {
    alert("xm01.flag:" + xm01.flag);
  }
}());

六、webpack

webpack起步

  • 入口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 = {
  entry: './src/main.js',
  ouput: {
    //必须使用绝对路径
    path: path.resolve(__dirname,'dist'),
    filename: 'bundle.js'
  }
};
  • package配置: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"
      },
    

七、vuecli

  • 介绍:

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

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

    • npm uninstall vue-cli -g / 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. 技术图片

  2. 技术图片

  3. 技术图片

  4. 会默认创建一个.git文件夹

  5. 自定义配置:

    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

    3比4性能更高,代码更少(少6kb)

//runtime-compiler
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})


//runtime-only,这个h是一个createElement('tagName',{attrName:'attrVal'},['innerHtml'])
//在vue中也可以传一个template对象靠vue-template-compiler解析成render(),也可以递归创建
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>

动态路由

  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>
    

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'
    })
    
    

    子路由

    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对象传参

导航守卫

全局守卫

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

  • 主要监听页面的跳转

  • 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 {
        width: 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

十六、vxios

介绍

  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

使用

初步使用
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=""

以上是关于vue基础的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段(vue主模板)

VSCode自定义代码片段11——vue路由的配置

VSCode自定义代码片段11——vue路由的配置

VSCode自定义代码片段11——vue路由的配置

VSCode自定义代码片段2——.vue文件的模板