1. 元素组件component
表单由三种要素组成:el-form、el-form-item、各种el-input,本次主要关注三点:
-
多层data
绑定的数据是嵌套的时候,如果不做验证尚可,
https://blog.csdn.net/lvdoubing81/article/details/121749930
-
验证validate的原理
参考
验证是依赖于数据、规则的,验证的逻辑应该是:判断 数据 是否 是规定的格式,1. 数据中的属性, 2. 规则
如果仅是绑定数据,通过el-input上的v-model已经可以完成双向绑定,在el-form的model与el-form-item的prop就是为了完成验证的。其逻辑是判断model对应的数据是否符合rule对应的规则。
- 通过el-form上绑定model来指定整体的数据;
- 通过在各个el-form-item上的prop指定其在数据上的哪一个项;
- 通过el-form-item上的props是获取el-form上的rules,或者获取el-form-item上的rules,进行验证。
第一种,默认方式,这里rules中key的定义需要修改
<el-form ref="formRef" :model="form" :rules="rules" label-width="148px">
<el-form-item label="项目名称" prop="Data.DataCenterBaseInfo.F_DataCenterName">
<el-input
type="text"
v-model="form.Data.DataCenterBaseInfo.F_DataCenterName"
autocomplete="off"
></el-input>
</el-form-item>
</el-form>
<script>
const form = reactive({
Data: {
DataCenterBaseInfo: {
F_DataCenterName: "",
},
}
})
const rules = reactive<FormRules>({
'Data.DataCenterBaseInfo.F_DataCenterName': [
{ required: true, message: "请录入项目名称", trigger: "blur" },
],
})
</script>
第二种,在item中通过rules去指定:
<el-form ref="formRef" :model="form" :rules="rules" label-width="148px">
<el-form-item label="项目名称" prop="Data.DataCenterBaseInfo.F_DataCenterName" :rules="rules.F_DataCenterName">
<el-input
type="text"
v-model="form.Data.DataCenterBaseInfo.F_DataCenterName"
autocomplete="off"
></el-input>
</el-form-item>
</el-form>
<script>
const form = reactive({
Data: {
DataCenterBaseInfo: {
F_DataCenterName: "",
},
}
})
const rules = reactive<FormRules>({
F_DataCenterName: [
{ required: true, message: "请录入项目名称", trigger: "blur" },
],
})
</script>
-
resetFields
这里主要是针对创建与修改通用一个表单的情况,为了避免修改与创建数据的混淆,完成提交后,需要重置表单resetFields,但这里需要注意:
1、resetFields 是重置成初始值
2、form 的初始值在 mounted 生命周期执行的时候被调用
3、如果在 mounted 生命周期执行之前修改了初始值,那么resetFields 方法会把修改后的值作为初始值
这就造成了如果表单首次打开方式是修改,容易在mounted时将数据绑定为update的数据,这样即使调用resetFields,也不会清空数据了。
参考
这就需要注意在mounted时候,一定要是空值,然后通过watch去监听update的数据(props或者store中数据),通过nextTick()再来修改数据。
这里还有一个小坑,修改数据时,不能将reactive的数据直接赋值,而是要通过各个属性分别去修改。这是reative的原理。
watch(
() => route.query.type,
(type) => {
if (type === undefined) {
return;
}
// console.log("watch");
let data: any = null;
if (type == "edit") {
data = convertToPlan(curProject.value);
disabled.value = false;
} else if (type == "create") {
data = useProject().form;
disabled.value = false;
} else if (type == "detail") {
data = convertToPlan(curProject.value);
disabled.value = true;
}
nextTick(() => {
// console.log("nextTick");
for (const key in form) {
form[key] = data[key];
}
});
},
{ immediate: true }
);
-
其他
1.2 page
对el-pagination进行了进一步分装,支持总数、每页数量、前一页、当前页、后一页、跳转等功能,向外提交change事件,包含当前页改变、每页数量。具体实现看源码,这里仅放一个template
<template>
<el-pagination
class="page margin_t-20"
background
:layout= "generateLayout()"
:current-page="page.current"
:page-sizes="page.sizes"
:page-size="page.size"
:total="page.total"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle" />
<!-- :hide-on-single-page="page.total <= page.size" -->
</template>
这里主要是将不同input进行封装,通过外边的数据来控制具体的input类型。主要在外边要有类似v-model的使用方式。
实现上这里使用过两种方式:
-
将v-model改写为input事件与value数据
这种实现后发现存在2个问题,其一:修改input数据后,不能及时反应到view上;其二:不是所有类型input都可以用input事件与value数据来代替v-model,像select就存在问题。
-
在外侧将整个data、对应的prop直接传入,在组件内直接使用v-model。
<template>
<el-input
v-if="props.type == 'input'"
:type="props?.meta?.type ? props?.meta?.type : 'text'"
v-model="props.value[props.prop]"
:disabled="props.disabled"
:placeholder="props.placeholder"
v-bind="$attrs"
></el-input>
<el-input-number
v-else-if="(props.type == 'input-number')"
v-model="props.value[props.prop]"
:disabled="props.disabled"
:placeholder="props.placeholder"
:precision="props?.meta?.precision ? props?.meta?.precision: 0 "
>
</el-input-number>
<el-select
v-else-if="props.type == 'select'"
v-model="props.value[props.prop]"
:disabled="props.disabled"
:placeholder="props.placeholder"
>
<el-option
v-for="(optItem, index) in props.meta.options"
:key="index"
:label="optItem.label"
:value="optItem.value"
></el-option>
</el-select>
<el-date-picker
v-else-if="props.type == 'date'"
v-model="props.value[props.prop]"
:disabled="props.disabled"
:placeholder="props.placeholder"
:type="props?.meta?.type ? props?.meta?.type : 'date'"
>
</el-date-picker>
</template>
参考:三种方法、组件上使用v-model
2. 业务模板template
2.1 表单
-
创建、修改、详情复用表单
先写表单,绑定state数据;
再监听传入类型与数据,将state数据进行刷新;
最后完成后通过resetFields刷新表单,并清空state数据
-
动态表单
动态表单需要对各种input进行封装,在外层写el-form与el-form-item,最后通过v-for来控制各个input的显示
-
分步表单
分布表单一般都是大表单,用el-steps
来进行一个控制,这里既可以使用一个el-form,也可以使用多个el-form。
本次使用了一个el-form,在通过v-show来进行显隐的控制,注意这里用的是v-show。v-show整个表单存在,显示上做了调整;而v-if则只存在一个片段(step)。v-if在validate、resetFields等操作时 不能满足要求。
2.2 xml代码
采用了2种不同的方式
-
vue-xml-viewer
https://github.com/leon737/vue-xml-viewer
刚上来不行,将源码下到本地直接引入,由于js与ts的问题,没成成功;
发现与项目的vue版本不一致,提升项目的版本后,成功显示;
但在某些闭元素处,插入了很多空行,影响查看;
源码下到本地,修改代码,重新build,然后替换项目module中的dist进行调试,依旧没有成功,应该是在content处出错,时间问题,放弃进一步的调试;采用第2种方案。
在这里看到组件中递归自己组件的使用方式
-
hightlight
引入:https://blog.csdn.net/zhengyinling/article/details/103677625
采用插件的方式调通,但console中有warning,并提示相关接口将遗弃,从git中找到原始的用法
原址:https://github.com/highlightjs/highlight.js#using-with-vuejs
更进:https://github.com/highlightjs/vue-plugin
通过它提供的plugin使用
第一种支持xml的代码的查看、折叠等操作,第二种支持各种代码的查看,但不能折叠
3. vue机制
3.1 vue3的setup
组合api
通过setup简化的代码,vue2中的概念都存在,props、生命周期、emit、computed等
props与emit
3.2 global
全局组件的注入方式,g-global 的统一注入:注入到vue中的方式、组件自己index的中的使用方式
这个叫自动导入模块,是webpack提供的api:require.context
(参考)下边简单整理一下它。
export default {
install: function (app: App<Element>) {
const globalComponents = require.context('./', true, /index\.(vue|js)$/iu)
globalComponents.keys().forEach(filePath => {
const component = globalComponents(filePath)
let nameArr = filePath.split('/')
let name = nameArr[nameArr.length - 2] .replace(/\.\w+$/u, '')
name = upperFirst(
camelCase(
name
)
)
app.component(`G${ name }`, component.default || component)
})
}
}
require.context()有3个入参:
- directory,读取文件的路径
- useSubdirectories,是否遍历文件的子目录
- regExp,匹配文件的正则
require.context()返回的是一个函数,而且这个杉树还有3个属性:
- keys ,返回匹配成功模块的名字组成的数组
- resolve ,接收keys()元素作为入参,返回是相对于工程的路径
- id -执行环境的id,返回的是一个字符串
另外返回值既然是一个函数,也就可以调用,它也接受keys()元素作为入参,返回的是Module(和使用import导入的模块是一样的),这里的globalComponents
如此。
3.3 vue插件
这种方式正式Vue的插件,通过插件向 Vue 添加全局级功能,官网描述如下:
插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者 property。如:vue-custom-element
- 添加全局资源:指令/过渡等。如:vue-touch)
- 通过全局 mixin 来添加一些组件选项。(如vue-router)
- 添加全局实例方法,通过把它们添加到
config.globalProperties
上实现。
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
每当这个插件被添加到应用程序中时,如果它是一个对象,就会调用 install
方法。如果它是一个 function
,则函数本身将被调用。在这两种情况下——它都会收到两个参数:由 Vue 的 createApp
生成的 app
对象和用户传入的选项。
3.4 nextTick()
参考、参考
官方的解释:将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。
说白了: 当数据更新了,会造成在dom重新渲染,完成后自动执行该函数。
原理:
Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 改变了一个新数据data,DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
应用:
当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中。
-
created中使用
Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。
-
动态添加事件
有时需要根据数据动态的为页面某些dom元素添加事件,这就要求在dom元素渲染完毕时去设置,但是created与mounted函数执行时一般dom并没有渲染完毕,所以就会出现获取不到,添加不了事件的问题,这回就要用到nextTick处理
-
在使用某个第三方插件时 ,希望在vue生成的某些dom动态发生变化时重新应用该插件,也会用到该方法
-
数据改变后获取焦点
3.5 响应式
-
reactive与ref
响应式
当响应式状态改变时视图会自动更新,响应式转换是“深度转换”——它会影响传递对象的所有嵌套 property。
想象一下,我们有一个独立的原始值 (例如,一个字符串),我们想让它变成响应式的。当然,我们可以创建一个拥有相同字符串 property 的对象,并将其传递给 reactive
。Vue 为我们提供了一个可以做相同事情的方法——ref
。
import { ref } from 'vue'
const count = ref(0)
reactive()是针对地址的,也就是将form指向别的变量,并不会响应.
-
toRef
参考,官网
toRef与ref进行对比的
ref:
const state = {
foo: 1,
bar: 2
}
const fooRef = ref(state.foo)
fooRef.value++
console.log(fooRef.value) // 2
console.log(state.foo) // 1
toRef:
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
需要注意ref(obj.name)相当于ref('alice')相当于reactive({value:'alice'}),所以在修改数据时,是修改newObj.value=xxx
(1). ref本质是拷贝,修改响应式数据不会影响原始数据;toRef的本质是引用关系,修改响应式数据会影响原始数据
(2). ref数据发生改变,界面会自动更新;toRef当数据发生改变是,界面不会自动更新
-
toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。当从组合式函数返回响应式对象时,toRefs
非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开。
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// 操作 state 的逻辑
// 返回时转换为ref
return toRefs(state)
}
export default {
setup() {
// 可以在不失去响应性的情况下解构
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
3.6 其他
4. Javascript
-
关于module的export与import
https://segmentfault.com/a/1190000017298307
https://chihokyo.com/post/35/
-
Object.prototype.toString.call()
https://www.jianshu.com/p/585926ae62cc
js的原型与call等概念
typeof只能判断数据类型,即number、string、undefined、boolean、object等,而对于null
、array
、function
、object
来说,使用typeof
都会统一返回object
字符串,而要区分object,就需要Object.prototype.toString
,如下:
// 基本类型
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(“abc”);// "[object String]"
Object.prototype.toString.call(123);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"
//**自定义类型**
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Rose", 18);
Object.prototype.toString.call(arr); // "[object Object]"
//** 数组 **
let a = [1,2,3]
Object.prototype.toString.call(a) // '[object Array]'
//**日期类型**
var date = new Date();
Object.prototype.toString.call(date); // "[object Date]"
// **函数类型**
Function fn(){
console.log(“test”);
}
Object.prototype.toString.call(fn); // "[object Function]"
//**正则表达式**
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg); // "[object RegExp]"