Vue 进阶

232 阅读6分钟

1. $on$emit

对事件的定义和消费进行解耦

<div id="test">
  <button @click="boost">触发事件</button>
</div>
<script>
  new Vue({
    el: '#test',
    created() {
      this.$on('my_events', this.handleEvent1) //定义事件,并交由指定方法处理
      this.$on('my_events', this.handleEvent2) 
    },
    methods: {
      handleEvent1(param) {
        console.log('my_event1 事件被处理了' + param)
      },
      handleEvent2(param) {
        console.log('my_event2 事件被处理了' + param)
      },
      boost() {
        this.$emit('my_events', '参数') //消费事件
      }
    }
  });
</script>

子组件到父组件通讯

<hello @pfn="parentFn"></hello>

<script>
  Vue.component('hello', {
    template: '<button @click="fn">按钮</button>',
    methods: {
      // 子组件:通过$emit调用
      fn() {
        this.$emit('pfn', '这是子组件传递给父组件的数据')
      }
    }
  })
  new Vue({
    methods: {
      // 父组件:提供方法
      parentFn(data) {
        console.log('父组件:', data)
      }
    }
  })
</script>

非父子组件通讯

var bus = new Vue()

// 在组件 B 绑定自定义事件
bus.$on('id-selected', function (id) {
  // ...
})
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
//可以传事件数组
this.$on(['my_events1', 'my_events2'], this.handleEvent1);
console.log(this._events);

2. Vue.directive

自定义指令

<body>
<div id="test" v-loading="isLoading">   <!--使用v-loading指令-->
  <div>{{data}}</div>
  <button @click="update">更新</button>
</div>
<script>
//自定义v-loading指令,可生成通用 loading 插件,实现无侵入式的 loading
  Vue.directive('loading', function update(el, binding, vnode) {    
  //el 指令绑定的元素,可以直接操作dom;
  //binding是一个对象,binding.value是指令绑定的值
    console.log(el, binding, vnode)
    if (binding.value) {
      const div = document.createElement('div')
      div.innerText = '加载中...'
      div.setAttribute('id', 'loading')
      div.style.position = 'fixed'
      div.style.left = 0
      div.style.top = 0
      div.style.width = '100%'
      div.style.height = '100%'
      div.style.display = 'flex'
      div.style.justifyContent = 'center'
      div.style.alignItems = 'center'
      div.style.color = 'white'
      div.style.background = 'rgba(0,0,0,.7)'
      document.body.append(div)
    } else {
      const div = document.getElementById('loading')
      div && document.body.removeChild(div)
    }
  })
  new Vue({
    el: '#test',
    data() {
      return {
        isLoading: false,
        data: ''
      }
    },
    methods: {
      update() {
        this.isLoading = true
        setTimeout(() => {
          this.data = '用户数据'
          this.isLoading = false
        }, 3000)
      }
    }
  })
</script>
</body>

3. Vue.component

自定义组件

<body>
<div id="root">
  <Test :msg="message"></Test>
</div>
<script>
//自定义 Test 组件
  Vue.component('Test', {
    template: '<div>{{msg}}</div>',
    props: {
      msg: {
        type: String,
        default: 'default message'
      }
    }
  })
  new Vue({
    el: '#root',
    data() {
      return {
        message: 'Test Component'
      }
    }
  })
</script>
</body>

4. Vue.extend

生成组件的构造函数

<body>
<div id="root">
  <Test :msg="message"></Test>
</div>
<script>
  const component = Vue.extend({
    template: '<div>{{msg}}</div>',
    props: {
      msg: {
        type: String,
        default: 'default message'
      }
    }
  })
  Vue.component('Test', component)
  new Vue({
    el: '#root',
    data() {
      return {
        message: 'Test Component'
      }
    }
  })
</script>
</body>

组件挂载,给 vue 添加了一个新的 api

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <style>
    #loading-wrapper {
      position: fixed;
      top: 0;
      left: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, .7);
      color: #ffffff;
    }
  </style>
</head>
<body>
<div id="root">
  <button @click="showLoading">显示Loading</button>
</div>
<script>
  const LoadingComponent = Vue.extend({
    template: '<div id="loading-wrapper">{{msg}}</div>',
    props: {
      msg: {
        type: String,
        default: 'loading...'
      }
    }
  }, 'LoadingComponent')

  function Loading(msg) {
    const div = document.createElement('div')
    div.setAttribute('id', 'loading-wrapper')
    document.body.append(div)
    //实例化一个LoadingComponent对象,挂载到loading-wrapper上,覆盖上面代码创建的div
    new LoadingComponent({
      props: {
        msg: {
          type: String,
          default: msg
        }
      }
    }).$mount('#loading-wrapper')
    //返回一个移除loading的方法
    return () => {
      document.body.removeChild(document.getElementById('loading-wrapper'))
    }
  }
  Vue.prototype.$loading = Loading
    
  //使用
  new Vue({
    el: '#root',
    methods: {
      showLoading() {
        const hide = this.$loading('正在加载,请稍等...')
        setTimeout(() => {
          hide()
        }, 3000)
      }
    }
  })
</script>
</body>
</html>

5. Vue.use

对比上述的组件的挂载,使用的方式变为 Vue.use(插件名)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <style>
    #loading-wrapper {
      position: fixed;
      top: 0;
      left: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, .7);
      color: #ffffff;
    }
  </style>
</head>
<body>
<div id="root">
  <button @click="showLoading">显示Loading</button>
</div>
<script>
  const loadingPlugin = function(vm) {
    const LoadingComponent = vm.extend({
      template: '<div id="loading-wrapper">{{msg}}</div>',
      props: {
        msg: {
          type: String,
          default: 'loading...'
        }
      }
    }, 'LoadingComponent')

    function Loading(msg) {
      const div = document.createElement('div')
      div.setAttribute('id', 'loading-wrapper')
      document.body.append(div)
      new LoadingComponent({
        props: {
          msg: {
            type: String,
            default: msg
          }
        }
      }).$mount('#loading-wrapper')
      return () => {
        document.body.removeChild(document.getElementById('loading-wrapper'))
      }
    }

    vm.prototype.$loading = Loading
  }
  //使用
  Vue.use(loadingPlugin)
  new Vue({
    el: '#root',
    methods: {
      showLoading() {
        const hide = this.$loading('正在加载中,请稍后...')
        setTimeout(() => {
          hide()
        }, 3000)
      }
    }
  })
</script>
</body>
</html>

6. provide 和 inject

实现跨组件通信的方案

<body>
<div id="root">
  <Test></Test>
</div>
<script>
  function registerPlugin() {
    Vue.component('Test', {
      template: '<div>{{message}}<Test2 /></div>',
      provide() {
        return {
          elTest: this
        }
      },//fuction 的用途是为了获取运行时的环境,否则 this 将指向 window
      data() {
        return {
          message: 'message from Test'
        }
      },
      methods: {
        change(component) {
          this.message = 'message from' + component
        }
      }
    })
    Vue.component('Test2', {
      template: '<Test3 />'

    })
    Vue.component('Test3', {
      template: '<button @click="changeMessage">change</button>',
      inject: ['elTest'],
      methods: {
        changeMessage() {
          this.elTest.change(this.$options._componentTag)
        }
      }
    })
  }
  Vue.use(registerPlugin)
  new Vue({
    el:'#root'
  })
</script>
</body>

7. filters

{{过滤内容 | 过滤条件}}

<body>
<div id="root">
  {{message | lower}}
</div>
<script>
  new Vue({
    el: '#root',
    filters: {
      lower(value) {
        return value.toLowerCase()
      }
    },
    data() {
      return {
        message: 'Hello Vue'
      }
    }
  })
</script>
</body>

8. watch

8.1 常见使用

<div id="root">
  <input v-model="message">
  <span>{{copyMessage}}</span>
</div>
<script>
  new Vue({
    el: '#root',
    watch: {
      message(value) {
        this.copyMessage = value
      }
    },
    data() {
      return {
        message: '',
        copyMessage: ''
      }
    }
  })
</script>

8.2 绑定方法

<div id="root2">
  <input v-model="message">
  <span>{{copyMessage}}</span>
</div>
<script>
  new Vue({
    el: '#root2',
    watch: {
      message: 'handleMessage' //传入方法名
    },
    data() {
      return {
        message: 'hello vue',
        copyMessage: ''
      }
    },
    methods: {
      handleMessage(value) {
        this.copyMessage = value
      }
    }
  })
</script>

8.3 deep + handler

<div id="root3">
  <input v-model="deepMessage.a.b">
  <span>{{copyMessage}}</span>
</div>
<script>
  new Vue({
    el: '#root3',
    watch: {
      deepMessage: {
        handler: 'handleDeepMessage',	//对象中,方法名传给 handler
        deep: true
      }
    },
    data() {
      return {
        deepMessage: {
          a: {
            b: 'Deep Message'
          }
        },
        copyMessage: ''
      }
    },
    methods: {
      handleDeepMessage(value) {
        this.copyMessage = value.a.b
      }
    }
  })
</script>

8.4 immediate

一加载完就监听默认值

<div id="root4">
  <input v-model="message">
  <span>{{copyMessage}}</span>
</div>
<script>
  new Vue({
    el: '#root4',
    watch: {
      message: {
        handler: 'handleMessage',
        immediate: true
      }
    },
    data() {
      return {
        message: 'hello vue',
        copyMessage: ''
      }
    },
    methods: {
      handleMessage(value) {
        this.copyMessage = value
      }
    }
  })
</script>

8.5 绑定多个 handler

<div id="root5">
  <input v-model="message">
  <span>{{copyMessage}}</span>
</div>
<script>
  new Vue({
    el: '#root5',
    watch: {
      message: [{
        handler: 'handleMessage'						//第一次处理
      }, 'handleMessage2',							//第二次处理
        function(value) {							//第三次处理
          this.copyMessage = this.copyMessage + '...'
        }]
    },
    data() {
      return {
        message: 'hello vue',
        copyMessage: ''
      }
    },
    methods: {
      handleMessage(value) {
        this.copyMessage = value
      },
      handleMessage2(value) {
        this.copyMessage = this.copyMessage + '*'
      }
    }
  })
</script>

8.6 监听对象属性

比 deep 节约性能

<div id="root6">
  <input v-model="deepMessage.a.b">
  <span>{{copyMessage}}</span>
</div>
<script>
  new Vue({
    el: '#root6',
    watch: {
      'deepMessage.a.b':'handleDeepMessage'
    },
    data() {
      return {
        deepMessage: {
          a: {
            b: 'Deep Message'
          }
        },
        copyMessage: ''
      }
    },
    methods: {
      handleDeepMessage(value) {
        this.copyMessage = value
      }
    }
  })
</script>

9 class 和 style 绑定的高级用法

9.1 class

<div id="root">    
  <div :class="['active','normal']">数组绑定多个 class</div>
  <div :class="[{active:isActive},'normal']">数组包含对象绑定 class</div>
  <div :class="[showWarning(),'normal']">数组包含方法绑定 class</div>
</div>
<script>
new Vue({
    el: '#root',
    data() {
      return {
        isActive: true,
      }
    },
    methods: {
      showWarning() {
        return 'warning'
      }
    }
  })
</script>

9.2 style

<div id="root">  
  <div :style="[warning,bold]">数组绑定多个 style</div>
  <div :style="[warning,mix()]">数组包含方法绑定 style</div>
  
    <!--优先执行最后一个值,浏览器不兼容向左依次适配-->
  <div :style="{display:['-webkit-box','-ms-flexbox','flex']}">style多重值</div> 
</div>
<script>
 new Vue({
    el: '#root',
    data() {
      return {
        warning: {
          color: 'orange'
        },
        bold: {
          fontWeight: 'bold'
        }
      }
    },
    methods: {
      mix() {
        return {
          ...this.bold,
          fontSize: 'x-large'
        }
      }
    }
  })
</script>

10 Vue.observable

<div id="root">
  {{message}}
  <button @click="change">Change</button>
</div>
<script>
  const state = Vue.observable({ message: 'Vue 2.6' })
  const mutation = {
    setMessage(value) {
      state.message = value
    }
  }
  new Vue({
    el: '#root',
    computed: {
      message() {
        return state.message
      }
    },
    methods:{
      change(){
        mutation.setMessage('Vue 3.0')
      }
    }
  })
</script>

11 v-solt

只能用在 component 和 <template> 上,简写 #

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
  <div>案例1:slot 的基本用法</div>
  <Test>
    <template v-slot:header="{user}">
      <div>自定义header({{user.a}})</div>
    </template>
    <template v-slot="{user}">
      <div>自定义body({{user.b}})</div>
    </template>
  </Test>
</div>
<div id="root2">
  <div>案例2:Vue2.6新特性 - 动态slot</div>
  <Test>
    <template v-slot:[section]="{section}">
      <div>this is {{section}}</div>
    </template>
  </Test>
  <button @click="change">switch header and body</button>
</div>
<script>
  Vue.component('Test', {
    template:
      '<div>' +
      '<slot name="header" :user="obj" :section="\'header\'">' +
      '<div>默认header</div>' +
      '</slot>' +
      '<slot :user="obj" :section="\'body\'">默认body</slot>' +
      '</div>'
  })
  new Vue({
    el: '#root',
    data() {
      return {
        obj: { a: 1, b: 2 }
      }
    },
    methods: {
      change() {
        return ''
      }
    }
  })
  new Vue({
    el: '#root2',
    data() {
      return {
        section: 'header'
      }
    },
    methods: {
      change() {
        this.section === 'header' ?
          this.section = 'default' :
          this.section = 'header'
      }
    }
  })
</script>
</body>
</html>