其他
25 个 Vue 技巧,开发了 5 年了,才知道还能这么用
The following article is from 大迁世界 Author 前端小智
作者 | 前端小智
1. 将一个 prop 限制在一个类型的列表中
export default {
name: 'Image',
props: {
src: {
type: String,
},
style: {
type: String,
validator: s => ['square', 'rounded'].includes(s)
}
}
};
2. 默认内容和扩展点
<button class="button" @click="$emit('click')">
<slot>
<!-- Used if no slot is provided -->
Click me
</slot>
</button>
<template>
<button class="button" @click="$emit('click')">
<slot>
<div class="formatting">
{{ text }}
</div>
</slot>
</button>
</template>
<!-- Uses default functionality of the component -->
<ButtonWithExtensionPoint text="Formatted text" />
<ButtonWithExtensionPoint>
<div class="different-formatting">
Do something a little different here
</div>
</ButtonWithExtensionPoint>
3. 使用引号来监听嵌套属性
watch {
'$route.query.id'() {
// ...
}
}
4. 知道何时使用v-if(以及何时避免使用)
<ComplicatedChart v-show="chartEnabled" />
5. 单个作用域插槽的简写(不需要 template 标签)
<DataTable>
<template #header="tableAttributes">
<TableHeader v-bind="tableAttributes" />
</template>
</DataTable>
<DataTable #header="tableAttributes">
<TableHeader v-bind="tableAttributes" />
</DataTable>
6. 有条件地渲染插槽
const $slots = {
default: <default slot>,
icon: <icon slot>,
button: <button slot>,
};
<!-- Slots.vue -->
<template>
<div>
<h2>Here are some slots</h2>
<slot />
<slot name="second" />
<slot name="third" />
</div>
</template>
<template>
<Slots>
<template #second>
This will be applied to the second slot.
</template>
</Slots>
</template>
$slots = { second: <vnode> }
<template>
<div>
<h2>A wrapped slot</h2>
<div v-if="$slots.default" class="styles">
<slot />
</div>
</div>
</template>
那么,为什么我们希望能够有条件地渲染插槽呢?
当使用封装的div来添加默认样式时 插槽是空的 如果我们将默认内容与嵌套槽相结合
<template>
<div>
<h2>This is a pretty great component, amirite?</h2>
<div class="default-styling">
<slot >
</div>
<button @click="$emit('click')">Click me!</button>
</div>
</template>
<div>
<h2>This is a pretty great component, amirite?</h2>
<div class="default-styling">
<!-- 槽中没有内容,但这个div 仍然被渲染。糟糕 -->
</div>
<button @click="$emit('click')">Click me!</button>
</div>
7. 如何监听一个插槽的变化
<!-- 可惜这个事件不存在 -->
<slot @change="update" />
MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。
export default {
mounted() {
// 当有变化时调用`update`
const observer = new MutationObserver(this.update);
// 监听此组件的变化
observer.observe(this.$el, {
childList: true,
subtree: true
});
}
};
8. 将局部和全局的style混合在一起
<style scoped>
.component {
background: green;
}
</style>
<style>
/* 全局 */
.component p {
margin-bottom: 16px;
}
</style>
<style scoped>
/* 在该组件内有效 */
.component {
background: green;
}
</style>
9. 重写子组件的样式--正确的方法
<style scoped>
.my-component >>> .child-component {
font-size: 24px;
}
</style>
10. 用上下文感知组件创造魔法
1.状态共享
<!-- 为简单起见,作为一个单一组件使用 -->
<Dropdown v-model="selected" :options="[]" />
<!-- 分多个组件,更灵活 -->
<Select v-model="selected">
<Option value="mustard">Mustard</Option>
<Option value="ketchup">Ketchup</Option>
<div class="relish-wrapper">
<Option value="relish">Relish</Option>
</div>
</Select>
2. Configuration
3.样式
.statistic {
color: black;
font-size: 24px;
font-weight: bold;
}
.statistic + .statistic {
margin-left: 10px;
}
11. 如何在Vue之外创建一个具有响应性的变量(Vue2和3)
const externalVariable = getValue();
export default {
data() {
return {
reactiveVariable: externalVariable,
};
}
};
import { ref } from 'vue';
// 可以完全在Vue组件之外完成
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable);
console.log(reactiveVariable.value);
import { reactive } from 'vue';
// 可以完全在Vue组件之外完成
const externalVariable = getValue();
// reactive 只对对象和数组起作用
const anotherReactiveVariable = reactive(externalVariable);
// Access directly
console.log(anotherReactiveVariable);
12. v-for 中的解构
<li
v-for="{ name, id } in users"
:key="id"
>
{{ name }}
</li>
<li v-for="(movie, index) in [
'Lion King',
'Frozen',
'The Princess Bride'
]">
{{ index + 1 }} - {{ movie }}
</li>
<li v-for="(value, key) in {
name: 'Lion King',
released: 2019,
director: 'Jon Favreau',
}">
{{ key }}: {{ value }}
</li>
<li v-for="(value, key, index) in {
name: 'Lion King',
released: 2019,
director: 'Jon Favreau',
}">
#{{ index + 1 }}. {{ key }}: {{ value }}
</li>
13. 在指定范围内循环
<template>
<ul>
<li v-for="n in 5">Item #{{ n }}</li>
</ul>
</template>
Item #1
Item #2
Item #3
Item #4
Item #5
14. 监听你的组件中的任何东西
export default {
computed: {
someComputedProperty() {
// Update the computed prop
},
},
watch: {
someComputedProperty() {
// Do something when the computed prop is updated
}
}
};
计算属性 props 嵌套值
15.窃取 prop 类型
<template>
<div>
<h2>{{ heading }}</h2>
<Icon
:type="iconType"
:size="iconSize"
:colour="iconColour"
/>
</div>
</template>
import Icon from './Icon';
export default {
components: { Icon },
props: {
iconType: {
type: String,
required: true,
},
iconSize: {
type: String,
default: 'medium',
validator: size => [
'small',
'medium',
'large',
'x-large'
].includes(size),
},
iconColour: {
type: String,
default: 'black',
},
heading: {
type: String,
required: true,
},
},
};
import Icon from './Icon';
export default {
components: { Icon },
props: {
...Icon.props,
heading: {
type: String,
required: true,
},
},
};
import Icon from './Icon';
const iconProps = {};
Object.entries(Icon.props).forEach((key, val) => {
iconProps[`icon${key.toUpperCase()}`] = val;
});
export default {
components: { Icon },
props: {
...iconProps,
heading: {
type: String,
required: true,
},
},
};
16. 检测元素外部(或内部)的单击
window.addEventListener('mousedown', e => {
// 获取被点击的元素
const clickedEl = e.target;
if (el.contains(clickedEl)) {
//在 "el "里面点击了
} else {
//在 "el "外点击了
}
});
17. 递归插槽
<!-- VFor.vue -->
<template>
<div>
<!-- 渲染第一项 -->
{{ list[0] }}
<!-- 如果我们有更多的项目,继续!但是不要使用我们刚刚渲染的项 -->
<v-for
v-if="list.length > 1"
:list="list.slice(1)"
/>
</div>
</template>
<template>
<div>
<!-- Pass the item into the slot to be rendered -->
<slot v-bind:item="list[0]">
<!-- Default -->
{{ list[0] }}
</slot>
<v-for
v-if="list.length > 1"
:list="list.slice(1)"
>
<!-- Recursively pass down scoped slot -->
<template v-slot="{ item }">
<slot v-bind:item="item" />
</template>
</v-for>
</div>
</template>
<template>
<div>
<!-- 常规列表 -->
<v-for :list="list" />
<!-- 加粗的项目列表 -->
<v-for :list="list">
<template v-slot="{ item }">
<strong>{{ item }}</strong>
</template>
</v-for>
</div>
</template>
18. 组件元数据
export default {
name: 'LiveUsersWidget',
// 👇 只需将其作为一个额外的属性添加
columns: 3,
props: {
// ...
},
data() {
return {
//...
};
},
};
export default {
name: 'LiveUsersWidget',
// 👇 只需将其作为一个额外的属性添加
columns: 3,
props: {
// ...
},
data() {
return {
//...
};
},
};
import LiveUsersWidget from './LiveUsersWidget.vue';
const { columns } = LiveUsersWidget;
export default {
name: 'LiveUsersWidget',
columns: 3,
created() {
// 👇 `$options` contains all the metadata for a component
console.log(`Using ${this.$options.metadata} columns`);
},
};
保持单个组件的版本号 用于构建工具的自定义标志,以区别对待组件 在计算属性、数据、watch 等之外为组件添加自定义功能 其它
19. 多文件单文件组件
<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>
20. 可重复使用的组件并不是你所想的那样
<!-- OverflowMenu.vue -->
<template>
<Menu>
<!-- 添加一个自定义按钮来触发我们的菜单 -->
<template #button v-slot="bind">
<!-- 使用bind来传递click处理程序、a11y 属性等 -->
<Button v-bind="bind">
<template #icon>
<svg src="./ellipsis.svg" />
</template>
</Button>
</template>
</Menu>
</template>
21. 从组件外部调用一个方法
<!-- Parent.vue -->
<template>
<ChildComponent ref="child" />
</template>
// Somewhere in Parent.vue
this.$refs.child.method();
<template>
<ChildComponent
:tell-me-what-to-do="someInstructions"
@something-happened="hereIWillHelpYouWithThat"
/>
</template>
// Child.vue
export default {
props: ['trigger'],
watch: {
shouldCallMethod(newVal) {
if (newVal) {
// Call the method when the trigger is set to `true`
this.method();
}
}
}
}
父组件将 true 传递给触发器 prop Watch 被触发,然后 Child 组件调用该方法 子组件发出一个事件,告诉父组件该方法已被成功触发 Parent组件将 trigger 重置为 false,所以我们可以从头再来一次
<!-- Parent.vue -->
<template>
<ChildComponent ref="child" />
</template>
// Somewhere in Parent.vue
this.$refs.child.method();
22. 监听数组和对象
export default {
name: 'ColourChange',
props: {
colours: {
type: Array,
required: true,
},
},
watch: {
// 使用对象语法,而不仅仅是方法
colours: {
// 这将让Vue知道要在数组内部寻找
deep: true,
handler()
console.log('The list of colours has changed!');
}
}
}
}
watch(
colours,
() => {
console.log('The list of colours has changed!');
},
{
deep: true,
}
);
23. 用Vue Router进行深度链接
someurl.com/edit?date-range=last-week
const dateRange = this.$route.query.dateRange;
<RouterLink :to="{
query: {
dateRange: newDateRange
}
}">
24. template 标签的另一个用途
<template>
<div class="card">
<img src="imgPath" />
<h3>
{{ title }}
</h3>
<h4 v-if="expanded">
{{ subheading }}
</h4>
<div
v-if="expanded"
class="card-content"
>
<slot />
</div>
<SocialShare v-if="expanded" />
</div>
</template>
<template>
<div class="card">
<img src="imgPath" />
<h3>
{{ title }}
</h3>
<template v-if="expanded">
<h4>
{{ subheading }}
</h4>
<div class="card-content">
<slot />
</div>
<SocialShare />
</template>
</div>
</template>
25. 处理错误(和警告)的更好方法
// Vue 3
const app = createApp(App);
app.config.errorHandler = (err) => {
alert(err);
};
// Vue 2
Vue.config.errorHandler = (err) => {
alert(err);
};
脚本之家官方视频号
推荐阅读:
Vue 中使用defineAsyncComponent 延迟加载组件
vue自定义指令实现一个可拖拽对话框(el-dialog专用)
手拉手带你用 vite2 + vue3 + elementPlus 做个博客尝尝鲜