Vue 进阶
1. 特征: 模板化
挑战: 动态化节点不易于维护
- 条件判断
- 组件化
- 动态组件
- 结构体
插槽 slot
- 允许父组件向子组件插入内容 由父组件决定内容,子组件决定样式结构。即:组件外部维护参数以及结构,内部安排位置
// 父组件
<template>
<div class="home">
<SlotComponents>
<template>
<p>{{ msg }}</p>
</template>
<template #header>
<h1>Header - 插槽 header 传递</h1>
</template>
<template v-slot:footer>
<h1>Footer - 插槽 footer v-slot:footer 传递</h1>
</template>
</SlotComponents>
</div>
</template>
<script>
import SlotComponents from "@/components/Slot.vue";
export default {
name: "HomeView",
components: {
SlotComponents,
},
data() {
return {
msg: "Hello Slot",
};
},
};
</script>
// 子组件
<template>
<div>
<h1>Slot 插槽组件</h1>
<slot></slot>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
-
面试点:默认插槽的实现方式?整体插件的默认聚合
-
多个插槽,如何区分?
-
多个插件会以多个节点的形式渲染,都会传递下去 默认插槽,通过
<slot></slot>实现,name属性用于区分其他插槽,存放在default属性中 -
多个插槽希望分开布局 | 出现在不同的地方 : 具名插槽
- 以
<template>标签包裹,name属性用于区分从而在组件内部做到可区分
- 以
// 父组件
<SlotComponents>
<template>
<p>{{ msg }}</p>
<p>{{ msg1 }}</p>
<p>{{ msg2 }}</p>
<p>{{ msg3 }}</p>
</template>
{/* 具名插槽 */}
<template #header>
<h1>Header - 插槽 header 传递</h1>
</template>
<template v-slot:footer>
<h1>Footer - 插槽 footer v-slot:footer 传递</h1>
</template>
</SlotComponents>
// 子组件
<template>
<div>
<h1>Slot 插槽组件</h1>
<slot></slot>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
mounted() {
console.log("Slot mounted");
console.log(this.$slots);
},
输出结果:
作用域插槽
外部做结构的描述,内部做传参
- 子组件向父组件传递数据
- 子组件向父组件传递数据,父组件决定样式结构
- 子组件向父组件传递数据,父组件决定样式结构,子组件决定位置
面试点:传参传递,父组件决定样式结构,子组件决定位置
// 父组件
<template>
<div class="home">
<SlotComponents>
{/* 作用域插槽 */}
<template #content="{ slotScopeData }">
<p>{{ slotScopeData }}</p>
<p>{{ slotScopeData.name }}</p>
<p>{{ slotScopeData.age }}</p>
</template>
</SlotComponents>
</div>
</template>
<script>
import SlotComponents from "@/components/Slot.vue";
export default {
name: "HomeView",
components: {
SlotComponents,
},
};
</script>
// 子组件
<template>
<div>
<h1>Slot 插槽组件</h1>
<slot name="content" :slotScopeData="slotScopeData"></slot>
</div>
</template>
<script>
export default {
name: "SlotComponents",
data() {
return {
slotScopeData: {
name: "张三",
age: 18,
msg: "slotScope data",
info: "slotScope info",
},
};
},
};
</script>
动态组件
- 动态组件:根据条件渲染不同的组件,组件之间切换时,组件不会被销毁和重新创建
- 动态组件:
<component :is="componentsType"></component>,componentsType为组件名,由is决定渲染哪个组件
- 动态组件:
<template>
<div class="home">
<component :is="componentsType"></component>
</div>
</template>
<script>
import SlotComponents from "@/components/Slot.vue";
import SlotComponents1 from "@/components/Slot1.vue";
export default {
name: "HomeView",
components: {
SlotComponents,
SlotComponents1,
},
data() {
return {
componentsType: 'SlotComponents',
};
},
};
</script>
模板的二次加工方案
watch、computed、methods
watch,computed,methods
- watch:监听数据变化,变化时执行回调函数。(监听结果,一对多)
- computed:计算属性,根据依赖的数据计算新的值,依赖的数据变化时,计算属性会重新计算。(监听方式,多对一)
- methods:方法,可以执行一些操作,但是不会缓存结果
使用上:watch注重流程,computed注重结果,methods注重过程
原理上:watch是对劫持的数据进行观察监听数据变化以触发相应的回调,computed是计算属性,对依赖项进行数据劫持触发计算回调,methods是方法
- watch: 是主动请求,有
once|deep|immediate方法
watch、computed 的区别
- watch:监听数据变化,变化时执行回调函数,可以执行异步操作,但是不能缓存结果
- computed:计算属性,根据依赖的数据计算新的值,依赖的数据变化时,计算属性会重新计算,可以缓存结果
- 其他
方案一:
- 函数:
{{ calcAdd(num) }} - 管道符:
{{ num | filterAdd }}
<template>
<div class="home">
<!-- 过滤器 -->
<span>{{ money | moneyFilter }}</span> <!-- 99元 -->
<span>{{ money_0 | moneyFilter }}</span> <!-- 0元 -->
<button @click="money -= 10">减十</button>
</div>
</template>
<script>
export default {
name: "HomeView",
data() {
return {
money: 99,
money_0: -1,
};
},
filters: {
// 过滤器
moneyFilter(value) {
const money = value < 0 ? 0 : value;
return money + "元";
},
},
};
</script>
方案二:指令(自定义指令)
- 指令:
v-money,v-money-0
<template>
<div class="home">
<!-- 指令 -->
<span v-money="money"></span> <!-- 99元 -->
<span v-money-0="money_0"></span> <!-- 0元 -->
<button @click="money -= 10">减十</button>
</div>
</template>
<script>
export default {
name: "HomeView",
data() {
return {
money: 99,
money_0: -1,
};
},
};
</script>
// 指令
// money指令
Vue.directive("money", {
// 指令的定义
inserted: function (el, binding) {
const money = binding.value < 0 ? 0 : binding.value;
el.innerText = money + "元";
},
});
// money-0指令
Vue.directive("money-0", {
// 指令的定义
inserted: function (el, binding) {
const money = binding.value < 0 ? 0 : binding.value;
el.innerText = money + "元";
},
});
方案三:直给
{{ ...三目运算符, js逻辑, 数据计算 }}
jsx 语法
更自由的 all in js
- JSX:JavaScript XML,一种 JavaScript 的语法扩展,允许在 JavaScript 代码中写类似 XML 的代码
- 可以用来做更加灵活且 js 化的方案,并没有拒绝去模板化
// jsx 组件
<script>
export default {
name: "JsxTest",
data() {
return {
options: [
{ value: "option1", label: "选项1" },
{ value: "option2", label: "选项2" },
{ value: "option3", label: "选项3" },
],
money: 99,
};
},
render(h) {
// 自定义节点,替代过滤器写法
const moneyNode = <span>{this.money < 0 ? "0元" : this.money + "元"}</span>;
return (
<ul>
{this.options.map((options) => {
return (
<li item={options} onclick={this.handleClick}>
{options.label}-{moneyNode}
<hr />
</li>
);
})}
</ul>
);
},
methods: {
handleClick() {
console.log("Button clicked");
},
},
};
</script>
// 父组件像引入正常写法的组件一样引入使用
<template>
<div class="home">
<JsxTest />
</div>
</template>
<script>
import JsxTest from "@/components/JsxTest.vue";
export default {
name: "HomeView",
components: {
JsxTest,
},
};
</script>
// 页面正常显示
2. 特征:组件化
- 抽象复用
- 精简 & 聚合
- 渲染顺序:父组件先渲染
created,子组件后渲染created,子组件先挂载mounted,父组件后挂载mounted
混入 mixin - 逻辑混入
- 应用:抽离公共逻辑(逻辑相同并且重复可拆离)
Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
- 对比
extends:extends是核心逻辑继承,mixins是混入,extends是继承一个组件,mixins是混入多个组件
- 对比
// mixin.js
export default {
data() {
return {
mixinData: "mixinData",
};
},
created() {
console.log("mixin created");
},
methods: {
mixinMethod() {
console.log("mixin method");
},
},
};
// 组件
<template>
<div class="home">
<span>{{ mixinData }}</span>
<button @click="mixinMethod">mixinMethod</button>
</div>
</template>
<script>
import mixin from "@/mixin.js";
export default {
name: "HomeView",
mixins: [mixin], // 混入
}
</script>
- 合并策略
mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
- 生命周期
mixin存在与组件之前 - 多
mixin存在的场景,mixin的钩子函数会按照传入顺序依次调用加载,并在调用组件自身的钩子之前被调用 - 变量的补充:不会覆盖组件的变量,而是会进行合并
- 生命周期
- 不建议在
mixin内套用mixin,容易造成混乱 - mixin 中的 data、methods、computed 等属性可能会与组件本身发生冲突。Vue 会以组件自身为优先,但这种行为容易引发维护困难,应避免使用同名字段。
3. 特征:插件补充拓展
- 插件:对 Vue 进行功能扩展
- 使用:
Vue.use(插件) - 插件:
Vue.use的参数必须是一个函数或者一个对象,如果是对象,必须提供install方法 - 插件:插件函数接受一个参数:Vue 构造器。需要在
install方法中给Vue原型添加功能
// 插件
// 插件函数
const plugin = {
install(Vue) {
// 给 Vue 添加原型方法
Vue.prototype.$myMethod = function () {
console.log("myMethod");
};
},
};
// 使用插件
// main.js
import Vue from "vue";
import App from "./App.vue";
import plugin from "./plugin.js";
Vue.config.productionTip = false;
// 使用插件
Vue.use(plugin);
new Vue({
render: (h) => h(App),
}).$mount("#app");
// 组件
<template>
<div class="home">
<button @click="myMethod">myMethod</button>
</div>
</template>
<script>
export default {
name: "HomeView",
methods: {
myMethod() {
this.$myMethod();
},
},
};
</script>