Vue3学习理解
vue3 新特性
整理的比较好的文档:https://www.jianshu.com/p/1fd73091e2e4
直接看代码可以直接看 实践 demo
组合式 API
vue3 中加入了组合式 ,这个功能的作用是将单个 vue 组件的,逻辑部分也能自由拆分组合,更深层次的实现解耦和高复用性
vue2 如果单个 vue 文件逻辑部分过大 ,我们往往 需要单独用一个 js 文件或 ts 文件存放 逻辑(函数)并且为了使用 vue 组件上的响应式属性不得不将 vue 实例传进这个函数中,总有一种怪怪的感觉;
// vue2 单独存放逻辑的文件
export default function handle(vue,xx){
vue.yy = xx ;
....
}
响应式的改变
响应式转换是“深层”的——它影响所有嵌套 property。在基于 ES2015 Proxy 的实现中,返回的 proxy 是不等于原始对象的。建议只使用响应式 proxy,避免依赖原始对象。
- ref 可以用于创建所有数据类型的响应式数据 需要.value 访问 (对象类型数据配合 reactive 使用)
- reactive 只能用于创建对象数据类型的相应式数据 不需要.value 就直接可以访问 深度响应
- toRefs 用于给 prop 添加响应数据,需要.value 访问
基础 api
reactive
返回对象的响应式副本
使用:
可以直接给对象添加任何属性都是响应式的 ,但不能直接赋值对象
let person = reactive({
name: "zhangshan",
age: 18,
});
// 有效
person.sex = "男";
// 无效
person = { ...person, ...{ sex: "男" } };
isReactive
检查对象是否是 reactive 创建的响应式 proxy。
readonly
除了只读以外跟 reactive 一样,但只读也代表了响应式没有意义了
isReadonly
检查对象是否是由 readonly 创建的只读 prox
shallowReactive 和 shallowReadonly
与上面的差别就是指对第一层属性响应,再深度的则不响应
isProxy
检查对象是否是由 reactive 或 readonly 创建的 proxy。
Refs
ref
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property
.value
。也就是相当于解决了 reactive 只能作用对象类型,和只能添加属性不能直接修改对象的不足
对象类型数据配合 reactive 使用
const count = ref(0);
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
// 对象类型
let person = ref(
reactive({
name: "zhangshan",
age: 18,
})
);
person.value = reactive({
name: "lisi",
age: 28,
});
unref
返回使用 ref 响应数据的 value (不是原始数据)
const flag = ref(
reactive({
number: 0,
name: "flag",
})
);
flag.value.number++;
console.log(
unref(flag),
"unref(flag) === flag.value",
unref(flag) === flag.value
); // 1 true
setup(组合式的核心)
定义:
个人理解 :setup 相当于一个盒子 (组合式的核心) ,将
methods
、watch
、computed
、data
的数据都在这里进行组合而
methods
、watch
、computed
、data
的数据,可以分别用不同的 js 文件生成,再导入到 vue 当文件组件中,或 setup 所在文件中
在 Vue3 中,定义
methods
、watch
、computed
、data
数据 等都放在了setup()
函数中 ,(实现了逻辑的拆分)
使用
setup 方法接收两个参数 :
- prop
- context 上下文对象也就是 this (当前 vue 实例)
import { ref, onMounted, watch, toRefs, computed } from "vue";
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true,
},
},
setup(props) {
// 使用 `toRefs` 创建对 props 中的 `user` property 的响应式引用
const { user } = toRefs(props);
const repositories = ref([]);
console.log(props); // { user: '' }
return {}; // 这里返回的任何内容都可以用于组件的其余部分
},
// 组件的“其余部分”
};
更多用法详见:setup.ts
setup 语法糖 <scritp setup>
可以使用<script setup>
标签 代替 setup()
import { ref } from "vue";
export default {
setup() {
const count = ref(0);
const inc = () => count.value++;
return {
count,
inc,
};
},
};
以上代码可以写成下面的代码
<template>
<button @click="inc">{{ count }}</button>
</template>
<script setup>
import { ref } from "vue";
export const count = ref(0);
export const inc = () => count.value++;
</script>
使用限制:
由于模块执行语义的不同,内部代码
<script setup>
依赖于 SFC 的上下文。当移至外部.js
或.ts
文件中时,可能会给开发人员和工具带来混乱。因此,<script setup>
不能与该src
属性一起使用。
个人感觉比较麻烦 还不如正常的使用 setup
Global API 全局 API
createApp
创建整个应用的实例
3.x 添加
createApp
方法用来创建应用的根 防止数据间的相互污染 官方文档地址
应用实例暴露当前全局 API 的子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上,以下是当前全局 API 及其相应实例 API 的表:
2.x 全局 API | 3.x 实例 API (app ) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | removed (见下方) |
Vue.config.ignoredElements | app.config.isCustomElement (见下方) |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use (见下方) |
Vue.prototype | app.config.globalProperties (见下方) |
所有其他不全局改变行为的全局 API 现在被命名为 exports,文档见全局 API Treeshaking。
全局 API Treeshaking 将一部分$xxx 和 指令 按需导入使用
Vue.nextTick() 将废弃 使用 导入方式如下
import { nextTick } from "vue";
nextTick(() => {
// 一些和DOM有关的东西
});
使用 render 语法中 可以使用如下 api 按需导入
import { h, Transition, withDirectives, vShow } from "vue";
export function render() {
return h(Transition, [withDirectives(h("div", "hello"), [[vShow, this.ok]])]);
}
teleport 指定标签父节点
定义
teleport 可以将组件生成的 dom 节点 ,转移到其他 dom 下作为其子节点
让我们修改
modal-button
以使用<teleport>
,并告诉 Vue “Teleport 这个 HTML 到该‘body’标签”。
app.component('modal-button', { template: `
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body"> // teleport 的 to属性就是作为哪个dom的字节的
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`, data() { return { modalOpen: false } } })
Props:
to
-string
。需要 prop,必须是有效的查询选择器(独一无二的类名或 id 或属性)或HTMLElement
(如果在浏览器环境中使用)。指定将在其中移动<teleport>
内容的目标元素
<!-- 正确 -->
<teleport to="#some-id" />
<teleport to=".some-class" />
<teleport to="[data-teleport]" />
<!-- 错误 -->
<teleport to="h1" />
<teleport to="some-string" />
disabled
-boolean
。此可选属性可用于禁用<teleport>
的功能,这意味着其插槽内容将不会移动到任何位置,而是在您在周围父组件中指定了<teleport>
的位置渲染。
<teleport to="#popup" :disabled="displayVideoInline">
<video src="./my-movie.mp4">
</teleport>
请注意,这将移动实际的 DOM 节点,而不是被销毁和重新创建,并且它还将保持任何组件实例的活动状态。所有有状态的 HTML 元素 (即播放的视频) 都将保持其状态。
片段(支持多根节点)
Vue 3 现在正式支持了多根节点的组件,也就是片段!
在 3.x 中,组件可以包含多个根节点!但是,这要求开发者显式定义 attribute 应该分布在哪里。
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
因为 2.x 只有一个根节点 \(attrs 就会绑定到根节点上,现在3.x会有多个根节点,所以需要定义\)attrs 需要绑定到到哪个节点上
v-model
的改变
2.x 使用 input + value 实现 v-model , 3.x 里使用使用 modelValue
( 自己定义字段名与 update:后面的字段一致即可 ,默认值是modelValue
) 作为 prop 和 update:modelValue
作为事件
app.component("my-component", {
props: {
title: String,
},
emits: ["update:title"],
template: ` <input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)">
`,
});
<my-component v-model:title="bookTitle"></my-component>
注意点:v-model 后面需要加对应的 update 后的的字段名 习惯于 2.x 语法后 前几次会忘记加
并且可以使用多个 v-model 只需要在 v-model 后面加上对应的 prop 字段名即可
v-model
修饰符
在 2.x 中 v-model 的修饰符有.trim、
.number和
.lazy,
在 3.x 中我们可以自定义修饰符,比如 v-model:title.capitalize="bar"
在子组件中 prop 中 titleModifiers
中可以接收到 capitalize:ture
再做对应处理后
prop 中修饰符的属性名为 : arg + "Modifiers"
:(绑定 value 字段名 + "Modifiers")
父组件
<HelloWorld v-model:title.capitalize="inputValue" />
子组件
<input type="text" :value="title" @input="handleCapitalize" />
//prop
props: {
title: String,
titleModifiers: { //使用title + Modifiers 获取修饰符对象 { capitalize:ture }
type: Object,
default: () => ({})
}
},
// mothods
handleCapitalize(event) {
let value = event.target.value;
if (this.titleModifiers.capitalize) { // capitalize:ture
value = value.toLocaleUpperCase();
}
this.$emit("update:title", value);
}
如果
v-modle
使用默认值modelValue
时,prop 修饰符对象使用modelModifiers
作为属性名
单文件组件 style 中使用 script 中变量(实验性)
3.x
单文件组件中 style 中可是使用当前vue
实例的 data 数据
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red', // data中的变量在style中都可以使用 使用v-bind (11月10日最新提议)
font: { // 之前的提议是 var(--变量名)
size: '2em'
}
}
}
</script>
<style>
.text {
color: v-bind(color);
/* 使用对象.属性 包在''引号中 */
font-size: v-bind("font.size");
}
</style>
单文件组件样式作用域的变化
<style lang="scss" scoped>
/* deep (css选择器) */
::v-deep(.foo) {}
/*可以在()里写, 在sass less scss 等预编译器中可以写在 {} 中*/
::v-deep() {
.foo{
...
}
}
/* 简写 */
:deep(.foo) {}
/* 作用于插槽 给 slot 加对应 class="foo" 就可以作用到插槽上*/
::v-slotted(.foo) {}
/* 简写 */
:slotted(.foo) {}
/* 在 scoped 中作用全局的样式 功能就 跟不加scoped一样 感觉有点脱裤子放屁的感觉 */
::v-global(.foo) {}
/* 简写 */
:global(.foo) {}
</style>
tip 名词含义
SFC
- 单文件组件
demo ( Vue3 + TypeScript )
直接使用 vue-cil 初始化 vue3 项目
Home.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld v-model:title.capitalize="inputValue">
<p class="default-solt">这里是默认插槽</p>
</HelloWorld>
</div>
</template>
<script lang="ts">
import { ref, defineComponent } from "vue"; // 使用defineComponent创建vue实例
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
export default defineComponent({
name: "Home",
components: {
HelloWorld,
},
created() {
console.log("created");
},
beforeCreate() {
console.log("beforeCreate");
},
setup() {
console.log("setup 在 created 之前");
const inputValue = ref("");
return {
inputValue,
};
},
});
</script>
<style lang="scss" scoped>
// 等价于下面
:deep(.skill) {
color: skyblue;
}
:deep() {
.skill {
color: skyblue;
}
}
// 等价于不加 scoped
:global(.modal) {
& > div {
color: skyblue !important;
}
}
</style>
HelloWorld.vue
<template>
<div class="hello">
<div>
<!-- 展示数据 -->
<div>鼠标x轴:{{ x }}</div>
<div>鼠标y轴:{{ y }}</div>
<span>{{ count }}</span>
<span>双倍:{{ doubelCount }}</span>
<button @click="count++">Increment count</button>
<p>
<input type="text" :value="title" @input="handleCapitalize" />
</p>
<p class="person">
<span>姓名:{{ person.name }}</span>
<span>年龄:{{ person.age }}</span>
</p>
<p class="skill">技能:敲代码</p>
<!-- 打开dialog按钮 -->
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<!-- dialog -->
<teleport to="body">
<div v-show="modalOpen" class="modal">
<div>
I'm a teleported modal! (My parent is "body")
<button @click="modalOpen = false">Close</button>
</div>
</div>
</teleport>
</div>
<!-- 默认插槽 -->
<slot class="default-solt"></slot>
</div>
</template>
<script lang="ts">
import { setup } from "@/components/setup"; // 直接导入 setup
import { defineComponent } from "vue";
export default defineComponent({
name: "HelloWorld",
emits: ["update:title"], // 显式的定义使用了哪些emits
props: {
title: String, // v-model绑定值
titleModifiers: {
// v-model绑定值对应的修饰符对象
type: Object,
default: () => ({}),
},
},
setup,
});
</script>
<style scoped lang="scss">
// 使用script中的变量 color
.person {
color: v-bind(color);
}
// 默认插槽加样式:无效
.default-solt {
color: slateblue !important;
}
// 默认插槽加样式:有效
:slotted(.default-solt) {
color: slateblue !important;
}
// dialog的样式
.modal {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
width: 300px;
height: 300px;
padding: 5px;
}
</style>
setup.ts
import {
ref, // 用于简单数据类型 需要.value 访问
reactive, // 用于复杂数据类型 不需要.value 就直接可以方法
toRefs, // 用于给prop添加响应数据,需要.value 访问
watch,
computed,
onMounted,
onUnmounted,
} from "vue";
export const setup = (props: any, context: any) => {
console.log("Bowen: setup -> props, context", props, context);
//============================================================================
// prop
const { titleModifiers } = toRefs(props);
//============================================================================
// data
const x = ref(0);
const y = ref(0);
const modalOpen = ref(false);
const count = ref(0);
const color = ref("red");
const person = reactive({
name: "Bowen",
age: 18,
});
//============================================================================
// computed
const doubelCount = computed(() => count.value * 2);
//============================================================================
// watch
watch(count, (val) => {
console.log("Bowen: setup -> val", val);
});
//============================================================================
// methods
// 判断 v-model 是否有 capitalize 标识符 转换大写
const handleCapitalize = (event: InputEvent) => {
let value = (event.target as HTMLInputElement).value;
if (titleModifiers.value.capitalize) {
value = value.toLocaleUpperCase();
}
context.emit("update:title", value);
};
// 获取鼠标定位
const getMousePosition = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
//============================================================================
// 生命周期
onMounted(() => {
document.addEventListener("mousemove", getMousePosition);
});
onUnmounted(() => {
document.removeEventListener("mousemove", getMousePosition);
});
return {
x,
y,
modalOpen,
count,
color,
person,
doubelCount,
handleCapitalize,
};
};
export default setup;
vue3 组件库
element-puls
elementUI 官方 bata 版 https://github.com/element-plus/element-plus