项目创建
vue -- version的版本要大于4.5.0
css库
安装最新版本@next
cnpm install bootstrap@next --save
interface 定义数据,props断言
子组件
ColumnList.vue
export interface ColumnProps {
id: number;
title: string;
avatar?: string;
description: string;
}
props: {
list: {
type: Array as PropType<ColumnProps[]>,
require: true,
},
},
setup(props: any) {
return {
list,
};
},
父组件
<column-list :list='list'></column-list>
import ColumnList, { ColumnProps } from "./components/ColumnList.vue";
const testData: ColumnProps[] = [
{
id: 1,
title: "test1的专栏",
description: "这是的test1专栏,有一段非常有意思的简介,可以更新一下欧",
avatar:
"http://vue-maker.oss-cn-hangzhou.aliyuncs.com/vue-marker/5ee22dd58b3c4520912b9470.jpg?x-oss-process=image/resize,m_pad,h_100,w_100",
},
{
id: 2,
title: "test2的专栏",
description: "这是的test2专栏,有一段非常有意思的简介,可以更新一下欧",
avatar:
"http://vue-maker.oss-cn-hangzhou.aliyuncs.com/vue-marker/5ee22dd58b3c4520912b9470.jpg?x-oss-process=image/resize,m_pad,h_100,w_100",
},
];
setup() {
return {
list: testData,
};
},
点击下拉框外面,隐藏下拉框
原始写法
<div class="dropdown"
ref="dropdownRef">
</div>
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
setup() {
const dropdownRef = ref<null | HTMLElement>(null);
const handler = (e: MouseEvent) => {
console.log("dropdownRef.value", dropdownRef.value);
console.log("e.target", e.target);
if (dropdownRef.value) {
if (
!dropdownRef.value.contains(e.target as HTMLElement) &&
isOpen.value
) {
isOpen.value = false;
}
}
};
onMounted(() => {
document.addEventListener("click", handler);
});
onUnmounted(() => {
document.removeEventListener("click", handler);
});
return {
dropdownRef, //返回和 ref 同名的响应式对象,就可以拿到对应的 dom 节点
};
},
dropdownRef.value是下拉框
e.target是鼠标点击的html
hook写法(自定义函数)
//hooks/useClickOutside.ts
import { ref, onMounted, onUnmounted, Ref } from 'vue'
const useClickOutside =(elementRef:Ref<null | HTMLElement>) => {
const isClickOutside = ref(false)
const handler = (e: MouseEvent) => {
if (elementRef.value) {
if (elementRef.value.contains(e.target as HTMLElement)) {
isClickOutside.value = false
} else {
isClickOutside.value = true
}
}
};
onMounted(() => {
document.addEventListener('click', handler)
})
onUnmounted(() => {
document.removeEventListener('click', handler)
})
return isClickOutside
}
export default useClickOutside
父组件调用
import { watch } from "vue";
import useClickOutside from "../hooks/useClickOutside";
setup() {
const dropdownRef = ref<null | HTMLElement>(null);
const isClickOutside = useClickOutside(dropdownRef);
watch(isClickOutside, () => {
if (isOpen.value && isClickOutside.value) {
isOpen.value = false;
}
});
const isOpen = ref(false);
return {
dropdownRef,
};
},
表单验证
父组件
<validate-input :rules="emailRules"
placeholder="请输入邮箱地址"
type="text">
</validate-input>
import ValidateInput, { RulesProp } from "./components/ValidateInput.vue";
const emailRules: RulesProp = [
{ type: "required", message: "电子邮箱地址不能为空" },
{ type: "email", message: "请输入正确的电子邮箱格式" },
];
子组件
<template>
<div class="validate-input-container pb-3">
<input type="text"
class="form-control"
:class="{'is-invalid': inputRef.error}"
:value='inputRef.val'
@blur="validateInput"
@input="updateValue"
v-bind="$attrs">
<span v-if="inputRef.error"
class="invalid-feedback">{{inputRef.message}}</span>
</div>
</template>
<script lang="ts">
/* eslint-disable */
import { defineComponent, reactive, PropType } from "vue";
const emailReg = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
interface RuleProp { //接口
type: "required" | "email";
message: string;
}
export type RulesProp = RuleProp[]; //type
export default defineComponent({
props: {
rules: Array as PropType<RulesProp>,
modelValue: String,
},
inheritAttrs: false, //其它属性
setup(props, context) {
const inputRef = reactive({
val: props.modelValue || "",
error: false,
message: "",
});
const validateInput = () => {
if (props.rules) {
const allPassed = props.rules.every((rule) => {
let passed = true;
inputRef.message = rule.message;
switch (rule.type) {
case "required":
passed = inputRef.val.trim() !== "";
break;
case "email":
passed = emailReg.test(inputRef.val);
break;
default:
break;
}
return passed;
});
inputRef.error = !allPassed;
}
};
const updateValue = (e: KeyboardEvent) => {
const targetValue = (e.target as HTMLInputElement).value;
inputRef.val = targetValue;
context.emit("update:modelValue", targetValue);
};
return {
inputRef,
validateInput,
updateValue,
};
},
});
</script>
jwt
jwt流程
声明一个方法,同时调用2个异步方法
Teleport
为什么我们需要 Teleport
Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术,就有点像哆啦A梦的“任意门”
场景:像 modals,toast 等这样的元素,很多情况下,我们将它完全的和我们的 Vue 应用的 DOM 完全剥离,管理起来反而会方便容易很多
原因在于如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难
另外,像 modals,toast 等这样的元素需要使用到 Vue 组件的状态(data 或者 props)的值
这就是 Teleport 派上用场的地方。我们可以在组件的逻辑位置写模板代码,这意味着我们可以使用组件的 data 或 props。然后在 Vue 应用的范围之外渲染它
api
拥有和components一样的prop,emit,slot,只不过多了一个to="#app",代表插入到id='app'的节点下
demo
teleport
// model.vue
<template>
<teleport to="#app">
<div class="model" v-if="isOpen">
<h2><slot>this is a modal</slot></h2>
<button @click="handleClose">Close</button>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
isOpen: Boolean
},
setup (props, context) {
const handleClose = () => {
context.emit('emitTeleportModelClose')
}
return {
handleClose
}
}
})
</script>
<style>
.model {
width: 200px;
height: 200px;
border: 2px solid black;
background: white;
position: fixed;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
}
</style>
调用teleport
<template>
<div>
<button @click="openModal">Open Modal</button><br />
<TeleportModel
:isOpen="modalIsOpen"
@emitTeleportModelClose="onTeleportModelClose"
>
My Modal !!!!</TeleportModel
>
</div>
</template>
<script lang="ts">
import TeleportModel from '@/components/teleport/model.vue'
export default defineComponent({
components: {
TeleportModel
},
setup (props) {
const modalIsOpen = ref(false)
const openModal = () => {
modalIsOpen.value = true
}
const onTeleportModelClose = () => {
modalIsOpen.value = false
}
return {
modalIsOpen,
openModal,
}
}
</script>
this.$el、reactive refs、template refs
2.x可以在组件挂载之后通过this.el没有存在的意义,建议通过refs访问DOM 当使用组合式 API 时,reactive refs 和 template refs 的概念已经是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup() 中声明一个 ref 并返回它
使用reactive refs和template refs
<template>
<div ref="root"></div>
</template>
<script>
import { ref, onMounted, getCurrentInstance } from 'vue'
export default {
setup() {
const vm = getCurrentInstance()
const root = ref(null)
onMounted(() => {
// 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
console.log(root.value) // <div/>
console.log(vm.refs.root) // <div/>
console.log(root.value === vm.refs.root) // true
})
return {
root
}
}
}
</script>
在v-for中使用
<template>
<div v-for="(item, i) in list" :key="i" :ref="el => { divs[i] = el }">
{{ item }}
</div>
</template>
<script>
import { ref, reactive, onBeforeUpdate } from 'vue'
export default {
setup() {
const list = reactive([1, 2, 3])
const divs = ref([])
// 确保在每次变更之前重置引用
onBeforeUpdate(() => {
divs.value = []
})
return {
list,
divs
}
}
}
</script>
获取模板内的属性和方法
<div ref="Upload"></div>
setup () {
const Upload: any = ref(null)
return {
Upload
}
}
Upload.value.属性
Upload.value.方法
hooks
下拉框展示隐藏,显示时,点击页面空白处,隐藏下拉框
hooks
import { ref, onMounted, onUnmounted, Ref } from 'vue'
const useClickOutside = (elementRef: Ref<null | HTMLElement>) => {
const isClickOutside = ref(false)
const handler = (e: MouseEvent) => {
if (elementRef.value) {
console.log('aa')
if (elementRef.value.contains(e.target as HTMLElement)) {
console.log('bb')
isClickOutside.value = false
} else {
console.log('cc')
isClickOutside.value = true
}
} else {
console.log('dd')
}
}
onMounted(() => {
document.addEventListener('click', handler)
})
onUnmounted(() => {
document.removeEventListener('click', handler)
})
return isClickOutside
}
export default useClickOutside
deopdown
<template>
<div ref="dropdownRef" class="dropdown-container">
<div @click.prevent="toggleOpen" class="container-show">
<i v-if="isOpen" class="el-icon-caret-top"></i>
<i v-else class="el-icon-caret-bottom"></i>
</div>
<div class="container-dialog" v-if="isOpen">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
import useClickOutside from '@/hooks/useClickOutside'
export default defineComponent({
props: {},
setup () {
const isOpen = ref<boolean>(false)
const dropdownRef = ref<null | HTMLElement>(null)
const toggleOpen = () => {
isOpen.value = !isOpen.value
}
const isClickOutside = useClickOutside(dropdownRef)
watch(isClickOutside, () => {
console.log('33')
if (isOpen.value && isClickOutside.value) {
console.log('11')
isOpen.value = false
isClickOutside.value = false
} else {
isClickOutside.value = true
console.log('22')
}
})
return {
isOpen,
toggleOpen,
dropdownRef
}
}
})
</script>
<style lang="scss" scoped>
.dropdown-container {
position: relative;
.container-show {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
}
.container-dialog {
position: absolute;
right: 0;
top: 50px;
z-index: 999;
}
}
</style>
组件调用
<Dropdown>
<div class="dropdown-ul">
</div>
</Dropdown>
slot
父组件
<Foo>
<template v-slot:header>
header1
</template>
<template v-slot:default>
main1
</template>
<template v-slot:footer>
header1
</template>
</Foo>
<Foo>
<template #header>
header2
</template>
<template #default>
main2
</template>
<template #footer>
header2
</template>
</Foo>
子组件
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
ele表单 自定义校验规则
setup (props, context) {
const ruleFormsss = ref(null)
const forceVersionCode = (rule: any, value: any, callback: any) => {
const isForce = state.ruleForm.isForce.toString()
console.log('isForce', isForce)
if (isForce === '1') {
if (value === '') {
callback(new Error('请输入最小版本'))
} else {
callback()
}
} else {
callback()
}
}
const state = reactive({
ruleForm: {
versionCode: '',
forceVersionCode: ''
},
rules: {
versionCode: [
{ required: true, message: '请输入版本号', trigger: 'blur' }
],
forceVersionCode: [
{ required: true, validator: forceVersionCode, trigger: 'blur' }
]
},
}