老生常谈组件通讯的几种方式
props/@on+$emit
不说了,vue 基础
父组件
<ChildrenCom :title="titile" @changeTitle="changeTitle" /> // 传递 titile
methods: { changeTitle(val) { this.title = val } }
子组件
<ChangeTitle @click="$emit('title', 'newTitle')" /> // 改变 titile
props: ['title'],
:prop.sync="prop"
简化 props/@on+$emit
父
<Children :options.sync="options" /> // 声明这个 props 可能会更新
子
接受:props:['options'];
更新:this.$emit('update:options', val)
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']
$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' ]
refs/$parent/$children/$root
当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。
当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
this[ $parent | $children | $root ]
当 ref 在普通的 DOM 元素上使用 this.$refs.xx 引用指向的就是 DOM 元素
当 ref 用在子组件上,引用就指向组件实例
$refs 只会在组件渲染完成之后生效 并且不是响应式
父组件
<子组件 ref="子组件" />
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
}
}
- 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' )
}
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;
- Vuex
不说了 自定百度
Watch
- 主动取消 watch
const removeWatchMsg = this.$watch('msg', val => {
...
})
removeWatchMsg(); // 取消 watch
- 监听多个变量
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
- 原生 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>
- 自定义事件
在自定义事件中,该值是从其子组件中捕获的值。
<!-- 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].js
,ctrl + 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
获取