VUE 是一个 js 网页前端开发框架。它是自底向上逐层应用。核心库关注视图层。特点是易于上手,还能于其他第三方库整合。
还有就是现代化的工具链支持。
vue 基于 html 设计了模板语法。将 DOM 和组件进行绑定。技术上,vue 将模板编译为虚拟 DOM 渲染函数,结合响应系统,计算出最少需要重新渲染的组件,从而提高性能。
如果不使用 vue 模板,也能直接写渲染 render 函数,支持 JSX 语法。
{{ msg }}
;vue 中绑定的值不止是单个值,也可以是 js 表达式
v-html=' html '
;注意:有安全风险v-bind:id='dynamicId
: 绑定到 id 属性
v-bind:[dy]='xxx'
: dy 是动态参数
v-on:submit.prevent='xxx'
: .prevent 是修饰符,即以特殊方式绑定:href
: 是 v-bind:herf 的缩写@click
: 是 v-on:click 的缩写html 使用 class 属性来设定样式组是否启用。因为样式是一个列表而不是一个简单值,因此 vue 支持增强的数据绑定。
一、绑定对象
如 :class='{ active:isActive, "text-danger":hasError }'
,v-bind 到一个对象。其中 isActive 和 hasError 是组件的属性。 也可以直接绑定到组件的对象,而不用内联书写在模板中。
二、绑定数组
:class='[activeClass,errorClass]'
三、内联样式
:style='{color:activeColor, fonSize: fontSize + "px"}
,其中,activeColor 和 fontSize 是组件的属性。内联样式也支持直接绑定到对象。
:style='[baseStype, overridingStyles]'
;数组语法
v-if 指令控制是否渲染指定内容。
<h1 v-if="awesome"> vue is ok</h1>
<h1 v-else>vue is bad</h1>
v-if 可以作用到一个模板节点上(该节点本身不会被渲染)
<template v-if="ok">
<h1>hi</h1>
<p>today is good!</p>
</template>
v-for 指令渲染列表。
<ul id="array-rendering">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
对应的视图模型:
Vue.createApp({
data(){
return {
items:[{message:'foo'},{messgae:'bar'}]
};
}
}).mount('#array-rendering');
组件上使用 v-for:
<my-component
v-for="(item, key, index) in items"
:item= "item"
:index="index"
:key="key">
</my-component>
app.component('my-component',{
template:`<li>{{ item }},{{key}},{{index}}</li>`,
props:['item', 'index', 'key'],
emits:[]
})
v-on 指令处理事件和视图模型之间的绑定。
Vue.createApp({
methods:{
greet(event){// 事件处理方法
console.log(event.target.tagName);
}
}
})
v-model 指令用在表单元素上创建双向数据绑定。所谓表单是 html 的元素,如输入<input>
,多行文本编辑<textarea>
,选项表<select>
等可以让用户输入数据,并可以提交数据的控件。
v-model 是一个语法糖,本质是监听输入事件来更新数据。它忽略表单元素默认的 value、checked、selected 属性的初始值,而直接使用绑定中的视图模型中的数据。
如:<input v-model="searchText" />
等价于 <input :value="searchText" @input="searchText=$event.target.value" />
。
绑定机制:
<input />
、<textarea />
: 绑定value 属性、使用 input 事件。<input type='checkbox' />
: 绑定 checked 属性、使用 change 事件。
<input type='radio' />
: 绑定 checked 属性、使用 change 事件。
<select>
: 绑定 value 属性、使用 change 事件。修饰符:
文本输入框:<input v-model='message' placeholder='edit me' />
多行文本输入:<textarea />
复选框:<input type='checkbox' />
单选框:<input type='radio' />
选择框(多选):<select multiple>
<option>A</option>
<option>B</option>
</select>
视图模型 vm:
_.debounce(this.click, 500)
{sum(){return a+b}}
,其中 a,b 是响应属性computed:{fullname:{get(){},set(){}}}
vue 采用组件化构建应用的策略。可以把组件理解为一个可复用的局部视图模型。
首先是 app, 然后是根组件(vm 视图模型),组件(独立的 vm 视图模型)和根组件高度相似,只是它拥有独立的模板,还有对应的 html 标签用法。
const app = Vue.createApp({});
app.component('button-counter',{
data(){
return {count:0};
},
template:`
<button @click="count++">
you clicked me {{ count }} times.
</button>
`
}).mount("#app");
<div id="app">
<button-counter />
</div>
组件可以采用以上内联的写法,也可以使用独立的组件文件(推荐)。
app.component({components:{组件1,组件2...}})
[名字1, 名字2...]
inject:['user']
接收方和模板中的 html 标签不一样的是,组件标签并不会直接接收外部的数据绑定,它只能通过组件本身定义的 props 属性和 emits 属性,开放特定的绑定接口。这样处理强调了组件的封闭性,避免暴露内部细节。
app.component('blog-post', {
data(){return {fsize:16};},
methods:{onEnlargeText(a){this.fsize += a;}},
props:['title'],
emits:['enlargeText'],
template:`<div :style='{fontSize:fsize + "px"}' @click='$emit(enlargeText)'>{{ title }}</div>`
}).mount("#blog")
<div id='blog'>
<blog-post title='games' @enlarge-text='onEnlargeText' />
</div>
<custom-input v-mode="searchText" />
等价于:
`<custom-input :model-value="searchText" @update:model-value="searchText = $event" />
也可以指定绑定的组件属性,如绑定 text 和 update:text:
<custom-input v-mode:text="searchText" />
组件的视图模型:
{
props:['modelValue'],
emits:['update:modelValue'],
computed:{
value:{
get(){return this.modelValue;},
set(value){return this.$emit('update:modelValue', value)}
}
},
template:`<input v-mode="value" />`
}
v-model 有内置的修饰符:
通过 props 中的 modeModifiers 属性(如果绑定到 text,即对应 textModifiers),可以定义自定义的修饰符。默认为空,当对组件标签设置了修饰符,该属性包含 {修饰符:true} 的对象。引发事件的方法就可以针对这个对象来编程。
// 使用
<custom-panle v-model.capitalize="myText" />
// 定义
const app = Vue.createApp({data(){return myText:''}});
app.component('custom-panle', {
props:{
modelValue:String,
modelModifiers:{
default:()=>({})
}
},
emits:['update:modelValue'],
methods:{
emitValue(e){
let value = e.target.value;
if (this.modelModifiers.capitalize)
value = value[0].toUpperCase() + value.slice(1);
this.$emit('update:modelValue', value);
}
},
template:`<input :value='modelValue' @input='emitValue' />`
})
vue 使用插槽标签 <slot>
引用组件标签内层的内容。
<custom-input>
子内容
</custom-input>
// 要引用组件包括起来的子内容,在模板中使用<slot>标签
template:`<div><slot />:<input /></div>`
// 效果类似: 子内容:<input />
子内容并不处于组件作用域(只是结构上像,所以称之为子内容,而非真正的子内容),它和组件其实是兄弟关系,只能使用组件的父级作用域的内容。
<slot>默认内容</slot>
<slot name='header' />
对应 <template v-slot:header />
#header
<slot :item='item' />
, 使用者 <template v-slot:default='slotProps'>{{ slotProps.item }}</template>
<template>
而直接在组件标签上使用 v-shot="slotProps"
。<custom-list v-slot="prop">
<span>{{ prop.item }}</span>
</custom-list>
// 对应的模板
data(){
return {item:""};
},
template:`<div><slot :item='item' /></div>`
vue 支持 component 标签,通过 is 属性来动态改变成实际的标签。
<component :is="custom-input" />
每次变化组件都会重新创建,需要缓存对象,可以:
<keep-alive>
<component :is="currentTabComponenet" />
</keep-alive>
vue 自带一些实用的组件:
import {defineAsyncComponent} from 'vue';
const AsyncComp = defineAsyncComponent(()=>
import('./components/AsyncComponet.vue'));
app.component('async-component', AsyncComp);
事件概念上分两个部分,一个是事件,一个是事件处理。发起(抛出)一个事件,对该事件处理的程序可以有多个,这是一个一对多的关系。
使用 $emit 发起事件,当事件需要验证,会接收多一个参数对象参数:$emit('事件名', {a:1,b:2...})。
组件上的 emits 属性,记录了组件定义的事件(但是不在里面定义的事件,实际也是有效的,因为事件具有向外传播的穿透性,可以传到外部)。但是 emits 里面可以定义验证事件有效性的函数。
事件处理部分,$event 包装了当前事件携带的信息。
// 组件内部引发和验证事件部分
emits:{
// 带验证的事件
submit:({email, password})=>email && password
},
methods:{
submitForm(email, password){
this.$emit('submit', {email, password});
}
},
template:`<button @click='submitForm("xx@xx.com", "xxx")'>`
// 组件外部事件处理部分
<curstom-control @submit="console.log($event)" />
html 元素格式有些特殊要求,因此组件有如下注意事项:
<table>
<tr v-is=" 'blog-post' " /> 注意值是字符串
</table>
<div v-once />
<div ref='fooder' />
,访问:this.$refs.fooder.xxx
组件除了内联在 js 代码中,还可以单独的文件来定义(推荐)。
hello.vue 示例文件内容:
<template>
<p>{{ greeting }} world!</p>
</template>
<script>
module.exports={
data: function(){
return {
greeting:'hello',
};
},
}
</script>
<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>
所谓响应式就是依赖的变量发生变化,关联变量也同步发生变化(更新)。
如 sum = a + b,当 a = 1, b =2, sum = 3; 当 a = 4, b = 3, sum = 7。js 语言并没有这种能力,但是可以把这个算式包含在函数中,通过调用该更新函数就能实现类似的效果。
如 let f = ()=>sum = a + b; 调用更新: f(); 现在问题是这个函数怎么绑定相关变量,和在什么时候发生更新调用。
vue3 中,使用 Proxy 来封装对象,实现这点。 Proxy 是 js 原生对象,它可以添加一个隔离界面,当访问原对象时,先运行 Proxy 的 get/set 函数,然后响应式就可以从中插入干涉代码。
const dinner = {
meal:'tacos',
}
const handler = {
get(target, property){
track(target, property); // 设置代理
const value = Reflect.get(...arguments);
return value;
}
set(target, property){
trigger(target, property); // 响应代理
return Reflect.set(...arguments);
}
}
const proxy = new Proxy(dinner, handler);
console.log(proxy.meal);
相关工具:
组件相关工具:
ref 响应式对象,等于打了一层外包装 {value:原值},要访问的时候需要 xx.vaule,不过 vue 的一些上下文支持自动解除这个包装,从而有基础类型的感觉:
// 对象
import { reactive } from 'vue';
let state = reactive({
sum: 0,
});
// 原始值
import { ref } from 'vue';
const count = ref(0); // 等于 reactive({value:0})
// 自动解包(特定情景)
reactive({
count // 等于 count:count.value
});
// 解构
import { toRefs } from 'vue';
let {sum} = toRefs(state); // sum 还是响应式
// 只读
import { readonly } from 'vue';
const copy = readonly(sum);
setup 组件选项,设置和返回关联的组件,用 ref 响应式变量包装。
将关注点转移到 setup 组件内,这种方式叫组合式 api,相对的就是选项式 api。
组合式 api 也有对应的生命周期,在原本的生命周期函数名前添加 on, 如 onMounted。
为什么 vue 3 要推崇新的组合式 api,而不是沿用 vue 2 的选项式 api 呢?
单个关联组件,使用组件选项来编程确实可以,但是当关联的组件过多的时候,选项式 api 会让各个组件的逻辑分散在各个组件选项中,混合在一起,让人难以区分哪些是哪个组件的逻辑。
如下示例代码是根据用户,读取数据。使用选项式 api,这个逻辑会分散到各个组件的选项中,如 data,methods 等。如果使用组合式 api,它就集中到组件的 setup 选项中。而不涉及其他选项。这会让其他选项可以集中精力做一些和组件自身密切相关的逻辑。
和选项式 api 不一样的地方,可以很容易将组件式 api 分割成多个函数,所以不用担心 setup() 变得巨大。
选项式 api:
import {fetchUserDatas} from '@/api/datas';
{
componenents:{},
props:{user:''},
data(){
return {datas};
},
watch:{user:'getUserDatas'},
methods:{
async getUserDatas(){
this.datas = await fetchUserDatas(this.user);
}
},
mounted(){
this.getUserDatas();
}
}
组合式 api:
import {fetchUserDatas} from '@/api/datas';
import { ref, onMounted, watch, toRefs } from 'vue';
{
componenets:{},
props:{user:''},
setup(props, context){
const { user } = toRefs(props);
const datas = ref([]);
const getUserDatas = async ()=>{
datas.value = await fetchUserDatas(user.value);
}
onMounted(getUserDatas);
watch(user, getUserDatas);
return {datas, getUserDatas};
}
}
对照表:
api | 选项式 | 组合式 |
---|---|---|
响应式变量 | 自动 | ref(), toRefs() |
函数 | methods | return {()=>...} |
属性 | data() | return {ref(...)} |
生命周期 | mounted | onMounted() |
侦听器 | watch | watch() |
计算属性 | computed | computed() |
输入字段 | props | 不变 |
事件 | emits | 不变 |
供应 | provide | provide() |
注入 | inject | inject() |
注:选项式是组件的属性段,组合式是在 setup() 内调用相应的 vue 接口。
setup 执行时,组件实例并没有创建(不建议使用 this),你只能访问:
即无法访问:
选项式 API | 钩子用在 setup |
---|---|
beforeCreate | 不需要* |
created | 不需要* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
注: setup 本身取代 beforeCreate/created,因此不需要。
组合式 api 也支持 供应/注入,使用的是 vue 提供的 provide 接口和 inject 接口。
provide 参数:
inject 参数:
import { provide, inject, ref, reactive, readonly } from 'vue';
{
components:{
MyMarker
},
setup(){
provide('location',readonly(ref('North pole')));
provide('geolocation',reactive({
longitude:90,
latitude:135
}));
const userLocation = inject('location', 'The Unverse');
const userGeolocation = inject('geolocation');
return {userLocation, userGeolocation};
}
}
组合式 api 也能使用标签引用。DOM 元素将在初始渲染后,分配给标签 ref 属性指定的对象,而且是响应式的,意思是在相应的生命周期中可以使用。
模板的标签通过 teleport 标签来改变自然的父节点位置。
teleport 只是改变 dom 的渲染,并不该变组件间的父子关系。
template:`
<button> ok </button>
<teleport to='body'> 父节点变为 body
<div>child</div>
</teleport>`
setup() 内不应该是用 this,因为组件尚且没有创建完毕。那改怎么调用实例的相关内容?
getCurrentInstance 全局 api 可以解决这一需求:
import {getCurrentInstance} from 'vue';
export default{
setup(){
let inst = getCurrentInstance();
let {proxy} = getCurrentInstance();
// proxy 可以类似 this 的用法
}
}
Mixin 对象包含任意组件选项,然后将其混合到组件本身。现在 vue 推荐使用组合式 api。
const myMixin{
created(){this.hello()},
methods:{
hello(){
consolo.log("hello");
}
}
}
const app = Vue.createApp({
mixins:[myMixin]
}).mount("#app");
{{value}}
v-bind/:
: 绑定属性
vue 内置一些比较特殊的指令:
对普通 DOM 元素进行底层操作,类似 v-model 和 v-show 这些 vue 内置指令,用户也可以自定义指令。
注册 v-focus 自定指令的方法:
app.directive('focus', {mounted(el){el.focus()}});
directives:{ focus:{mounted(el){el.focus()}}};
钩子函数:
钩子参数:
v-focus:[参数]='value'
在组件中使用:
vue 推荐使用模板来创建 html,但也可以使用更底层的渲染函数。
// 模板
template:`
<h1 v-if='level == 1'>
<slot></slot>
</h1>
<h2 v-if='level == 2'>
<slot></slot>
</h2>
...
<h6 v-if='level == 6'>
<slot />
</h6>
`,
props:{
level:{
type:Number,
required:true
}
}
// 渲染函数
const {h} = Vue;
app.component('link-head',{
render(){
return h('h' + this.level,{},this.$slots.default());
},
props:{
level:{
type:Number,
required:true
}
}
})
h() 渲染函数并不是返回 html,而是返回一个虚拟的DOM树,告诉 vue 需要怎样渲染 dom,而对应节点的描述,称为 VNode。
h() 也可以单纯的返回文本,null, 或者混合 VNode 的数组。
所谓描述信息,简而言之,就是 h() 对应的参数:
注:vnode 实例必须唯一。内容相同,也要创建一个新的 vnode 实例。
{modelValue:this.modelValue, 'onUpdate:modelValue': value=>this.$emit('update:modelValue', value) }
。
onClick:$event => console.log('clicked',$event.targe)
<div v-pin:top.animate='200' />
:pin = Vue.resolveDirective('pin'); return Vue.withDirectives(h('div'),[[pin, 200, 'top', {animate:true}]])
渲染函数写法有点复杂,可以借助 Bable 插件使用 JSX 语法:
// <anchored-heading :level='1'><span>Hello</span> world! </anchored-heading>
import AnchoredHeading from '.AnchoredHeading.vue';
const app = createApp({
render(){
return (
<AnchoredHeading level={1}>
<span>Hello</span> world!
</AnchoredHeading>
);
}
}).mount('#app');
实际是渲染函数。
const fc = (props, context){
}
模板实际会编译为渲染函数,如:
模板:
<div v-if="isOK" id='ok'/>
<div v-else id='bad'/>
<ul v-for="item in 100" :key="item">
<li>{{item}}</li>
</ul>
<button @click.once.stop.self.enter.ctrl="console.log(1)" />
<my-div>
<slot />
</my-div>
<componet is='div' v-pin:top.animate='200' />
编译成渲染函数的样子:
import { openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, renderList as _renderList, Fragment as _Fragment, toDisplayString as _toDisplayString, createVNode as _createVNode, withModifiers as _withModifiers, renderSlot as _renderSlot, resolveComponent as _resolveComponent, withCtx as _withCtx, resolveDirective as _resolveDirective, withDirectives as _withDirectives, createTextVNode as _createTextVNode } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_my_div = _resolveComponent("my-div")
const _component_div = _resolveComponent("div")
const _directive_pin = _resolveDirective("pin")
return (_openBlock(), _createBlock(_Fragment, null, [
(_ctx.isOk)
? (_openBlock(), _createBlock("div", {
key: 0,
id: "ok"
}))
: (_openBlock(), _createBlock("div", {
key: 1,
id: "bad"
})),
_createTextVNode(),
(_openBlock(), _createBlock(_Fragment, null, _renderList(100, (item) => {
return _createVNode("ul", { key: item }, [
_createVNode("li", null, _toDisplayString(item), 1 /* TEXT */)
])
}), 64 /* STABLE_FRAGMENT */)),
_createTextVNode(),
_createVNode("button", {
onClickOnce: _withModifiers($event => (_ctx.console.log(1)), ["stop","self","ctrl"])
}, null, 40 /* PROPS, HYDRATE_EVENTS */, ["onClickOnce"]),
_createTextVNode(),
_createVNode(_component_my_div, null, {
default: _withCtx(() => [
_renderSlot(_ctx.$slots, "default")
], undefined, true),
_: 3 /* FORWARDED */
}),
_createTextVNode(),
_withDirectives(_createVNode(_component_div, { is: "div" }, null, 512 /* NEED_PATCH */), [
[
_directive_pin,
200,
"top",
{ animate: true }
]
])
], 64 /* STABLE_FRAGMENT */))
}
// Check the console for the AST
导入全局 api:
import {createApp, h} from 'vue';
简介:
{data(){...}}
选项式对象[directive,value,arg,modifiers]
格式的数组属性:
方法:
vue 插件种类:
// 插件对象
export default {
install:(app, options)=>{
app.config.globalProperties.$translate = key =>{
return key.split('.').reduce((o,i)=>{
if (o) return o[i]
}, options)
}
}
}
vue app 提供 config 配置对象。
import {createApp} from 'vue';
const app = createApp();
console.log(app.config);
config 成员:
<template />
<script>
export default {
name:'MyComponent',
components:[TheHeader],
directives:[Pin],
extends:LittleList,
mixins:undefined,
provide:undefined,
inject:undefined,
inheritAttr:undefined,
props:undefined,
emits:undefined,
setup(){return undefined},
data(){return {};},
computed:{},
watch:{},
beforeCreate(){},
methods:{},
template:undefined,
render:undefined,
}
</script>
<style />
<div is="MyComponent"
v-for="item in@ items"
:key="item">
<span v-if="item"
:id="item"
:ref="item+'span'"
v-model:class="code"
:lang="lang"
@click="console.log('message')"
v-text="good idea!">
</span>
</div>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。