阅读 132

Vue 常见知识点集合

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

ref的使用

一般用ref来拿到某一个DOM元素,虽然Vue不推荐来操作DOM,但是复杂情况除外。 如下例:子组件复用,通过点击子组件来修改父组件中的值,用到的知识点有子组件向父组件传值,和获取子组件的数据。

  <div id="app">
    <couter @change="handleChange" ref="one"></couter>
    <couter @change="handleChange" ref="two"></couter>
    <div>{{total}}</div>
  </div>
复制代码
Vue.component('couter', {
      template: '<div @click="handleClick">{{number}}</div>',
      data() {
        return {
          number: 0
        }
      },
      methods: {
        handleClick() {
        //  this.number++ 控制的是子组件数据
          this.number++;
          // 数据改变的同时,触发`change`事件,父组件就会监测到这个事件,就会执行对应的方法
          this.$emit('change')
        }
      }
    })
    var vm = new Vue({
      el: '#app',
      data: {
        total: 0
      },
      methods: {
        handleChange() {
        // 由于子组件通过触发change事件来执行了这个函数,所以子组件每点击一次,父组件都要重新计算total
          this.total = this.$refs.one.number + this.$refs.two.number
          // this.$refs.one 就是这个Vue实例组件
        }
      }
    })
复制代码

子组件想改变父组件传过来的值

思路:父组件传过来的值,子组件不能修改(避免数据造成混乱),可以将父组件传过来的值,copy一份,然后进行修改。

<div id="app">
    <couter number="0"></couter>
</div>
复制代码
var couter = {
      template: '<div @click = "handleClick">{{couterNumber}}</div>',
      props: ['number'],
      data() {
        return {
          couterNumber: this.number
        }
      },
      methods: {
        handleClick() {
          this.couterNumber++
          // 不直接修改父组件传过来的值,应当将传过来的值赋值一份,操作复制的那份
        }
      }
    }
    var vm = new Vue({
      el: "#app",
      components: {
        couter
      }
    })
复制代码

1.png

给组件绑定原生事件

思路:在子组件里面的事件添加.native事件修饰符,在父组件中的子组件上写click事件不会起作用。

<div id="app">
// 在子组件标签上绑定点击事件,不会起作用。
    <couter @click.native="handleClick"></couter>
</div>
复制代码

跨组件之间的传值:发布与订阅

思路:兄弟组件想要相互控制,相当于相互通信。设置一个公共bus,该bus包含所有vue实例的方法,因为涉及到$emit来触发事件和$on方法来监听事件。

<div id="app">
        <Child content="hello"></Child>
        <Child content="world"></Child>
</div>
复制代码
        Vue.prototype.bus = new Vue()
      //  每一个Vue实例都会有这个bus属性,相当于每一个组件(包括子组件)都会有这个属性
        Vue.component('Child', {
            template: '<div @click="handleClickChild">{{myContent}}</div>',
            props: ['content'],
            data() {
                return {
                    myContent: this.content
                }
            },
            mounted() {
                // this指向当前组件
                var _this = this
                // 用$on来监测这个change事件,只要一触发就会执行
                this.bus.$on('change', function (val) {
                // 也要注意一下这个this的指向,是把当前组件的值更改,所以可以提前存一下this值。
                    _this.myContent = val
                })
            },
            methods: {
                handleClickChild() {
                // 因为每个组件都会有bus这个属性,而这个属性又是Vue的实例,所以都有$emit方法,触发change事件并传值
                // 注意bus是公共的。
                    this.bus.$emit('change', this.myContent)
                }
            },
        })
        var vm = new Vue({
            el: "#app",
        })
复制代码

beforecCreate 生命周期函数中,所调用的函数都是未声明的,在created()中,声明了,要想调用函数,要在created()里面,否则函数会有is not undefined报错。

组件中的细节点

  1. 在标签中利用子组件渲染的时候,注意一些未知的bug,可以考虑一下is属性
  2. 子组件中的data必须是一个函数,而不是对象。是为了让每一个子组件都有一个独立的数据存储,不会出现多个子组件互相影响的情况。
  3. 通过$refs.ref来获取DOM节点,也可以在子组件上绑定ref来获取这个子组件的引用,里面的数据可以直接写this.$refs.ref.number(number是组件中data中的数据)
  4. 事件名一般采用动宾的形式,程序中出现的常量,可以单独写在文件前面,以大写表示。
  5. props参数校验,可配置哪些属性:(注意一下自定义校验器)

2.png

插槽的使用

插槽的使用场景用于:父组件通过子组件,向子组件中传递DOM标签,而子组件中通过来接收传过来的标签。

5.png

具名插槽,在外围包含一个template标签,用v-slot来什么插槽名称.注意 v-slot 只能添加在 <template> 上 (只有一种例外情况),

 <div id="app">
    <Child>
      <template v-slot:"header">
        <p slot="header">
          <span>
            返回
          </span>
        </p>
      </template>
      <template v-slot:"footer">
        <p slot="footer">
          尾部
        </p>
      </template>
    </Child>
  </div>
复制代码
Vue.component('Child', {
      template: `<div>
                   <div @click="handleClick">
                   <slot name='header'>默认值header</slot>
                    </div>
                   <p>hello world</p>
                   <slot name='footer'>默认值footer</slot>
                </div>`,
      methods:{
        // 注意 插槽不能绑定方法 可以在slot标签外放一个div,事件绑在div上
        handleClick(){
          console.log('返回')
        }
      }
    })
    // slot标签中可以设置默认值,不传标签过来的时候,以默认显示的内容来占位,传了DOM元素过来,直接替换,不管里面是啥
    var vm = new Vue({
      el:'#app'
    })
复制代码

动态组件

顾名思义,就是动态的去渲染哪个组件<component :is=type></component>,其中的type为对应的组件名称,可以通过js来获得.可实现两个组建的切换显示

        <button @click="change('Child')">点击切换Child</button>
        <button @click="change('myfooter')">点击切换myfooter</button>
        <component :is="componentId"></component>
复制代码
import Child from './childOne' 
import myfooter from './childTwo'

methods:{
    change(val){
           this.componentId = val
           // componentId写对应的组件名
     }
}
复制代码

组件的刷新

<div @click="refresh">点击刷新组件</div> 
<myfooter v-if="isShow"></myfooter >
复制代码
import myfooter from './myfooter'
export default {
	data(){
		isShow: true
	},
	methods:{
		refresh(){
			this.isShow = false;
			this.$nextTick(() => {  
                            //DOM更新之后,重新加载组件
       			this.isShow = true
      		})
		}
	},
        componets:{
		myfooter
	},
}
复制代码

v-once指令

可以更好地展示一些静态内容,就是将一些静态内容只展示一次,再放在内存中,再次渲染的时候,直接在内存中获取.可以很好地提高性能.

computed 和 watch

html模板里面最好不要写逻辑性的代码,可以写在computed里面,在computed里面可以写get()和set()方法,来获取属性,或者设置属性。 watch侦听器可以侦听data里面的属性,属性一旦改变,就会立即执行。 比如:

watch:{
    letter () {
        console.log(this.letter) //这个letter是当前的这个data里面的当前值
    }
}
复制代码
  • computed

    1. 支持缓存,只有依赖数据发生改变,才会重新进行计算,计算属性可用于快速计算视图(View)中显示的属性。这些计算将被缓存,并且只在需要时更新。computed是计算属性的; 它会根据所依赖的数据动态显示新的计算结果, 该计算结果会被缓存起来。computed的值在getter执行后是会被缓存的。如果所依赖的数据发生改变时候, 就会重新调用getter来计算最新的结果。
    2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
    3. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
    4. 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
    5. computed和methods:计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。响应式依赖:data和props里面的数据?
       computed: {
               fullName: { 
                   // getter
                   get: function () { 
                       return this.firstName + ' ' + this.lastName 
                   }, 
                   // setter
                   set: function (newValue) {
                       var names = newValue.split(' ') 
                       this.firstName = names[0] 
                       this.lastName = names[names.length - 1]
                   }
               }
           }
    复制代码

    获取属性,默认调用get方法。

  • watch

    当需要在数据变化时执行异步开销较大的操作时,这个方式是最有用的。

    大部分的使用场景是,在输入框绑定v-model的时候,监测里面的值,在回调中会传入newVal和oldVal两个参数。可以参考官网的例子,可以在watch里面发异步请求,而computed不可以。

        <div>
            <van-field 
                @focus="focus('num_s',800,350,'up')" 
                @blur="blur('up')" 
                placeholder="现金值" 
                v-model="begin_cash"
                maxlength="7" 
                type="number" 
                input-align="center" 
            />
        </div>
    复制代码
    watch: {
            begin_cash(newVal, oldVal) {
                    if (newVal < 0) {
                        this.$toast('输入金额不能为负数!');
                        this.begin_cash = '';
                    }
                    if (newVal.toString().indexOf('.') === 0) {
                        this.begin_cash = '';
                    }
                    if (newVal === '' && oldVal.toString().indexOf('.') > 0) {
                        this.begin_cash = oldVal;
                        return;
                    }
                    if (newVal) {
                        newVal = newVal.toString();
                        var pointIndex = newVal.indexOf('.');
                        if (pointIndex > 0 && (newVal.length - pointIndex) > 3) {
                            this.begin_cash = oldVal;
                            return;
                        }
                    }
    }
    复制代码


    作者:夏末_阳光依然耀眼
    链接:juejin.cn/post/699992… 来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

vue中数组更新时的注意点

比如,要改变data中的数据,如果这个数据是数组格式,数据本身可能已经更新了,但是视图不会更新。

  <div id="app">
    <p v-for="item in arr">{{item}}</p>
    <!-- 还是打印出 1 2 3 -->
  </div>
复制代码
let vm = new Vue({
      el: '#app',
      data: {
        arr: [1, 2, 3]
      },
      created() {
        setTimeout(() => {
          this.arr[0] = 100
          console.log(this.arr) // [100,2,3],但是视图不会更新
        }, 2000)
      }
    })
复制代码

vue钩子函数

  • beforeCreate(实例创建前)

    实例组件刚开始创建,元素dom和数据都还没有初始化

    应用场景:可以在这加个loading事件;可以混入公共逻辑,Vue.mixin()

  • created(实例创建后) 数据data已经初始化完成,方法也已经可以调用,但是dom未渲染,在这个周期里面如果进行请求是可以改变数据并渲染,由于dom未挂载,请求过多或者占用时间过长会导致页面线上空白。

    应用场景:在这结束loading,还做一些初始化,实现函数自执行

  • beforeMoute(元素挂载前) dom未完成挂载,数据初始化完成,但是数据的双向绑定还是{{}},这是因为vue采用了虚拟dom技术。

  • render函数执行 创建虚拟DOM,将它进行挂载。

  • mouted(元素挂载后) 数据和dom都完成挂载,在上一个周期占位的数据把值渲染进去,一般请求会放在这个地方,因为这边请求改变数据之后刚好能渲染。

  • beforeUpdate(实例更新前) 只要是页面数据改变了都会触发,数据更新之前,页面数据还是原来的数据,当你请求赋值一个数据的时候就会执行这个周期,如果没有数据改变不执行。

  • updated(实例更新后) 第一次加载数据也会执行,只要是页面数据改变了都会触发,数据更新完毕,页面的数据是更新完成的,beforeUpdatedupdated要谨慎使用,因为页面更新数据的时候都会触发,在这里操作数据很影响性能和死循环。 注意:beforeUpdateupdated用的都不多,可以用watch来监测。

  • beforeDestory(实例销毁前) 实例销毁之前调用,在这一步,实例仍然完全可用。 应用场景:可以做自定义事件的解绑,去除DOM事件的绑定,以及定时器的清理。

  • destory(实例销毁后) vue实例销毁后调用,调用后,Vue实例指示的所有内容都会解除绑定,所有的事件监听器都会被移除,所有的子实例也会被销毁。 注意:切换路由,组件会自动执行beforeDestorydestory钩子函数。如果切换的很频繁,可以用<keep-alive>来做缓存。

父子组件执行的顺序

同步和异步组件,父子组件执行的钩子函数顺序不同。

  • 同步组件:import Page from '@/components/page'

    生命周期钩子函数执行顺序: 父组件的beforeCreatecreatedbeforeMount --> 所有子组件的beforeCreatecreatedbeforeMount --> 所有子组件的mounted --> 父组件的mounted

  • 异步组件:

    1. const Page = () => import('@/components/page')
    2. const Page = resolve => require(['@/components/page'], page)

    生命周期钩子函数执行顺序: 父组件的beforeCreate、created、beforeMount、mounted --> 子组件的beforeCreate、created、beforeMount、mounted

Vue.mixin()

混入逻辑:是将各个钩子函数,或者data数据组成一个数组,依次执行。抽离公共方法和编写插件 [beforeCreate,beforeCreate],Vue.mixin()中可以增加一些钩子函数。

Vue.mixin({
    beforeCreate(){
        console.log('1111111111') // 最先执行
    }
})
复制代码

缺陷:导致数据来源不明,Vue3用compositionApi来解决这个问题。

<transition>动画:切换显示与隐藏效果

<transiton></transition>标签里面包裹要切换的元素

    <transition name="fade">
      <!-- transition把要切换动画的元素包裹起来 -->
      <div 
        style="width:100px;height:100px" 
        class="box" 
        v-show="isShow"
        >
      </div>
    </transition>
    <button 
      style="width:100px;height:40px"
      @click="change"
      >点我切换
    </button>
复制代码
.box{
  background red
}
// 动画一开始的一瞬间,比如显现的一瞬间
.fade-enter{
  background green
}
// 持续时间为2.fade-enter-active{
  transition all 2s linear
}
// 最后的颜色为蓝色
.fade-enter-to{
  background blue
}
// 总体效果就是:green => blue(持续2s),最后变成红色
// 动画结束之后,会移除所有样式
.fade-leave{
  // 看不出效果
}
.fade-leave-active{
  transition all 2s linear
}
.fade-leave-to{
  background pink
}
// // 总体效果就是:red => pink(持续2s),最后隐藏
// 结束之后直接隐藏
复制代码

注意:v-指令可能会重名,如果动画很多的情况下,所以可以取名字。

组件化开发

  • 为什么组件化开发?

    1. 实现组件复用
    2. 可以方便维护代码
    3. 只用组件级别更新,会给每一个组件给一个watcher,不用所有组件都去看,哪个组件出现问题,就去查找该组件。
    4. 能抽离的就尽早抽离,可以减少更新。
  • 组件实例化过程

    会将当前传入的对象,创建出一个Vue实例来。 将当前组件的html,css,js放在一起。

    Vue.component('Child', {
            template: '<div @click="handleClickChild">{{myContent}}</div>',
            data() {
                return {
                    myContent: this.content
                }
            },
    复制代码

    Vue.component传入一个对象,会默认调用Vue.extend()

    Vue.extend(
        {
            template: '<div @click="handleClickChild">{{myContent}}</div>',
            data() {
                return {
                    myContent: this.content
                }
            }
    )
    复制代码

    data为什么要是一个函数? 函数可以返回一个对象,在每一个组件中,返回的值不同,可以避免data中的数据冲突。

父子组件传值(单向数据流)

  • Props的大小写

    在父组件中以短横线来传递

    <!-- 在 HTML 中是 kebab-case 的 --> 
    <blog-post post-title="hello!"></blog-post>
    复制代码

    在子组件中用驼峰命名法来接收

    Vue.component('blog-post', { 
    // 在 JavaScript 中是 camelCase 的 
        props: ['postTitle'],
        template: '<h3>{{ postTitle }}</h3>' 
    })
    复制代码

    注意: 任何类新的数据都可以传给prop

  • 想把整个对象都传给prop

    post: { 
        id: 1, 
        title: 'My Journey with Vue' 
    }
    <blog-post v-bind="post"></blog-post>
    // 相当于
    <blog-post 
        v-bind:id="post.id" 
        v-bind:title="post.title">
    </blog-post>
    复制代码

    注意:父子传递,默认是单向数据流,子组件更改,父组件是不会更改的,如果传递给prop是对象或者数组,则传递的是引用(地址),在子组件中更改数据,(不推荐),则会影响父组件。

  • 也可以传function给子组件,子组件直接使用,像props属性一样,直接使用。并且传对应的值给父组件 在parent父组件中:

     <div>
       parent
       <son1 :money="mny" :change-money="changeMoney"></son1>
     </div>
    复制代码
     data(){
       return {
         mny: 100
       }
     },
     methods:{
       changeMoney(val){
         console.log(val)
       }
     },
     components: {
       son1
     }
    复制代码

    在子组件son1中:

     <div>
       {{money}}
       <button @click="changeMoney(500)">点我</button>
       <!-- 点击直接调用父组件传过来的方法 -->
     </div>
    复制代码
    props: {
       money: {
         type: Number
       },
       changeMoney: {
         type: Function, // 注意这里是大写
         default: () => {}
       }
     }
    复制代码

父子组件传值(同步数据)

若想子组件通过$emit把值传给父组件,父组件又可以同步给子组件,可以这样:

在父组件中:

<!-- 父组件把值改了,顺便又传给了子组件,就实现了父子组件的同步更新 -->
<ShopList :value="mny" @input="val => mny = val"></ShopList>
<!-- 这里注意一下,input是绑定给子组件的,绑定的对应时间是啥,是父组件的,所以下面的$emit方法触发的,也是自己组件本身的方法。-->
复制代码
data() {
  return {
     mny: 100
  };
},
复制代码

在子组件中:

<button @click="go">点击按钮</button>
{{value}}
复制代码
props:{
  value:Number
},
methods: {
    go(){
       // 将值传给父组件
        this.$emit('input',500)
     }
}
复制代码

可以直接优化为:

<ShopList v-model="mny"></ShopList>
复制代码

这个valueinput为固定搭配,v-model是他俩的语法糖,这里的500会替换掉v-model中的mny属性,同时更新到了子组件中。

-自定义v-model属性和事件名

有时候不想使用value+input组合,可以自己更改名字,如

<HomeList v-model="mny"></HomeList>
复制代码
model:{
 prop: 'mny', // 自定义属性名
 event: 'handleClick' // 自定义事件名
},
props:{
 mny:{
   type: Number // 接收数据名
 }
},
methods:{
 goto(){
   this.$emit('handleClick',300)
 }
}
复制代码

又有一种简洁的语法:

<HomeList :money.sync="mny"></HomeList>
// 等价于
<HomeList :money="mny" @update:money="val => mny = val"></HomeList>
// 在子组件的$emit中,也以update:money为事件名把值传给父组件。
复制代码

注意:这个.sync语法糖在子组件中接收数据和事件的时候有具体的格式,注意。

这个详细案例可以看vue.js官网,在这里我自己认为在子组件上绑定v-model的含义的,想让父子组件数据同步,一般子组件传数据给父组件就无了,现在子组件也想保持数据同步,就可以想到用v-model.

父孙组件传值

  1. 父 => 子 => 孙,props传值,性能差,速度慢
  2. 利用provideinject,父组件中使用provide将当前父组件实例暴露出去,孙级组件用inject来接收。注意不要在子组件中直接更改父组件数据!可以通过调用方法来改变数据。通常是在自己写的库中进行使用。
    • provideinject的使用示例

      这里有三级组件,parent,son2,grandson

      parent.vue:

        <div>
          parentdata属性:
          {{mny}}
          <son2></son2> 
        </div>
      复制代码
      import son2 from './son2.vue'
      export default {
          provide() {
              return {
                parent: this // 将当前实例暴露出去
              }
            },
            data() {
              return {
                mny: 100
              }
            },
      }        
      复制代码

      son2.vue:

        <div> 部分代码省略
           son2引入的孙组件:
          <grandson></grandson>
        </div>
      复制代码

      grandson.vue:

        <div>
          孙级组件直接用parent属性:
          {{parent.mny}} 
        </div>
      复制代码
      export default {
        inject: ['parent'] // 注入parent实例
      }
      复制代码

      111.png

  3. $parent,$children,获取当前组件的父组件和子组件,可以直接触发儿子的事件或者父亲的事件。尽量少用,不知道父级是谁,子是谁
    • $parent,$children使用示例

      在父组件parent.vue中给son2绑定方法,准备在孙级组件grandson中去触发

      parent.vue:

        <div>
          <son2 @eat="eat()"></son2> 
          // 给son2绑定eat方法
        </div>
      复制代码
      eat(){
        console.log('触发了son2上的eat方法')
      }
      复制代码

      grandson.vue:

      <button @click="$parent.$emit('eat')">
          点击触发父组件的eat方法,当前的父组件是son2,相当于触发son2上的eat方法。
      </button>
      复制代码
  4. 可以使用$dispatch,一层一层的找父级元素,直到该父级有这个事件。可以说是$parent的一种改良方案。
  • 改写组件原型上的$dispatch方法
   // 向上派发这个事件,只要组件上绑定过这个事件,就会触发
   // 想要某个特定组件触发,传入一个参数,这个参数是组件名称
  Vue.prototype.$dispatch = function(eventName,componentName, value){
    let parent = this.$parent
    while(parent){
      if(parent.$options.name === componentName){
        parent.$emit(eventName, value) // 触发某一组件的eventName事件。
        return
      }
      parent = parent.$parent
    }
  }
复制代码
    <HomeList v-model="ischecked" @eat="eat"></HomeList>
复制代码

这个是给HomeList组件绑定了一个eat事件。 5. 通过父组件,给具体子组件派发事件$broadcast

// 向下通知某个触发某个事件,可能会有多个子组件,所以会涉及到循环
Vue.prototype.$broadcast = function (eventName, componentName, value) {
  let children = this.$children // 获取的是个数组,直接子元素,不包含孙级
  // 递归函数,一层一层地找
  function broadcast(children) {
    for (let i = 0; i < children.length; i++) {
      if (children[i].$options.name === componentName) {
        children[i].$emit(eventName, value)
        return
      } else {
        if (children[i].$children) {
          broadcast(children[i].$children)
        }
      }
    }
  }
  broadcast(children)
}
复制代码
// 在父级的父级组件里,想要找绑定了drink事件的子组件
<button @click="$broadcast('drink','grandson',300)">
      点击触发孙级组件的broadcast
</button>
// 在父级组件里
<grand-son @drink="drink"></grand-son>
复制代码
drink(val){
      console.log('=====================',val)
}
// =====================,300
复制代码
  1. $attrs$listeners来传递所有属性和方法,传递属性:

v-bind,传递方法:v-on

在App.vue里面,给parent组件传了属性和方法

  <div style="height:30px">
    <parent @sing="sing" a="111"></parent>
  </div>
复制代码

在parent组件中,可以直接使用这些属性和方法,也可以把属性和方法传给下一个孙级组件son1.

<div>
    属性:{{$attrs}} // {"a":"111"}
    <son1 v-bind="$attrs"></son1>
    <son1 v-on="$listeners"></son1>
</div>
复制代码
mounted(){
    console.log(this.$listeners) // 有sing方法
 },
复制代码

在son1组件中

mounted(){
    console.log('son1',this.$listeners) // 有sing方法
},
复制代码

这样就实现了父 => 子 => 孙,数据及方法传递

  • 关于$attrs$listeners来传递所有属性和方法注意的地方
  1. $attrsprops声明的属性是互斥的,相当于你在props声明的属性是不会出现在$attrs对象里面的。

  2. $attrs是响应式的

  3. props没有声明的属性,但是父级组件又传过来了,这些属性会默认渲染到传给这个组件的最外层的div标签上,如果不想要添加到DOM上,可以直接在组件中声明:inheritAttrs: false,

  4. $listeners在html模板中看不到,可以在钩子函数中看到,$attrs可以在html和钩子中都可以看到。

  5. $attrs$listeners传递的是所有的属性和方法。

文章分类
前端
文章标签