Vue.js 实用技巧
Posted 前端之巅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue.js 实用技巧相关的知识,希望对你有一定的参考价值。
vue-cli 是官方推出的一款 Vue 脚手架工具它可以很方便的搭建简单或者完整的 Vue 开发环境。最近它加入了一个新功能 —— vue build。
类似于 React 的 next.js 但又不完全等同。它的功能是可以让你不用任何配置,只写一个\*.vue
文件就可以通过 vue build
命令启动。非常适合于只想写一点 Vue 的 demo 或者小型项目的用户使用。如果你已经安装了 vue-cli 并更新到 2.8.0,那你现在就可以开始体验!
这是一个例子,创建一个 app.vue
文件并随意写些内容:
<template>
<h1>
Hello <span>Vue.js</span>
</h1>
</template>
<style>
h1 {
font-family: 'Helvetica Neue';
color: #2c3e50;
text-align: center;
margin-top: 30vh;
}
span {
color: #42b983;
}
</style>
在终端里输入 vue build app.vue
并打开 就可以预览到结果了。
vue build app.vue
> WAIT Compiling...
> DONE Compiled successfully in 110ms
> Open http://localhost:4000
当然如果你想自定义配置也是支持的,具体内容可以参考 vue build 文档(
vue-loader 是处理 \*.vue
文件的 webpack loader。它本身提供了丰富的 API,有些 API 很实用但很少被人熟知。例如接下来要介绍的 preserveWhitespace 和 transformToRequire。
有些时候我们在写模板时不想让元素和元素之间有空格,可能会写成这样:
<ul>
<li>1111</li><li>2222</li><li>333</li>
</ul>
当然还有其他方式,目的是为了去掉元素间的空格。其实我们完全可以通过配置 vue-loader 实现这一需求。
{
vue: {
preserveWhitespace: false
}
}
它的作用是阻止元素间生成空白内容,在 Vue 模板编译后使用 v(" ")
表示。如果项目中模板内容多的话,它们还是会占用一些文件体积的。例如 Element( 配置该属性后,未压缩情况下文件体积减少了近 30Kb。
以前在写 Vue 的时候经常会写到这样的代码:把图片提前 require 传给一个变量再传给组件。
<template>
<div>
<avatar :default-src="DEFAULT_AVATAR"></avatar>
</div>
</template>
<script>
export default {
created () {
this.DEFAULT_AVATAR = require('./assets/default-avatar.png')
}
}
</script>
其实通过配置 transformToRequire 后,就可以直接配置。
{
vue: {
transformToRequire: {
avatar: ['default-src']
}
}
}
于是我们代码就可以简化不少:
<template>
<div>
<avatar default-src="./assets/default-avatar.png"></avatar>
</div>
</template>
vue-loader 还有很多实用的 API 例如最近加入的Custom Blocks( ),感兴趣的各位可以去文档里找找看。
在写 Vue 模板时,有时会遇到不得不手写 Render Function 的情况。如需要根据 prop
更改布局——Element 分页组件( ) ——或者根据 prop
判断生成指定标签。
比如我们想实现 Element 里的 input 组件的用法:
<field label="标题" type="input" />
<field label="内容" type="textarea" />
会渲染出一个 input 和 textarea :
那么我们用 Vue 模板写就需要根据 type
判断当前渲染哪个组件。
<template>
<div>
<label>{{ label }}</label>
<input v-if="type !== 'textarea'" :type="type">
<textarea v-else></textarea>
</div>
</template>
<script>
export default {
name: 'field',
props: ['type', 'label']
}
</script>
如果我们还需要传原生组件上的属性,例如 placeholder
, name
, disabled
以及各种校验属性和事件,那么重复代码就会非常多。但是如果我们用 jsx 写就会容易许多且代码也会更清晰。
export default {
name: 'field',
props: ['type', 'label'],
render (h) {
const tag = this.type === 'textarea' ? 'textarea' : 'input'
const type = this.type === 'textarea' ? '' : this.type
return (
<div>
<label>{ this.label }</label>
{ h(tag, { props: { type } }) }
</div>
)
}
}
可是如果组件再复杂一些,需要加入表单验证的错误提示或者一些 icon
等内容,那么写模板就比写 Render Function 更容易阅读。那我们是否可以将两种方式结合起来?
在 Vue 里有一个强大的特性:Slots —— 给组件设置一个特殊的 slot
组件,让使用者可以传入自定义的模板内容。但是在实际使用中,我发现其实是可以给 slot
赋值的。还是用上面的例子,假设 label
部分我们想写成模板,input
的部分根据 type
生成特性的内容。那么我们模板可以写成:
<template>
<div>
<label>{{ label }}</label>
<slot></slot>
</div>
</template>
input
部分用 slot
代替,但是并不是让使用者自己定义,而是我们给这个 slot
赋值:
<script>
export default {
name: 'field',
props: ['type', 'label'],
created() {
this.$slots.default = [ this.renderField() ]
},
methods: {
renderField() {
const h = this.$createElement
const tag = this.type === 'textarea' ? 'textarea' : 'input'
const type = this.type === 'textarea' ? '' : this.type
return h(tag, { props: { type } })
}
}
}
</script>
其中 renderField
就是我们手写的 Render Function,在组件 created 时调用并赋值给 this.$slots.default
。意思就是我们用手写的 vnode 强制替换掉了 $slots.default
的 vnode,从而达到 vue template 和 Render Function 结合的目的。
但是这有个问题,这么做我们就破坏了 slot
的更新规则。看过源码可以知道 slots
是在[父组件 的 vdom 更新时才更新的 slots ( renderField
方法,除非用 watch,但是需要 watch 的 prop 多的话也很麻烦。
所以如果你只是需要在初始化时(created)用这种方式渲染组件,并且明白它的限制,其实还是可以发挥很大的用处的。例如 Element 的 notification,通过传入一段内容可以显示一个消息提醒。其实我们还可以传入 vdom 来显示一段自定义内容,在线例子(
const h = this.$createElement
this.$notify({
title: 'GitHub',
message: h('div', [
h('p', '[GitHub] Subscribed to ElemeFE/element notifications'),
h('el-button', {}, '已读')
])
})
希望你喜欢这一期的分享,后面我们还会连载一些 Vue 实用技巧,下期见!
今日荐文
前端每周清单第3期:Instant App将至,WebAssembly将获默认支持,PWA实践渐增
是由InfoQ主办的全球顶级技术盛会,关注移动、前端、跨平台、AI应用等多个技术领域、促进全球技术交流,推动国内技术升级。GMTC为期两天,面向移动开发、前端、AI技术人员,聚焦前沿技术及实践经验,打造技术人员的学习和交流平台。前往官网了解详细信息:
以上是关于Vue.js 实用技巧的主要内容,如果未能解决你的问题,请参考以下文章