Vue.js基础

362 阅读13分钟

1.Vue.js介绍

  • Vue.js是渐进式JavaScript框架(由浅入深)

Vue.js优点

  • 体积小:压缩后33k;
  • 拥有更高的运行效率:基于虚拟DOM,一种可以先通过JavaScript进行各种计算,把最终的DOM操作并优化的技术,由于这个DOM操作属于预处理操作,并没有真实的操作DOM,所以叫做虚拟DOM。
  • 数据双向绑定:让开发者不再操作DOM对象,把更多的精力投入到业务上。
  • 生态丰富,学习成本低。

2.Vue.js入门

Vue.js引用

   <!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

   <!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

Vue.js基本代码结构

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue基本代码结构</title>
    <style>
        /* v-cloak 让vue渲染完成前显示空白 */
        [v-cloak] {
            display: none;
        }
    </style>
</head>

<body>
    <!-- 容器 (必须项)-->
    <div id="app" v-cloak>{{nickname}}</div>
    <!-- 引入vue (必须项) -->
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
     <script>
        Vue.config.devtools = false//解决默认警告
        Vue.config.productionTip = false
        // 新建vue实例
        let vm = new Vue({
            el: "#app",//容器(必须项)
            created() {},//data初始化完成,el没有,在此可以进行ajax请求或数据处理
            mounted() {},//el挂载完成(data和el都初始化完成了),可在此进行DOM相关操作
            watch: {},//数据监听器,可以监听到data中变量的变化,可以做一些处理。
            computed: {},//计算属性。可以简化表达式,方便使用(通过已有变量,得到一个新的变量)
            data() {//放置数据
                return {}
            },
            methods: {}//放置方法
        })
</body>

</html>

3.Vue.js生命周期

图片

Vue.js生命周期是什么?

Vue的生命周期通俗来讲就是我们用Vue写的网页在浏览器运行起来之后,我们写的代码要在内存里执行。例如我们都会的let vm = new Vue();就是new出来的一个Vue实例。这个实例从创建一直到我们关掉浏览器这个实例消亡,这段时间里,Vue这个框架干了啥,Vue的实例做了啥,先做啥,后做啥,这一系列的关系是怎样的,这就是Vue的生命周期。

Vue.js的生命周期分为三个阶段:创建阶段,运行阶段,销毁阶段
钩子函数
  • beforeCreate: 创建实例前,在实例初始化之后,数据观测和事件、生命周期初始化配置之前被调用。
  • created:(data已存在) 实例创建后,实例已经创建完成之后被调用。在这一步,实例已经完成以下配置:数据观测,属性和方法的运算,事件回调。然而挂载阶段还没开始,$el目前不可见。
  • beforeMount: 实例挂载前,在挂载之前被调用:相关的render函数首次被调用,此时有了虚拟DOM。
  • mounted:(el已存在) 实例挂载后,el被创建的vm.$el替换,并挂载到实例上去之后调用该钩子,渲染为真实的DOM.
  • beforeUpDate: 在数据更新前调用,发生在虚拟DOM重新渲染和打补丁之前。在此进一步更改状态,不会重复渲染。
  • upDate: 数据更新后,由于数据更改导致的虚拟DOM重新渲染和补丁,在这之后调用该钩子,应避免在此期间更改状态,因为可能会导致更新出现无限循环。
  • beforeDestroy: 实例销毁前调用,此时实例仍然是可用的。
  • destroyed: 实例销毁之后调用。调用后Vue实例指示的所有东西都会被解绑, 所有的事件监听会被卸载移除,所有的子实例也会被销毁。 注意:该钩子函数在服务端渲染期间不被调用。

4.Vue.js常用指令

v-model

用于数据双向绑定。v-model不仅可以给input赋值,还可以获取input中的数据,而且数据是实时的。(本质上是一个语法糖),目前只能给表单使用。 例如:

  <input type="text" v-model="nickname">

可以在v-model后面添加修饰符:

<input type="text" v-model.trim="nickname">  //去空格
<input type="text" v-model.trim.number="num_a">  //去空格且为数字(Vue自动pasInter)

v-for

用于循环 例如:

 <li v-for="(item,i) in list">{{item}}</li>  //item为数组元素 i为下标 list为循环的数组

无需用到下标时

<li v-for="item in list">{{item}} </li>

v-for除了可以循环数组,也可以是数字

<li v-for="num in 10">{{num}}</li>

v-on

绑定事件,语法糖为@ 例如:

<button v-on:click="add"></button>
<button @click="add"></button>

v-cloak

Vue渲染完成之前有,渲染完成后消失。结合display:none,可以提高用户体验 例如:

 <style>
      /* v-cloak 让vue渲染完成前显示空白 */
      [v-cloak] {
          display: none;
      }
  </style>
 <div id="app" v-cloak></div>

v-if

v-if是标签的删除和创建,标签的删除和创建需要耗内存,因此对于不需要频繁切换的场景,用v-if 比较好。

 <div v-if="false"> 123 </div> //v-if为false时,此div不会被渲染
 <div v-if="true"> 123 </div> //v-if为true时,此div会被渲染

可以给v-if绑定一个变量

<div v-if="isShow"> 123 </div>  //当isShow为false时,不显示,为true时显示

v-show

v-show只是切换元素的display为none还是block,也就是切换它的显示还是隐藏,因此,对于需要频繁切换显示和隐藏时,用v-show合适,如:轮播图 v-show用法与v-if同理

 小案例:点击按钮时,让isShow取反(在标签中,this可以省略)

  <div v-if="isShow">
         你好
     </div>
     <button @click="isShow=!isShow">切换</button>
     <br>
     <div v-show="isShow">
         哈哈哈
     </div>
     <button @click="isShow=!isShow">切换</button>
     <br>

v-bind

在标签内绑定属性,语法糖":"例如:

   <a v-bind:href="'#detail?id='+item.id">跳转</a>
   <a :href="'#detail?id='+item.id">跳转</a>

v-html

v-html可以渲染标签

 <div>
    <p v-html="str"></p>  //可以把str中的字符串当作html标签渲染
 </div>

  data() {
             return {
                  str: "<h1>你好</h1>"
              }

5.Vue.js混入

混入(mixin)定义了一部分可复用的方法或者计算属性。混入对象可以包含任意组件选项。当组件使用汇入对象时,所有混入对象的选项将被混入该组件本身的选项。 mixin其实就相当于一个Vue的小型实例,里面也可以写data、methods等。下面我们结合mixin做一个axios的封装小案例。

  • 第一步:新建文件request.js,代码如下
let req = axios.create({
  baseURL: 'http://47.92.50.43:8888',
})
// 添加请求拦截器
req.interceptors.request.use(function (config) {
  let loadingBar = document.getElementById('global-loading')
  loadingBar.style.display = 'block'
  let { method, url } = config
  console.log(`${method}${url}`)
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
req.interceptors.response.use(function (response) {
  let loadingBar = document.getElementById('global-loading')
  loadingBar.style.display = 'none'
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});
  • 第二步新建文件mixin.js,代码如下:
// 混入
Vue.mixin({
  methods: {
      $post(url, params) {
          return req.post(url, params)
      },
      $get(url, params) {
          return req.get(url, {
              params
          })
      },
  }
})
  • 第三步使用封装好的axios请求数据(注意:使用时要先引入上面两个文件)
//调接口增删查数据
methods: {
               //添加数据
               async save() {
                   let { data } = await this.$post('/sys/savefl', {//get请求
                       name: this.name,
                       path: this.path
                   })
                   if (data.success) {
                       this.name = this.path = ''
                       // alert('添加成功')
                       this.getList()
                   }
               },
               //查询数据
               async getList() {//get请求
                   let { data } = await this.$get('/sys/firendslink')
                   this.list = data
               },
               //删除数据
               async del(i, id) {
                   if (confirm("确定删除?")) {//post请求
                       let { data } = await this.$post('/sys/removefl', { id })
                       if (data.success) {
                           // alert('删除成功')
                           this.getList()
                       }
                   }
                   //数组删除
                   // this.list = this.list.filter(r => r.id !== id)
                   //根据下标删除
                   //this.list.splice(i, 1)
               }
           }

6.Vue.js过滤器

  • filter:将模板中的值处理完再返回

局部过滤器

  // 写在vue实例内部
filters: {
             fmtGender(val) {//可以过滤性别
                 return ['男', '女'][val]
             }

全局过滤器

 // 写在vue实例内部,也可以建一个vue_filter.js文件,把过滤器都写在里面,需要用到时引入即可
Vue.filter('fmtGender', function (val) {
           return ['男', '女'][val]
       }, )

7.Vue.js组件

局部组件

  • 局部组件:写在vue实例内部,与el同级
components:{
               star:{//star为自定义组件的名称,切记:自定义组件名称不可与html标签重复
                   template:`
                      //注意:组件一定要有根节点
                      <div>
                         <h1>我是局部组件</h1>
                      </div>
                   `
               }
           },

全局组件

  • 全局组件:写在vue实例外部或放到外部文件中,需要用时引入即可
Vue.component('star', {//star为自定义组件的名称,切记:自定义组件名称不可与html标签重复
           template: `
                      //注意:组件一定要有根节点
                      <div>
                         <h1>我是局部组件</h1>
                      </div>
                   `
       })

父子组件传值

  • 1.父组件向子组件传值props
  • props:在组件中可以当data来用,vue组件通过props属性声明一个自己的属性,然后父组件就可以往子组件里面传递数据。
  • 父组件传过来的值,子组件不可以修改(即props里的值不能修改),如果需要改props中的值,可以设置一个中转变量。
  • 若绑定的属性是Number类型或变量,绑定时要在属性前加冒号。
<body>
   <div id="app">
       <zz></zz>
       <!-- 传入的值为字符串时 属性名前不需要加冒号 -->
       <zz nickname='Alice'></zz>
       <!-- 把nickName绑定在v-model上,则在文本框输入文本时,对应的子组件中的值也会随之被改变 -->
       <input type="text" v-model="nickName">
       <!-- 传入的值为变量时 属性名前必须加冒号 -->
       <zz :nickname='nickName'></zz>
   </div>
   <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
   <script>
       Vue.component('zz', {
           // props可以为一个数组或者对象:
           //若不需要限制属性类型,可以用数组 属性名要小写且不能为html标签
           // props:['nickname'],

           //若需要限制属性类型,则用对象
           props: { //接收父组件传的值
               nickname: {
                   type: String, //传入类型
                   default: "alice", //默认值
                   required: false //false表示此属性为非必须传入项 若为true 则为必须传入项
               }

           },
           template: `
           <div>{{nickname}}</div>
           `
       })
       new Vue({
           el: "#app",
           data() {
               return {
                   nickName: ""
               }
           }
       })
   </script>
</body>
  • 2.子组件向父组件传值$emit
  • 子组件需要响应一个$emit方法,在这里定义一个事件,然后在父组件里面进行监听这个事件,最后进行响应。
<body>
   <div id="app">
       <input type="text" v-model="nickName">
       <!-- 外面的父组件会响应change事件 并将值赋给nickName -->
       <zz :nickname='nickName' @change="nickName=$event"></zz>
   </div>
   <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
   <script>
       Vue.component('zz', {
           props: ['nickname'],
           data() {
               return {
                   innerName: this.nickname //innerName中转变量 
               }
           },
           watch: {
               nickname(val) { //一旦外面的值发生改变,就把外面的值传给中转变量
                   this.innerName = val
               },
               innerName(val) { //当里面的值发生改变时 会触发change事件 并把值传出去
                   this.$emit('change', val)
               }
           },
           template: `
           <div>{{innerName}}
           <input type="text" v-model="innerName">   
           </div>
           `
       })
       new Vue({
           el: "#app",
           data() {
               return {
                   nickName: "alice"
               }
           }
       })
   </script>
</body>

组件中使用v-model

  • 将value属性绑定到一个名叫value的props上,在其input事件被触发时,将新的值通过自定义的input事件抛出
  • 示例:
<body>
   <div id="app">
       <input type="text" v-model="nickName">
       <!--  v-bind:value="nickName" v-on:input="nickName=$event" =>语法糖  v-model="nickName" -->
       <zz v-model="nickName"></zz>
       <!-- <zz :value='nickName' @input="nickName=$event"></zz> -->
   </div>
   <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
   <script>
       Vue.component('zz', {
           props: ['value'],
           data() {
               return {
                   innerName: this.value //innerName中转变量 
               }
           },
           watch: {
               value(val) { //一旦外面的值发生改变,就把外面的值传给中转变量
                   this.innerName = val
               },
               innerName(val) { //当里面的值发生改变时 会触发input事件 并把值传出去
                   this.$emit('input', val)
               }
           },
           template: `
           <div>{{innerName}}
           <input type="text" v-model="innerName">   
           </div>
           `
       })
       new Vue({
           el: "#app",
           data() {
               return {
                   nickName: "alice"
               }
           }
       })
   </script>
</body>

修饰符sync

  • v-model与sync:
  • 如果外面需要拿到里面的值做后续操作,用v-model(一般用1个)
  • 如果只是为了拿到值来显示、隐藏,则用sync就可以了(可以写多个)
  • 一般v-model用的多一些,如果项目比较复杂,可以结合一起用
<body>
   <div id="app">
       <input type="text" v-model="nickName">
       <!-- 属性名.sync是vue提供的固定写法 -->
       <zz :nickname.sync="nickName"></zz>
   </div>
   <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
   <script>
       Vue.component('zz', {
           props: ['nickname'],
           data() {
               return {
                   innerName: this.nickname //innerName中转变量 
               }
           },
           watch: {
               nickname(val) { //一旦外面的值发生改变,就把外面的值传给中转变量
                   this.innerName = val
               },
               innerName(val) {
                   this.$emit('update:nickname', val) //'update:属性名是vue提供的固定写法'
               }
           },
           template: `
           <div>{{innerName}}
           <input type="text" v-model="innerName">   
           </div>
           `
       })
       new Vue({
           el: "#app",
           data() {
               return {
                   nickName: "alice"
               }
           }
       })
   </script>
</body>

组件嵌套

  • 父组件获取子组件中的值:this.$children(在mounted)
  • 子组件获取父级值:this.$parent
  • 注:普通标签不算子级、父级,若页面结构发生改动,用this.children和this.parent就不太合适,因此用的比较少(除非是不需要改动的结构才用这种方法)

星星评分小案例

  • 利用父子组件传值实现
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>星星评分</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.2.0/css/font-awesome.css" rel="stylesheet">
    <style>
        .star {
            color: gold;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- $event:是vue内部的一个对象,表示组件里面自定义事件触发的那个值 -->
        <star label="客服态度" :score="score" @change="score = $event"></star>
        <!-- <star label="物流速度"></star>
        <star label="商品质量" :count="count"></star> -->
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
    <script>
        Vue.component('star', {
            props: { //接收父组件传的值
                count: {
                    type: Number,
                    default: 5 //默认值
                },
                score: {
                    type: Number,
                    default: 0
                },
                label: {
                    type: String,
                    required: true //必须传
                }
            },
            template: `
            <div>
                {{label}}--{{innerScore}}
                <i v-for="order in count" @click="innerScore=order" @mouseleave="score1 = innerScore" 
                @mouseenter="score1=order"
                :class="order<=score1?'fa-star':'fa-star-o'" class="fa star" aria-hidden="true">
                </i>    
            </div>
            `,
            watch: {
                innerScore(val) {
                    //手动触发一个叫change的事件,事件对象e值为val
                    this.$emit('change', val)
                }
            },
            data() {
                return {
                    innerScore: this.score, //中转变量
                    score1: this.score //临时变量
                }
            },
        })

        let app = new Vue({
            el: "#app",
            data() {
                return {
                    score1: 2,
                    score: 2,
                    count: 10
                }
            }
        })
    </script>
</body>

8.Vue.js插槽

默认插槽

  • 组件
Vue.component('cc', {
            template:`
            <div>
               <slot></slot>
            </div>
            `
        })
  • 使用
<cc>
  默认插槽
</cc>

具名插槽

  • 组件
Vue.component('cc', {
            template: `
            <div>//给插槽起名字
               <slot name="login"></slot>
            </div>
            `
        })
  • 使用:#与v-slot:作用相同
 <cc #login>登录</cc>
<!-- <cc v-slot:login>登录</cc> -->

作用域插槽

  • 作用域插槽:带有数据的插槽。
  • 如果希望调用者自行控制组件里面的样式,并且拿到组件内部的数据,就可以使用作用域插槽的方式,先从组件内部把数据传出来,再在外面接收处理。
  • 示例:
  • html
 <cc :list='list'>
         <!-- 给插槽绑定一个变量,用<template v-slot="scope"></template>获取插槽内的数据,scope为自定义的名称,里面包含了绑定的所有内容。 -->
         <template v-slot="scope">{{scope.item.name}}</template>
         <!-- 解构方式 -->
         <!-- <template v-slot="{item}">{{item.name}}</template> -->
 </cc>
  • js
        Vue.component('cc', {
            props:['list'],
            //从组件内部把数据传出来 <slot :item="item"></slot>
            template: `
            <div>
                <ul>
                    <li v-for="item in list">
                       <slot :item="item">{{item.name}}</slot>
                    </li>
                </ul>
            </div>
            `
        })
        new Vue({
            el: "#app",
            data() {
                return {
                  list:[
                      { id:1, name:"zs"},
                      { id:2, name:"ls"},
                      { id:3, name:"ww"},
                  ]
                }
            }
        })

9.Vue.js自定义指令

除了默认设置的核心指令( v-model 和 v-show ), Vue 也允许注册自定义指令。

下面我们注册一个全局指令 v-focus, 该指令的功能是在页面加载时,元素获得焦点:

<div id="app">
    <p>页面载入时,input 元素自动获取焦点:</p>
    <input v-focus>
</div>
 
<script>
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
  // 当绑定元素插入到 DOM 中。
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
// 创建根实例
new Vue({
  el: '#app'
})
</script>

我们也可以在实例使用 directives 选项来注册局部指令,这样指令只能在这个实例中使用:

<div id="app">
  <p>页面载入时,input 元素自动获取焦点:</p>
  <input v-focus>
</div>
 
<script>
// 创建根实例
new Vue({
  el: '#app',
  directives: {
    // 注册一个局部的自定义指令 v-focus
    focus: {
      // 指令的定义
      inserted: function (el) {
        // 聚焦元素
        el.focus()
      }
    }
  }
})
</script>

有时候我们不需要其他钩子函数,我们可以简写函数,如下格式:

Vue.directive('runoob', function (el, binding) {
  // 设置指令的背景颜色
  el.style.backgroundColor = binding.value.color
})

10.Vue.js自定义插件

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

10.Vue.js特殊 attribute key

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

最常见的用例是结合 v-for:

<ul>
  <!-- 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute: -->
  <li v-for="item in items" :key="item.id">...</li>
</ul>

它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景时它可能会很有: 完整地触发组件的生命周期钩子 触发过渡 例如:

<transition>
  <span :key="text">{{ text }}</span>
</transition>

当 text 发生改变时, 总是会被替换而不是被修改,因此会触发过渡。

10.Vue.js路由

Vue.js 路由允许我们通过不同的 URL 访问不同的内容。

通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。

Vue.js 路由需要载入 vue-router 库

中文文档地址:vue-router文档。

安装

1.直接下载 / CDN

https://unpkg.com/vue-router/dist/vue-router.js

2.NPM 推荐使用淘宝镜像:

cnpm install vue-router

简单实例

Vue.js + vue-router 可以很简单的实现单页应用。

是一个组件,该组件用于设置一个导航链接,切换不同 HTML 内容。 to 属性为目标地址, 即要显示的内容。

以下实例中我们将 vue-router 加进来,然后配置组件和路由映射,再告诉 vue-router 在哪里渲染它们。代码如下所示:

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
 
<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>

tag

有时候想要 渲染成某种标签,例如

  • 。 于是我们使用 tag prop 类指定何种标签,同样它还是会监听点击,触发导航。

    <router-link to="/foo" tag="li">foo</router-link>
    <!-- 渲染结果 -->
    <li>foo</li>
    

    路由配置

    let router = new VueRouter({
        routes: [{
                path: "/",
                component: Layout,
                children:[
                    {
                        path: "",
                        meta: {
                            title: "首页"
                        },
                        component: Home
                    },
                    {
                        path: "foo/:id",
                        meta: {
                            title: "学生管理"
                        },
                        props: true,
                        component: Foo
                    },
                    {
                        path: "bar",
                        meta: {
                            title: "教师管理"
                        },
                        component: Bar
                    }
                ]
            },
            {
                path: "/login",
                meta: {
                    title: "登录"
                },
                component: Login
            },
            {
                path: "*",
                meta: {
                    title: "页面未找到"
                },
                component: Err404
            },
        ]
    
    })