美文网首页
Vue 常见 API 及问题

Vue 常见 API 及问题

作者: 橙色流年 | 来源:发表于2020-10-19 22:49 被阅读0次

组件通信


父子组件通信通过 props$emit 相信小伙伴们都清楚,那么毫无关联的两个组件如果需要实现通信,又有哪些方法呢?相信小伙伴们可能第一时间想到了 vuex,但是如果只是简单的业务逻辑,vuex 的引入和维护就没有那么必要~~~我百度了一下,大部分有说通过 使用 Vue事件总线(EventBus) 来进行处理无关联组件之间的通信,当然也有说 vue 自身就有自定义事件 event.$on、event.$emit、event.$off 来进行相关逻辑处理,其实两者殊途同归,用法差异不大,来看栗子:

  • 初始化 event
// event.js
import Vue from 'vue'
export default new Vue()
  • 组件 A,通过 event.$emit 发送事件
<template>
  <div class="index">
    <button @click="sendMsg()">按钮</button>
  </div>
</template>

<script>
import event from './event'
export default {
  methods: {
    sendMsg() {
      event.$emit('aMsg', '来自A页面的消息')
    }
  }
}
</script>
  • 组件 B,通过 event.$on 接收事件
<template>
  <div class="list">{{ msg }}</div>
</template>

<script>
import event from './event'
export default {
  data() {
    return {
      msg: ""
    }
  },
  methods: {
    addMsgFromA(msg) {
      this.msg = msg
      console.log(msg) // 来自A页面的消息
    }
  },
  mounted() {
    // event.$on('aMsg', (msg) => {
    //   this.msg = msg
    //   console.log(msg)
    // })
    event.$on('aMsg', this.addMsgFromA)
  },
  beforeDestroy() {
    // 及时销毁,否则可能造成内存泄漏
    event.$off('aMsg', this.addMsgFromA)
  }
}
</script>

上述代码中,特意将组件 B 中的 mounted 函数内的方法进行了封装,而不是直接写,是为了保证在页面销毁时对该方法进行及时移除,我们也可以使用 event.$off('aMsg', this.addMsgFromA) 来移除应用内所有对此某个事件的监听。或者直接调用 event.$off() 来移除所有事件频道,不需要添加任何参数 。

生命周期


Vue 的声明周期基本可以分为三个阶段:

  • 挂载阶段
    beforeCreatecreatedbeforeMountmounted
  • 更新阶段
    beforeUpdateupdated
  • 销毁阶段
    beforeDestroydestroyed

createdmounted 的区别?

  • created 页面还没有开始渲染,但是页面的实例已经初始化完成(此时可以获取到 datamethods 中的数据,无法获取 DOM 节点);mounted 页面完成渲染,此时组件在网页上绘制完成。此时可获取到 DOM 节点。

父子组件生命周期调用顺序


写一个简略版的 todoList ,查看父子组件的生命周期调用顺序。

  • 父组件
<template>
  <div class="index">
    <Input @addTitle="addTitleHandler" />
    <List :list="list" @delete="deleteHandler" />
  </div>
</template>

<script>
import Input from './Input'
import List from './List'
export default {
  components: { Input, List },
  data() {
    return {
      list: [
        {
          id: 'id-1',
          title: '标题1'
        },
        {
          id: 'id-2',
          title: '标题2'
        }
      ]
    }
  },
  methods: {
    addTitleHandler(title) {
      this.list.push({
        id: `id-${Date.now()}`,
        title
      })
    },
    deleteHandler(id) {
      this.list = this.list.filter(item => item.id !== id)
    }
  },
  created() {
    console.log('index created')
  },
  mounted() {
    console.log('index mounted')
  },
  beforeUpdate() {
    console.log('index beforeUpdate')
  },
  updated() {
    console.log('index updated')
  },
  beforeDestroy() {
    console.log('index beforeDestroy')
  },
  destroyed() {
    console.log('index destroyed')
  }
}
</script>
  • 子组件 List
<template>
  <div class="list">
    <ul>
      <li v-for="item in list" :key="item.id">
        {{ item }}
        <button @click.stop="deleteItem(item.id)">删除</button>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      default() {
        return []
      }
    }
  },
  methods: {
    deleteItem(id) {
      this.$emit('delete', id)
    }
  },
  created() {
    console.log('list created')
  },
  mounted() {
    console.log('list mounted')
  },
  beforeUpdate() {
    console.log('list beforeUpdate')
  },
  updated() {
    console.log('list updated')
  },
  beforeDestroy() {
    console.log('list beforeDestroy')
  },
  destroyed() {
    console.log('list destroyed')
  }
}
</script>
  • 子组件 Input
<template>
  <div>
    <input type="text" v-model="title" />
    <button @click="addTitle">add</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: ''
    }
  },
  methods: {
    addTitle() {
      this.$emit('addTitle', this.title)
      this.title = ''
    }
  }
}
</script>

挂载阶段的执行渲染结果为:index created => list created => list mounted => index mounted,基本可以说明:父子组件渲染的挂载阶段是由外向内在向外进行执行的。而更新阶段渲染结果为:index beforeUpdate => list beforeUpdate => list updated => index updated,基本逻辑流程和挂载阶段差不多。

$nextTick


  • Vue 是异步渲染

  • data 改变之后,DOM 不会立刻渲染

  • $nextTick 会在 DOM 渲染之后被触发,以获取最新 DOM 节点

其实很好理解,因为 Vue 中所有 DOM 的渲染都是异步的,所以我们在 DOM 渲染之后直接获取 DOM 元素可能会有偏差,使用 $nextTick 可以保证所有异步渲染完成之后再来执行,如下栗子:

<template>
  <div>
    <ul ref="ul1">
      <li v-for="(item, index) in list" :key="index">{{ item }}</li>
    </ul>
    <button @click="addItem">添加一项</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: ['a', 'b', 'c']
    }
  },
  methods: {
    addItem() {
      this.list.push(`${Date.now()}`)
      this.list.push(`${Date.now()}`)
      this.list.push(`${Date.now()}`)

      // 获取 DOM 元素
      const ulElem = this.$refs.ul1
      console.log(ulElem.childNodes.length) // 3
      // 使用 $nextTick
      this.$nextTick(() => {
        const ulElem = this.$refs.ul1
        console.log(ulElem.childNodes.length) // 6
      })
    }
  }
}
</script>

slot


  • 基本使用 (父组件往子组件中插入一段内容)
// 父组件
<template>
  <div id="app">
    <slot-demo :url="website.url">
      {{ website.title }}
    </slot-demo>
  </div>
</template>

<script>
import SlotDemo from './components/SlotDemo'
export default {
  name: "App",
  components: { SlotDemo },
  data() {
    return {
      name: '张三',
      website: {
        url: 'https://www.baidu.com',
        title: '百度',
        subTitle: '百度一下,你就知道'
      }
    }
  },
};
</script>

// 子组件
<template>
  <div>
    <a :href="url">
      <slot>默认内容,父组件没设置内容,我就会被显示出来</slot>
    </a>
  </div>
</template>
<script>
export default {
  props: {
    url: {
      type: String,
      default() {
        return ''
      }
    }
  }
}
</script>
  • 作用域插槽 (子组件 data 中的数据传递给父组件)。子组件通过 :slotData 绑定要传递的 data 中的数据,父组件通过 template 包裹定义 v-slot 接收,然后直接插值调用。如下栗子:
// 父组件
<div id="app">
  <slot-demo :url="website.url">
    <template v-slot="slotProps">
      {{ slotProps.slotData.title }}
    </template>
  </slot-demo>
</div>
<script>
data() {
  return {
    name: '张三',
    website: {
      url: 'https://www.baidu.com',
      title: '百度',
      subTitle: '百度一下,你就知道'
    }
  }
}
</script>
// 子组件
<div>
  <a :href="url">
    <slot :slotData="website">{{ website.subTitle }}</slot>
  </a>
</div>
<script>
data() {
  return {
    website: {
      url: 'https://www.qq.com',
      title: 'QQ',
      subTitle: '每一天,乐在沟通'
    }
  }
}
</script>
  • 具名插槽 (父组件通过 v-slot 与子组件 slot 中的 name 值进行绑定)
// 父组件
<slot-demo>
  <template v-slot:header>
    <h1>这里的内容会被插入到子组件的 header 中</h1>
  </template>
  <p>这里的内容会被插入到子组件的 main 中</p>
  <template v-slot:footer>
    <p>这里的内容会被插入到 footer slot 中</p>
  </template>
</slot-demo>
// 子组件
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

mixin


  • 多个组件有相同的逻辑,抽离出来

  • mixin 并不是完美的解决方案,会有一些问题

  • 变量来源不明确,不利于阅读

  • mixin 可能会造成命名冲突

  • mixin 和组件可能会出现多对多的关系,复杂度较高

  • Vue 3 提出的 Composition API 旨在解决这些问题

当然缺点固然很多,但是日常开发中使用 mixin 还是非常符合 真香定律 的。基础用法:

<template>
  <div>
    <p>{{ name }} {{ major }} {{ city }}</p>
    <button @click="showName">显示姓名</button>
  </div>
</template>

<script>
import myMixin from './mixin'
export default {
  mixins: [myMixin], // 可添加多个,如[myMixin, myMixin1, myMixin2]
  data() {
    return {
      name: '张三',
      major: 'web 前端'
    }
  },
  methods: {},
  mounted() {
    console.log('mixin mounted', this.name)
  }
}
</script>
//mixin.js
export default {
  data() {
    return {
      city: '上海'
    }
  },
  methods: {
    showName() {
      console.log(this.name)
    }
  },
  mounted() {
    console.log('mixin.js mounted', this.name) // 先于 vue 模板中的 mounted 执行
  }
}

cityshowName 我们都没有在模板中直接定义,而是定义在 mixin.js 中,但是我们却可以直接使用,这是因为 mixins 会将 mixin.js 中的内容和我们 vue 模板中的内容进行融合,从而导致多个地方都可以直接使用 mixin.js 中的内容。

动态组件


  • :is = "component-name" 用法

  • 需要根据数据,动态渲染的场景。即组件类型不确定。

光说可能有点混,但是开发中还真的遇到过~~~举个栗子:

<template>
  <div id="app">
    <!-- 使用 :is 和 data 中的值进行绑定 -->
    <component :is="nextTickName"></component>
  </div>
</template>
<script>
import NextTick from './components/NextTick'
export default {
  name: "App",
  components: { NextTick }, // 此处还是要引入和注册组件
  data() {
    return {
      nextTickName: NextTick // 将组件赋值到 data 中
    }
  },
};
</script>

有没有感觉多此一举,但是 vue 将其作为 API 独立出来还是有一定意义的,开发中很多位置还是会用到的,像组件切换,Tab 等确实会用到,具体实用场景小伙伴可自行斟酌哦~~~

异步组件


  • import() 函数

  • 按需加载,异步加载大组件

感觉这个用法和 vue-router 差不多,具体看栗子一目了然:

<template>
  <div id="app">
    <next-tick></next-tick>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {
    NextTick: () => import('./components/NextTick') // import 按需加载组件
  },
  data() {
    return {
    }
  },
};
</script>

相关文章

网友评论

      本文标题:Vue 常见 API 及问题

      本文链接:https://www.haomeiwen.com/subject/pidkmktx.html