vue-基础知识

213 阅读7分钟

实例方法

vm.$mount:将实例挂载在指定节点上

    let vm = new Vue({
      // el:"#app",
      template: '<div>{{msg}}</div>',
      data() {
        return {
          msg: {a: 1},
          arr: [1,23,3, {c: 5}],

        }
      }
    })
    vm.$mount('#app'); // 可以在挂载在页面的任何地方 是替换

vm.$watch:自定义watcher

给同一属性进行多次赋值时,只会生效最后一次 原理:内部有个queueWatcher,它将所有更新对应的watcher进行存储,一定时间后一起更新;相同的watcher只有一个,所以只会更新一次

    vm.$watch('msg', (newValue, oldValue) => {
      console.log(newValue)
    })
    vm.msg = 'world'
    vm.msg = 123
    vm.msg = 456

vm.$options:用户传入的所有属性

vm.$data:实例上的data

  • v2.0中,通过defineProperty添加getter和setter重新定义属性在data上,实现响应式;所以,data数据应尽量少的嵌套,影响性能;但是在vue3.0中,通过proxy实现响应式,性能更好
  • 数组只能通过七个方法改变时,才能实现响应式:push shift unshift pop slice...

vm.$nextTick:异步触发

  • 保证页面渲染完毕后获取最新的dom元素

vm.$set:添加新的数据,对新的数据进行劫持

  • 不存在的属性,如果新增的话,不会渲染视图;可以通过$set实现响应式;
  • $set原理:如果是对象属性,通过defineProperty添加getter和setter重新定义属性在data上;如果是数组,内部通过splice实现

vm.$delete:删除某个数据

指令

template:无意义标签;不会被渲染,但是可以写指令;key不能添加在上面

v-html、v-text、{{}}

<div id="app">
    <p>{{message}}</p> <!-- 输出:<span>通过双括号绑定</span> -->
    <p v-html="html"></p> <!-- 输出:html标签在渲染的时候被解析 -->
    <p v-text="text"></p> <!-- 输出:<span>html标签在渲染的时候被源码输出</span> -->
</div>
let app = new Vue({
    el: "#app",
    data: {
        message: "<span>通过双括号绑定</span>",
        html: "<span>html标签在渲染的时候被解析</span>",
        text: "<span>html标签在渲染的时候被源码输出</span>",
    }
});
  • v-html:值是一个变量,相当于innerHTML;插入的内容会被转为标签,可能导致xss攻击,所有尽量采用可信任内容 会覆盖子元素
  • v-text: 等价于{{}}

v-once: 静态节点,只会渲染一次 数据改变不会出现重新渲染

v-if和v-show

  • v-if:控制dom是否存在;内部原理:被编译成render函数,函数中是一个三元表达式;可配合template使用(vue-template-compiler插件将dom编译)

  • v-show: 控制dom的样式;不能用在template上

    当用户频繁切换显示隐藏时,使用v-show;当控制是否产生时用v-if;v-if还可以阻止后续逻辑的发生

v-for

循环字符串、对象、数组、数字,如果使用在template上,key不能使用,key必须放在真实的dom元素上

  • 尽量避免使用索引作为key,影响性能 原因:如果用index,内容交换时,可能操作两次dom;如果是唯一值,只需要位置交换 解决办法:可以为index增加唯一的标识,如添加前缀,避免渲染冲突
<div v-for='(a, index) in 3' :key='`a_${index}`'>{{a}}</div>
<div v-for='(a, index) in 3' :key='`b_${index}`'>{{a}}</div>
  • v-for和v-if不能连用,两者有优先级问题,同时使用会引起冲突;先执行v-for循环,再执行v-if判断是否渲染,有性能问题;解决办法:在元素的外层或里层添加template,使用v-if
// 大致原理如下
arr.map(item => { // 先执行v-for循环,再执行v-if判断
    return true ? item : ''
 })
// 解决办法:
<div v-for='(a, index) in 3' :key='`d_${index}`' >
  <template v-if='index%2'>
    <div>{{a}}</div>
  </template>
</div>

v-on:动态绑定事件

等同于@;内部为原生事件div.addEventListener;如果添加(),需要手动添加事件源$event

<div @clicl='fn($event)'>事件绑定</div>

v-bind:动态绑定数据,等同于:

指令修饰符:

    .stop冒泡  .prevent阻止默认行为  .self当前元素生效  .once只执行一次  .passive 提高滚动事件效率 .lazy失去焦点时更新 .trim去除空格

v-model:双向绑定的语法糖

  • 可用于input|textarea|select|checkbox|radio,等同于value+input|checked+change;
  • 对于input,监控输入事件,将输入的值赋值给变量;但是,v-model不完全等同于value+input,因为,在输入时,v-model会对输入法进行处理,例如,进行汉字输入时,当输入过程中,选中汉字时,汉字内容才会被填入输入框中
    // input
    <!-- <input type="text" :value='msg' @input='(e) => msg = e.target.value'> -->
    <input type="text" v-model='msg'/>
    
    // select
    <select v-model='selected' multiple='true'>
      <option value="" disabled>请选择</option>
      <option v-for='o in opts' :key='o.value'
      :value="o.value">{{o.name}}</option>
    </select>
    
    // checkbox
    游泳<input type="checkbox" v-model='checked' value='游泳'>
    洗澡<input type="checkbox" v-model='checked' value='洗澡'>
    睡觉<input type="checkbox" v-model='checked' value='睡觉'>
    
    let vm = new Vue({
      el:"#app",
      data() {
        return {
          red: 'back',
          s: {fontSize: '20px'},
          msg: "q",
          opts: [{ name: "a", value: 1 }, { name: 'b',value: 2 }],
          selected: [],
          checked: ['游泳']
        }
      },
    })

监听属性-watch

    let vm = new Vue({
      el:"#app",
      data() {
        return {
         msg: {a: '游泳'},
         msg2: 'haha',
        }
      },
      watch: { 
        // 1.函数方式
        msg2: function (newVau, oldVal) {
            console.log(newVau, oldVal)
        },
        msg: [{ // 3. 数组方式:如果一个msg有多个handler,可以放在数组中;如果msg为对象类型,只执行第一个handler
          handler(newVau, oldVal) {
            console.log(newVau, oldVal)
          },
          deep: true // 2. 对对象深度监控;如果是对象类型,无法获取oldValue;因为引用类型数据指向同一个数据对象
        }, {
          handler(newVau, oldVal) {
            console.log(newVau, oldVal)
          },
          immdiate: true // 4.在 wacth 里声明msg后,立即执行里面的handler
        }, 
        'fn' // 5.调用methods中的方法
      ]
      },
      methods: {
        fn() {
          console.log('watch调用methods中的函数')
        }
      }
    })
    
    setTimeout(() => {
      vm.msg.a = 100
      // vm.msg = 200
    }, 1000);

计算属性-computed

    <!-- 全选和反选--点击子,会触发总的get;点击总,触发set-->
    <input type="checkbox" v-model='checkAll'>
    <input type="checkbox" v-for='checkbox in checks' v-model='checkbox.check'>
    
    let vm = new Vue({
      el:"#app",
      data() {
        return {
         checks: [{check: true}, {check: true}, {check: true}]
        }
      },
      computed: {
        checkAll: {
          get() { // 取值执行get方法
            return this.checks.every(item => item.check)
          },
          set(newVal) { // 计算属性很少用set 只有使用v-model时可能用到 不能用于改自己 会死循环
            this.checks.forEach(item => {
              item.check = newVal
            });
          }
        }
      },
    })

computed和watch区别

两者都是watcher
computed不会立马获取值,只有取值时执行;有缓存,如果依赖的结果不变,则不进行更新
watch默认会在内部先执行,算出一个老值,数据变化执行回调
如果计算一个结果 不会使用methods,如果使用methods,每次渲染时都会重新执行,他不具有缓存

过滤器-filter

将元数据格式化显示,而不改变原数据;用于时间格式化、货币符号添加等

    {{timer | format('YYYY:MM:DD')}}
    // 1.全局过滤器
    Vue.filter('format', function(timer, formatter) { // filter中没有this
      return moment(timer).format(formatter)
    })
    
    let vm = new Vue({
      el:"#app",
      data() {
        return { timer: 123123123123 }
      },
      filters: { // 2.局部过滤器 }
    })

自定义指令-directive

指令主要用于操作dom;分类:全局指令和局部指令

生命周期:

bind:指令绑定到指定元素时执行
insert:指令元素插入父节点时调用
update:被绑定元素所在模板更新时调用,而且无论绑定值是否有变化;通过比较更新前后的绑定值,忽略不必要的模板更新
componentUpdate:被绑定的元素所在模板完成一次更新时调用
unbind:指令与元素解绑时调用

eg:面板显示|隐藏

    <div v-click-outside1='hide'>
        <input type="text" @focus='show'>
        <div v-if='isShow'>
          显示面板
        </div>
    </div>
    let vm = new Vue({
      el:"#app",
      data() {
        return { isShow: false }
      },
      methods: {
        show() { this.isShow = true },
        hide() { this.isShow = false }
      }
    })
指令的函数写法

函数写法 = bind + update

directives: {
    clickOutside1(el, bindings, vnode) {
    // 参数:绑定指令的元素el 相关属性bindings  虚拟dom vnode  
      document.addEventListener('click', function(e){
        if (!el.contains(e.target)) { // el是否包含目标元素--原生
          let method = bindings.expression;
          vnode.context[method](); // vnode.context为当前vm实例
        }
      })
      console.log(el, bindings, vnode)
    },
}
指定的对象写法
directives: {
    clickOutside2: {
      bind(el, bindings, vnode) {
        el.handler = function(e){
          if (!el.contains(e.target)) {
            let method = bindings.expression;
            vnode.context[method]();
          }
        }
        document.addEventListener('click', el.handler)
      },
      unbind(el) {
        document.removeEventListener('click', el.handler)
      }
    },
 }

进入页面时,获取焦点

    <input type="text" v-focus>

    let vm = new Vue({
      el:"#app",
      directives: { // 局部指令
        focus: {
          /* inserted(el, bindings, vnode) { // 当指令元素插入到dom后获取焦点
            el.focus()
          } */
          bind(el, bindings, vnode) { // 当指令绑定到元素上时获取焦点,注意使用nextTick
            Vue.nextTick(() => {// 页面更新后获取焦点
              el.focus()
            })
          }
        }
      }
    })

生命周期

beforeCreate:初始化父子关系和内部事件on、emit等;此时还没有进行数据观测;可以混入公共逻辑,vue.mixin

    Vue.mixin({ // 原理:内部将生命周期函数放在一个数组中,mixin中的生命周期先放在数组中,生命周期被调用时一次执行数组中方法
    beforeCreate() {
        console.log('初始化前的公共逻辑')
      },
   })

created:实例创建完成

  • 可以访问data、computed、watch、methods上的方法和数据;可进行初始化数据源,但是没有真实挂载元素,无法获取dom

beforeMount:实例挂载dom前

render:渲染元素

mounted:实例挂载在dom上(真实的dom替换老的节点,vm.$el替换el)

  • 可获取dom,进行ajax请求

beforeUpdate:响应式数据更新时调用

  • 可进行更新前dom访问,移除事件监听

updated:虚拟dom重新渲染后执行

  • 不能再次更新数据,否则导致死循环;可执行依赖于DOM的操作

beforeDestroy:实例销毁前调用

  • 可进行自定义事件的解绑,取消dom的事件绑定,定时器清理

destroyed:实例销毁后调用

  • 实例上说有的东西都被解绑,事件监听被移除,子实例被销毁

注:

  1. 生命周期中的this指向当前实例;生命周期为同步执行;如果需要所有组件挂载完成执行,使用vm.$nextTick
  2. ajax在哪发请求? created beforeMount mounted都可以;但是如果操作dom,只能在mounted ;异步请求一定是在mounted后执行,因为生命周期是同步的;服务端渲染不支持mounted,使用created

动画-transition

  • 当dom显示或隐藏时,vue可以管理动画,如v-if、v-show
  • transition中,如果没指定name,统一v-开头;如果指定name为xx,则以xx-开头
    <transition name='fade'>
      <div class="box" style='width: 100px;height: 100px;' v-if='isShow'></div>
    </transition>
    <button @click='change'>点我</button>
    
    let vm = new Vue({
      el:"#app",
      data() { return { isShow: false } },
      methods: {
        change() { this.isShow = !this.isShow; },
      }
    })
    
    .box {
      background: red;
    }
    .fade-enter {/* 进入动画时的样式 */
      background: blue;
    }
    .fade-enter-active {
      transition: all 2s linear;
    }
    .fade-enter-to {/* 进入动画结束时的样式 */
      background: yellow;
    }
    .fade-leave { /*离开动画开始时样式*/
      background: purple;
    }
    .fade-leave-active {
      transition: all 2s linear;
    }
    .fade-leave-to {/*离开动画结束时样式*/
      background: blue;
    }
  • transform-group:动画元素必须添加animated样式;推荐使用animate.css动画库
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
    <input type="text" v-model='content'>
    <transition-group
      enter-active-class='animated bounceInLeft'
      leave-active-class='animated bounceOutRight'
    >
      <li class='arrItem' v-for='(arr, index) in computedArr' :key='index'>{{arr}}</li>
    </transition-group>
    
    data() {
        return {
          content: '',
          arrs:['abc', 'bsd', 'aab', 'bbc']
        }
     },
    computed: {
        computedArr() {
          return this.arrs.filter(item => item.includes(this.content))
        }
    },
    
    .arrItem {
      background: blue;
      color: #fff;
      line-height: 35px;
      width: 200px;
    }

通过js实现动画,eg:购物车

购物车原理:定义一个动画元素,默认隐藏;当点击某个商品的添加购物车时,将动画元素定位在当前商品上,开始动画,动画元素从被定为商品移动到购物车

    <div v-for='(p, index) in products' ref='lists'>
      <img :src="p" alt="">
      <button @click='addCar(index)'>添加购物车</button>
    </div>
    <transition @enter='enter' @after-enter='afterEnter'>
      <div class="animated" v-if='isShow'></div>
    </transition>
    <div class="cart" ref='cart'></div>
    
    data() {
        return {
          isShow: false,
          currentIndex: -1,
          products: ['https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg', 'https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg']
        }
    },
    methods: {
        addCar(index) {
          this.isShow = true
          this.currentIndex = index
        },
        enter(el, done) {// 获取当前点击的元素的坐标,将动画元素定位到被点击元素上,设置它的显示样式;获取购物车的位置,设置动画元素的动画样式;动画移除后将动画元素归位
          let div = this.$refs.lists[this.currentIndex];
          let {x, y} = div.getBoundingClientRect();
          el.style.left = x + 'px'
          el.style.top = x + 'px';
          el.style.background = `url(${this.products[this.currentIndex]})`;
          el.style.backgroundSize = '100% 100%';
          let {x: cartX, y: cartY} = this.$refs.cart.getBoundingClientRect();
          el.style.transform = `translate3d(${cartX - x}px, ${cartY - y}px, 0) scale(0,0)`
          el.addEventListener('transitionend', done); // 动画结束后进行重置
        },
        afterEnter(el){
          this.isShow = false
        }
    }