单文件组件
vue2 单文件组件基本写法
参考:https://cn.vuejs.org/v2/style-guide/index.html
<template>
<div>嘻嘻</div>
<!-- v-if v-for -->
<ul v-if="shouldShowUsers">
<li v-for="user in users" :key="user.id">
<!-- 插值 -->
{{ user.name }}
</li>
</ul>
<!-- v-else-if v-else -->
<div v-else-if="shouldShowMe" />
<div v-else />
<!-- 事件注册 -->
<input :value="value" @input="$emit('input', $event.target.value)"
@click="openModal">
<!-- 使用子组件的几种方式 -->
<the-modal>
<component is="TheInput" />
<component :is="'TheDropdown'" />
<TheButton :some-prop="prop">CTA</TheButton>
</the-modal>
<!-- 几种样式的写法 -->
<div :class={ 'is-active': true, 'red': isRed }></div>
<div :class=['is-active', isRed ? 'red' : '' ]></div>
<div :style={ color: textColor, fontSize: '18px' }></div>
<div
:style=[{ color: textColor, fontSize: '18px' }, { fontWeight: '300' }]
></div>
</template>
<script>
import TheButton from 'components/TheButton.vue'
import TheModal from 'components/TheModal.vue'
import TheInput from 'components/TheInput.vue'
import TheDropdown from 'components/TheDropdown.vue'
// 私有方法
myPrivateFunction() {
// todo
}
export default {
// 组件名应该始终是多个单词的
name: "TodoItem",
// 注册使用的组件
components: {
TheButton,
TheModal,
TheInput,
TheDropdown,
},
// 注册局部指令
directives: {
waves,
// 定义一个指令
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
},
// 注册局部过滤器
filters: {
filterA
},
// mixin,注意其是个数组
mixins: [mixin],
// 继承其他组件
extends: CompParent,
// props详细定义
props: {
// 检测类型
height: Number,
// 默认值
info: {
type: Object,
default: () => {
return {
name: "fang",
};
},
},
// 检测类型 + 其他验证
age: {
type: Number,
default: 0,
required: true,
validator: function(value) {
return value >= 0;
},
},
},
// 组件的 data 必须是一个函数。
// data中不要出现计算属性,因为还未声明
data() {
return {
foo: "bar",
};
},
// 计算属性
computed: {
aDouble: function() {
return this.age + 2
},
aPlus: {
get: function() {
return this.age + 1
}
}
},
// watch监听,这里不能使用箭头函数,否则this指不到vue实例
watch: {
/* ✓ GOOD */
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
b: 'someMethod',
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
d: {
handler: 'someMethod',
immediate: true
},
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
'e.f': function (val, oldVal) { /* ... */ },
}
// 组件的方法
methods: {
// 触发事件
openModal() {
this.$emit('input', 'test')
},
publicMethod() {
// 调用私有方法
myPrivateFunction()
}
},
// 生命周期
// 在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。
beforeCreate() {},
// 生命周期
// 在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,
// 意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数
created() {},
// 生命周期
// 在挂载开始之前被调用:相关的 render 函数首次被调用。
beforeMount() {},
// 生命周期
// 实例被挂载后调用,不会保证所有的子组件也都被挂载完成,要确保则使用nextTick
mounted() {
this.$nextTick(function () {
// 仅在整个视图都被渲染之后才会运行的代码
})
},
// 生命周期
// 在数据发生改变后,DOM 被更新之前被调用
beforeUpdate() {
//这里适合在现有 DOM 将要被更新之前访问它,
//比如移除手动添加的事件监听器。
},
// 生命周期
// 在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
// 不会保证所有的子组件也都被重新渲染完毕
updated() {},
// 生命周期
// 实例销毁之前调用。在这一步,实例仍然完全可用
beforeDestroy() {},
// 生命周期
// 实例销毁后调用。该钩子被调用后,
// 对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,
// 所有的子实例也都被销毁。
destroyed() {},
};
</script>
<style scoped>
.do-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
overflow: hidden;
}
</style>
sync修饰符
通过语法糖达到“修改prop”的目的:
<template>
<div class="details">
<myComponent :show.sync='valueChild'></myComponent>
<button @click="changeValue">toggle</button>
</div>
</template>
<script>
import Vue from 'vue' //导入
//子组件
Vue.component('myComponent', {
template: `<div v-if="show">
<p>默认初始值是{{show}},所以是显示的</p>
<button @click.stop="closeDiv">关闭</button>
</div>`,
props:['show'],
methods: {
closeDiv() {
this.$emit('update:show', false); //触发 input 事件,并传入新值
}
}
})
//父组件
export default{
data(){
return{
valueChild:true,
}
},
methods:{
changeValue(){
this.valueChild = !this.valueChild
}
}
}
</script>
其本质:
<comp :foo.sync="bar"></comp>'
会被扩展为:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
slot透传
二次封装组件时经常会用到
<template>
<div class="do-infinite-scroll-list">
<virtual-list
v-bind="$attrs"
ref="list"
:style="styles"
v-on="$listeners"
>
<!-- 实现 slot 透传 -->
<template
v-for="slot in Object.entries($slots)"
:slot="slot[0]"
>
<slot :name="slot[0]" />
</template>
</virtual-list>
</div>
</template>
vue2使用@vue/composition-api的单文件组件
<template>
<div class="ui__vue_hotspot_hotspot"
:style="`top: ${positionTop}; left: ${positionLeft}; background-color: ${hotspotColor};`"
:class="isActive || interactivity === 'none' ? 'active' : ''"
@mouseenter="interactivity === 'hover' ? isActive=true : null"
@mouseleave="interactivity === 'hover' ? isActive=false : null"
@click="interactivity === 'click' ? toggleActive() : null">
<!-- message box -->
<div :style="`color:${textColor}`">
<div
class="ui__vue_hotspot_title"
:style="`
background: ${messageBoxColor};
opacity: ${opacity}`"
>
{{ hotspot['Title'] }}
</div>
<div
class="ui__vue_hotspot_message"
:style="`
background: ${messageBoxColor};
opacity: ${opacity}`"
>
{{ hotspot['Message'] }}
</div>
</div>
</div>
</template>
<script>
import { throttle } from '../utils/common.js'
import {
createComponent,
ref,
reactive,
toRefs,
onMounted,
onUnmounted,
computed,
watch
} from '@vue/composition-api'
export default createComponent({
// 在使用Composition API时,props的定义不需要指定default值,
// setup 函数默认会等待 props 解析完成之后才会调用,所以props参数会是一个已经解析完的对象
props: {
hotspot: Object,
config: Object,
imageLoaded: Boolean,
vueHotspotBackgroundImage: HTMLImageElement,
vueHotspot: HTMLDivElement
},
setup (props, { emit }) {
const isActive = ref(false)
const conf = reactive({
positionTop: 0,
positionLeft: 0,
hotspotColor: computed(() => props.config && props.config.hotspotColor),
interactivity: computed(() => props.config && props.config.interactivity),
textColor: computed(() => props.config && props.config.textColor),
messageBoxColor: computed(() => props.config && props.config.messageBoxColor),
opacity: computed(() => props.config && props.config.opacity)
})
watch(() => props.imageLoaded, (loaded, prev) => {
if (loaded) {
getHotspotStyle()
}
})
onMounted(() => {
window.addEventListener('resize', throttle(getHotspotStyle, 50))
})
onUnmounted(() => {
window.removeEventListener('resize', throttle(getHotspotStyle, 50))
})
function getHotspotStyle () {
conf.positionTop = `${(props.hotspot.y * props.vueHotspotBackgroundImage.clientHeight / 100) + (props.vueHotspotBackgroundImage.offsetTop - props.vueHotspot.clientTop)}px;`
conf.positionLeft = `${(props.hotspot.x * props.vueHotspotBackgroundImage.clientWidth / 100) + (props.vueHotspotBackgroundImage.offsetLeft - props.vueHotspot.clientLeft)}px;`
}
function toggleActive () {
isActive.value = !isActive.value
}
return {
// data
isActive,
// reactive 返回的是一个Proxy对象,而模板中直接使用会有问题,
// toRefs 可以将一个 reactive 对象转换成普通对象,其中每一个属性都转换成一个 ref,从而保持响应性
...toRefs(conf),
// methods
getHotspotStyle,
toggleActive
}
}
})
</script>
对标下vue2的option版本:
export default {
props: {
hotspot: Object,
config: Object,
imageLoaded: Boolean
},
data() {
return {
isActive: false,
positionTop: 0,
positionLeft: 0,
// ...其他状态数据
}
},
computed: {
hotspotColor() {
return this.config.hotspotColor
},
// ...其他计算属性
},
watch: {
imageLoaded(loaded) {
if (loaded) {
this.getHotspotStyle()
}
}
},
methods: {
getHotspotStyle() {
// 计算热点位置的方法
},
toggleActive() {
this.isActive = !this.isActive
}
},
mounted() {
window.addEventListener('resize', _.throttle(this.getHotspotStyle, 50))
},
beforeDestroy() {
window.removeEventListener('resize', this.getHotspotStyle)
}
}
这里有个一个完整的官方的vue2组件 改为vue3-非setupscript和vue3-setupscript的最佳实践