Vue 3 学习笔记 - 基础
Vue 3 学习笔记 - 基础
主要参考 Vue 3 教程
基础
引入
<script src="https://unpkg.com/vue@next"></script> <!--开发环境-->
<script src="https://unpkg.com/vue@3.2.33/dist/vue.global.prod.js"></script> <!--生产环境 最好指定版本号-->
响应式渲染
<div id="counter">
Counter: {{ counter }}
<br><!--甚至可以是一个表达式-->
3+3={{3+3}}
</div>
const Counter = {
data: ()=>({counter: 0}) //一个函数,返回可以在 {{<表达式>}} 里使用的变量,直接成为 this 的属性(可以被外部函数改变)
//(实际上以 $data 的形式存储在组件实例中)
}
Vue.createApp(Counter).mount('#counter') //创建组件,绑定 id=counter 的元素,只会绑定第一个
v-bind
<div id="bind-attribute">
<span v-bind:title="message"> <!--把 message 变量绑定到这个 div 对象的 title 属性 (attribute) 上-->
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
<span :title="message"> <!--可以使用简写形式 `:<属性>` ,或许支持几乎所有 HTML 属性-->
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
const AttributeBinding = {data: ()=>({message: 'You loaded this page on ' + new Date().toLocaleString()})}
Vue.createApp(AttributeBinding).mount('#bind-attribute')
参考 HTML 属性参考
v-on
<div id="event-handling">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转 Message</button>
<!--把 reverseMessage 方法绑定到这个 button 对象的 onclick 事件上-->
<button @click="reverseMessage">反转 Message</button> <!--或者使用简写形式 `@事件`-->
<button v-on:[onname]="reverseMessage">反转 Message</button> <!--v-on 和 v-bind 可以使用动态参数-->
</div>
const EventHandling = {
data:
() =>({
message: 'Hello Vue.js!',
onname: 'click'
}),
methods: {
reverseMessage() //切不可用箭头函数 因为箭头函数的作用域是全局 this 不是这个对象了
{this.message = this.message.split('').reverse().join('')}
}
}
Vue.createApp(EventHandling).mount('#event-handling')
参考 HTML 事件参考手册 及 GlobalEventHandlers
v-model
<div id="two-way-binding">
<p>{{ message }}</p>
<input v-model="message" /> <!--输入框-->
<!--双向绑定:不仅改变 message 变量值时 inputbox 内容会变,改变 inputbox 内容 message 变量也会变-->
<input :value="message" @input="message = $event.target.value"> <!--是这个的语法糖-->
</div>
const TwoWayBinding = {data: ()=>({message: 'Hello Vue!'})}
Vue.createApp(TwoWayBinding).mount('#two-way-binding')
v-if
<div id="conditional-rendering">
<button @click="Switch">切换</button>
<h1 v-if="awesome">Vue is awesome!</h1>
</div>
const ConditionalRendering = {
data: ()=>( { awesome: true } ),
methods: { Switch(){ this.seen = !this.seen } }
}
Vue.createApp(ConditionalRendering ).mount('#conditional-rendering')
v-for
<div id="list-rendering">
<ol><li v-for="todo in todos">
{{ todo.text }}
</li></ol>
</div>
const ListRendering = {
data:() =>({
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
})
}
Vue.createApp(ListRendering).mount('#list-rendering')
应用实例
是时候回过头看看组件的创建 Vue.createApp
了
创建
const app = Vue.createApp({
data:()=>({}), //参考 响应式渲染 例子
methods:{/* 一些方法 */}, //参考 `v-on` 例子
computed:{}, //参考 计算属性 例子
watch:{} //参考 监听器 例子
})//注册“全局”组件
//简单的例子
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)
//或者链式
const app = Vue.createApp({})
.component('SearchInput', SearchInputComponent)
.directive('focus', FocusDirective)
.use(LocalePlugin)
计算属性 computed
const app = Vue.createApp({
data:()=>({}),
computed: { // 计算属性 与 method 的区别在于 它是有缓存的,加快静态数据读取速度 (即它是“属性”)
// 若使用了Vue的`响应式依赖`时会自动更新,而其他方法不会自动更新 如 `Date.now()`
publishedBooksMessage() {
return this.author.books.length > 0 ? 'Yes' : 'No'
},
fullName: { // 一个完整的计算属性包括 getter 和 setter
// getter 事实上之前的一个计算属性的例子 getter 部分
get() {
return this.firstName + ' ' + this.lastName
},
// setter 可以用 app.fullName = 'Alice Bob' 赋值
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
})
监听器 watch
<div id="watch-example">
Ask a yes/no question: <input v-model="question" />
<br>
{{ answer }}
</div>
<script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>
const watchExampleVM = Vue.createApp({
data: () => ({
question: "",
answer: "Questions usually contain a question mark. ;-)",
}),
watch: { // 监听器,提供了一个更通用的方法来响应数据的变化
// 最好在需要在数据变化时执行异步或开销较大的操作时使用 通常可以用计算属性代替
question(newQuestion, oldQuestion) { //每当 question 发生变化时,该函数将会执行
if (newQuestion.indexOf("?") > -1) {
this.getAnswer();
}
},
},
methods: {
getAnswer() {
this.answer = "Thinking...";
axios
.get("https://yesno.wtf/api")
.then((response) => {
this.answer = response.data.answer;
})
.catch((error) => {
this.answer = "Error! Could not reach the API. " + error;
});
},
},
}).mount("#watch-example");
一个 compute
的重写例子:
computed: {
question: {
get() {return this.ques;}, // 在只有一个变量且是本身的情况下可能用 watch 更好
set(newValue) {
this.ques = newValue;
if (newValue.indexOf("?") > -1) {
this.getAnswer();
}
},
},
},
再看元素属性 (attribute)
v-bind
之与 Class/Style 绑定
:class
可以是一个对象
<div :class="{ active: isActive }"></div>
<div :class="classObject"></div>
data:()=>({
isActive: true,
classObject:{
active: true,
'text-danger': false
}
})
也可以是一个数组
<div :class="[activeClass, errorClass]"></div>
<!--甚至可以组合-->
<div :class="[{ active: isActive }, errorClass]"></div>
data:()=>({
isActive: true,
activeClass: 'active',
errorClass: 'text-danger'
})
:style
是一个对象
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <!--使用驼峰命名法-->
<div :style="{ 'color': activeColor, 'fontSize': fontSize + 'px' }"></div> <!--或者短横线分隔 要有引号-->
<div :style="styleObject"></div> <!--使用对象-->
data:()=>({
isActive: true,
activeClass: 'active',
errorClass: 'text-danger'
styleObject: {
color: 'red',
fontSize: '13px'
}
})
也可以糅合数组
<div :style="{styleObj1, display: ['-webkit-box', '-ms-flexbox', 'flex']}"></div> <!--数组内选取最后一个支持浏览器的-->
<div :style="[styleObj1, styleObj2]"></div> <!--相同 style 会覆盖 (override)-->
v-if
与逻辑链
- if-else
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
- else if
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
v-show
用法类似 v-if
,与之不同 参考
v-show
不支持<template>
元素,也不支持v-else
如果需要非常频繁地切换,则使用
v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
v-for
之列表渲染
<ul id="array-rendering">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
如果 items
是数组,会遍历数组;如果是对象,会遍历属性值
或者使用 (value, name) in myObject
来同时使用键名和键值
或者使用 (value, name, index) in myObjec
来使用键名、键值和索引
推荐使用 :key
,在同一元素中的 key
必须唯一, key
变化会触发替换/重渲染/重排序,因此设置确定的 key
可以防止重复渲染,例如
<div v-for="item in items" :key="item.id"><!-- 内容 --></div>
永远不要在一个元素上同时使用 v-if
和 v-for
。 参考
v-on
与 事件修饰符
-
事件修饰符 需要者自会用到
-
按键修饰符
<input @keyup.enter="submit" />
监听键盘按键
enter
在按下时调用submit
,可以用 Key Values 中所有键名转换成短横线分隔格式Vue对下列有特殊修饰符:
- 最常用的键的别名
.enter
,.tab
,.esc
,.space
,.up
,.down
,.left
.right
,.delete
(捕获“删除”和“退格”键) - 系统修饰键
.ctrl
,.alt
,.shift
,.meta
可以串用 .exact
修饰符 有且仅有修饰的按键被按下时执行- 鼠标按键
.left
,.right
,.middle
- 最常用的键的别名
v-model
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件;- checkbox 和 radio 使用
checked
property 和change
事件;- select 字段将
value
作为 prop 并将change
作为事件。
-
下拉列表
<div id="select"> <select v-model="selected"> <option value="A被选">A</option> <option value="B被选">B</option> <option value="C被选">C</option> </select> <span>Selected: {{ selected }}</span> </div>
const Selected = {data: ()=>({selected: ''})} Vue.createApp(Selected).mount('#select')
-
单选
<div id="pick"> <input type="radio" id="small" value="small_value" v-model="picked"> <label for="small">small</label> <br> <input type="radio" id="big" value="big_value" v-model="picked"> <label for="big">big</label> <br> <span>Picked: {{ picked }}</span> </div>
const Selected = {data: ()=>({picked: ''})} Vue.createApp(Selected).mount('#pick')
-
复选
<div id="check"> <input type="checkbox" id="one" value="value_one" v-model="checkedNames"> <label for="one">选项一</label> <input type="checkbox" id="two" value="value_two" v-model="checkedNames"> <label for="two">选项二</label> <input type="checkbox" id="three" value="value_three" v-model="checkedNames"> <label for="three">选项三</label> <br> <span>Checked names: {{ checkedNames }}</span> </div>
const Selected = {data: ()=>({checkedNames: []})} Vue.createApp(Selected).mount('#check')
更多请参考 表单输入绑定
组件
要注意: HTML 内使用短横杠分割, Js 内使用驼峰命名
创建
<div id="components-demo">
<button-counter></button-counter>
</div>
// 创建一个Vue 应用
const app = Vue.createApp({})
// 在app内(全局)定义一个名为 button-counter 的组件
app.component('button-counter', {
data:()=>({count: 0}),
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
// 我们采用反引号包裹的模板字符串作为模板
})
app.mount('#components-demo')
自定义属性 (attribute)
<div id="blog-post-demo" class="demo">
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
</div>
const app = Vue.createApp({})
app.component('blog-post', {
props: ['title'], // 这里定义一个 `title` 的属性
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-post-demo')
与 bind
结合
<div id="blog-posts-demo">
<blog-post v-for="post in posts" :key="post.id" :title="post.title"></blog-post>
</div>
const App = {
data:() =>({posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]})
}
const app = Vue.createApp(App)
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-posts-demo')
自定义事件
<div id="blog-posts-events-demo" class="demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
@enlarge-text="postFontSize += 0.1" <!--这里定义了一个父组件的事件 `enlarge-text`-->
></blog-post>
</div>
</div>
const app = Vue.createApp({
data:() =>({
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
],
postFontSize: 1
})
})
app.component('blog-post', {
props: ['title'],
emits: ['enlargeText'], //可选,Vue会自动推导,注意用驼峰命名法
template: `
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlargeText')"> <!--这里调用父级 'enlargement' 事件 -->
Enlarge text
</button>
</div>
`
})
app.mount('#blog-posts-events-demo')
传参:
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post> <!--这里接收一个参数作为 $event -->
<blog-post ... @enlarge-text="onEnlargeText"></blog-post> <!--这里接收所有参数传入 `onEnlargeText` -->
template: `
<button @click="$emit('enlargeText', 0.1)"> <!--这里传入一个参数-->
Enlarge text
</button>
`
自定义 v-model
<custom-input v-model="searchText" ></custom-input>
<!--等价于-->
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
const app = Vue.createApp({data:() =>({searchText:""})})
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
自定义内容 <slot>
<div id="slots-demo" class="demo">
<alert-box>
Something bad happened. <!--这些内容会插入<slot></slot>中间-->
</alert-box>
</div>
const app = Vue.createApp({})
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
app.mount('#slots-demo')
综合 · 动态组件
<div id="dynamic-component-demo" class="demo">
<button
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="['tab-button', { active: currentTab === tab }]"
v-on:click="currentTab = tab"
>
{{ tab }}
</button>
<!--`:is` 的内容可以是 已注册组件的名字 或是 一个组件选项对象 甚至是 常规的 HTML 元素-->
<component v-bind:is="currentTabComponent" class="tab"></component>
</div>
const app = Vue.createApp({
data() {
return {
currentTab: 'Home',
tabs: ['Home', 'Posts', 'Archive']
}
},
computed: {
currentTabComponent() {
return 'tab-' + this.currentTab.toLowerCase()
}
}
})
app.component('tab-home', {
template: `<div class="demo-tab">Home component</div>`
})
app.component('tab-posts', {
template: `<div class="demo-tab">Posts component</div>`
})
app.component('tab-archive', {
template: `<div class="demo-tab">Archive component</div>`
})
app.mount('#dynamic-component-demo')
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现