Vue进阶学习

150 阅读1分钟

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);
},

输出结果:

2025-05-22-23-16-17-image.png

作用域插槽

外部做结构的描述,内部做传参

  • 子组件向父组件传递数据
  • 子组件向父组件传递数据,父组件决定样式结构
  • 子组件向父组件传递数据,父组件决定样式结构,子组件决定位置

面试点:传参传递,父组件决定样式结构,子组件决定位置

// 父组件
<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

  1. watchcomputedmethods
  • watch:监听数据变化,变化时执行回调函数。(监听结果,一对多)
  • computed:计算属性,根据依赖的数据计算新的值,依赖的数据变化时,计算属性会重新计算。(监听方式,多对一)
  • methods:方法,可以执行一些操作,但是不会缓存结果

使用上:watch注重流程,computed注重结果,methods注重过程

原理上:watch是对劫持的数据进行观察监听数据变化以触发相应的回调,computed是计算属性,对依赖项进行数据劫持触发计算回调,methods是方法

  • watch: 是主动请求,有 once|deep|immediate 方法

watch、computed 的区别

  • watch:监听数据变化,变化时执行回调函数,可以执行异步操作,但是不能缓存结果
  • computed:计算属性,根据依赖的数据计算新的值,依赖的数据变化时,计算属性会重新计算,可以缓存结果
  1. 其他

方案一:

  • 函数: {{ 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-moneyv-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. 特征:组件化

  1. 抽象复用
  2. 精简 & 聚合
  3. 渲染顺序:父组件先渲染created,子组件后渲染created,子组件先挂载mounted,父组件后挂载mounted

混入 mixin - 逻辑混入

  1. 应用:抽离公共逻辑(逻辑相同并且重复可拆离) Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
    • 对比extendsextends是核心逻辑继承,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>
  1. 合并策略 mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
    • 生命周期mixin存在与组件之前
    • mixin存在的场景, mixin的钩子函数会按照传入顺序依次调用加载,并在调用组件自身的钩子之前被调用
    • 变量的补充:不会覆盖组件的变量,而是会进行合并
  • 不建议在mixin内套用mixin,容易造成混乱
  • mixin 中的 data、methods、computed 等属性可能会与组件本身发生冲突。Vue 会以组件自身为优先,但这种行为容易引发维护困难,应避免使用同名字段。

3. 特征:插件补充拓展

  1. 插件:对 Vue 进行功能扩展
  2. 使用:Vue.use(插件)
  3. 插件:Vue.use的参数必须是一个函数或者一个对象,如果是对象,必须提供install方法
  4. 插件:插件函数接受一个参数: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>