简介 | Vue.js (vuejs.org)
基础介绍
环境准备
vue安装
方式一 vue CLI脚手架
npm安装:npm install -g @vue/cli
,通过vue create 项目名
创建
方式二 vite
npm安装:npm init vue@latest
注意: cli(webpack编写)和vite(es6编写)是完全不同的,两者在配置上有区别。
项目配置
Vue创建项目的步骤_npm init vue@latest-CSDN博客
框架介绍
单文件组件
单文件组件( .vue
文件,缩写为 SFC)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。
声明式渲染
Vue 的核心功能是声明式渲染:通过扩展于标准 HTML 的模板语法,我们可以根据 JavaScript 的状态来描述 HTML 应该是什么样子的。当状态改变时,HTML 会自动更新。
API风格
选项式API:
可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 mounted
。选项所定义的属性都会暴露在函数内部的 this
上,它会指向当前的组件实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <script> export default { // data() 返回的属性将会成为响应式的状态 // 并且暴露在 `this` 上 data() { return { count: 0 } },
// methods 是一些用来更改状态与触发更新的函数 // 它们可以在模板中作为事件处理器绑定 methods: { increment() { this.count++ } },
// 生命周期钩子会在组件生命周期的各个不同阶段被调用 // 例如这个函数就会在组件挂载完成后被调用 mounted() { console.log(`The initial count is ${this.count}.`) } } </script>
<template> <button @click="increment">Count is: {{ count }}</button> </template>
|
组合式API:
可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与<script setup>
搭配使用。这个 setup
attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup>
中的导入和顶层变量/函数都能够在模板中直接使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> import { ref, onMounted } from 'vue'
// 响应式状态 const count = ref(0)
// 用来修改状态、触发更新的函数 function increment() { count.value++ }
// 生命周期钩子 onMounted(() => { console.log(`The initial count is ${count.value}.`) }) </script>
<template> <button @click="increment">Count is: {{ count }}</button> </template>
|
模板语法
data
文本插值
使用**data
组件选项声明响应式状态,此选项是一个返回对象的函数**。
1 2 3 4 5 6 7 8
| export default { data() { return { message: 'Hello World!', htmlMessage: '<h1>Hello World!</h1>' } } }
|
返回的属性对象可以在模板中使用,使用双花括号法渲染动态文本:
1 2 3
| <h1>{{ message }}</h1> // 花括号中的内容可以使用任何能被求值的js表达式(能够写在`return`后面) <h1>{{ message.split('').reverse().join('') }}</h1>
|
v-html
双大括号的内容为纯文本,而不是html。插入html语句时需要使用v-html
。
1
| <p v-html='htmlMessage'></p>
|
v-bind
属性绑定
属性绑定静态值与html相同:
1
| <h1 class="title">Make me red</h1>
|
但这样不能绑定动态值,双大括号也不能绑定html属性。使用v-bind
可以给动态绑定属性。
和文本插值类似,指令的值是可以访问组件状态的 JavaScript 表达式。例如:
1
| <div v-bind:id="dynamicId"></div>
|
v-bind:id
可以简写为:id
。
冒号后面的部分 (:id
) 是指令的“参数”。此处,元素的 id
将与组件状态里的 dynamicId
属性保持同步。
如果绑定的值是null
或undefined
,该属性则会被移除
v-bind的多种使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <script> export default { data() { return { titleClass: 'title', errorClass: 'text-danger', isActive: true, hasError: false, object:{ isActive: true, hasError: false } } } } </script> <template> <!-- 1. 此处添加一个动态 class 绑定data中的属性,通过此属性内容再绑定js表达式中的内容 --> <h1 :class="titleClass">Make me red</h1> <!-- 2. 表示color和text-danger是否存在取决于isActive和hasError的真假值 --> <div :class={ color: isActive ,'text-danger': hasError }></div> <!-- 3. 可以直接绑定一个对象,与2渲染结果相同 --> <div :class="object"></div> <!-- 4. 可以绑定一个数组来绑定多个class --> <div :class="[titleClass, errorClass]"></div> </template> <style> .title { color: red; } </style>
|
v-on
事件监听
使用v-on
监听DOM事件,v-on
可简写成@
:
1 2
| <button v-on:click="increment">{{ count }}</button> <button @click="increment">{{ count }}</button>
|
函数声明放在methods
选项中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <script> export default { data() { return { count: 0 } }, methods: { increment() {this.count++}, say(message) {alert(message)}, warn(message, event) { // 这里可以访问 DOM 原生事件 if (event) {event.preventDefault()} alert(message) } } } </script> <template> <!-- 内联事件处理器 --> <button @click="count++">Add 1</button> <!-- 方法事件处理器 --> <button @click="increment">Count is: {{ count }}</button> <!-- 事件处理器中传参 --> <button @click="say('hello')">Say hello</button> <!-- 事件处理器中传入事件参数 --> <!-- 使用特殊的 $event 变量 --> <button @click="warn('Form cannot be submitted yet.', $event)">Submit</button> <!-- 使用内联箭头函数 --> <button @click="(event) => warn('Form cannot be submitted yet.',event)">Submit</button> </template>
|
事件修饰符:事件处理 | Vue.js (vuejs.org)
v-model
表单绑定
通过v-on和v-bind的使用可以使元素与属性内容双向绑定,但手动使用事件监听器修改值是一件麻烦的事,使用v-model
可以简化此步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script> export default { data() { return { text: '' } } } </script>
<template> <input v-model="text" placeholder="Type here"> <p>{{ text }}</p> </template>
|
v-model
会将被绑定的值与 <input>
的值自动同步,这样就不必再使用事件处理函数了。
更多操作:表单输入绑定 | Vue.js (vuejs.org)
v-if
条件渲染
使用v-if
对元素是否渲染进行条件判断,当条件为真时渲染,否则不渲染。
也可以使用 v-else
和 v-else-if
来表示其他的条件分支:
1 2 3 4 5
| <template> <button @click="!isToggle">Toggle</button> <h1 v-if="isToggle">Vue is awesome!</h1> <h1 v-else>Oh no 😢</h1> </template>
|
v-show
v-show
也可以设置按条件显示元素:
1
| <h1 v-show="ok">Hello!</h1>
|
不同之处在于 v-show
会在 DOM 渲染中保留该元素;v-show
仅切换了该元素上名为 display
的 CSS 属性。
v-show
不支持在 <template>
元素上使用,也不能和 v-else
搭配使用。
v-if
与v-show
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display
属性会被切换。
总的来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show
较好;如果在运行时绑定条件很少改变,则 v-if
会更合适。
v-for
列表渲染
使用v-for
渲染一个基于源数组的列表,指令的值需要使用 item in items
或者item of items
形式的特殊语法,其中 items
是源数据数组,而 item
是迭代项的别名:
1 2 3 4 5 6 7 8 9 10
| data() { return { parentMessage: 'Parent', items: [{ message: 'Foo' }, { message: 'Bar' }] } } <li v-for="item in items">{{ item.message }}</li> <!-- 添加顺序 --> <li v-for="(item, index) in items">{{ parentMessage }} - {{ index }} - {{ item.message }} </li>
|
渲染为:
通过key管理状态:Vue 默认按照“就地更新”的策略来更新通过 v-for
渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key
attribute:
1
| <div v-for="item in items" :key="item.id">
|
**v-for
可以遍历范围:**但起点为1,不是0。
1
| <span v-for="n in 10">{{ n }}</span>
|
v-for
更新数组列表有两种方式。
在源数组上添加,如:this.todos.push(newTodo)
使用新的数组替代原数组,如:this.todos = this.todos.filter(/* ... */)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <script> // 给每个 todo 对象一个唯一的 id let id = 0
export default { data() { return { newTodo: '', todos: [ { id: id++, text: 'Learn HTML' }, { id: id++, text: 'Learn JavaScript' }, { id: id++, text: 'Learn Vue' } ] } }, methods: { addTodo() { this.todos.push({ id: id++, text: this.newTodo }) this.newTodo = '' }, removeTodo(todo) { this.todos = this.todos.filter((t) => t !== todo) } } } </script>
<template> <form @submit.prevent="addTodo"> <input v-model="newTodo" required placeholder="new todo"> <button>Add Todo</button> </form> <ul> <li v-for="todo in todos" :key="todo.id"> {{ todo.text }} <button @click="removeTodo(todo)">X</button> </li> </ul> </template>
|
computed
计算属性
模板中可以进行表达式计算,但重复且复杂的计算会使模板显得臃肿。使用计算属性在**computed
**中描述依赖响应式状态的复杂逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export default { data() { return { author: { name: 'John Doe', books: [ 'Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', ] } } }, computed: { // 一个计算属性的 getter publishedBooksMessage() { // `this` 指向当前组件实例 return this.author.books.length > 0 ? 'Yes' : 'No' } } }
<p>Has published books:</p> <span>{{ publishedBooksMessage }}</span>
|
计算属性computed
与方法methods
的差别:
1 2 3 4 5
| methods: { calculateBooksMessage() { return this.author.books.length > 0 ? 'Yes' : 'No' } }
|
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books
不改变,无论多少次访问 publishedBooksMessage
都会立即返回先前的计算结果,而不用重复执行 getter 函数。
相比之下,方法调用总是会在重渲染发生时再次执行函数。
ref
模板引用
通过ref
模板引用可以手动操作DOM。
通过给元素添加ref
属性,可将此元素挂载到ref属性名上:
挂载结束后引用都会被暴露在 this.$refs
之上。需要使用此元素时,通过this.$refs.属性名
进行引用(注意是refs)。
1 2 3 4 5 6 7 8 9 10 11 12
| <script> export default { mounted() { <!-- 可以访问原生js的属性 --> console.log(this.$refs.input.innerHTML) } } </script>
<template> <div ref="input" />hello</div> </template>
|
除非特殊需求,一般不建议直接操作DOM。
组件
component
注册组件
vue组件在使用前需要先进行注册,这样在渲染时才能找到对应实现。
全局注册
在main.js中使用App.components()
全局注册组件:
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import App from './App.vue'
import Register_g from './components/register_g.vue' const app = createApp(App)
app.component('register_g',Register_g)
app.mount('#app')
|
在任一vue中均可直接应用:
1 2 3 4
| <template> <!-- 3. 显示组件 --> <register_g /> </template>
|
局部注册
当单一vue需要引用组件时,直接在此vue上引用并注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <!-- 3. 显示组件 --> <register/> </template>
<script> // 1. import 引入组件 import register from "./components/register.vue" export default{ // 2. components 注入组件 components:{ register } } </script>
|
局部注册的组件在后代组件中不可用。
props
传递数据
使用props
可以实现父级向子级传递数据。例如以下情况:
父级app.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <register :turnOn="login"/> <div @click="login=!login">登录/注册</div> </template> <script> export default{ components:{ register, }, data(){ return{ login:false } } </script>
|
子级register.vue
:
1 2 3 4 5 6 7 8
| <template > <div class="window" v-if="turnOn"></div> </template> <script> export default{ props:['turnOn'] } </script>
|
可以实现控制register页面显示的效果。
在子级注册props
属性,添加需要接收属性的字符串,即可在父级使用组件时传递数据。
props
在子级是只读,不能修改的。
**props
类型效验:**可以指定传入的数据类型
1 2 3 4 5 6 7 8 9 10
| props:{ turnOn:{ <!--可以规定类型,使用数组[]可以规定多种类型--> type:Boolean, <!--设置默认值--> default:false, <!--设置传参是否必要--> required:true } }
|
props可以实现子传父:使用props让父级给子级传递一个函数,子级通过此函数传递数据给父级。但用$emit
更易理解且操作方便。
$emit
组件事件
通过设置$emit
组件事件,可以实现子级对父级的事件调用。
子级设置this.$emit("自定义事件名")
调用自定义事件:
1 2 3 4 5
| <script> methods:{ closeEvent(){this.$emit("closeMenu")} } </script>
|
父级通过自定义事件实现事件调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <register :turnOn="login" @closeMenu="openLoginMenu"/> </template> <script> export default{ components:{register,}, data(){return{login:false}}, methods:{ openLoginMenu(){ this.login=!this.login; } } } </script>
|
子级可以通过调用closeEvent()
向父级调用自定义事件closeMenu
,进而调用父级函数,修改参数。
attributes
透传
调用组件时,可以添加class、style、id等属性,当组件以单个元素为根作渲染时,透传的属性会添加到根元素上。
1 2 3
| <template> <myComponent class="abc"/> </template>
|
子组件可以禁用attributes继承:
1 2 3 4 5
| <script> export default{ inheritAttrs:false } </script>
|
透传在实际使用较少,了解即可。
slots
插槽
组件设置slot
,父级可以添加元素到slot
对应的位置上。
1 2 3 4 5 6 7 8 9 10
| <!-- 父级 --> <FancyButton> Click me <!-- slot content --> </FancyButton> <!-- 子组件FancyButton 设置slot --> <template> <button class="fancy-btn"> <slot/> <!-- slot --> </button> </template>
|
父级可以直接使用双括号添加动态元素。
组件的slot可以添加默认内容,当父级没有传入时会自动显示。
1
| <slot>HELLO<!-- 默认内容 --></slot>
|
v-slot
具名插槽
父级传入多个插槽时,通过给插槽命名,可以指定插槽位置。
具名插槽需要使用一个含 v-slot
(简写为#
) 指令的 <template>
元素,并将目标插槽的名字传给该指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!-- 父级 --> <BaseLayout> <template v-slot:header><!-- header 插槽的内容放这里 --></template> <!-- 隐式默认插槽 --> <p>A paragraph for the main content.</p> <p>And another one.</p>
<template #[footer]><!-- 动态名字footer 插槽的内容放这里 --></template> </BaseLayout>
<!-- 子级 --> <div class="container"> <header><slot name="header"></slot></header> <footer><slot name="footer"></slot></footer> </div>
|
传入多个插槽时,未使用template
包裹的部分会视作默认插槽。
动态插槽名使用[]
包裹即可。
条件插槽
需要根据插槽是否存在来渲染内容时,可以通过$slots
实现。
1 2 3 4 5 6
| <template> <div class="card"> <div v-if="$slots.header" class="card-header"><slot name="header" /></div> <div v-if="$slots.default" class="card-content"><slot /></div> </div> </template>
|
当header, default存在时,会得到额外的class渲染。
后端接口
axios
AJAX笔记 | Luvmand
跨域
以vite举例:vite.config.js文件添加proxy代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export default defineConfig({ server: { // proxy 配置代理会规则 proxy: { // 需要代理的接口路径,以'/api'为开头的访问路径会被代理至target目标地址 '/api': { // 需要访问的服务器地址 target: 'http://192.168.1.103:80', // 是否允许跨域 changeOrigin: true, // 重写请求路径,将路径中的'/api'重写为'' pathRewrite: { '^/api': '' } } } } )}
|
注意,cli 要将server换成devServer。vite和cli不一样。踩了很久的坑。