vue3知识点
Vue.js
核心是一个允许采用简洁的模板语法来声明式地将数据渲染进DOM的系统
组件本质上是一个具有预定义选项的实例
const app = vue.createApp({ ... }) // 在应用中创建“全局”组件
// 创建 Vue 应用实例
const app = Vue.createApp(...)
// 定义名为 todo-item 的新组件
app.component('todo-item', {
template: `<li>This is a todo</li>`
})
// 挂载 Vue 应用
app.mount(...)
Demo
const ComponentsApp = {
data() {
return {
groceryList: [
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
}
}
// 允许链式写法
// Vue.createApp({})
// .component('SearchInput', SearchInputComponent)
// .directive('focus', FocusDirective)
// .use(LocalePlugin)
const app = Vue.createApp(ComponentsApp)
app.component('todo-item', {
props: ['todo'],
template: `<li>{{ todo.text }}</li>`
})
app.mount('#components-app')
<div id="todo-list-app">
<ol>
<!--
现在我们为每个 todo-item 提供 todo 对象
todo 对象是变量,即其内容可以是动态的。
我们也需要为每个组件提供一个“key”,稍后再
作详细解释。
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
一、根组件
- 传递给
createApp
的选项用于配置根组件
。当我们挂载应用时,该组件被用作渲染的起点
*** 根组件与其他组件没有什么不同,配置选项是一样的,所对应的组件实例 行为也是一样的
*** mount 返回的是根组件实例,不是应用本身
Vue 应用挂载到 <div id="app"></div>
const RootComponent = {
/* 选项 */
}
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
二、生命周期
- 生命周期函数、选项property(元素属性)或回调上
不可使用箭头函数
。生命周期钩子this上下文指向调用它的当前活动实例。 -
箭头函数并没有this
,this作为变量一直向上级词法作用域查找。直至找到为止,经常导致Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。
vue.createApp({
data() {
return { count: 1 }
},
created() {
console.log('count is' + this.count) // => count is 1
}
})
image.png
三、模板语法
在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。结合响应性系统,Vue能够智能地计算出最少需要重新渲染多少组件,并把DOM操作次数减到最少。
*** v-html不能复合局部模板,因为Vue不是基于字符串的模板引擎,反之,对于用户界面(UI),组件更适合作为可重用和可组合的基本单位
*** 动态渲染任意的HTML是非常危险的,因为很容易导致XSS攻击
1. 文本插值使用双大括号{{ 变量 }}
2. 动态渲染HTML使用v-html
const RenderHtmlApp = {
data() {
return {
rawHtml: '<span style="color: red">这几个文字是红色</span>'
}
}
}
Vue.createApp(RenderHtmlApp).mount('#example')
<div id="example">
<p> v-html解析<span v-html="rawHtml"></span> </p>
</div>
3. 插值双大括号{{ 单个表达式 }}
可以使用js表达式
{{ n + 1 }}
{{ ok ? 'Yes' : 'No' }}
{{ arrayData.split(',') }}
...
{{ var a = 1 }} // 不会生效, 这是语句,不是表达式
{{ if( ok ) { return message } }} // 流控制也不会生效
4. v-
指令职责是:当表达式的值改变时,将其产生的连带影响,响应式作用于DOM
5. 参数
<a v-on:click="doSomthing"> ... </a>
6. 动态参数
*** 动态参数预期是求出一个字符串,异常情况下为null。这个null会被显性地用于移除绑定
// 若attrname的值为click
<a v-on:[attrname]="doSomthing">...</a>
等价于
<a v-on:click="doSomthing">...</a>
*** 在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>
7. 缩写
v-on
// 完整语法
<a v-on:click="doSomthing">...</a>
// 缩写
<a @click="doSomthing">...</a>
// 动态参数缩写
<a @[attrname]="doSomthing">...</a>
v-bind
// 完整语法
<a v-bind:href="url">...</a>
// 缩写
<a :href="url">...</a>
// 动态参数缩写
<a :[key]="url">...</a>
四、Data Property
vue自动为methods绑定this,以便它始终指向组件实例
。这将确保方法再用作事件监听或回调时保持正确的this。定义methods时应避免使用箭头函数
1. vue没有内置的防抖、节流,可以引用lodash
为了使组件实例彼此独立,可以在生命周期钩子created里添加防抖函数
app.component( 'save-button', {
created() {
// 使用lodash 防抖函数
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 移除组件时,取消定时器
this.debouncedClick.cancle()
},
methods: {
click() {
// ... 响应事件...
}
},
template: `
<button @click='debouncedClick'> Save </button>
`
})
五、计算属性、侦听器
1.computed 计算属性
computed:{ ... }计算属性是基于它们的响应依赖关系缓存的。
以下:如果data的值不发生改变,即使多次访问computFun计算属性会立即返回之前的计算结果,而不必再次执行函数。如果不希望缓存,可用methods来代替
computed: {
computFun() {
return this.data == 1 ? '是' :'否'
}
}
以下计算属性将不再更新,因为Date.now()不是响应式依赖
computed: {
now() {
return Date.now()
}
}
2.watch 侦听器
当需要在数据变化时执行异步 或 开销较大
的操作时,watch方式最有用
六、条件渲染
v-if
、v-else-if
、v-else
、v-show
1.v-if、v-else-if、v-else
v-if和v-for不推荐同时使用,如果一起使用了v-if的优先级更高
<template>
元素当做不可见的包裹元素,在上面使用v-if。最终的渲染结果不包括<template>
元素
<template v-if="ok">
<p>渲染后template标签不存在</p>
</template>
// ok为true, 渲染结果为
<p>渲染后template标签不存在</p>
2.v-show
v-show
的元素始终会被渲染并保留在DOM中,v-show
只是简单地切换元素的CSS property display
**** v-show不支持<template>元素,也不支持v-else
3.v-if VS
v-show
-
v-if
是真正的条件渲染,因为它会确保再切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建
。 -
v-if
也是惰性
的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 -
v-show
不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS进行切换(样式display控制的显示与隐藏) **** 一般而言,v-if具有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件很少改变,则使用v-if较好
七、列表渲染
用v-for把一个数组对应为一组元素
1.数组
item
为迭代数组元素别名。index
为索引。key
作为Vue识别节点的一个通用机制,key需使用字符串或数值类型的值
- v-for可以在template元素中使用
当Vue正在更新使用v-for
渲染的元素列表时,它默认使用“就地更新”
的策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染
v-for与v-if同时使用时建议使用以下写法:
<template v-for="(item, index) in items" :key="item.id">
<li v-if="!item.isShow">{{item.name}}</li>
<li>{{item.name}}</li>
</template>
2.对象
value
为value。name
为key。index
为索引- 会按照
object.keys()
结果遍历对象,但是不能保证它在不同JavaScript引擎下的结果一致
<li v-for="(value, name, index) in myObject">
{{ index }}. {{ name }}: {{ value }}
</li>
3.数组更新
变更方法
:变更调用了这些方法的原始数组
替换数组
:即非变更方法,不会变更原始数组,而总是返回一个新数组
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。当你直接修改了对象属性的值,会发现只有数据改了,页面内容没有更新。Vue将被侦听数组的变更方法进行了包裹,所以它们也将会触发视图更新。
- 变更方法:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()
- 替换数组方法:
filter()、concat() 、slice()
八、事件处理
1. @click监听点击事件
<button @click="btnFun('click me', $event)">点击事件</button>
...
methods: {
btnFun(msg, event) {
// doSomthing
}
}
2. @click多事件处理器
<button @click="one($event), two($event)">处理多事件按钮</button>
...
methods: {
one(event) {
// 第一个事件处理逻辑
},
two(event) {
// 第二个事件处理逻辑
}
}
3. 事件修饰符
.stop、.prevent、.capture、.self、.once、.passive
使用修饰符时,顺序很重要;相应代码会以同样的顺序产生
<!-- 阻止单击事件继续传播 -->
<a @click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div @scroll.passive="onScroll">...</div>
4. 其它修饰符
- 按键修饰符:
.enter、.tab、.delete、.esc、.space、.up、.down、.left、.right
直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
<input @keyup.page-down="onPageDown" />
- 系统修饰符:
.ctrl、.alt、.shift、.meta(在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞))
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
- .exact 修饰符:
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
- 鼠标按钮修饰符:
.left、.right、.middle
九、表单输入绑定
-
v-model语法糖
:可以在表单<input>、<textarea>、<select>等元素上创建双向数据绑定
。它负责监听用户的输入事件来更新数据
,并在某种极端场景下进行一些特殊处理 -
v-model
会忽略所有表单元素的value
、checked
、selected
attribute的初始值而总是将当前活动实例的数据作为数据来源
,应该通过JavaScript在组件的data选项中声明初始值
1. 值绑定
<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a" />
<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle" />
<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
<!-- 当选中时vm.pick === vm.a-->
<input type="radio" v-model="pick" v-bind:value="a" />
2. 修饰符
-
.lazy
:在默认情况下,v-model在每次input
事件触发后将输入框的值与数据进行同步。添加.lazy
修饰符,可以转为change
事件之后进行同步
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" />
-
.number
:自动将用户的输入值转为数值类型
<input v-model.number="age" type="number" />
-
.trim
: 自动过滤用户输入的首位空白字符
<input v-model.trim="msg" />
十、组件基础
- 父组件通过
props
向子组件传递数据 - 子组件通过调用内建的
$emit()
并传入事件名称来触发一个事件
<button @click="$emit('enlargeText', 0.1)">
Enlarge text
</button>
...
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
1. 动态组件
- is:不同组件之间进行动态切换
以下currentTabComponent
可以包括:已注册的名字 或 一个组件的选项对象
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component :is="currentTabComponent"></component>
- 有些HTML元素,诸如<table>内只能出现<tr>元素,可以使用is变通
// blog-post-row为 自定义组件
<table>
<tr is="vue:blog-post-row"></tr>
</table>
2. 属性名称 大小写
HTML attribute 名不区分大小写,因此浏览器将所有大写字符解释为小写。这意味着当你在 DOM 模板中使用时,驼峰 prop 名称和 event 处理器参数需要使用它们的 kebab-cased (横线字符分隔) 等效
app.component('blog-post', {
props: ['postTitle'],
template: `
<h3>{{ postTitle }}</h3>
`
})
...
<blog-post post-title="hello!"></blog-post>
十一、组件注册
1.全局注册: 在注册以后可以用在任何新创建的组件实例模板中
app.component
的第一个参数是组件名
const app = Vue.createApp({ ... })
app.component('my-component-name', {
...
})
...
<my-component-name></my-component-name>
2.局部注册: 在components选项中定义组件以后才可使用
局部注册的组件在其子组件中不可使用
在 ComponentB 中可用ComponentA。举例:
const ComponentA = {
/* ... */
}
const ComponentB = {
components: {
'component-a': ComponentA
}
// ...
}
或
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
}
// ...
}
十二、props:父组件向子组件传值
props
:单向下绑定的数据流向
。数据只能从父组件流向子组件,在子组件中不可修改prop
app.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default() {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator(value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
},
// 具有默认值的函数
propG: {
type: Function,
// 与对象或数组默认值不同,这不是一个工厂函数 —— 这是一个用作默认值的函数
default() {
return 'Default function'
}
}
}
})
- type 还可以是一个自定义的构造函数
用于验证author
prop的值是否通过new Person
创建的。举例:
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
...可以使用
app.component('blog-post', {
props: {
author: Person
}
})
- prop的大小写命名
HTML中的attribute名是大小写不敏感
的,所以浏览器会把所有的大写字符解释为小写字符。这意味着当你使用DOM中的模板时,驼峰命名法
的prop名需要使其等价的短横线分割命名
:
const app = Vue.createApp({})
app.component('blog-post', {
// camelCase in JavaScript
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
...
<!-- kebab-case in HTML -->
<blog-post post-title="hello!"></blog-post>