# vue2
组件化是 vue 的核心思想,主要目的是为了代码重用。
# 1. 组件通信
# 1.1. 父组件 --> 子组件
属性(props
):
<!-- child -->
<script>
export default {
props: {
msg: String
}
}
</script>
<!-- parent -->
<template>
<Child msg="hello" />
</template>
引用($refs
):
<!-- child -->
<script>
export default {
data() {
return {
userInfo: null,
}
}
}
</script>
<!-- parent -->
<template>
<Child ref="childRef" />
</template>
<script>
export default {
mounted() {
const childVm = this.$refs.childRef;
childVm.userInfo = { name: '张三', age: 18 };
}
}
</script>
子元素($children
):
// parent
const childVm = this.$children[0];
// 只针对唯一子组件有效,$children 不保证顺序
# 1.2. 子组件 --> 父组件
自定义事件:
<!-- child -->
<script>
export default {
methods: {
submit() {
this.$emit('submit', { name: '张三', age: 18 });
}
}
}
</script>
<!-- parent -->
<template>
<Child @submit="handleSubmit" />
</template>
# 1.3. 兄弟组件
说明:
- 通过共同的祖先组件作为中间人,
$parent
或$root
// brother1
this.$parent.$on('add', handleAdd);
// brother2
this.$parent.$emit('add', { msg: 'hello' });
# 1.4. 祖先 --> 后代
说明:
- 多用于组件库
provide
/inject
: 祖先给后代传值(属性传值)
<!-- 祖先 -->
<script>
export default {
provide() {
return {
msg: 'hello',
parent: this, // 把自己的实例也传过去
}
}
}
</script>
<!-- 后代 -->
<script>
export default {
inject: [
'msg',
'parent'
]
}
</script>
# 1.5. 任意两个组件
说明:
- 事件总线 或 vuex
事件总线:
// main.js
new Vue({
beforeCreated() {
this.$bus = new Vue();
}
});
// 1.vue
this.$bus.$on('add', ({ msg }) => {
console.log(msg);
})
// 2.vue
this.$bus.$emit('add', { msg: 'hello' });
vuex:
- 创建唯一的全局数据管理者 store,通过它管理数据并通知组件状态变更。
# 2. 插槽
插槽语法是 Vue 实现的内容分发 API,用于复合组件开发。
该技术在通用组件库开发中有大量应用。
# 2.1. 匿名插槽
<!-- child -->
<template>
<div class="panel">
<slot></slot>
</div>
</template>
<!-- parent -->
<template>
<Panel>
内容
</Panel>
</template>
# 2.2. 具名插槽
将内容分发到指定的位置
<!-- child -->
<template>
<div class="panel">
<slot name="body">内容</slot>
</div>
</template>
<!-- parent -->
<template>
<Panel>
<template v-slot:body>
内容xxx
</template>
</Panel>
</template>
# 2.3. 作用域插槽
父组件使用子组件传递的数据
<!-- child -->
<template>
<div class="panel">
<slot name="test" :list="list"></slot>
</div>
</template>
<script>
export default {
data() {
return {
list: ['a', 'b', 'c']
}
}
}
</script>
<!-- parent -->
<template>
<Child>
<template v-slot:test="{ list }">
<div v-for="item in list" :key="item">{{ item }}</div>
</template>
</Child>
</template>
# 3. v-model 和 .sync
说明:
- 一般情况下,父组件传递的属性 子组件是无法修改的
- 但
v-model
和.sync
可以。
区别:
v-model
常用于表单元素或与表单元素相关的组件.sync
常用于非表单元素的组件
应用场景:
对话框
- 父组件引入对话框(子组件)
- 父组件通过属性绑定(
visible
)控制子组件的显示和隐藏 - 子组件可以主动关闭自己,并通知父组件改变
visible
的值,避免父组件无法再次打开子组件的情况 - 比如: Dialog (visible.sync)—— element-ui (opens new window)
其他(待补充)
# 3.1. v-model
表单组件:
可直接使用,默认绑定
value
属性,监听input
事件<template> <!-- 不使用 v-model --> <input :value="username" @input="username = $event.target.value"> <!-- 使用 v-model --> <input v-model="username"> </template> <script> export default { data() { return { username: '', } } } </script>
自定义组件:
需要指定
model
属性,默认值:{ prop: 'value', event: 'input' }
<template> <input :value="message" @input="$emit('update-message', $event.target.value)" /> </template> <script> export default { props: [ 'message', ], model: { prop: 'message', // 绑定的属性名 event: 'updateMessage', // 侦听的事件名 } } </script>
# 3.2. .sync
修饰符
说明:
.sync
也是语法糖,省略了监听@update:属性名
<!-- parent -->
<template>
<Child :message.sync="content" />
</template>
<script>
export default {
data() {
return {
content: '张三',
}
}
}
</script>
<!-- child -->
<script>
export default {
props: [
'message'
],
mounted() {
setTimeout(() => {
this.$emit('update:message', '娃哈哈');
}, 2000);
}
}
</script>
# 4. 弹出层(弹窗)
说明:
- alert 、 confirm 等弹窗组件
- 它们在当前 Vue 实例之外独立存在,通常挂载到 body
- 它们通过 JS 动态创建的,不需要在任何组件中声明
使用方式:
const dialog = this.$dialog({ title: '提示', content: '确认删除?' });
dialog.show();
// dialog.hide();
示例:
App.vue
<!--04-src-dialog/App.vue--> <template> <div id="app"> <button @click="handleClickOpenDialogButton">open a dialog</button> <button @click="handleClickCloseDialogButton">close the dialog</button> </div> </template> <script> import create from './create'; import Dialog from './Dialog'; export default { methods: { handleClickOpenDialogButton() { const dialog = create(Dialog, { title: '提示', content: '中奖了!!!'}); dialog.show(); this.dialog = dialog; }, handleClickCloseDialogButton() { this.dialog.hide(); }, } } </script>
Dialog.vue
<!--04-src-dialog/Dialog.vue--> <template> <div v-show="visible" class="dialog" > <div class="dialog__head">{{ title }}</div> <div class="dialog__body">{{ content }}</div> </div> </template> <script> export default { props: { title: String, content: String, }, data() { return { visible: false, } }, methods: { show() { this.visible = true; }, hide() { this.visible = false; if (typeof this.$$destroy === 'function') { this.$$destroy(); } }, } } </script>
create.js
/*04-src-dialog/create.js*/ import Vue from 'vue'; /** * 从头创建指定组件实例,并挂载到 document.body * * @param component * @param props * @return {Vue|VNode} */ export default function create(component, props) { const vm = new Vue({ render(createElement) { return createElement(component, { props }); } }).$mount(); // 挂载到“内存 DOM”(生成 $el) // 将 “内存 DOM” 添加到 DOM 树 document.body.appendChild(vm.$el); const componentVm = vm.$children[0]; componentVm.$$destroy = () => { document.body.removeChild(vm.$el); vm.$destroy(); }; return componentVm; }
# 5. 递归组件
说明:
- 递归组件是可以在它们自己模板中调用自身的组件
条件:
- 组件有
name
属性 - 数据有
children
属性 - 收敛条件
node.children && node.children.length > 0
示例:
App.vue
<!--05-src-tree/App.vue--> <template> <div id="app"> <Tree :data="tree"></Tree> </div> </template> <script> import Tree from './Tree.vue'; export default { components: { Tree }, data() { return { tree: [ { id: '1', label: '一级节点-1', children: [ {id: '1-1', label: '二级节点-1'}, {id: '1-2', label: '二级节点-2'}, ] }, { id: '2', label: '一级节点-2', children: [ {id: '2-1', label: '二级节点-1'}, { id: '2-2', label: '二级节点-2', children: [ {id: '2-2-1', label: '三级节点-1'}, {id: '2-2-2', label: '三级节点-2'}, ] }, ] }, ] }; }, } </script>
Tree.vue
<!--05-src-tree/Tree.vue--> <template> <div class="tree"> <TreeNode v-for="node in data" :node="node" :key="node.id" /> </div> </template> <script> import TreeNode from './TreeNode.vue'; export default { name: 'Tree', components: { TreeNode, }, props: { data: { type: Array, required: true, } }, } </script>
TreeNode.vue
<!--05-src-tree/TreeNode.vue--> <template> <div class="tree-node"> <div class="tree-node__label">{{ node.label }}</div> <div v-if="node.children && node.children.length > 0" class="tree-node__sub-tree" > <TreeNode v-for="subNode in node.children" :node="subNode" :key="subNode.id" /> </div> </div> </template> <script> export default { name: 'TreeNode', props: { node: { type: Object, } } } </script> <style> .tree-node { font-size: 14px; padding: 8px 16px; } .tree-node__label { } .tree-node__sub-tree { padding-left: 24px; } </style>
# 6. 路由
# 6.1. 基础
vue-cli 添加路由:
vue add router
指定路由器:
new Vue({
router,
render: (h) => h(App),
}).$mount('#app');
路由视图:
<router-view />
路由链接:
<router-link to="/">home</router-link>
<router-link to="/about">about</router-link>
# 6.2. 路由传参
说明:
- 将路由参数直接设置到路由对应组件的
props
示例:
router.js
/*06-src-router/router.js*/ import Vue from 'vue' import VueRouter from 'vue-router' import Detail from './Detail.vue' Vue.use(VueRouter) function setQueryToPropsOfComponent(route) { return route.query; } const routes = [ // 动态路由 { path: '/dynamic-detail/:id', component: Detail, props: true }, { path: '/detail', component: Detail, props: setQueryToPropsOfComponent }, ] const router = new VueRouter({ mode: 'hash', base: '06-src-router.html', routes }) export default router
App.vue
<!--06-src-router/App.vue--> <template> <div id="app"> <ul> <li><router-link to="/detail?id=123">/detail?id=123</router-link></li> <li><router-link to="/dynamic-detail/456">/dynamic-detail/456</router-link></li> </ul> <router-view/> </div> </template>
Detail.vue
<!--06-src-router/Detail.vue--> <template> <div class="detail"> <p>id: {{ id }}</p> <p>$route.query: {{ $route.query }}</p> <p>$route.params: {{ $route.params }}</p> </div> </template> <script> export default { props: { id: { type: String, } }, } </script>
# 6.3. 路由守卫
说明:
- 路由导航过程中有若干生命周期钩子,可以在这里实现逻辑控制。
全局前置守卫:
const route = {
path: '/foo',
component: Foo,
meta: { auth: true }, // auth 需要认证
};
router.beforeEach((to, from ,next) => {
if (to.meta.auth && !store.state.isLogin) {
next({ name: 'Login' });
return;
}
next();
});
单个路由独享的守卫:
const route = {
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
};
组件内的守卫:(hooks)
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
# 6.4. 动态路由
利用 $router.addRoutes()
可以实现动态路由添加,常用于用户权限控制。
const routersOfServer = [
{
path: '/oa',
component: 'oa',
children: [
{ path: '/oa/user-manage', component: 'oa.UserManage' }
],
},
{
path: '/crm',
component: 'crm',
children: [
{ path: '/crm/user-manage', component: 'crm.UserManage' }
],
},
];
// 异步获取路由
routerService.getRouters().then((routesOfServer) => {
const routes = routesOfServer.map((route) => mapComponent(route));
router.addRoutes(routes);
});
// 隐射关系
const componentsMap = {
'oa': Oa,
'oa.UserManager': Oa.UserMamager,
'crm': Crm,
'crm.UserManager': Crm.UserMamager,
};
// 递归替换
function mapComonent(route) {
route.component = componentsMap[route.component];
if (route.children) {
route.children = route.children.map((childRoute) => mapComonent(childRoute));
}
return route;
}
# 6.5. 面包屑导航
利用 $route.matched
可以得到路由匹配数组,按顺序解析可以得到路由层次关系
<!-- Breadcrumb.vue -->
<template>
<div>
<ul><li v-for="item in pageNameList" :key="item">{ item }</li></ul>
</div>
</template>
<script>
export default {
data() {
return {
pageNameList: [],
}
},
watch: {
$route() {
/*
[
{ path: '/oa', meta: { name: '首页' } },
{ path: '/oa/user-manage', meta: { name: '用户管理' } }
]
*/
console.log(this.$route.matched);
this.pageNameList = this.$route.matched.map((item) => item.meta.name);
}
}
}
</script>
1:13:17
# 7. 生命周期
父组件的 created 早于子组件
父组件的 mounted 晚于子组件
上一篇: 下一篇:
本章目录