前言
一个页面里面有着不同的操作,每个操作对应打开一个弹窗,比如我之前实习做的一个项目,有 10 种操作对应 10 个不同的弹窗,所以基于代码复用原则,我们需要对弹窗进行复用
组件注册(局部注册)
基于 JavaScript 的模块化系统的 vue + Webpack 项目,组件注册方式:(或者说是自定义组件)
- component-one.vue
<template>
<div>...</div>
</template>
- index.vue
<template>
<component-one></component-one>
</template>
<script>
import ComponentOne from './component-one.vue'
export default{
components: {
ComponentOne
}
}
</script>
注意的是
-
组件是可复用的 Vue 实例,也有data、methods、computed、watch等选项,但没有像el这样的根实例特有的选项;
-
其中的data必须是函数,将写在new创建的实例的data选项,以return的方式传递给该组件创建的实例;因为每用一次组件,都会创建出新的实例;每次使用该组件,都独立维护一份data数据,不能因某一个实例修改data而影响其他实例维护的data
-
每个组件必须有且只有一个根元素,如用一个
template包裹这些子元素
全局注册
有些组件可以在很多页面中被使用,我们称其为“基础组件”。像这样的“基础组件”我们应该对它进行全局注册,不用在每次使用的时候都要引入和在component注册:
- dialog.vue
<div>
<el-dialog>
<div>
<!-- 父组件中自定义标签 -->
<slot></slot>
</div>
</el-dialog>
</div>
<script>
export default {
name: 'comDialog', //name为组件名称,全局注册必备的
props: {
dialogList: Object
},
}
</script>
- main.js
/**
* require.context()参数的意义
* 参数1.文件目录
* 参数2.是否查找子集
* 参数3.查找规则
*/
const requireComponent = require.context('./', true, /\.vue$/)
const install = (Vue) => {
if (install.installed) return // 如果组件被注册就返回,没有就注册
install.installed = true
requireComponent.keys().forEach(filename => { // filename 文件
const config = requireComponent(filename) // 第i个组件
const componentName = config.default.name // 组件名
Vue.component(componentName, config.default || config) // 循环注册组件
})
}
Vue.use(install)
- 在任何页面都可以直接使用:
<com-dialog></com-dialog>
父子组件传参
父传子——props
- index.vue
<template>
<component-one :prop1="value1" :prop2="value2"></component-one>
</template>
<script>
import ComponentOne from './component-one.vue'
export default{
components: {
ComponentOne
},
data: {
value1: ...,
value2: ...,
}
}
</script>
- component-one.vue
<template>
<div>{{prop1}}</div>
<div>{{prop2}}</div>
</template>
<script>
export default{
props: {
prop1: { type:..., default:... },
prop2: { type:..., default:... },
}
}
</script>
子传父——emit
- component-one.vue
<template>
<div @click="goBacktoIndex">{{value1}}</div>
<div>{{value2}}</div>
</template>
<script>
export default{
props: {
prop1: { type:..., default:... },
prop2: { type:..., default:... },
},
methods:{
goBacktoIndex(){
this.emit("fromComponentOne",params)
}
}
}
</script>
- index.vue
<script>
export default{
methods:{
fromComponentOne(res){
console.log(res) // res 的值就是 component-one.vue 里面的 params 的值
}
}
}
</script>
子组件是否每次打开都需要刷新
- 需要刷新 法一:
<component-one v-if="showComponentOne"></component-one>
法二:
<component-one v-show="showComponentOne" :key="key"></component-one>
key = Math.random()
或
key = new Date().getTime()
- 不需要刷新
<component-one v-show="showComponentOne"></component-one>
是否刷新关乎是否会触发生命周期函数 created、mounted 等
在父组件里修改子组件的内容——插槽
例如从组件库里引入一个模板组件,想对模板组件里面的内容进行自定义,那就需要插槽:
base-layout.vue 子组件
<div class="container">
<header> <!-- 想在这里自定义页头内容 --> </header>
<main> <!-- 想在这里自定义主要内容 --> </main>
<footer> <!-- 想在这里自定义页脚内容 --> </footer>
</div>
vue 3.0 的处理方案是:
vue3.0 提供了slot元素,含有name属性的叫具名插槽,没有含name属性的叫匿名插槽,用来指定父组件中自定义内容所放的位置。
base-layout.vue 子组件加入slot元素
<div class="container">
<header>
<slot name="header"></slot> <!-- 具名插槽 -->
</header>
<main>
<slot></slot> <!-- 匿名插槽 -->
</main>
<footer>
<slot name="footer"></slot> <!-- 具名插槽 -->
</footer>
</div>
- 具名插槽看name的值锁定自定义内容位置
- 匿名插槽的话就“接纳”来自所有没有指定name值的自定义内容
index.vue 父组件用<template v-slot:插槽名称>包裹自定义内容
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p> <!-- 没有被<template v-slot:插槽名称>包裹,被匿名插槽接纳 -->
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
最终渲染结果(slot元素被自定义内容替换)
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
父组件能在插槽区域用子组件的数据——作用域插槽
current-user.vue 子组件
<span>
<slot name="userName">{{ user.lastName }}</slot>
</span>
index.vue 父组件,我们想把user.lastName替换成user.firstName
<current-user>
<template v-slot:userName>
{{ user.firstName }}
</template>
</current-user>
但是这样处理是行不通的,因为只能在子组件中访问到数据user,解决方案是:
- 在子组件中使用 v-bind 绑定 user 数据
<span>
<slot :user="user">{{ user.lastName }}</slot>
</span>
- 在父组件中自定义一个名字slotProps,通过slotProps.user来访问子组件中user数据
<current-user>
<template v-slot:userName="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
v-slot 可以被缩写为
#(v-bind缩写成:,v-on缩写成@)
另外,还可以动态绑定插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
vue 2.0 的处理方法:
- slot 属性做具名插槽
<base-layout>
<h1 slot="header">Here might be a page title</h1> <!-- 具名插槽 -->
<p>A paragraph for the main content.</p> <!-- 匿名插槽 -->
<p>And another one.</p>
<p slot="footer">Here's some contact info</p> <!-- 具名插槽 -->
</base-layout>
这里和 vue3.0 的区别在于,自定义内容不需要被
<template v-slot:插槽名称>包裹,而是在自定义内容组件上直接加slot属性
- slot-scope 属性做作用域插槽
<slot-example>
<span slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</span>
</slot-example>
这里和 vue3.0 的区别在于,自定义内容不是必须被
<template>包裹,而是可以是任意元素,还可以用 ES5 解构
<slot-example>
<span slot="default" slot-scope="{ msg }">
{{ msg }}
</span>
</slot-example>