阅读vue的英文官网
中文的vue官网比vue的英文官网差很多,这个其实很容易理解,毕竟vue是服务于全球的开源项目之一。
所以程序员的第一生产力还是英语
不管学什么都要去获取第一手资料,不要看中文官网,直接去看英文官网
vite初始化项目
1、pnpm create vite
2、选择vue,再选择typescript
3、启动项目
cd test1
pnpm install
pnpm run dev
vscode的vue代码片段
根据自己的使用习惯,设置vscode的vue代码片段,推荐使用snippet-generator.app
"vue3模版": {
"prefix": "vue3",
"body": [
"<template>",
" <div class='${1:box}'></div>",
"</template>",
" ",
"<script setup lang='ts'>",
" import {ref,reactive} from \"vue\";",
" ${3}",
"</script>",
" ",
"<style lang=\"scss\" scoped>",
" .${2:box} {",
" }",
"</style>"
],
"description": "vue3模版"
}
另外vscode的不仅可以设置vue的代码片段,理论上你在vscode上写的任何代码,都可以设置成代码片段,方便自己以后使用。
这个自己根据自己的个人习惯,自己挖掘。
另外因为使用vscode开发vue的typescript项目,vscode还需要安装对应的插件,比如TypeScript Vue Plugin
3. 组件引入
当使用setup
的时候,组件直接引入就可以了,不需要再自己手动注册
4. ref和reactive
ref一般用于基本的数据类型,比如string,boolean
reactive一般用于对象
使用reactive的注意事项:
1、reactive不能用于string,number,boolean
vue官方网站说明如下:It cannot hold primitive types such as string, number or boolean
2、不能修改reactive设置的值
比如:
let state = reactive({ count: 0 })
// the above reference ({ count: 0 }) is no longer being tracked (reactivity connection // is lost!)
// 这里state如果重新赋值以后,vue就不能双向绑定
state = reactive({ count: 1 })
ref的底层实现,其实也是调用的reactive实现的,有点类似react hooks的useState和useReducer;
5. defineEmits和defineProps获取父组件传过来值和事件
// 第一种不带默认值props
const props = defineProps<{
foo: string
bar?: number
}>()
// 第二种带默认值props
export interface ChildProps {
foo: string
bar?: number
}
const props = withDefaults(defineProps<ChildProps>(), {
foo: "1qsd"
bar?: 3
})
// 第一种获取事件
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 第二种获取事件
const emit = defineEmits(["dosth"])
6. 使用useAttrs和useSlots
useAttrs
可以获取父组件传过来的id和class等值。
useSlots
可以获得插槽的内容。
例子中,我们使用useAttrs获取父组件传过来的id和class,useSlots获取插槽的内容。
父组件:
<template>
<div class="father">{{ fatherRef }}</div>
<Child :fatherRef="fatherRef" @changeVal="changeVal" class="btn" id="111">
<template #test1>
<div>1223</div>
</template>
</Child>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
const fatherRef = ref("1");
function changeVal(val: string) {
fatherRef.value = val;
}
</script>
<style lang="scss" scoped>
.father {
margin-top: 40px;
margin-bottom: 40px;
}
.btn {
font-size: 20px;
color: red;
}
</style>
子组件:
<template>
<!-- <div class="child">{{ props.fatherRef }}</div> -->
<div v-bind="attrs">
<slot name="test1">11</slot>
<input type="text" v-model="inputVal" />
</div>
</template>
<script setup lang="ts">
import { computed, useAttrs, useSlots } from "vue";
const props = defineProps<{
fatherRef: string;
}>();
const emits = defineEmits(["changeVal"]);
const slots = useSlots();
const attrs = useAttrs();
console.log(122, attrs, slots);
const inputVal = computed({
get() {
return props.fatherRef;
},
set(val: string) {
emits("changeVal", val);
},
});
</script>
<style lang="scss" scoped>
.child {
}
</style>
使用自定义指令
在setup里边自定义指令的时候,只需要遵循vNameOfDirective 这样的命名规范就可以了
比如如下自定义focus指令,命名就是vMyFocus,使用的就是v-my-focus
自定义指令
<script setup lang="ts">
const vMyFocus = {
onMounted: (el: HTMLInputElement) => {
el.focus();
// 在元素上做些操作
},
};
</script>
<template>
<input v-my-focus value="111" />
</template>
7. 使用defineExpose子组件传父组件
子组件
<template>
<div class="child"></div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
function doSth() {
console.log(333);
}
defineExpose({ doSth });
</script>
<style lang="scss" scoped>
.child {
}
</style>
父组件
<template>
<div class="father" @click="doSth1">222</div>
<Child ref="childRef"></Child>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import Child from "./Child.vue";
const childRef = ref();
function doSth1() {
childRef.value.doSth();
}
</script>
<style lang="scss" scoped>
.father {
}
</style>
8. 父组件传子组件
父组件
<template>
<div class="father"></div>
<Child @click="doSth"></Child>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import Child from "./Child.vue";
function doSth() {
console.log(112);
}
</script>
<style lang="scss" scoped>
.father {
}
</style>
9. toRefs
当从父组件向子组件传props的时候,必须使用toRefs或者toRef进行转一下,这是为什么呢?
这里是因为如果不使用toRefs
转一次的话,当父组件中的props
改变的时候,子组件如果使用了Es6的解析,会失去响应性。
可以看下如下例子
父组件
<template>
<div class="father" @click="changeVal">{{ fatherRef }}</div>
<Child :fatherRef="fatherRef"></Child>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import Child from "./Child.vue";
const fatherRef = ref(1);
function changeVal() {
fatherRef.value = 2;
}
</script>
<style lang="scss" scoped>
.father {
margin-bottom: 40px;
}
</style>
子组件
<template>
<div class="child" @click="changeVal">{{ fatherRef }}</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, toRefs } from "vue";
const props = defineProps<{
fatherRef: any;
}>();
const { fatherRef } = props;
function changeVal() {
fatherRef.value = 34;
}
</script>
<style lang="scss" scoped>
.child {
}
</style>
可以看到当父组件如果点击之后,因为使用const { fatherRef } = props;
进行解析,就失去了响应性
所以当父组件变成2的时候,子组件还是1。
这里有两种解决办法
使用const { fatherRef } = toRefs(props);
在模版中中使用props.fatherRef
10. 子组件使用v-model
10.1 可以在子组件中使用computed,实现双向绑定
父组件
<template>
<div class="father">{{ fatherRef }}</div>
<Child :fatherRef="fatherRef" @changeVal="changeVal"></Child>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
const fatherRef = ref("1");
function changeVal(val: string) {
fatherRef.value = val;
}
</script>
<style lang="scss" scoped>
.father {
margin-top: 40px;
margin-bottom: 40px;
}
</style>
子组件
<template>
<!-- <div class="child">{{ props.fatherRef }}</div> -->
<input type="text" v-model="inputVal" />
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{
fatherRef: string;
}>();
const emits = defineEmits(["changeVal"]);
const inputVal = computed({
get() {
return props.fatherRef;
},
set(val: string) {
emits("changeVal", val);
},
});
</script>
<style lang="scss" scoped>
.child {
}
</style>
10.2 可以从父组件传递值和改变值的方法,然后子组件也可以使用v-model
例子中父组件传递 modelValue和update:modelValue
方法 父组件:
<template>
<Child :modelValue="searchText" @update:modelValue="changeVal"> </Child>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
const searchText = ref(1);
function changeVal(val: number) {
searchText.value = val;
}
</script>
<style lang="scss" scoped>
.father {
margin-top: 40px;
margin-bottom: 40px;
}
.btn {
font-size: 20px;
color: red;
}
</style>
子组件:
<template>
<!-- <div class="child">{{ props.fatherRef }}</div> -->
<!-- <div v-bind="attrs">
<slot name="test1">11</slot>
<input type="text" v-model="inputVal" />
</div> -->
<input v-model="modelValue" />
</template>
<script setup lang="ts">
import { computed, useAttrs, useSlots } from "vue";
const props = defineProps<{
modelValue: number;
}>();
// const emits = defineEmits(["changeVal"]);
</script>
<style lang="scss" scoped>
.child {
}
</style>
11. 递归组件
组件本身是可以调用组件自身的,也就是递归。 比如名为 Child.vue 的组件可以在其模板中用 引用它自己。这里需要注意的是需要设置条件语句,用来中断递归,不然递归会无限递归下去。
父组件
<template>
<Child :modelValue="searchText" @update:modelValue="changeVal"> </Child>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
const searchText = ref(1);
function changeVal(val: number) {
searchText.value = val;
}
</script>
<style lang="scss" scoped>
.father {
margin-top: 40px;
margin-bottom: 40px;
}
.btn {
font-size: 20px;
color: red;
}
</style>
子组件
<template>
<input v-model="modelValue" />
<Child
:modelValue="test"
@update:modelValue="changeTest"
v-if="modelValue > 2"
></Child>
</template>
<script setup lang="ts">
import { computed, useAttrs, useSlots, ref } from "vue";
const props = defineProps<{
modelValue: number;
}>();
const test = ref(0);
function changeTest(val: number) {
test.value = val;
}
// const emits = defineEmits(["changeVal"]);
</script>
<style lang="scss" scoped>
.child {
position: relative;
}
</style>