Vue 进阶技巧

老生常谈组件通讯的几种方式
  1. props/@on+$emit

不说了,vue 基础

父组件
<ChildrenCom :title="titile" @changeTitle="changeTitle" /> // 传递 titile
methods: { changeTitle(val) { this.title = val } }

子组件
<ChangeTitle @click="$emit('title', 'newTitle')" /> // 改变 titile
props: ['title'],
  1. :prop.sync="prop"

简化 props/@on+$emit

<Children :options.sync="options" /> // 声明这个 props 可能会更新
子
  接受:props:['options'];
  更新:this.$emit('update:options', val)

  1. v-bind="props"

一次传入指定对象的所有属性:v-bind.sync="props"

father.vue
  <Children v-bind.sync="dataToChild" />
  data() {
    return {
      dataToChild: {
        name: 1,
        age: 2
      }
    }
  }

child.vue
  props: ['name', 'age']
  1. $attrs/$lintener
    $attrs 是父组件传过来的除了 props 和 class/style 外的所有属性
father.vue
  <Children :childData1="childData1" :childData2="childData2" />
  
child.vue
  props: [ 'childData1' ],
  created() { console.log(this.$attrs) } // -> { childData2 }

结合 v-bind="props" 可以将 $attrs 这个对象直接传给孙组件
ps: 封装一个公共组件,公共组件内需要使用另一个组件,在业务代码里需要传两个组件的值过去

业务代码.vue
  <GlobalComponent
    :dataForGloablComponent="dataForGloablComponent"
    :dataForBaseComponent="dataForBaseComponent"
    :anotherDataForBaseComponent="anotherData"
  />
  
GlobalComponent.vue
  <BaseComponent v-bind="$attrs" />
  props: [ 'dataForGloablComponent' ]

BaseComponent.vue
  props: [ 'dataForBaseComponent', 'anotherDataForBaseComponent' ]
  1. refs/$parent/$children/$root

当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
this[ $parent | $children | $root ]
当 ref 在普通的 DOM 元素上使用 this.$refs.xx 引用指向的就是 DOM 元素
当 ref 用在子组件上,引用就指向组件实例

$refs 只会在组件渲染完成之后生效 并且不是响应式

父组件
  <子组件 ref="子组件" />
  
  1. provide/inject

provide 允许祖先组件给所有后代组件注入依赖,类似放大版的 props
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的

father.vue
  data() {
    return {
      provideData: {
        time_range: []
      }
    }
  },
  provide() {
    return {
      provideData: this.provideData,
    }
  }

son.vue
  inject: [ 'provideData', '__store' ],
  watch: {
    'provideData.time_range'() {
      debugger
    }
  }
  1. EventBus
    反正我是没用过

main.js

Vue.prototype.$EventBus = new Vue();

某组件内订阅消息

created() {
  this.$EventBus.$on( 'EventBus', val => { console.log(val) } )
}

某处发布消息

created() {
  this.$EventBus.$emit( 'EventBus', 'a msg from EventBus' )
}
  1. Vue.observable 实现小型 Vuex
import Vue from 'vue'

const store = Vue.observable({
  someData: {},
  otherData: {},
});

const mutations = {
  setSomeData(payload) { store.someData = payload },
  setOtherData(payload) { store.otherData = payload }
}

export { store, mutations }

main.js

import { store, mutations } from '@/store'
Vue.prototype.$store = store;
Vue.prototype.$mutations = mutations;
  1. Vuex

不说了 自定百度


Watch
  1. 主动取消 watch
const removeWatchMsg = this.$watch('msg', val => {
  ...
})
removeWatchMsg(); // 取消 watch
  1. 监听多个变量
computed: {
  computedValues () {
    return {
      value1: this.value1,
      value2: this.value2,
    }
  }
},
watch: {
  computedValues (val, oldVal) {
    console.log(val)
  }
}

template 标签

.vue 文件中,所有元素需要放在 <template>元素内。
除此之外,它还能在模板内使用,<template>元素作为不可见的包裹元素,只是在运行时做处理,最终的渲染结果并不包含它

<template>
  <header>
    <template v-for="">
      <div v-if=""></>
    </tempalte>
  </>
</>
过滤器
<div>{{ text | formatText }}</div>

export default {
  data() {
    return {
      text: '1a2b3c',
    };
  },
  filters: {
    formatText(val) {
      return val.replace(/c/g, 'd');
    },
  },
};

但是这个看起来只能在 {{ }} 中用,

created() {
  console.log( this.$options.filters.formatText('ccc'))
}
拿到 vue options

vue 有些 data/ methods/computed 被挂载在组件 this 上,但很多没有被挂载,这时候想拿怎么办?

this.$options.xxx
自定义指令
directives: {
  throttle(el, undefind, vnode) {
    const handle = () => {
      el.style.pointerEvents = 'none';
      setTimeout(() => {
        el.style.pointerEvents = 'auto';
      }, 1500);
    };
    el.addEventListener('click', handle);
    vnode.context.$once('hook:beforeDestroy', () => {
      el.removeEventListener('click', handle);
    });
  },
}
优雅注册按需组件

这里提供的方法是错误的 require 引入的都是全量

如果按需引入 elementui

src/plugins/element.config.js

import 'element-ui/lib/theme-chalk/index.css';
const components = ['Button'];

const elCompnents = {
  install(Vue) {
    components.forEach((component) =>
      Vue.use(require('element-ui')[component])
    );
  },
};

export default elCompnents;

main.js

import elCompnents from './plugins/element.config';
Vue.use(elCompnents);

自动引入
let importAll = require.context('./modules', false, /\.js$/);

class Api extends Request {
  constructor() {
    super();
    //importAll.keys()为模块路径数组
    importAll.keys().map((path) => {
      //兼容处理:.default获取ES6规范暴露的内容; 后者获取commonJS规范暴露的内容
      let api = importAll(path).default || importAll(path);
      Object.keys(api).forEach((key) => (this[key] = api[key]));
    });
  }
}

export default new Api();

自动全局注册
import Vue from 'vue';

/** require.context()
 * @param {String} 相对路径
 * @param {Boolean} 是否查询文件夹
 * @param {RegExp} 过滤文件正则
 */
const context = require.context('.', true, /\.vue$/);

context.keys().forEach(filePath => { // filePath => ./*.vue 或 ./*/*.vue
  const componentModule = context(filePath); // context(filePath) 引入模块
  
  const config = componentModule.defalut || componentModule;  // es6 -> `export default` 或 COMMONJS -> module.exports = {}
  
  const componentName = config.name || filePath.replace('./', '').replace(/(\/index)?\.vue$/, ''); // Com.vue 或 Com/index.vue
  
  Vue.component(componentName, config); // 调用全局注册组件 API
});
路由懒加载
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);

const routeOptions = [
  {
    path:'/index',
    name:'index',
  },
  {
    path:'/login',
    name:'login',
  }
];

const routes = routeOptions.map((route) => ({
  ...route,
  component: route.component
    ? route.component
    : () =>
        import(/* webpackChunkName: "[request]" */ `@/views/${route.name}`), // 路径按照自己的来,后缀可以不写 可以不用 name 在 routeOptions 每一项内自定义一个属性
}));

const router = new Router({
  // ...
  routes,
})
export default router;

https://mp.weixin.qq.com/s/McsEsb7rGVy6IOTfWM0gRg

异步组件

异步组件绑定 ref 可能获取不到

<ChildCom  ref="ChildCom" />

components: {
  ChildCom: () => import('./ChildCom')
},
mounted() {
  console.log(this.$refs.ChildCom) // -> undefined
}

监听组件生命周期 hook

如果父组件想知道子组件生命周期的进度并做一些事儿
之前的写法:

Parent
<Child @onMounted="handleOnMounted" />

Child
mounted () {
  this.$emit('onMounted')
}

现在你可以这样:

Parent
<Child @hook:mounted="handleChildMounted" />
主动触发生命周期 hook

一般我们需要在组件初始化时做定时器或者绑定全局事件,
在组件销毁钩子内取消定时器或事件绑定

mounted() {
  this.intervalId = setInterval(() => {}, 1000)
},
beforeDestory() {
  clearInterval(this.intervalId)
}

现在我们可以这样写

mounted() {
  let intervalId = setInterval(() => {}, 1000);
  this.$once('hook:beforeDestroy', () => {
    clearInterval(intervalId)
  })
}

通过编程方式挂载组件

UI组件内有很多this.$message('success') 的触发形式,不需要写组件标签,也不需要引入特别的东西,挂载在全局上下文中通过函数方式调用触发,非常酷炫

import Vue from 'vue'
import Popup from './popup'

const PopupCtor = Vue.extend(Popup)

const PopupIns = new PopupCtr()

PopupIns.$mount()

document.body.append(PopupIns.$el)

Vue.prototype.$popup = Vue.$popup = function () {
  PopupIns.open()
}

自定义 v-model

默认情况下,v-model@input:value 的语法糖。

<template>
  <input :v-model="name" />
</template>

// 等同于
<template>
  <input :value="name" @input="name = $event.target.value" />
</template>

但可以在组件中指定一个 model options来定义事件类型和绑定值的 key

export default: {
  model: { // model 选项
    event: 'change',
    prop: 'checked'  
  }
}
自定义组件的双向绑定
// BaseInput.vue
<template>
  <input :value="value" @input="$emit('input', $event.target.value)" />
</template>

// 使用组件
<BaseInput v-model="name" />

// 等同于
<BaseInput @input="name=$event" :value="name" />
插槽

简写:#

// Component.vue
<slot name="default">
  默认内容
</slot>

// 使用

<Component /> // 默认内容

<Component>
  <template #defalut>
    自定义内容
  </template>
</Component>
动态指令参数

Vue 2.6的最酷功能之一

假设你有一个按钮组件,并且在某些情况下想监听单击事件,而在其他情况下想监听双击事件

<template>
	...
	<aButton @[someEvent]="handleSomeEvent()" />...
</template>
<script>
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? "click" : "dbclick"
    }
  },
  methods: {
    handleSomeEvent(){
      // handle some event
    }
  }  
</script>
同模板路由渲染

当多个路由对应一个模版时,vue 出于性能考虑,默认不会重新熏染,在这些路由中切换不会发生任何变化

const routes = [
  {
    path: '/a',
    components: SomeCom
  },
  {
    path: '/b',
    components: SomeCom
  }
]

解决

<template>
  <router-view :key="$route.path"></router-view>
</template>
使用 JSX

函数式组件

无状态,没有生命周期或方法,因此无法实例化

创建一个函数组件非常容易,你需要做的就是在SFC中添加一个 functional: true 属性 ,或者在模板中添加 functional

由于它像函数一样轻巧,没有实例引用,所以渲染性能提高了不少。

函数组件依赖于上下文,并随着其中给定的数据而突变

<template functional>
  <div class="book">
    {{props.book.name}} {{props.book.price}}
  </div>
</template>

<script>
Vue.component('book', {
  functional: true,
  props: {
    book: {
      type: () => ({}),
      required: true
    }
  },
  render: function (createElement, context) {
    return createElement(
      'div',
      {
        attrs: {
          class: 'book'
        }
      },
      [context.props.book]
    )
  }
})
</script>
css 穿透

>>> /deep/ ::v-deep

<style scoped>
>>> .scoped-third-party-class {
  color: gray;
}

/deep/ .scoped-third-party-class {
  color: gray;
}

::v-deep .scoped-third-party-class {
  color: gray;
}
</style>
$event
  1. 原生 DOM 事件中与 JS 表现一致
<template>
  <input type="text" @input="handleInput('hello', $event)" />
</template>

<script>
export default {
  methods: {
    handleInput (val, e) {
      console.log(e.target.value) // hello
    }
  }
}
</script>
  1. 自定义事件
    在自定义事件中,该值是从其子组件中捕获的值。
<!-- Child -->
  <input type="text" @input="$emit('custom-event', 'hello')" />

<!-- Parent -->
  <Child @custom-event="handleCustomevent" />

<script>
export default {
  methods: {
    handleCustomevent (value) {
      console.log(value)
    }
  }
}
</script>
Vue.observable

有时候项目很小,但是不用 Vuex 又很不爽,试试这个 API 吧

src/store/index.js

const store = Vue.observable({
  activeIndex: 0
});

const mutations = {
  setActiveIndex: payload => store.activeIndex = payload
}

export { store, mutations }
export default store

main.js

import Vue from 'vue';

import { store, mutations } from '@/store'

Vue.prototype.$store = store;
Vue.prototype.$mutations = mutations;

组件中使用

this.$store.setActiveIndex(index)

Vue 小知识

import BaseInput from '@/components/BaseInput';
console.log(1, BaseInput);

/* BaseInput
{
	beforeCreate: [ƒ]
	beforeDestroy: (2) [ƒ, ƒ]
	computed: {}
	created: ƒ created()
	data: ƒ data()
	methods: {close: ƒ}
	mounted: ƒ mounted()
	name: "V2D"
	render: ƒ ()
	staticRenderFns: []
	__file: "src/components/V2D.vue"
	_compiled: true
	_scopeId: "data-v-8c2
}
*/

.vue 文件被 Vue 的 loader decode 后会变成一个组件配置/JS 对象, template 内的内容会变成 render 函数

将配置项还原成组件

import BaseInput from '@/components/BaseInput';

// 1
new Vue({
  render: h => h(BaseInput, { props })
}).$mount();

// 2
Vue.extend(BaseInput)

// 3
Vue.component('component Name', BaseInput);

虚拟 DOM

render 的 createElement 函数会将 component Options 变成虚拟 DOM


Vue-router 鉴权

`` 使用 路由守卫 进入路由前判断


后端返路由
router.addRoutes(routes: Array<RouteConfig>) API 实现

动态添加更多的路由规则。参数必须是一个符合 routes 选项要求的数组

const comMap = {
  'Home': () => import('./view/Home'),
  'ShopCar': () => import('./view/ShopCar'),
}

function addComponent(route) {
  route.component = comMap[route.component];
  if(route.children) {
    route.children = route.children.map( child => addComponent(child))
  }
  return route
}

this.$http.geUesrRoutes().then( res => {
  // res -> { path: 'Home', compomemt: 'Home' }
  // 处理 res 添加 component/children
  const routeConfig = routes.map( route => addComponent(route))
  router.addRoutes(addComponent)
})


面包屑

$route.matched

// Breadcrumb.vue

<template>
  <el-breadcrumb separator="/">
    <el-breadcrumb-item v-for="(route, i) in crumbData" :key="i">
      <router-link :to="route.path || '/'"> {{ route.name }} </router-link>
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

watch: {
  $route: {
    deep: true,
    immediate: true,
    handler(route) {
      this.crumbData = route.matched.map( route => ({}) );
    }
  }
}

如果点击当前面包屑控制台报错Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxx

不要在element组件上用 to <router-link :to=""></router-link>

生产环境打开 devtools 的两种方法

方案一

F12 切换到源码 tab,找到 app.[hash].jsctrl + F 当前文件内搜索 #app ,找到

new n["default"]({
    router: ui,
    store: J,
    render: function(e) {
        return e($i)
    }
}).$mount("#app");

在 new 行打 debugger,然后刷新页面进入 debugger,n[“default”].config.devtools 修改为 true,关闭 F12 重新打开

方案二

上述对于一些项目不管用,比如我,这里提供一行代码
__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('init', (app.__vue__.__proto__.constructor.config.devtools = true) && app.__vue__.__proto__.constructor)

如果报错或不管用,下边的试试

__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('init', (app.__vue__.__proto__.__proto__.constructor.config.devtools = true) && app.__vue__.__proto__.__proto__.constructor)


输出 Vue 实例 this 的第二种方式

js
chrom DOM tab 中,选中一个 DOM ,控制台输入 $0 就可以快速获取
id 的元素,也可以通过在控制台输入 id 属性值获取 DOM

div#app -> app

app.__vue__可以通过根DOM快速拿到实例的 this


获取 DOM 的第二种方式

一般来讲 我们通过 ref 绑定获取 DOM,如果想获取一个实例的根元素,可以通过 this.$el 获取

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值