一、环境配置问题
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类型);运算有限(==
、===
、!=
、!==
、||
、&&
、?
、!
、typeof
、instanceof
);
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>