vue3.2的一些写法,菜鸡学习记录

115 阅读4分钟

一.Vue2升级Vue3.2写法

1.获取 this

<script setup>
import { getCurrentInstance } from 'vue'

// proxy 就是当前组件实例,可以理解为组件级别的 this,没有全局的、路由、状态管理之类的
const { proxy, appContext } = getCurrentInstance()

// 这个 global 就是全局实例
const global = appContext.config.globalProperties
</script>

2.全局注册属性,方法,使用

// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 添加全局属性
app.config.globalProperties.name = '沐华'

<script setup>
import { getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()

const global = appContext.config.globalProperties
console.log(global.name) // 沐华
</script>

3.支持多个根节点

<template>
    <div>1</div>
    <div>2</div>
</template>

4.dom获取

<template>
    <el-form ref="formRef"></el-form>
    <child-component />
</template>
<script setup lang="ts">
import ChildComponent from './child.vue'
import { getCurrentInstance } from 'vue'
import { ElForm } from 'element-plus'

// 方法一,这个变量名和 DOM 上的 ref 属性必须同名,会自动形成绑定
const formRef = ref(null)
console.log(formRef.value) // 这就获取到 DOM 了

// 方法二
const { proxy } = getCurrentInstance()
proxy.$refs.formRef.validate((valid) => { ... })

// 方法三,比如在 ts 里,可以直接获取到组件类型
// 可以这样获取子组件
const formRef = ref<InstanceType<typeof ChildComponent>>()
// 也可以这样 获取 element ui 的组件类型
const formRef = ref<InstanceType<typeof ElForm>>()
formRef.value?.validate((valid) => { ... })
</script>

5.初始化

<script setup>
import { onMounted } from 'vue'

// 请求接口函数
const getData = () => {
    xxxApi.then(() => { ... })
}

onMounted(() => {
    getData()
})
</script>

6.解除绑定

<script setup>
import { onBeforeUnmount, onDeactivated } from 'vue'

// 组件卸载前,对应 Vue2 的 beforeDestroy
onBeforeUnmount(() => {
    clearTimeout(timer)
    window.removeAddEventListener('...')
})

// 退出缓存组件,对应 Vue2 的 deactivated
onDeactivated(() => {
    clearTimeout(timer)
    window.removeAddEventListener('...')
})
</script>
       

7.响应式参数

<template>
    <div>{{ count }}</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const count = ref(1)

// 有人这么用 
const arr = ref([])
console.log(arr.value) // []

// 也有人这么用,一个组件里所有的属性全部定义在一个对象里,有点 Vue2 data 的味道
const data = reactive({
    name: '沐华',
    age: 18,
    ...
})
console.log(data.name) // 沐华

// 也有人一个组件里 ref 和 reactive 两种都用,随便你
</script>

8.toRef 和 toRefs

<script setup>
import { reactive, toRef, toRefs } from 'vue'

const data = reactive({
    name: '沐华',
    age: 18
})

// 这样虽然能拿到 name / age,但是会变成普通变量,没有响应式效果了
const { name, age } = data

// 取出来一个响应式属性
const name = toRef(data, 'name')

// 这样解构出来的所有属性都是有响应式的
const { name, age } = toRefs(data)

// 不管是 toRef 还是 toRefs,这样修改是会把 data 里的 name 改掉的
// 就是会改变源对象属性,这才是响应式该有的样子
name.value = '沐沐华华'
</script>

9.watch

<script setup>
import { watch, ref, reactive } from 'vue'

const name = ref('沐华')
const data = reactive({
    age: 18,
    money: 100000000000000000000,
    children: []
})

// 监听 ref 属性
watch(name, (newName, oldName) => { ... })

// 监听其他属性、路由或者状态管理的都这样
watch(
    () => data.age, 
    (newAge, oldAge) => { ... }
)

// 监听多个属性,数组放多个值,返回的新值和老值也是数组形式
watch([data.age, data.money], ([newAge, newMoney], [oldAge, oldMoney]) => { ... })

// 第三个参数是一个对象,为可配置项,有5个可配置属性
watch(data.children, (newList, oldList) => { ... }, {
    // 这两个和 Vue2 一样,没啥说的
    immediate: true,
    deep: true,
    // 回调函数的执行时机,默认在组件更新之前调用。更新后调用改成post
    flush: 'pre', // 默认值是 pre,可改成 post 或 sync
    // 下面两个是调试用的
    onTrack (e) { debugger }
    onTrigger (e) { debugger }
})
</script>


10.watchEffect

<script setup>
import { watchEffect } from 'vue'

// 正常使用
watchEffect(() => {
    // 会自动收集这个函数使用到的属性作为依赖,进行监听
    // 监听的是 userInfo.name 属性,不会监听 userInfo
    console.log(userInfo.name)
})

// 有两个参数,参数一是触发监听回调函数,参数二是可选配置项
watchEffect(() => {...}, {
    // 这里是可配置项,意思和 watch 是一样的,不过这只有3个可配置的
    flush: 'pre',
    onTrack (e) { debugger }
    onTrigger (e) { debugger }
})

// 回调函数接收一个参数,为清除副作用的函数,和 watch 的同理
watchEffect(onInvalidate => {
    console.log('沐华')
    onInvalidate(() => {
        console.log(2222)
    })
})
</script>

11.computed

<script setup>
import { computed } from 'vue'
const props = defineProps(['visible', 'type'])
const emit = defineEmits(["myClick"])

// 函数写法,计算类型
const isFirst = computed(() => props.type === 1)

// 对象写法
const status = computed({
    get () { return props.visible }, // 相当于 Vue2中的 this.visible
    set (val) { emit('myClick', val) } // 相当于 Vue2中的 this.$emit('input', val)
})

// computed 第二个参数也是一个对象,调试用的
const hehe = computed(参数一上面两种都可, {
    onTrack (e) { debugger }
    onTrigger (e) { debugger }
})
</script>

12.nextTick

<script setup>
import { nextTick} from 'vue'

// 方式 一
const handleClick = async () => {
  await nextTick()
  console.log('沐华')
}

// 方式二
nextTick(() => {
    console.log('沐华')
})

// 方式三
nextTick().then(() => {
    console.log('沐华')
  })
</script>

13.mixins 和 hooks

// xxx.js
expport const getData = () => {}
export default function unInstance () {
    ...
    return {...}
}

// xxx.vue
import unInstance, { getData } from 'xx.js'
const { ... } = unInstance()
onMounted(() => {
    getData()
})

14.多个v-model

// 父组件写法
<template>
    <child v-model:name="name" v-model:age="age" />
</template>
<script setup>
import { ref } from "vue"
const name = ref('沐华')
const age = ref(18)
</script>

// 子组件
<script setup>
const emit = defineEmits(['update:name', 'update:age'])

const handleClick = () => {
    console.log('点击了')
    emit('update:name', '这是新的名字')
}
</script>

15.路由使用

// 需要用到路由的 .vue 文件里
<script setup>
import { useRoute, useRouter } from "vue-router"
// route 对应 Vue2 里的 this.$route
const route = useRoute()
// router 对应 Vue2 里的 this.$router
const router = useRouter()
</script>

16.样式穿透

<style lang="scss" scoped>
// 这样写不生效的话
.el-form {
    .el-form-item { ... }
}
// Vue2 中这样写
/deep/ .el-form {
    .el-form-item { ... }
}
// Vue3 中这样写
:deep(.el-form) {
    .el-form-item { ... }
}
</style>

// 别再一看没生效,就这样加一个没有 scoped 的 style 标签了,一鼓脑全都加到全局上去了
// 除非是全局都要改的
// 还有些需要加到全局的场景,但只改当前页的,比如有些ui组件是挂在全局上的,可以加个当前页独有的类名就是了
// <style lang="scss">
//  .el-form {
//     .el-form-item { ... }
//  }
// </style>

17.css添加变量

<template>
    <div class="name">沐华</div>
</template>
<script setup>
import { ref } from "vue"
const str = ref('#f00') // 红色
</script>
<style scoped lang="scss">
.name {
    background-color: v-bind(str); // JS 中的色值变量 #f00 就赋值到这来了
}
</style>

二.Vue3.2.47+TS 使用指南

1.值ref

const initCode = ref('200'); //默认推导出string类型

//或者手动定义更复杂的类型
const initCode = ref<string | number>('200');

2.模版ref

<template>
  <div ref="el"></div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
const el = ref<HTMLImageElement | null>(null);
</script>

3.组件ref

<template>
  <HelloWorld ref="helloworld" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue';

const helloworld = ref<InstanceType<typeof HelloWorld> | null>(null);

onMounted(() => {
  //调用子组件的handleClick方法
  helloworld.value?.handleClick();
});
</script>

4.reactive


<script setup lang="ts">
import { reactive } from 'vue';

interface User {
  name: string;
  age: number;
}
//reactive 会隐式地从它的参数中推导类型
//也可以使用接口直接给变量定义类型
const user: User = reactive({
  name: 'zhangsan',
  age: 20,
});
</script>

5.computed

const age = computed<number>(() => {
  return 2;
});

6.defineProps

<template>
  <div>
    <div>{{ msg }}</div>
    <div>{{ address }}</div>
    <div>{{ user.name }}--{{ user.age }}</div>
  </div>
</template>

<script setup lang="ts">
import { PropType } from 'vue';

interface User {
  name: string;
  age: number;
}

const props = defineProps({
  msg: {
    type: String,
    default: 'hello',
  },
  address: {
    type: [String, Number], //联合类型,类似于ts的 |
    default: 2,
  },
  user: {
    type: Object as PropType<User>, //定义更具体的类型
    default: () => ({
      name: 'zhangsan',
      age: 12,
    }),
  },
});

console.log(props.msg);

console.log(props.user.name);
</script>


使用泛型
<template>
  <div>{{ name }}</div>
</template>

<script setup lang="ts">
interface User {
  name?: string;
  age?: number;
}

const props = defineProps<User>();

console.log(props.name);
</script>

6.withDefaults

设置默认值
<script setup lang="ts">
interface User {
  name: string;
  age: number;
}

const props = withDefaults(defineProps<User>(), {
  name: 'hello',
  age: 10,
});
</script>

7.defineEmits

<template>
  <div>
    <HelloWorld @change="handleChange" />
  </div>
</template>

<script setup lang="ts">
import HelloWorld from '@/components/HelloWorld.vue';
import type { User } from '@/types/user';
const handleChange = (value: User) => {
  console.log(value);
};
</script>

<!-- 子组件 -->
<template>
  <div>
    <button @click="handleClick">按钮</button>
  </div>
</template>

<script setup lang="ts">
const emit = defineEmits(['change']);

const handleClick = () => {
  emit('change', { name: '2', age: 21 });
};
</script>

<!-- 子组件 -->
<template>
  <div>
    <button @click="handleClick">按钮</button>
  </div>
</template>

<script setup lang="ts">
import type { User } from '@/types/user';
const emit = defineEmits<{ (e: 'change', value: User): void }>();

const handleClick = () => {
  emit('change', { name: '2', age: 21 });
};
</script>

8.defineExpose

<template>
  <div></div>
</template>

<script setup lang="ts">
const handleClick = () => {
  console.log('子组件方法');
};
defineExpose({ handleClick });
</script>

9.provide/inject

import type { InjectionKey } from 'vue';

export const key = Symbol() as InjectionKey<string>;

<!-- 父组件提供provide -->
<script setup lang="ts">
import { ref, provide } from 'vue';
const state = ref(0);
const handlerState = () => {
  state.value = 1;
};
provide('info', state); //提供响应式对象
provide('func', handlerState); //提供改变响应式对象的方法
</script>

<!-- 子组件使用inject取值 -->
<script setup lang="ts">
import { inject } from 'vue';

//通过泛型参数显式声明
const state = inject<number>('info');
const func = inject<() => void>('func');
</script>

undefined问题
const state = inject<number>('info', 20);
const state = inject('info') as number;


10.事件类型

<template>
  <input type="text" @change="handleChange" />
</template>

<script setup lang="ts">
const handleChange = (evt: Event) => {
  console.log((evt.target as HTMLInputElement).value);
};
</script>


<template>
  <button @click="handleClick">按钮</button>
</template>

<script setup lang="ts">
const handleClick = (evt: Event) => {
  //获取按钮的样式信息
  console.log((evt.target as HTMLButtonElement).style);
};
</script>


11.HTML标签映射关系

interface HTMLElementTagNameMap {
  "a": HTMLAnchorElement;
  "abbr": HTMLElement;
  "address": HTMLElement;
  "applet": HTMLAppletElement;
  "area": HTMLAreaElement;
  "article": HTMLElement;
  "aside": HTMLElement;
  "audio": HTMLAudioElement;
  "b": HTMLElement;
  "base": HTMLBaseElement;
  "basefont": HTMLBaseFontElement;
  "bdi": HTMLElement;
  "bdo": HTMLElement;
  "blockquote": HTMLQuoteElement;
  "body": HTMLBodyElement;
  "br": HTMLBRElement;
  "button": HTMLButtonElement;
  "canvas": HTMLCanvasElement;
  "caption": HTMLTableCaptionElement;
  "cite": HTMLElement;
  "code": HTMLElement;
  "col": HTMLTableColElement;
  "colgroup": HTMLTableColElement;
  "data": HTMLDataElement;
  "datalist": HTMLDataListElement;
  "dd": HTMLElement;
  "del": HTMLModElement;
  "details": HTMLDetailsElement;
  "dfn": HTMLElement;
  "dialog": HTMLDialogElement;
  "dir": HTMLDirectoryElement;
  "div": HTMLDivElement;
  "dl": HTMLDListElement;
  "dt": HTMLElement;
  "em": HTMLElement;
  "embed": HTMLEmbedElement;
  "fieldset": HTMLFieldSetElement;
  "figcaption": HTMLElement;
  "figure": HTMLElement;
  "font": HTMLFontElement;
  "footer": HTMLElement;
  "form": HTMLFormElement;
  "frame": HTMLFrameElement;
  "frameset": HTMLFrameSetElement;
  "h1": HTMLHeadingElement;
  "h2": HTMLHeadingElement;
  "h3": HTMLHeadingElement;
  "h4": HTMLHeadingElement;
  "h5": HTMLHeadingElement;
  "h6": HTMLHeadingElement;
  "head": HTMLHeadElement;
  "header": HTMLElement;
  "hgroup": HTMLElement;
  "hr": HTMLHRElement;
  "html": HTMLHtmlElement;
  "i": HTMLElement;
  "iframe": HTMLIFrameElement;
  "img": HTMLImageElement;
  "input": HTMLInputElement;
  "ins": HTMLModElement;
  "kbd": HTMLElement;
  "label": HTMLLabelElement;
  "legend": HTMLLegendElement;
  "li": HTMLLIElement;
  "link": HTMLLinkElement;
  "main": HTMLElement;
  "map": HTMLMapElement;
  "mark": HTMLElement;
  "marquee": HTMLMarqueeElement;
  "menu": HTMLMenuElement;
  "meta": HTMLMetaElement;
  "meter": HTMLMeterElement;
  "nav": HTMLElement;
  "noscript": HTMLElement;
  "object": HTMLObjectElement;
  "ol": HTMLOListElement;
  "optgroup": HTMLOptGroupElement;
  "option": HTMLOptionElement;
  "output": HTMLOutputElement;
  "p": HTMLParagraphElement;
  "param": HTMLParamElement;
  "picture": HTMLPictureElement;
  "pre": HTMLPreElement;
  "progress": HTMLProgressElement;
  "q": HTMLQuoteElement;
  "rp": HTMLElement;
  "rt": HTMLElement;
  "ruby": HTMLElement;
  "s": HTMLElement;
  "samp": HTMLElement;
  "script": HTMLScriptElement;
  "section": HTMLElement;
  "select": HTMLSelectElement;
  "slot": HTMLSlotElement;
  "small": HTMLElement;
  "source": HTMLSourceElement;
  "span": HTMLSpanElement;
  "strong": HTMLElement;
  "style": HTMLStyleElement;
  "sub": HTMLElement;
  "summary": HTMLElement;
  "sup": HTMLElement;
  "table": HTMLTableElement;
  "tbody": HTMLTableSectionElement;
  "td": HTMLTableDataCellElement;
  "template": HTMLTemplateElement;
  "textarea": HTMLTextAreaElement;
  "tfoot": HTMLTableSectionElement;
  "th": HTMLTableHeaderCellElement;
  "thead": HTMLTableSectionElement;
  "time": HTMLTimeElement;
  "title": HTMLTitleElement;
  "tr": HTMLTableRowElement;
  "track": HTMLTrackElement;
  "u": HTMLElement;
  "ul": HTMLUListElement;
  "var": HTMLElement;
  "video": HTMLVideoElement;
  "wbr": HTMLElement;
}