Vue3快速入门学习
初始化项目
# 创建项目
npm create vite@latest my-vue-app -- --template vue
# 安装依赖
npm i
# 运行
npm run dev
模板语法
文本插值
最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):
<span>Message: {{ msg }}</span>
双大括号标签会被替换为相应组件实例中 msg
属性的值。同时每次 msg
属性更改时它也会同步更新。
原始 HTML
双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令:
<template>
{{ num }}
<span>Message: {{ msg }}</span>
<p>Using text interpolation: {{ html }}</p>
<p>Using v-html directive: <span v-html="html"></span></p>
</template>
<script setup>
let num = 123
let msg = "(*´▽`)ノノ"
let html = '<span style="color: red">This should be red.</span>'
</script>
Attribute 绑定
双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind 指令
<div v-bind:id="dynamicId"></div>
<input v-bind:value="dynamicId">
let dynamicId=1
v-bind
指令指示 Vue 将元素的 id
attribute 与组件的 dynamicId
属性保持一致。如果绑定的值是 null
或者 undefined
,那么该 attribute 将会从渲染的元素上移除。
简写
因为 v-bind
非常常用,我们提供了特定的简写语法:
<div :id="dynamicId"></div>
<input v-bind:value="dynamicId"><br>
<input :value="dynamicId">
开头为 :
的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。此外,他们不会出现在最终渲染的 DOM 中。简写语法是可选的,但相信在你了解了它更多的用处后,你应该会更喜欢它。
接下来的指引中,我们都将在示例中使用简写语法,因为这是在实际开发中更常见的用法。
布尔型 Attribute
布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上。disabled 就是最常见的例子之一。
v-bind
在这种场景下的行为略有不同:
<button :disabled="isButtonDisabled">Button</button>
<button :disabled="!isButtonDisabled">Button</button>
当 isButtonDisabled
为真值或一个空字符串 (即 <button disabled="">
) 时,元素会包含这个 disabled
attribute。而当其为其他假值时 attribute 将被忽略。
动态绑定多个值
如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
通过不带参数的 v-bind,你可以将它们绑定到单个元素上:
<div v-bind="objectOfAttrs"></div>
<div v-bind="objectOfAttrs">
233
</div>
使用 JavaScript 表达式
至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
let number = 1
let ok = 1
let message = '如何快速进行多任务切换:多任 务切换的关键在 于能不能在一 段 时间内集 中火力, 把一件事做好,再去做下一件事。如果已经定下具 体的目标,一切的工具都只是辅助 ,必要时可以全都 舍弃掉。 '
let id = 1
这些表达式都会被作为 JavaScript ,以当前组件实例为作用域解析执行。
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
-
在文本插值中 (双大括号)
-
在任何 Vue 指令 (以
v-
开头的特殊 attribute) attribute 的值中
仅支持表达式
每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return
后面。
因此,下面的例子都是无效的:
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}
<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}
调用函数
可以在绑定的表达式中使用一个组件暴露的方法:
<time :title="toTitleDate(date)" :datetime="date">
{{ formatDate(date) }}
</time>
TIP
绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。
受限的全局访问
模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math
和 Date
。
没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window
上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。
指令 Directives
指令是带有 v-
前缀的特殊 attribute。Vue 提供了许多内置指令,包括上面我们所介绍的 v-bind
和 v-html
。
指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-for
、v-on
和 v-slot
)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 v-if 为例:
<p v-if="seen">Now you see me</p>
这里,v-if
指令会基于表达式 seen
的值的真假来移除/插入该 <p>
元素。
参数 Arguments
某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind
指令来响应式地更新一个 HTML attribute:
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>
这里 href 就是一个参数,它告诉 v-bind 指令将表达式 url 的值绑定到元素的 href attribute 上。在简写中,参数前的一切 (例如 v-bind:) 都会被缩略为一个 : 字符。
另一个例子是 v-on 指令,它将监听 DOM 事件:
<a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>
这里的参数是要监听的事件名称:click
。v-on
有一个相应的缩写,即 @
字符。我们之后也会讨论关于事件处理的更多细节。
动态参数
同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
这里的 attributeName
会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName
,其值为 "href"
,那么这个绑定就等价于 v-bind:href
。
相似地,你还可以将一个函数绑定到动态的事件名称上:
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething">
在此示例中,当 eventName
的值是 "focus"
时,v-on:[eventName]
就等价于 v-on:focus
。
动态参数值的限制
动态参数中表达式的值应当是一个字符串,或者是 null
。特殊值 null
意为显式移除该绑定。其他非字符串的值会触发警告。
动态参数语法的限制
动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>
如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,也是 Vue 最基础的概念之一,我们很快就会讲到。
当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:
<a :[someAttr]="value"> ... </a>
上面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]
。如果你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。单文件组件内的模板不受此限制。
修饰符 Modifiers
修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent
修饰符会告知 v-on
指令对触发的事件调用 event.preventDefault()
:
<form @submit.prevent="onSubmit">...</form>
之后在讲到 v-on 和 v-model 的功能时,你将会看到其他修饰符的例子。
最后,在这里你可以直观地看到完整的指令语法:
总结
<template>
<h3>{{ text }}</h3>
<h3>{{ user }}</h3>
<!--
指令 v-
v-model 数据双向绑定
v-if 判断表达式的值,true则显示,false则隐藏 -- 控制dom元素的创建和销毁,应避免频繁切换状态
v-show 和v-if区别 -- 始终会被渲染并保留在dom中,只是css被隐藏了 "display: none;" 一次性的
v-for 循环
v-bind 绑定属性或对象
v-on 注册事件
-->
<input v-model="user.age" />
<span v-if="user.age == 18">成年人:{{ user.age }}</span>
<span v-else-if="user.age < 18">未成年</span>
<span v-else>长大了...</span>
<span v-show="user.age >= 18">
和v-if区别:始终会被渲染并保留在dom中,只是css被隐藏了 "display: none;"
</span>
<!-- <button v-bind:disabled="true">v-bind</button> -->
<button :disabled="true">v-bind</button>
<h6
v-for="(friends,index) in user.friends"
:key="index"
style="color: rgb(70, 238, 146)"
>
{{ friends.name }} - {{ friends.age }} - {{ index }}
</h6>
<!-- <button v-on:click="addFriend">添加好友</button> -->
<button @click="addFriend">添加好友</button>
<button @click="deleteFriend">删除</button>
<p :class="{ active: isActive }">Class 与 Style 绑定</p>
<div v-for="i in 5">{{i}}</div>
</template>
<script setup>
import { ref, reactive } from "vue";
const text = ref("HelloWorld");
const isActive = ref(true);
const user = reactive({
name: "小郑",
age: 18,
friends: [
{
name: "小张",
age: 18,
},
{
name: "小李",
age: 20,
},
{
name: "张三",
age: 201,
},
],
});
function addFriend() {
user.friends.push({
name: "哈基米",
age: 18,
});
}
function deleteFriend() {
user.friends.pop()
}
</script>
<style scoped>
.active {
color: rgb(134, 17, 250);
}
</style>
ref 和 reactive 是 Vue 3 中用于管理数据的两个方法。
ref 用于创建一个可响应的指针。指针可以指向任何值,包括原始值、对象或数组。当指针指向的值发生变化时,Vue 会自动更新指针的值。
reactive 用于创建一个响应式的对象。对象的所有属性都将成为响应式变量。当对象的属性发生变化时,Vue 会自动更新该属性的值。
- text 是一个 ref 对象,指向字 符串 "HelloWorld"。当字符串发生变化时,text 的值也会发生变化。
- isActive 也是一个 ref 对象,指向布尔值 true。当布尔值发生变化时,isActive 的值也会发生变化。
- user 是一个 reactive 对象,包含三个属性:
name
、age
和friends
。当这些属性发生变化时,user 对象的值也会发生变化。
使用场景
ref 通常用于以下场景:
- 需要在组件中使用原始值、对象或数组时。
- 需要监听值的变化时。
reactive 通常用于以下场景:
- 需要在组件中使用对象时。
- 需要监听对象属性的变化时。
导入其他vue组件
<template>
<HelloWorld />
</template>
<script setup>
import HelloWorld from "./components/HelloWorld.vue";
</script>
响应式API
<template>
<h3>{{ count }}</h3>
<h3>{{ data }}</h3>
<hr />
<h3>{{ getStr }}</h3>
<h3>{{ getStr2 }}</h3>
<input type="text" v-model="getStr2" />
<hr />
<h3>name:{{ name }}</h3>
<button @click="changeData">修改</button>
</template>
<script setup>
import { ref, reactive, computed, watch, watchEffect, toRefs } from "vue";
// ref:定义基本数据类型的响应式数据
var count = ref(0);
// reactive:定义“数组/对象/map”复杂数据类型的深层响应式数据,shallowReactive:浅层响应式(只保留对这个对象顶层次访问的响应性)
const data = reactive({
name: "小王",
age: 18,
girlfriends: [{ name: "小张" }],
});
// toRefs:解构响应式,没有的话,无法修改name值,修改的时候使用 name.value 修改
const { name } = toRefs(
reactive({
name: "小郑",
})
);
// computed:计算属性
const str = ref("hello");
const getStr = computed(() => {
console.log("getStr计算属性执行了...");
return str.value;
});
// 如果要修改计算属性值,上面的方式会报错 Write operation failed: computed value is readonly
// 使用下面的方式
const getStr2 = computed({
get() {
console.log("getStr2计算属性执行了...");
return str.value;
},
set(val) {
str.value = val;
},
});
// watch:监听器
watch(
// count, // ref
// () => geoObj.value.lng, // ref 对象中的某一个属性值
data, // reactive
// () => data.age, // reactive 对象中的某一个属性值
// [count, data], // 监听多个数据
// () => props.list, // 监听defineProps中的数据
// () => proxy.$router.currentRoute.value, // 监听路由变化
(newValue, oldValue) => {
console.log("监听器执行了... ", newValue, oldValue);
},
{
immediate: true, // 初始化执行一次
// deep: true, // 深度监听 -- eg: 监听数组里面的数据变更
}
);
// watchEffect:副作用函数,里面涉及到的属性有变更就会被触发执行
watchEffect(() => {
console.log("watchEffect执行了... ", data.age);
});
function changeData() {
count.value++;
data.age++;
data.girlfriends = [{ name: "小甜" }, { name: "小李" }];
data.girlfriends.push({ name: "哈基米" });
name.value = "小郑变了"+count.value;
str.value += "1";
}
</script>
<style scoped>
.active {
color: rgb(134, 17, 250);
}
</style>
toRefs: toRefs是一个方法,它可以把一个响应式对象转换成普通对象,同时保留它的响应性。
比如有一个响应式对象:
const state = reactive({ foo: 1, bar: 2 })
如果我们解构它:
const { foo, bar } = state
那么foo和bar就失去了响应性。
但是如果我们使用toRefs:
const state = reactive({ foo: 1, bar: 2 });
const { foo, bar } = toRefs(state);
这样foo和bar仍然是响应式的!
所以toRefs的作用就是让解构后的变量也保持响应式。
computed: computed是一个方法,它可以创建一个计算属性。计算属性会根据它的依赖进行缓存和更新。监听一个值
比如:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
count.value++
console.log(plusOne.value) // 3
这里plusOne会跟踪count的变化并实时更新自己的值。
所以computed的作用就是创建一个有缓存的属性,避免重复计算。
相同效果
import { ref, reactive, computed, watch, watchEffect, toRefs } from "vue";
const state = reactive({ foo: 1, bar: 2 });
function changeData() {
state.foo++
state.bar++
}
const state = reactive({ foo: 1, bar: 2 });
const { foo, bar } = toRefs(state);
function changeData() {
foo.value++
bar.value++
}
解决ref大量引入问题
unplugin-auto-import插件
解决 import { ref , reactive ..... } from 'vue'
大量引入的问题
配置后可以不用引入,直接使用
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm i -D unplugin-auto-import
vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ["vue", "vue-router"],
}),
],
});
$ref语法糖 告别 .value
一、配置
法一
vue3.4版本之后废除
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
reactivityTransform: true, // 启用响应式语法糖 $ref $computed $toRef ...
})
]
})
法二
https://vue-macros.sxzz.moe/zh-CN/features/reactivity-transform.html
tips: store(pinia版) 中使用 $ref 无法正常持久化数据!!!
cnpm i -D @vue-macros/reactivity-transform
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ReactivityTransform from '@vue-macros/reactivity-transform/vite';
export default defineConfig({
plugins: [
vue(),
ReactivityTransform(), // 启用响应式语法糖 $ref ...
]
})
解决ESLint警告: '$ref' is not defined.
.eslintrc.cjs
module.exports = {
globals: { $ref: 'readonly', $computed: 'readonly', $shallowRef: 'readonly', $customRef: 'readonly', $toRef: 'readonly' },
};
二、测试
原本 .value 响应式
<template>
<h1>{{ count }}</h1>
<button @click="handleClick">click</button>
</template>
<script setup>
let count = ref(0);
function handleClick() {
count.value++;
}
</script>
现在 $ref 去除 .value
<template>
<h1>{{ count }}</h1>
<button @click="handleClick">click</button>
</template>
<script setup>
let count = $ref(0);
function handleClick() {
count++;
}
</script>
三、注意事项
$ref 在以下情况无法直接使用
store pinia
watch 监听器
组件通信
通信方式:
- props:父传子(子组件接收的数据只读)
- emit:子传父
- provide/inject:跨代传值
- vuex/pinia:跨组件传值
- 全局事件总线(mitt 或 tiny-emitter):不推荐使用
- v-model
- refs
一、props & emit
- 父组件:
:list
传值 - 子组件:
defineProps
接收父组件值,defineEmits
调用父组件方法传值到父组件中 eg: proxy.$emit('handle-succ', data);
父组件 https://cn.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
<template>
<h3>{{ count }}</h3>
<p>子组件:</p>
<hello :list=list2 @add="handleAdd"/>
</template>
<script setup>
import {ref} from "vue";
import hello from "./components/hello.vue";
var list2 = [{name: '小郑'}, {name: '张三'}]
const count = ref(0);
function handleAdd(data) {
console.log(data);
count.value++;
}
</script>
<style scoped></style>
子组件
<template>
<h3>{{ list }}</h3>
<button @click="handleSubmit">提交数据</button>
</template>
<script setup>
// 父传子
const props = defineProps({
list: {
type: Array,
required: false,
default: () => [],
},
});
// 子传父
const emits = defineEmits(["add"]);
function handleSubmit() {
emits("add", "child");
}
// 或直接通过 proxy.$emit("add", "child");
</script>
<style scoped></style>
二、v-model 父子组件双向绑定
- 父组件:
v-model
- 子组件:
props
接收父组件值modelValue
,proxy.$emit("update:modelValue", 666);
传值
tips: 如果父组件是
v-model:num
,那么子组件的modelValue
变更为num
uniapp中可通过 props 来获取页面参数 (tips:子组件内无法通过这种方式获取到路径参数!) eg: /pages/index/index?code=xxx
==> const props = defineProps({ code: { type: String, required: false } });
父组件
<template>
<h3>父组件:{{ data }}</h3>
<HelloWorld ref="helloRef" v-model="data" />
</template>
<script setup>
import HelloWorld from "./components/HelloWorld.vue";
const data = ref(0);
</script>
<style scoped></style>
子组件
<template>
<h3>子组件:{{ modelValue }}</h3>
<button @click="changeData">click</button>
</template>
<script setup>
const { proxy } = getCurrentInstance();
const props = defineProps({
modelValue: {
type: Number,
required: false,
default: () => 0,
},
});
function changeData() {
proxy.$emit("update:modelValue", 666);
}
</script>
<style scoped></style>
三、provide/inject:跨代传值
父组件
import { provide } from 'vue'
provide('msg', xxx)
子子组件
import { inject } from 'vue'
const msg = inject('msg')
const msg = inject('msg', 'hello') // 没值的话使用默认值hello
父组件调用子组件方法
如果父组件想要调用子组件的方法
- 子组件为选项式api 可以在父组件中使用 `proxy.$refs.helloRef.changeData();` 调用
- 子组件为组合式api 需在子组件中使用 `defineExpose` 暴露需要调用的方法
父组件
```
<template>
<HelloWorld ref="helloRef" />
<button @click="handleClick">click</button>
<br />
<button @click="$refs.helloRef.changeData()">调用子组件方法</button>
</template>
<script setup>
const { proxy } = getCurrentInstance();
import HelloWorld from "./components/HelloWorld.vue";
function handleClick() {
proxy.$refs.helloRef.changeData();
}
</script>
<style scoped></style>
```
子组件
```
<template>
<h3>count:{{ count }}</h3>
</template>
<script setup>
const count = ref(0);
function changeData() {
count.value++;
}
// 暴露方法
defineExpose({
changeData,
});
</script>
<style scoped></style>
```
子组件调用父组件方法
### 父组件
```
<template>
<HelloWorld @ok="handleOk" />
</template>
<script setup>
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
function handleOk(data) {
console.log(data);
}
</script>
<style scoped></style>
```
### 子组件
法一:
```
<script setup>
const { proxy } = getCurrentInstance();
function changeData() {
proxy.$emit('ok', 'hello');
}
</script>
```
法二:
```
<script setup>
const { proxy } = getCurrentInstance();
const emits = defineEmits(["ok"]);
function changeData() {
emits("ok", "hello");
}
</script>
```
插槽
这是一个使用 Vue 3 的组件插槽(slots)的示例。
<template>
标签内定义了一个名为 HelloWorld
的组件。
这个组件有以下几种类型的插槽:
具名插槽(Named Slots)
<slot name="left">
<slot name="right">
使用方法:
<template v-slot:left>...</template>
<template #right>...</template>
作用域插槽(Scoped Slots)
<slot :data="item">
可以将组件内的数据(item)传递给插槽。
使用方法:
<template #default="{ data }">...</template>
动态插槽(Dynamic Slots)
<slot name="dynamic">
插槽的名称可以动态绑定。
使用方法:
<template #\[xx\]>...</template>
这里的 xx 可以是一个变量。
主要功能是组件内通过不同类型的插槽向组件外暴露数据或标记点,以便组件外插入自定义内容。
这提高了组件的灵活性和可复用性。
Vue 的插槽(slot)允许组件内注入自定义内容。它的工作原理可以简单概括为:
组件内部使用 <slot> 标签作为插槽容器并定义名称
<div class="component">
<slot name="header">默认内容</slot>
<div class="content">...</div>
<slot name="footer"></slot>
</div>
组件外部使用特殊的 template 语法插入自定义内容到插槽中
<MyComponent>
<template v-slot:header>
<!-- 自定义header -->
</template>
<template v-slot:footer>
<!-- 自定义footer -->
</template>
</MyComponent>
Vue 渲染组件时:
如果有插入内容,则替换 <slot> 对应位置为自定义内容
如果没有插入内容,则保留 <slot> 的默认内容
所以,插槽允许用户决定组件内部的某些部分应显示什么内容,提高了组件的灵活性和可复用性。
同时为了模块化,插槽内容和组件其他部分相互独立。这就是插槽的工作原理。
作用域插槽(Scoped Slots)
在组件中,可以为插槽传递数据:
<slot :user="user">
{{ user.lastName }}
</slot>
组件使用方可以接收传递的数据并自定义插槽内容:
<MyComponent>
<template #default="slotProps">
{{ slotProps.user.firstName }}
</template>
</MyComponent>
这样就实现了从组件内部将数据传递给插槽。
动态插槽(Dynamic Slots)
插槽名可以是动态绑定的:
<slot :name="slotName">...</slot>
使用模板引用作为插槽名:
<MyComponent>
<template #[slotName]>
...自定义内容
</template>
</MyComponent>
好的,举两个例子说明 Vue 插槽的高级用法:
slotName 可以是一个变量,这样插槽名就可以动态变化了。
这种方式非常灵活,允许根据条件自定义不同的插槽内容。
所以这两种机制都大大提高了插槽的可扩展性。
父组件:
<template>
<HelloWorld>
<!-- <template v-slot:left>具名插槽-left</template>-->
<template v-slot:left></template>
<template #right>具名插槽-right</template>
<template #default="{ data }">作用域插槽:{{ data }}</template>
<template #[xx]>动态插槽</template>
</HelloWorld>
</template>
<script setup>
import HelloWorld from "./components/HelloWorld.vue";
let xx = ref("dynamic"); // 这里可以随时变 dynamic/left => xx
</script>
<style scoped></style>
子组件:
<template>
<h3>hello</h3>
<slot name="left"><h1>具名插槽-left</h1></slot>
<br />
<slot name="right"></slot>
<div v-for="item in list" :key="item.id">
<slot :data="item"></slot>
<!-- 作用域插槽可回调值给父组件使用 <template #right-show="{ isShow }"></template> -->
<!-- <slot name="right-show" :is-show="isShowRightMenu" /> -->
</div>
<slot name="dynamic"></slot>
</template>
<script setup>
let list = ref([
{ id: 1, name: "小张" },
{ id: 2, name: "小李" },
]);
</script>
<style scoped></style>
生命周期钩子
<template>
<h3>hello</h3>
<button id="count" @click="count++">{{ count }}</button>
</template>
<script setup>
console.log(111);
onMounted(() => {
console.log(222);
});
const count = ref(0)
onUpdated(() => {
// 文本内容应该与当前的 `count.value` 一致
// console.log(document.getElementById('count').textContent)
console.log(count.value)
})
</script>
<style scoped></style>
1. setup()
```
// 组件初始化时执行,通常用于定义数据、方法等
setup(){
// 代码逻辑
}
```
2. onBeforeMount()
```
// 在组件渲染到页面之前执行
onBeforeMount(){
// 获取渲染前的 DOM 状态
}
```
3. onMounted()
```
// 组件渲染 complet 后执行
// 可以获取到 DOM 元素
onMounted(){
// 已经可以通过 ref 获取到 DOM
}
```
4. onBeforeUpdate()
```
// 数据更新前执行
onBeforeUpdate(){
// 可以在这里获取更新前的状态
}
```
5. onUpdated()
```
// 数据更新后执行(在组件的任意 DOM 更新后被调用)
onUpdated(){
// 组件重新渲染完成
}
```
这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行(考虑到性能因素)。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!
6. onBeforeUnmount()
```
// 组件销毁前执行
onBeforeUnmount(){
// 清除定时器、事件监听等
}
```
这些钩子函数可以让我们在不同阶段执行自定义逻辑,非常实用。
Teleport
https://cn.vuejs.org/guide/built-ins/teleport.html
可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
<template>
<div class="body"></div>
<button @click="open = open^1">Open Modal</button>
<Teleport to=".body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
</template>
<script setup>
let open = ref(false);
</script>
<style scoped>
.body {
width: 200px;
height: 200px;
background-color: #0ee757;
}
.modal {
background-color: #1789ca;
}
</style>
动态组件
有些场景会需要在两个组件间来回切换,比如 Tab 界面
<template>
<div class="home">
<ul>
<li v-for="(item, index) in tabList" :key="index" @click="change(index,item)">
{{ item.name }}
</li>
</ul>
<component :is="currentComponent.com"></component>
</div>
</template>
<script setup>
// import { reactive } from "vue";
import A from "./components/A.vue";
import B from "./components/B.vue";
let tabList = reactive([
{ name: "显示组件A", com: markRaw(A) },
{ name: "显示组件B", com: markRaw(B) },
]);
let currentComponent = reactive({
com: A,
});
const change = (index,item) => {
console.log('index',index,'item',item.name)
currentComponent.com = tabList[index].com;
};
</script>
<template><h1>A组件</h1></template>
<template><h2>B组件</h2><button >按钮B</button></template>
这段代码实现了一个 tab 切换不同组件的功能。
主要逻辑是:
1. 定义了一个 tabList 数组,里面是不同的 tab 项,每个项包含 name 和 com 两个属性。
- name:tab 的名称
- com:对应的组件,使用 markRaw 包装过可以直接当组件使用
2. 定义一个当前组件 currentComponent,里面有一个 com 属性,默认设置为组件 A。
3. 使用 v-for 渲染 tab 列表,当点击不同的 tab 时,调用 change 方法。
4. change 方法中,根据点击的 tab 索引,设置 currentComponent.com 为对应的组件,这样就切换了当前的组件。
5. 使用 component 标签,动态绑定 currentComponent.com,所以就可以渲染不同的组件了。
这样通过 tab 切换,动态渲染不同的组件,实现了组件的动态切换。
举个例子,默认情况下渲染 A 组件,当我们点击“显示组件 B”这个 tab 时,就会切换到 B 组件。
所以这个例子实现了一个通过 tab 动态切换组件的功能。
在 `<component>` 组件中,`:is` 是用于指定动态组件的属性。
`:is="currentComponent.com"` 表示动态绑定 currentComponent.com 中指定的组件,并渲染该组件。
一些关键点:
- `<component>` 是 Vue 提供的一个内置组件,用于动态渲染组件
- `:is` 特殊 attribute,可以用来指定要动态渲染的组件
- 其值 `currentComponent.com` 需要是一个组件对象,这里通过引入并 `markRaw` 包装了组件满足要求
- 当 `currentComponent.com` 更新时,`:is` 也会动态更新,实现组件切换
所以简单说,`:is` 指定了 `<component>` 需要渲染的目标组件。其动态性允许我们实现组件的动态切换。
这样通过 `currentComponent.com` 和 `:is` 的配合,我们就可以灵活地切换不同的组件了。
发送数据,接收数据更新网页
<div class="container">
<table>
<thead>
<tr>
<th>标题</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.title }}</td>
<td>{{ item.description }}</td>
</tr>
</tbody>
</table>
</div>
这两个代码示例的主要区别在于使用了不同的 HTTP 客户端来获取数据:
1. 使用 axios 获取数据:
```
const response = await axios.get('http://127.0.0.1:8000/api/data');
items.value = response.data;
```
axios 是一个流行的 HTTP 客户端,它会自动为我们转换响应数据到 JSON。所以我们可以直接通过 `response.data` 来访问 JSON 数据。
2. 使用原生 fetch 获取数据:
```
const response = await fetch('http://127.0.0.1:8000/api/data');
const data = await response.json();
items.value = data;
```
fetch 是浏览器原生提供的 API。它获取到的响应体是一个流数据,我们需要通过调用 `response.json()` 方法来自己把它转换成 JSON 对象。然后才能保存到 `data` 变量中,最后赋值给 `items.value`。
总结主要区别:
- axios 会自动转换响应数据,fetch 需要手动转换
- axios 是第三方库,fetch 是原生 API
- 语法上 axios 更简单直观,fetch 需要额外转换步骤
所以对于大多数用例来说,使用 axios 会更加便捷。但 fetch 作为原生 API 也有其优势,比如体积更小,兼容性更好。
vue-element-admin介绍
vue-element-admin是一个后台前端解决方案,它基于vue和element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。
目录结构
一上来就看到那么多文件夹确实头疼,咱先不管别的,主要先了解标注部分文件
├── build # 构建相关
├── config # 配置相关
├── mock # 项目mock 模拟数据
├── plop-templates
├── public # 静态资源
│ ├── favicon.ico
│ └── index.html
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── directive # 全局指令
│ ├── filters # 全局 filter
│ ├── icons # 项目所有 svg icons
│ ├── lang # 国际化 language
│ ├── layout # 布局相关
│ ├── mock # 项目mock 模拟数据
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # view
│ ├── App.vue # 入口页面
│ ├── main.js # 入口 加载组件 初始化等
│ └── permission.js # 权限管理
├── tests
├── static # 第三方不打包资源
│ └── Tinymce # 富文本
├── .babelrc # babel-loader 配置
├── eslintrc.js # eslint 配置项
├── .gitignore # git 忽略项
├── favicon.ico # favicon图标
├── index.html # html模板
├── .env.xxx
├── .eslintrc.js
├── .travis.yml
├── vue.config.js # vue-cli 配置
└── package.json
安装
# 克隆项目
git clone https://github.com/PanJiaChen/vue-element-admin.git
# 进入项目目录
cd vue-element-admin
# 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 安装依赖
npm install
# 本地开发 启动项目
npm run dev
跨域配置
新建一下request.js文件,修改如下
import request from '@/utils/request_new'
export function system_status() {
return request({
url: '/api/system_status',
method: 'get'
})
}
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// create an axios instance
const service = axios.create({
baseURL:'http://127.0.0.1:8003', // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000, // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
return res
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
参考资料:手摸手,带你用vue撸后台 系列一(基础篇) - 掘金
vue-element-admin 跨域配置 - CSDN文库
vue配置多个服务端请求地址(使用vue-admin-template举例说明)_vue-admin-template改变请求地址-CSDN博客