寒假学的时候属于是有点过于敷衍了,现在update一下😮💨
最近是跟ts一起学的,所以里面会有ts相关的内容(无伤大雅无伤大雅
内容
1. 创建Vue3.0工程(vue-cli / vite)
- 使用
vue-cli
创建
// 确保@vue/cli版本在4.5.0以上
vue --version
// 安装或者升级你的@vue/cli
npm i -g @vue/cli
// 创建(xxx为项目名)
vue create xxx
// 启动
cd xxx
npm run serve
- 使用
vite
创建
- 什么是vite
新一代前端构建工具 - 优势:
开发环境中无需打包操作,可以快速的冷启动
轻量快速的热重载
真正的按需编译,不再等待整个应用编译完成
// 创建
npm init vite@latest
// 输入项目名
xxx
// 之后可以选择要使用的框架
// vanilla vanilla-ts
// vue vue-ts
// react react-ts
// preact preact-ts
// lit lit-ts
// svelte
// 安装依赖
npm i
// 启动
npm run dev
2. vite构建项目目录
和其他构建工具生成的目录相同的地方就不提了。
最大的一个不同在于vite
项目中的index.html
文件在根目录中,而不是在public
文件夹中,在vite
项目中index.html
作为项目的入口文件,而不同于其他使用js文件作为入口文件
vite.config.js
是vite的配置文件
3. Volar
在学习vue2时,我们在vscode中安装的插件是Vetur
,但是Vetur
对vue3不太友好,vue3的一些语法在Vetur
中会报错。
所以学习vue3时我们会使用Volar
⚠️:Volar
和Vetur
会冲突,使用时要禁用Vetur
第二个提供ts支持:
4. composition api
- vue2中我们使用
option api
,在data中定义数据,在methods中定义方法,在props中设置接受参数…这顺其自然的造成了代码分割,一个功能被分割在多个地方,方法嵌套方法,方法之间共享变量,功能之间互相使用this
导致了强耦合。(代码量一多就是堆成了屎山 - 所以vue3出现了composition api(组合式api),将代码通过功能分块写,一个功能写在一个地方,(代码量多的时候可以配合自定义hooks抽出功能模块,composition api解耦option api实现低耦合高内聚。
5. setup
- vue3中组合式api的一个配置项函数,作为组合式api的入口
- 组件中所用到的:
数据
、方法
均要配置在setup
中 setup
的返回值
:若返回一个对象
,则对象中的属性、方法,均可模板中使用
export default {
setup() {
const name = 'AIpoem'; // data
const sayName = (() => { // method
console.log(name);
})
return {
name,
sayName
}
}
}
- ⚠️:
setup
中不要使用this
,在这里面找不到组件实例,setup
的调用发生在组件创建之前 - ⚠️:不要跟vue2.x配置混用
- vue2.x(data、method…)中可以访问到setup中的属性、方法,但
setup
中无法访问到vue2.x(data、method…) setup
不能是一个async
函数,否则其返回值
不再是return
的对象,而是promise
- vue2.x(data、method…)中可以访问到setup中的属性、方法,但
5.1 setup的两个参数
setup
函数接受两个参数:
props
context
5.1.1 props
props
是一个响应式对象
,用于接收传参
但要注意不能使用解构拿到参数,会消除prop的响应性
解构时使用toRef
或toRefs
export default {
props: {
title: String
},
setup(props) {
console.log(props.title);
// 错误写法❌
const { title } = props;
// 以下两种写法✅
const { title } = toRef(props, 'title');
const title = toRefs(props)
}
}
5.1.2 context
context
是一个普通的对象,暴露一些可能有用的值
export default {
setup(props, context) {
// 值为对象,包含了:组件外部传递过来,但没有在props配置中声明的属性
// 相当于vue2中的this.$attrs
console.log(context.attrs)
// slots: 收到的插槽内容
// 相当于vue2中的this.$slots
console.log(context.slots)
// 分发自定义事件的函数
// 相当于vue2中的this.$emit
console.log(context.emit)
// 可以用expose暴露一些属性和方法,这些变量和方法可以被外部访问到
console.log(context.expose)
}
}
因为context
不是响应式的,所以可以直接对其进行解构
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
5.1.2.1 expose
setup return
的所有属性和方法,父组件都能通过ref访问到
但如果我们希望一些属性方法能不被父组件访问,可以定义在expose
中,表明只允许外部使用这些属性和方法,未写在expose
的属性和方法相当于变成当前组件的私有,外部访问不到
expose
的使用:
给它传一个对象, 里面放希望外部能访问的属性和方法
import { ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const add = () => ++count.value
// 这个add方法现在将可以通过父组件的模板 ref 访问
expose({
add
})
return {
count,
add
}
}
}
5.2 setup语法糖
vue3.2 新增setup语法糖<script setup>
(后面的我应该都会用语法糖了,比较爱😮💨
5.2.1 不用再return啦
无需return{}
,声明的变量、函数、import的内容都可以直接在template
中使用
(export default{}
也不需要写)
<script setup>
import { getX } from './utils'; //import引入的内容
const name = 'AIpoem'; // 变量
const sayName = (() => { // 函数
console.log(name);
})
</script>
<template>
<div @click="sayName">{{ name }}</div>
<div>{{ getX() }}</div>
</template>
⚠️:<script setup>
中的代码都会被编译成setup()
函数里的内容,这意味着<script setup>
和<script>
不同,<script setup>
里的代码会在每次组件实例被创建
的时候执行,<script>
只在组件被首次引入时执行一次
<script>
console.log('script'); // 多次实例化组件,但只触发一次
export default {
setup() {
console.log('setup'); // 每次实例化组件都触发和<script setup>一样
}
}
</script>
5.2.2 引入的组件无需注册
<script setup>
中import
的组件不用在components: {}
中注册,可以直接在template
中使用
5.2.3 defineProps、defineEmits、defineExpose API
setup
在这不是函数了,那怎么拿到props
和emits
?怎么使用expose
?
必须使用defineProps
、defineEmits
、defineExpose
API
vue3.2中并不需要引入 这些可以直接用
defineProps
替代props
,接收父组件传递的数据:
(父组件正常传就ok)
模版中不需要<script setup lang="ts"> // 仅限于ts的功能,类型声明 defineProps<{ title: string }>(); // 使用defineProps没有给props提供默认值的方式,我们如果需要可以使用withDefaults interface Props { title: string, age: number[] } withDefaults(defineProps<Props>(), { title: 'AIpoem', age: () => [10, 20] }) // 以下是不使用ts的写法 defineProps({ title: String }); // 简易写法 defineProps(['title', ...]); // 如果需要在script中使用这个prop,可以接收 const { title } = defineProps({ title: String }); </script>
props.xx
,可以直接使用<template> <div>父组件传递的值:{{title}}</div> </template>
defineEmits
替代emits
,用于子组件向父组件传递数据:<script setup lang="ts"> import { ref } from 'vue' // 要传递的数据 const name = ref<string>('AIpoem') // 触发的事件名称,传递的数据,返回值 const emits = defineEmits<{ (e: "send-name", name: string): void }>(); // 以下是不使用ts的写法 const emits = defineEmits(['send-name']); const toEmits = () => { // 触发父组件中暴露的send-name方法并携带数据 emits('send-name', name) } </script>
defineExpose
替代expose
,并且<script setup>
默认是封闭的,这就和setup()
默认暴露组件的全部内容是不同的,希望外部访问的属性和方法必须显式expose
出去
// 子组件 <script setup> import { ref, defineExpose } from 'vue' const name = ref('AIpoem') const b = ref(2) //主动暴露组件属性 defineExpose({ name }) </script>
<!-- 父组件 --> <template> <Child ref='childRef' /> <button @click='getChildData'>通过ref获取子组件的属性</button> </template> <script setup> import { ref } from 'vue' import Child from './child.vue' const childRef = ref() // 注册响应数据 const getChildData =()=>{ // 子组件接收暴露出来得值 console.log(childRef.value.name) //1 } </script>
6. 父组件调用子组件的方法
(补充一下
对应vue2中的this.$refs.xxx
<!-- 子组件 -->
<script setup lang="ts">
import { reactive } from "vue";
const list = reactive<number[]>([1, 2, 3]);
// 把list暴露给父组件
defineExpose({
list
});
</script>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import Child from "./components/Child.vue";
const c1 = ref();
// setup中dom还未挂载,获取会是undefined❌
console.log(c1.value);
// 可以获取到
onMounted(() => {
// Proxy {list: Proxy, __v_skip: true}
console.log(c1.value);
})
</script>
<template>
<child ref="c1"></child>
</template>
7. ref
- 接收一个值,返回一个
响应式
的ref对象
ref对象
只有一个value
属性,指向内部值- 在
js
中操作数据:xxx.value
,在template
中读取数据:<div>{{ xxx }}</div>
<script setup lang="ts">
import { ref } from "vue";
let message = ref<string>("poem");
const changeMsg = () => {
message.value = "change poem";
};
</script>
<template>
<div>{{ message }}</div>
</template>
- ⚠️:
接收的数据可以是基本类型,也可以是对象类型- 基本类型的数据:响应式是依然通过
Object.defineProperty()
的get
和set
完成的 - 对象类型的数据:内部求助了vue3.0的一个新函数——
reactive函数
- 基本类型的数据:响应式是依然通过
8. shallowRef
shallowRef
只保证自身响应式,不保证对象里属性的响应式
也就是说这个ref
是浅层的
<script setup lang="ts">
import { shallowRef } from "vue";
let message = shallowRef<{ name?: string }>({
name: "poem",
});
const changeMsg = () => {
// 里面的属性不具有响应式,以下这样是不具有响应式的
// message2.value.name = "change poem";
// 正确写法:可以这样整个对象重新赋值
message.value = { name: "change poem" };
console.log(message);
};
</script>
9. triggerRef
手动执行与 shallowRef
关联的任何作用
可以让shallowRef
做到属性也能变成响应式的
<script setup lang="ts">
import { shallowRef, triggerRef } from "vue";
let message = shallowRef<{ name?: string }>({
name: "poem",
});
const changeMsg = () => {
message.value.name = "change poem";
triggerRef(message2);
};
</script>
10. customRef
创建一个自定义的ref
必须传一个工厂函数,有track
和trigger
两个参数
<script setup lang="ts">
import { customRef } from "vue";
// <T>指定泛型
function useMyRef<T>(value: T) {
return customRef((track, trigger) => {
return {
get() {
track();
return value
},
set(newValue: T) {
console.log('设置了新value:', newValue);
value = newValue;
trigger();
}
}
})
}
let message = useMyRef<string>('poem')
const changeMsg = () => {
message.value = 'new poem'
// 打印:设置了新value: new poem
};
</script>
参考博文:
script-setup
https://juejin.cn/post/7083401842733875208