Vue3+Ts 仿写ElementPlus组件库(更ing)

一、环境配置问题

node-v 16.0以上
npm ls管理不同的node版本
npm install -g typescript 下载全局
问题:
可能会出现镜像不行:(换镜像)

S:\web\code>npm install -g typescript
npm ERR! code CERT_HAS_EXPIRED
npm ERR! errno CERT_HAS_EXPIRED
npm ERR! request to https://registry.npm.taobao.org/typescript failed, reason: certificate has expired

npm ERR! A complete log of this run can be found in: C:\Users\luna_\AppData\Local\npm-cache\_logs\2024-05-23T08_30_12_129Z-debug-0.log

S:\web\code>npm config set registry https://registry.npmmirror.com

S:\web\code>npm install -g typescript

added 1 package in 4s

tsc --version 查看ts的版本

执行.ts文件

1)方式:tsc xxx.ts —> xxx.js ts文件编译为js文件,执行
2)方式:终端 ts-node xxx.ts
tips:
安装ts-node

npm install ts-node -g

另外ts-node需要依赖tslib和@types/node两个包:

npm install tslib @types/node -g

报错:ts-node:无法加载文件, 系统上禁止运行脚本。
原因:PowerShell执行策略的问题,安全考虑,windows系统默认设置为“Restricted”。
解决:管理员身份运行PowerShall

set-ExecutionPolicy RemmoteSigned

二、本地项目git push -u origin master 报错:ssh: connect to host github.com port 22: Connection timed out fatal: Could not read from remote repo

梯子上网,git无法在端口22通过ssh服务链接到github

ssh -T git@github.com  
// 检测结果,无法连接到github上的ssh服务器
ssh: connect to host github.com port 22: Connection timed out
解决办法:配置ssh的config文件
// 编辑创建这个文件
nano ~/.ssh/config
// 在config文件里写入
Host github.com
    Hostname ssh.github.com
    Port 443
    User gi
// 正确链接结果
$ ssh -T git@github.com
> Hi lunabar! You've successfully authenticated, but GitHub does not
> provide shell access.

具体做法链接:https://gist.github.com/Tamal/1cc77f88ef3e900aeae65f0e5e504794
之后push或者pull就没有问题了

三、TypeScript结合Vue的知识点

1.any和unkown

any关闭了类型检查,可以赋值给其他任何类型的变量,“污染”其他变量。
let a: any = ‘123’
let b: number
b = a //不会报错
unkown解决“污染”问题,它不能直接赋值给其它变量(除any和unkown类型);运算有限(=====!=!==||&&?!typeofinstanceof);
unkown其它运算需要“类型缩小”,才能使用

let a:unkown = 1
if (type of a === 'number') {
	let r = a + 10
}
2.function函数

写法一:

const add1 = (x: number, y: number): number => x + y

写法二:

const add2: (x: number, y:number) => number = add1

写法三:对象写法

let add: {
	(x: number, y: number): number
}
add = function (x, y) {
	return x + y
}

与对象里含函数属性易混淆

const obj: {
	add: (x: number, y: number) => number
	或者是
	add(x: number, y: number): number
} = {
	add(x, y) {
		return x + y
	}
}
3.Class
类Class作为类型,其一为类型的实例的值类型,其二为构造函数类本身

其一:
如果是实例类型
let classObj :Class = new Class()
如例子:实际fn()函数传入的是类Foo的实例,所以类型是Foo。但Class有"结构类型原则",bar是和Foo实例具有相同属性(一个对象只要满足 Class 的实例结构,就跟该 Class 属于同一个类型)。

class Foo {
  id!: number;
}

function fn(arg: Foo) {
  // ...
}

const bar = {
  id: 10,
  amount: 100,
};

fn(bar); // 正确,因为 bar 具有 Foo 类型所需的所有属性

其二:
如果是类本身(即类型是函数)
let class:typeof Class = Class
如例子:createPoint函数里的PointClass参数是想传入的是一个构造函数,也就是Point类,所以类型为typeof Point (function)。

function createPoint(PointClass: typeof Point, x: number, y: number): Point {
  return new PointClass(x, y);
}
class类构造函数的参数,…args 剩余参数(任意个数)
…args: any[ ]

语法定义:是传入参数类型是数组(独立参数组成的数组),数组子项数据类型为任意类型,数据数量为任意数量
传入参数形式:是一个个任意类型的独立参数

class Person {
    constructor(public name: string, public age: number) {}
}

type MyClass<T> = new (...args: any[]) => T;

const createInstance = <T>(ctor: MyClass<T>, ...args: any[]): T => {
    return new ctor(...args);
};

const person = createInstance(Person, "John", 30);
console.log(person); // Person { name: 'John', age: 30 }

4.interface是对象模版

interface A {
}
interface 继承 type为对象的类型,继承class

5.watch监听的规则

1.watch监听的对象是基础数据类型,new value和old value是不一样的;监听的对象是引用数据类型,数据变化,new value和old value是一样的,因为是浅拷贝,引用的是同一个数据;若想深度监听到对象里的某个属性,写成函数的形式 () => obj.property,或者deep:true。

watch(监听的对象,回调函数)

2.回调函数是在组件更新之前执行的,默认是在DOM更新之气触发的,如果想改变触发时机:

watch(source, (newValue, oldValue) => {
	// DOM 更新完毕之后触发
}, {flush: post})

3.立即执行的监听器

watch(source, (newVaule, oldValue) => {
	// 立即执行,且当 'source' 改变时再次执行
}, {immediate: true})
6.标注复杂属性类型
interface Person {
	name: string;
	old: number;
}
props: {
	user: {
		type: Object as PropType<Person>,
		required: true		
	}
}
7.解构赋值Reactive可能出现丢失响应性的问题
const state = reactive({ count: 0 })
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 sate
n++

解决办法:使用 toRefs 将 reactive 转换成多个ref

const state = reactive({
	foo: 1,
	bar: 2
})
/*
{
	foo: number,
	bar: number
}
*/
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型: {
	foo: Ref<number>,
	bar: Ref<number>
}
*/
8.defineProps && defineEmits
import type { PropType } from 'vue'
let props = defineProps({
	props1:{
		// 数据类型
		type: Object as PropType<Interface>,
		required: true
	},
	props2:{
		type: Number,
		required: true
	}
})

9.withDefaults

defineProps<传入类型后>,需要withDefaults()函数,来设置<>里面的默认值。基本数据类型直接设置具体默认值,数组、对象,要用函数返回。
在这里插入图片描述

10.provide && inject

如果不明确父传孙的key属性值的数据类型,不方便维护,且key属性会重复。

import { InjectionKey, Ref } from 'vue'
export const key: InjectionKey<Ref<boolean>> = Symbol()
import { key } from ''
const value = ref(false)
provide(key, value)
inject(key)

provide(key,value)   inject(key)的出现,使组件间的传递信息不用逐次层级传递,父组件提供依赖(可响应式),任意子组件注入就可以使用。
vue3 & typescript的做法(调用InjectionKey API)

// type.ts
export const key = Symbol() as InjectionKey<T>
// T为provide(key,value)value值的数据类型
// 因为 type InjectionKey<T> = Symbol & { __type?: T }

四、vite.config.ts文件 无法找到模块“vite-plugin-eslint”的声明文件

在这里插入图片描述
原因:vite超过5的版本兼容性问题
解决办法:https://github.com/nabla/vite-plugin-eslint
在这里插入图片描述

五、Button组件

在这里插入图片描述
在这里插入图片描述

defineProps 父传子的属性
defineProps({
	prop1: {
		type: Object
	},
	prop2: {
		type: String
	}
)

在这里插入图片描述

Button组件逻辑架构

type.ts

  • Button—— 定义button特别属性类型 type ButtonType/ButtonSize/NativeType
           |
         button元素的自定义属性集合类型interface ButtonProps对象
         暴露出的button dom节点,类型定义interface ButtonInstance {ref:HTMLButtonElement }
知识点回顾:

1.什么时候给属性加:(v-bind:属性)- 动态属性,什么时候不给属性加?
加的情况

  • 变量
  • 表达式
  • 整数、布尔、字符串或其它数据类型的字面值

不加的情况
属性值部分按普通字符串处理

eg:
<button :scr='10'>scr的类型为number</button>
<button scr='10'>scr的类型为string</button>

特殊ref作为dom节点的属性,不用在template的属性attribute上加v-bind(:),但是效果是动态属性的效果

2.子组件给父组件传递属性、方法、dom节点,用defineExpose(对象)
vue3子组件setup语法糖的缘故,给父组件子组件暴露出的对象,必须父组件对子组件标注ref承接,在子组件挂载后,对象在ref.value中可查找。

// Button.vue
<template>
	<button ref="_ref" ></button>
</template>
<script setup>
	import { ref } from 'vue'
	const _ref = ref<HTMLButtonElement>()
	// 将ref属性暴露出去,值为_ref
	defineExpose({
		ref: _ref,
	})
</script>

// App.vue
<template>
	<Button ref="buttonRef"></Button>
</template>
<script>
	import { onMounted, ref } from 'vue'
	const buttonRef = ref<ButtonInstance | null>(null)
	onMounted(() => {
		if ( buttonRef.value ) {
			buttonRef.value.ref
			// 或者defineExpose()暴露出的其它属性、方法等,都可以在ref.value里得到
		}
	})
</script>

vue2父组件想调用子组件的属性及方法等,只需给子组件添加ref属性,用this调用。
<childComponent ref="child" </childComponent>
this.$ref.child .props/.methods()

3.defineProps<props定义的泛型>若要定义默认值,需要搭配withDefaults()使用

样式变量

变量可以在伪类root里声明(全局变量),格式为"–vk-color":…,变量在其它.css文件引用用var()

.className{“–vk-color”:} 声明局部变量,这块作用域内(类本身的dom及它的子节点),都能用这变量

& + &代表相同且相邻的兄弟元素

// scss文件
.button {
  padding: 10px 20px;
  background-color: blue;
  color: white;
  border-radius: 5px;

  // 为相邻的按钮添加左侧间距
  & + & {
    margin-left: 12px;
  }
}
// 编译后的css文件
.button {
  padding: 10px 20px;
  background-color: blue;
  color: white;
  border-radius: 5px;
}
  // 为相邻的按钮添加左侧间距
.button + .button {
  margin-left: 12px;
 }
// html
<div class="button">按钮1</div>
<div class="button">按钮2</div>
<div class="button">按钮3</div>

效果是按钮2和按钮3分别与各自左元素间距为12px

样式不需要一部到位

先设置通用的样式,再细化选择器(更高的优先级)里的样式(添加样式、覆盖已有样式)
归纳样式,
在默认样式的基础上,因为增加了类,而改变默认样式的部分样式,可以使用css动态变量

例如:下面这个例子在共享作用域上引用css变量,之后随之增加的类动态改变引用的变量的值,就会在该类的作用域下,重新覆盖引用变量样式。
在这里插入图片描述

// vars.css 全局变量
:root {
	--vk-color-primary: #409eff;
	--vk-color-primary-light-7: #c6e2ff;
	--vk-color-primary-light-9: #ecf5ff;
}
// style.css
.vk-button {
	--vk-button-hover-text-color: var(--vk-color-primary);
	--vk-button-hover-border-color: var(--vk-color-primary-light-7);
	--vk-button-hover-bg-color: var(--vk-color-primary-light-9);
}
.vk-button {  
  &:hover,
  &:focus {
    color: var(--vk-button-hover-text-color);
    border-color: var(--vk-button-hover-border-color);
    background-color: var(--vk-button-hover-bg-color);
  }
  &.is-plain {
    --vk-button-hover-text-color: var(--vk-color-primary);
    --vk-button-hover-bg-color: var(--vk-fill-color-blank);
    --vk-button-hover-border-color: var(--vk-color-primary);
  }
 }
样式嵌套

类之间有空格和没空格的区别
没空格:

// scss 
.parent {
	&.child {
		...
	}
}
// 编译后的css
.parent.child {
	// 选中两个类同时作用到一个dom的结点
}

有空格:

// scss
.parent {
	.child {
	}
}
// 编译后的css
.parent .child {
	// 选中parent父类下的所有child类的dom节点
}

六、Collapse组件

知识点回顾

组件实现双向绑定V-model(defineModel)

写法一:

底层原理:v-bind (:) + v-on (@)的语法糖

父组件:

<ChildComponent
	:modelVaue = "refOfVariable"
	@update:modelValue = "$event => (refOfVariable = $event)"
/>

子组件:

<template>
	<input 
		:value = "props.modelValue"
		@input = "emit('update:modelValue', $event.target.value)"
	>
	</input>
</template>
<script setup>
	const props = defineProps(['modelValue'])
	const emits = defineEmits(['update:modelValue'])
</script>

写法二:

vue3.4版本推出 defineModel建立父组件与子组件之间的联系

父组件

<ChildComponent v-model = "refOfValue" />
// 或者
<ChildComponent v-model:modelValue = "refOfValue" />

子组件
defineModel类似于defineProps,接受父组件传递的属性,创建的是ref响应式的变量属性
子组件中ref.value的值等价于refOfValue

<template>
	<input v-model="modelValue"></input>
</template>
<sctipt setup>
	// v-model
	const modelValue = defineModel()
	// v-model:modeValue
	const modelValue = defineModel('modelValue')
</script>

注意:
底层写法,场景:父组件给子组件传递props,子组件props赋值给本地ref的变量(props是响应的,但是本地ref只是引用了props的初始值,独立于props,与props构不成响应),在响应引用变量动态绑定props后有异步操作,这样组件v-model实现不了数据双向绑定(子可以传父—emits,父只能传递给异步操作之前的值)。本地ref —> props属性的数据绑定变量(单向的)
解决办法:
watch监听props的改变,props非ref、reactive、[],变量用函数返回,props一改变就重新赋值给这个本地ref。本地ref一改变,触发emits函数传递.value值给父组件props属性的数据绑定变量。这样保证了,本地ref可以拿到props的响应式值。
eg:watch(() => (props), () => ref.value = props)

// Collapse.vue
  const props = defineProps<CollapseProps>()
  // const activeNames = defineModel<NameType[]>({required: true})
  // defineModel底层原理如下
  const emits = defineEmits<CollapseEmits>()
  const activeNames = ref<NameType[]>(props.modelValue)
  watch(() => props.modelValue, () => {
    activeNames.value = props.modelValue
  })
  if (props.accordion && activeNames.value.length>1) {
    console.warn('arrodion model should only have one active item')
  }
  const handleItemClick = (item: NameType) => {
    if (props.accordion) {
      activeNames.value = [activeNames.value[0] === item ? '' : item]
    }else{
      const index = activeNames.value.indexOf(item)
      if (index > -1) {
        activeNames.value.splice(index, 1)
      }else {
        activeNames.value.push(item)
      }
      emits('update:modelVlaue', activeNames.value)
      // emits('change:modelVlaue', activeNames.value) 
      }
  }

若用const activeName = defineModel<NameType[]>(props.modelValue)
defineModel生成的activeName是一个ref引用变量
就不用watch监听了,且也无需emits,就能实现双向绑定。

// APP.vue
<script setup>
  const openedValue = ref(["a"])
  const buttonRef = ref<ButtonInstance | null>(null)
  const open = () => {
    alert(123)
  }
  onMounted(() => {
    if (buttonRef.value){
      console.log(buttonRef.value.ref)
    }
    setTimeout(() => {
      openedValue.value = ['a', 'b']
    },2000)
  })
</script>
<template>
  <VCollapse v-model="openedValue">
</template>
你可以尝试以下步骤来封装一个Vue 3和TypeScript下使用Element Plus的表单提交组件: 1. 安装必要的依赖: - Vue 3:`npm install vue@next` - TypeScript:`npm install -D typescript` - Element Plus:`npm install element-plus` 2. 创建一个新的Vue组件,并为其选择一个合适的名称,例如`FormSubmit.vue`。 3. 在`FormSubmit.vue`文件中,导入必要的模块和样式: ```vue <template> <!-- 表单内容 --> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { ElButton, ElForm, ElFormItem } from 'element-plus'; export default defineComponent({ components: { ElButton, ElForm, ElFormItem, }, }); </script> <style scoped> /* Element Plus 样式 */ @import 'element-plus/packages/theme-chalk/src/index.scss'; /* 自定义样式 */ /* ... */ </style> ``` 4. 在模板中编写表单内容,并使用Element Plus的组件来构建表单: ```vue <template> <el-form ref="form" :model="formData" label-width="120px"> <el-form-item label="姓名" prop="name"> <el-input v-model="formData.name"></el-input> </el-form-item> <!-- 多表单项 --> <el-form-item> <el-button type="primary" @click="submitForm">提交</el-button> </el-form-item> </el-form> </template> <script lang="ts"> // ... export default defineComponent({ // ... data() { return { formData: { name: '', // 多表单字段 } }; }, methods: { submitForm() { // 表单提交逻辑 if (this.$refs.form.validate()) { // 表单验证通过,执行提交操作 // ... } } } }); </script> ``` 这样,你就可以使用封装好的表单提交组件来方便地处理表单提交了。你可以根据实际需求添加多的表单项,并在`submitForm`方法中实现你的提交逻辑。希望这可以帮到你!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值