4 Star 16 Fork 9

deepinwiki / wiki

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
vue.md 41.48 KB
一键复制 编辑 原始数据 按行查看 历史
htqx 提交于 2023-08-02 00:12 . 排版格式变更

VUE

前言

VUE 是一个 js 网页前端开发框架。它是自底向上逐层应用。核心库关注视图层。特点是易于上手,还能于其他第三方库整合。

还有就是现代化的工具链支持。

  1. 声明式渲染
    1. 动态绑定属性 v-bind
    2. 动态绑定事件 v-on
    3. 动态绑定表单(双向绑定)v-model
    4. 动态绑定条件 v-if
    5. 动态绑定循环 v-for
  2. 组件化应用构建

框架

  1. 应用实例 : 需要绑定到某个 DOM 节点
  2. 根组件 : 即绑定的节点对应的模型,遵循 MVVM 模式,这种组件也叫 viewModel(视图模型)。
    1. 属性 : data() 成员函数的返回对象
    2. 生命周期钩子
      1. beforeCreate(): 应用创建前
      2. created() : 应用创建后
      3. beforeMount(): 绑定组件前
      4. mounted() : 绑定组件后
      5. beforeUpdate(): 更新组件前
      6. updated() : 更新组件后
      7. beforeUnmount(): 卸载组件前
      8. unmounted() :卸载组件后

模板语法

vue 基于 html 设计了模板语法。将 DOM 和组件进行绑定。技术上,vue 将模板编译为虚拟 DOM 渲染函数,结合响应系统,计算出最少需要重新渲染的组件,从而提高性能。

如果不使用 vue 模板,也能直接写渲染 render 函数,支持 JSX 语法。

  1. 文本 : {{ msg }};vue 中绑定的值不止是单个值,也可以是 js 表达式
    1. 表达式的功能受限,只有沙盒中提供的功能才能使用
  2. HTML : v-html=' html ';注意:有安全风险
  3. 指令 : v-bind:id='dynamicId: 绑定到 id 属性
    1. id 是 v-bind 指令的参数
    2. v-bind:[dy]='xxx': dy 是动态参数
      1. 要求不存在空格和引号字符
      2. 会被强制转换为小写
    3. v-on:submit.prevent='xxx': .prevent 是修饰符,即以特殊方式绑定
    4. :href : 是 v-bind:herf 的缩写
    5. @click : 是 v-on:click 的缩写
    6. 当属性值为 true(或转换为真)时,该属性就被渲染出来,否则它不会出现在 DOM 里面。(少数例外会渲染空字符串)
    7. 如果这样绑定一个对象: v-bind="obj", 其中obj={a:1,b:2},会将对象的所有属性分别绑定,即等价于 :a="obj.a" :b="obj.b"。
    8. 单向数据流:绑定应该是父级传递到组件内部的数据,组件内部原则上不应该逆向改动数据。

样式

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 指令控制是否渲染指定内容。

  1. v-if
  2. v-else
  3. v-else-if
  4. v-show : 单纯控制 display 样式属性
<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 指令渲染列表。

  1. (value, key, index) in items :特殊的内置语法,items 可以是整数
  2. :key 指定排序的键
  3. 自定义组件可以使用 v-for 指令,但自定义组件需要通过 props 传递属性,emits 传递事件。
<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 指令处理事件和视图模型之间的绑定。

  1. $event : 模板中的可访问的上下文变量,代表原始的 DOM 事件。
  2. v-on 可以绑定方法,也可以直接内联 js 代码。
  3. 事件修饰符 : 可以串联,根据先后顺序有不同含义
    1. .stop : 阻止事件继续传播
    2. .prevent : 不重载页面
    3. .capture : 监听事件(不阻止内部的事件处理函数)
    4. .self : 当引发事件的是当前元素,才触发事件处理
    5. .once : 只触发一次(可用在自定义组件事件)
    6. .passive : 滚动事件会立即触发,而不会等待上次事件处理完成。
  4. 按键修饰符:修饰鼠标和键盘事件的按键,如: .keyup.enter
    1. .enter
    2. .tab
    3. .delete
    4. .esc
    5. .space
    6. .up
    7. .down
    8. .left
    9. .right
    10. .middle
  5. 系统修饰符:修饰鼠标和键盘事件的组合键
    1. .ctrl
    2. .alt
    3. .shift
    4. .meta (即 windows 键)
    5. .exact :对以上修饰符做修饰,限定精确的按键组合
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" />

绑定机制:

  1. <input /><textarea />: 绑定value 属性、使用 input 事件。
  2. <input type='checkbox' />: 绑定 checked 属性、使用 change 事件。
    1. 多个复选: value (对应视图模型的数组属性)
    2. 单个复选: true-value / false-value 对应选中和没有选中的值
  3. <input type='radio' />: 绑定 checked 属性、使用 change 事件。
    1. 多个单选: value (对应视图模型的单值属性)
  4. <select>: 绑定 value 属性、使用 change 事件。

修饰符:

  1. .lazy : 从 input 事件延迟到 change 事件触发数据更新
  2. .number : 数据类型为数值
  3. .trim : 去掉首尾空白符
文本输入框:<input v-model='message' placeholder='edit me' />
多行文本输入:<textarea />
复选框:<input type='checkbox' /> 
单选框:<input type='radio' />
选择框(多选):<select multiple>
            <option>A</option>
            <option>B</option>
        </select>

视图模型

视图模型 vm:

  1. 属性:可以在三个地方定义,意思是三个集合是相互映射的等同集合
    1. data() 返回对象
    2. 顶级属性(在内部定义,而非从外部新增)
    3. vm.$data 的属性
  2. 方法:
    1. methods 属性关联对象的方法
    2. 一般用在事件处理,用在其他地方要求不带任何副作用(即修改数据),修改数据应该采用生命周期钩子方法。
    3. 防抖 _.debounce(this.click, 500)
  3. 计算属性:即简单的求值方法,根据函数体内包含的响应属性的变化,计算结果会缓存起来
    1. computed: {sum(){return a+b}} ,其中 a,b 是响应属性
    2. 支持 get / set 访问器,如 computed:{fullname:{get(){},set(){}}}
  4. 侦听器:适用耗时的求值方法,当侦听对象发生变化时调用函数
    1. watch: {侦听对象名: 回调方法}
    2. 回调方法有两个参数,即侦听对象的新值和旧值

组件

vue 采用组件化构建应用的策略。可以把组件理解为一个可复用的局部视图模型。

  1. 创建应用实例: const app = Vue.createApp({})
  2. 注册组件: app.component('组件名',{组件视图模型的定义,template:组件模板})
  3. html 中被 app 挂载的 dom 节点下可以使用组件名作为标签使用该独立组件。
  4. 专属模板(即 app 绑定的 DOM 标签,vue 文件中的标签, 或 template 字段内联的标签)中可以访问组件中:
    1. data() 返回的响应式变量
    2. methods 返回的函数
    3. computed 计算属性
    4. 等等,可见 vue 开发也类似传统的 html+js 组合,但它限定了范围,规范了接口,从而建立了基础框架
  5. 生命周期钩子函数:vue 按固定顺序执行的回调函数,如 app = createApp({}).mount(el):
    1. 初始化事件和生命周期
    2. beforeCreate()
    3. 初始化注入和响应性
    4. created()
    5. 是否有 template 属性
      1. 有:编译 template 到渲染函数
      2. 无:编译 el 的 innerHTML 到模板
    6. beforeMount()
    7. 创建 app.$el 并添加到 el
    8. mounted()
    9. 已挂载
    10. 数据变化时
    11. beforeUpdate()
    12. 虚拟 DOM 重新渲染和更新
    13. updated()
    14. 当调用 app.unmount() 时
    15. beforeUnmount()
    16. unmounted()
    17. 已卸载。

首先是 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>

组件可以采用以上内联的写法,也可以使用独立的组件文件(推荐)。

组件标签的交互

  1. 首先要注册
    1. 全局注册:app.component(名字, 内容)
    2. 局部注册:app.component({components:{组件1,组件2...}})
  2. 通过标签属性和组件的 props 属性进行交互。
    1. 数组形式: [名字1, 名字2...]
    2. {名字1:类型1,名字2:{验证设置}...}
  3. 通过标签事件和组件的 emits 属性进行事件绑定。
    1. 类似 props,也支持验证格式书写
    2. $event : DOM 事件对象
    3. $emit('事件名', 参数) : 激活事件
    4. e.srcElement.datasrc.aid : 事件对象中记录的标签自定义属性 data-aid
  4. 不在 props/emits 的标签属性,将会继承到组件内部根节点
    1. inheritAtts:false 通过组件属性禁用继承
    2. $attrs : 包含所有非 prop/emits 属性。
    3. 具有多根模板的组件,需要显式绑定 $attrs
  5. provide(供应)/inject(注入) 解决了 props 要逐层传递信息的弊端,它可以括越多个内嵌层级,直接绑定供应方和接收方。
    1. provide:{user:'jone'} 供应独立的属性
    2. provide(){return {this.user};} 供应组件的属性(默认非响应式)
    3. provide(){return {user:Vue.computed(()=>this.user)};} 响应式
    4. inject:['user'] 接收方
  6. 强制绑定标签:设置 ref='name' 标签属性,然后可以通过 $refs 引用该标签,这是一个低层级的操作,应尽量避免在模板或计算属性中使用。

和模板中的 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>

组件上使用 v-model

<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 有内置的修饰符:

  1. .trim
  2. .number
  3. .lazy

通过 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 />

子内容并不处于组件作用域(只是结构上像,所以称之为子内容,而非真正的子内容),它和组件其实是兄弟关系,只能使用组件的父级作用域的内容。

  1. 默认子内容: <slot>默认内容</slot>
  2. 命名插槽:<slot name='header' /> 对应 <template v-slot:header />
    1. v-slot:header 可以缩写为 #header
  3. 组件传递信息到插槽:<slot :item='item' />, 使用者 <template v-slot:default='slotProps'>{{ slotProps.item }}</template>
    1. 当只有一个默认插槽,可以省略<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 自带一些实用的组件:

  1. keep-alive : 可保持内部组件的状态
    1. include: 名称匹配的组件会被缓存
    2. exclude: 排除
    3. max : 最多缓存个数
    4. 作用特定生命周期(不会重新创建和卸载):
      1. activated : 恢复
      2. deactivated : 保存
  2. transition : css 动画转换
    1. name : css 过度类名
    2. appear : 是否在初始渲染使用过渡
    3. persisted: 是否有实体
    4. css : 是否使用 css 过渡类
    5. type: 过渡事件类型
    6. mode: 离开模式或进入模式
    7. duration: 持续时间
    8. enter-from-class
    9. leave-from-class
    10. appear-class
    11. enter-to-class
    12. leave-to-class
    13. appear-to-class
    14. enter-active-class
    15. leave-active-class
    16. apper-active-claas
    17. 事件
      1. before-enter
      2. before-leave
      3. enter
      4. leave
      5. appear
      6. after-enter
      7. after-leave
      8. after-appear
      9. enter-appear
      10. enter-cancelled
      11. leave-cancelled
      12. appear-cancelled
  3. transition-group : css 动画转换组
    1. tag
    2. move-class
    3. 其他 props 和 emits 类似 transition
    4. 内部标签需要定义 key
  4. teleport : 将内层组件移动到特定元素的子层
    1. to : 移动到指定元素的内层
    2. disabled: 是否禁止移动
  5. slot(非正常组件) : 插槽,即将组件内部实际内容(如内部标签)替换到该标签处
    1. name : 具名插槽
  6. component(非正常组件): 动态渲染组件
    1. is : 渲染的实际组件

异步组件

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 元素格式有些特殊要求,因此组件有如下注意事项:

  1. ul、ol、table、select 的内部元素类型有限制,v-is 指令将自定义组件标签伪装成特定内部元素。
  2. 驼峰命名 camelCase(或PascalCase)改成横线字符分隔(kebab-case)的等效形式,如 postTitle --> post-title。
    1. DOM 中使用横线字符分隔
    2. .vue 文件和字符串中使用 PascalCase 命名组件,如 MyComponent;用驼峰命名字段,如 postTitle,html 中用 kebab-case 命名属性,如 post-title
<table>
    <tr v-is=" 'blog-post' " /> 注意值是字符串
</table>

底层操作

  1. 强制更新绑定: $forceUpdate
  2. 只渲染一次: <div v-once />
  3. 绑定标签: <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);

独立对象添加响应式

相关工具:

  1. reactive : 将对象包装成响应式
  2. ref : 将原始值类型(Number,String等)包装 {value:值} 的对象,然后用reactive包装成响应式
    1. isRef : 判断是否经过 ref
    2. unref : isRef(val) ? val.value : val
    3. toRef : 将对象属性包装为 ref
    4. customRef(track, trigger) : 自定义 ref
      1. track() : 侦听器
      2. trigger() : 触发器
    5. shallowRef : 浅层 ref
      1. triggerRef : 触发浅层 ref 副作用
  3. toRefs : 解构响应式对象时,默认得到一个非响应式的类型,通过 toRefs 将响应式对象的属性包装成 ref,进而解构的时候得到一个响应式对象。
  4. readonly : 将响应式对象(或普通对象)转换为只读响应式对象
  5. watchEffect : 响应式侦听器,响应式变化触发侦听器。
  6. 判断
    1. isProxy : 判断是否经过 reactive 或 readonly
    2. isReactive : 判断是否经过 reactive
    3. isReadOnly : 判断是否经过 readonly
  7. toRaw : 获取原始对象(建议临时使用)
  8. markRaw : 禁止被代理为响应式(提高性能)
  9. 浅层代理(默认都是深层代理)
    1. shallowReactive : 浅层响应式代理
    2. shallowReadonly : 浅层只读响应式代理

组件相关工具:

  1. computed : 计算属性实际返回 ref
  2. watch : 侦听器侦听 ref

ref 响应式对象,等于打了一层外包装 {value:原值},要访问的时候需要 xx.vaule,不过 vue 的一些上下文支持自动解除这个包装,从而有基础类型的感觉:

  1. 作为响应式对象的属性时,自动解包
  2. 在模板标签中使用(内层不支持自动解包,如作为数组的元素)
// 对象
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);

组合式 api

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()

  1. 参数 props: 响应式变量,不能用 es6 解构,应使用 toRefs/toRef 解构。
    1. const {title} = props 换成 const {title} = toRefs(props);
    2. 可选项解构: const title = toRef(props, 'title');
  2. 参数 context 对象成员(非响应式变量):
    1. attrs : 非 props 属性
    2. slots : 插槽
    3. emit : 事件

setup 执行时,组件实例并没有创建(不建议使用 this),你只能访问:

  1. props
  2. attrs
  3. slots
  4. emit

即无法访问:

  1. data
  2. computed
  3. methods

生命周期钩子

选项式 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,因此不需要。

provide / inject

组合式 api 也支持 供应/注入,使用的是 vue 提供的 provide 接口和 inject 接口。

provide 参数:

  1. name
  2. value

inject 参数:

  1. name
  2. 默认值
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};
    }
}

标签引用 ref

组合式 api 也能使用标签引用。DOM 元素将在初始渲染后,分配给标签 ref 属性指定的对象,而且是响应式的,意思是在相应的生命周期中可以使用。

  1. 如 : onMounted(()=>...)
  2. 支持 JSX
  3. v-for 内部没有特殊处理。
  4. 侦听器 watch/watchEffect 在 DOM 挂载前运作,因此标签引用还不可用
    1. 可添加参数 {flush:'post'},让侦听器在 DOM 更新后运行

teleport 标签指定父节点

模板的标签通过 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

Mixin 对象包含任意组件选项,然后将其混合到组件本身。现在 vue 推荐使用组合式 api。

  1. 混合的 data() 函数都会被调用,并将结果合并
    1. 属性冲突时,组件自身的属性优先
  2. 混合的钩子, mixin 对象的钩子优先调用
  3. methods、components、directives 合并为一个对象
    1. 冲突时,优先采用组件自身的键值对
  4. 全局注册 app.mixin({mixin对象})
  5. 修改默认合并策略: app.config.optionMergeStrategies.属性名 = (toVal, fromVal) =>{...return 结果值};
const myMixin{
    created(){this.hello()},
    methods:{
        hello(){
            consolo.log("hello");
        }
    }
}
const app = Vue.createApp({
    mixins:[myMixin]
}).mount("#app");

指令

  1. v-text : 更新元素的 textContent(文本内容)
    1. Mustache 插值: {{value}}
  2. v-html : 更新元素的 innerHTML(内部html) (可能受到XSS攻击)
  3. v-show : 隐藏/显示标签
  4. v-if : 渲染/不渲染标签
    1. v-else
    2. v-else-if
  5. v-for : 循环渲染
  6. v-on/@ : 绑定事件处理器(DOM标签只能处理原生事件,组件也可以处理子组件事件)
    1. 修饰符
      1. .stop : 调用 event.stopPropagation()
      2. .prevent : 调用 event.preventDefault()
      3. .capture : 添加事件处理器时使用 capture 模式
      4. .self : 触发事件的元素是绑定的事件处理器的元素时触发回调
      5. .{keyAlias}: 特定按键时
      6. .once : 只触发一次
      7. .left : 鼠标按下左键时
      8. .right : 鼠标按下右键时
      9. .middle : 鼠标按下中键时
      10. .passive : {passive:true} 模式添加事件处理器
    2. 事件上下文对象: $event
  7. v-bind/: : 绑定属性
    1. .camel : 将 kebab-case 名转换为 camelCase 名
    2. props 上下文: $props
  8. v-model: 双向绑定表单(input、select、textarea、组件)
    1. .lazy: 监听 change 而不是默认的 input
    2. .number: 输入转化为数字
    3. .trim: 过滤首尾空格
  9. v-slot/#: 用于 template 和组件,指定对应的插槽名和上下文对象
  10. v-pre: 不编译,按原始文本输出
  11. v-cloak: 编译结束前隐藏
  12. v-once: 只编译一次
  13. v-is: 用于页面HTML中写VUE模板,将标签替换为实际组件

特殊指令

vue 内置一些比较特殊的指令:

  1. key : vue 内部经常需要使用 key 来实现逻辑(如 v-for 排序)
  2. ref : 将标签对象(DOM 或组件)添加到 vm.$refs 属性内进行操作
  3. is : 动态标签中用来指定实际的组件

自定义指令

对普通 DOM 元素进行底层操作,类似 v-model 和 v-show 这些 vue 内置指令,用户也可以自定义指令。

注册 v-focus 自定指令的方法:

  1. 全局注册: app.directive('focus', {mounted(el){el.focus()}});
  2. 局部指令: directives:{ focus:{mounted(el){el.focus()}}};
  3. 省略钩子时,自动绑定 mounted 和 updated

钩子函数:

  1. created : 绑定元素的属性或事件监听器被应用前调用。当自定义指令需要在 v-on 事件监听器前调用时。
  2. beforeMount: 第一次绑定到元素并且在挂载父组件之前调用
  3. mounted: 父组件被挂载后调用。
  4. beforeUpdate: 更新 VNode 之前调用。
  5. updated: 更新之后。
  6. beforeUnmount: 卸载父组件之前调用。
  7. unmounted: 卸载父组件之后。

钩子参数:

  1. el: Vnode
  2. binding: 指令的参数,如 v-focus:[参数]='value'
    1. value: 值
    2. arg : 参数

在组件中使用:

  1. 作用在根节点上
  2. 多根节点上会被忽略,并警告

渲染函数

vue 推荐使用模板来创建 html,但也可以使用更底层的渲染函数。

  1. 当需要动态生成不同的标签时,模板只能用 v-if 嵌套,代码可能变得冗长
// 模板
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
        }
    }
})

虚拟 DOM 树

h() 渲染函数并不是返回 html,而是返回一个虚拟的DOM树,告诉 vue 需要怎样渲染 dom,而对应节点的描述,称为 VNode。

h() 也可以单纯的返回文本,null, 或者混合 VNode 的数组。

所谓描述信息,简而言之,就是 h() 对应的参数:

  1. 标签名,组件,异步组件,函数式组件
    1. Vue.resolveComponent(组件名) ==> 组件
  2. props
  3. 子 VNode、插槽、文本组成的数组 (如果 props 为null,也可作为第二个参数传入)

注:vnode 实例必须唯一。内容相同,也要创建一个新的 vnode 实例。

渲染函数替代模板中的功能

  1. v-if、v-for 替代为 js 原生语法
  2. v-mode --> props 的{modelValue:this.modelValue, 'onUpdate:modelValue': value=>this.$emit('update:modelValue', value) }
    1. 事件:update:modelValue --> onUpdate:modelValue
  3. v-on --> click --> onClick:$event => console.log('clicked',$event.targe)
    1. 修饰符: click.once --> onClickOnce
    2. .stop --> event.stopPropagation()
    3. .prevent --> event.preventDefault()
    4. .self --> if (event.target !== event.currentTarget) return
    5. 按键:.enter --> if (event.keyCode !== 13) return
    6. 组合键:.ctrl --> if (!event.ctrlKey) return
  4. 插槽: this.$slots.default()
    1. header:this.$slots.header
    2. default:(props)=>{return h(...)}
  5. component 标签: Vue.resoleDynamicComponent(this.name)
    1. 如果 is='普通标签',直接渲染即可
  6. 自定义指令 <div v-pin:top.animate='200' />pin = Vue.resolveDirective('pin'); return Vue.withDirectives(h('div'),[[pin, 200, 'top', {animate:true}]])
  7. 内置组件:需要手动导入

JSX 插件

渲染函数写法有点复杂,可以借助 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');

函数式组件

实际是渲染函数。

  1. props
  2. context
    1. attrs
    2. emit
    3. slots
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

导入全局 api:

import {createApp, h} from 'vue';

简介:

  1. createApp: 创建应用实例
    1. 第一个参数: {data(){...}} 选项式对象
    2. props
  2. h: 创建虚拟节点 VNode
    1. type: 标签名,组件,函数式组件
    2. props(可选)
    3. children: 子 VNdoe(字符串,数组,VNode)
  3. defineComponent: 定义组件
    1. 选项式对象/setup函数
  4. defineAsyncComponent: 异步组件
    1. Promise 工厂方法:()=>import('./components/AsyncComponent.vue')
  5. resolveComponent : 根据名称获取组件
    1. name
  6. resolveDynamicComponent : 根据名称获取动态组件
    1. name
  7. resolveDirective: 根据名称获取指令
    1. name
  8. withDirectives: 用于 render()/setup() 中,返回一个可以使用指令的 VNode
    1. vnode
    2. directives: [directive,value,arg,modifiers] 格式的数组
  9. createRenderer: 创建渲染器
    1. hostNode
    2. hostElemet
  10. nextTick: 下一个 DOM 更新周期
  11. mergeProps: 合并生成新的 props
  12. useCssModule: 用于 setup() 中访问 css 模块。
    1. name: 默认 "$style"
  13. version: vue 版本号

应用 api(vm)

  1. component : 注册组件或检索组件(不传入definition 将返回组件定义)
    1. name : 组件名字
    2. definition : 组件定义
  2. config: 配置对象
  3. directive: 注册或检索指令(不传入definition 将返回指令定义)
    1. name
    2. definition
      1. created() : 绑定前
      2. beforeMount(): 挂载父组件前
      3. mounted(): 挂载父组件后
      4. beforeUpdate(): 更新VNode 前
      5. updated(): 更新 VNode 后
      6. beforeUnmount(): 卸载父组件前
      7. unmounted(): 卸载父组件后
      8. 参数:
        1. el: DOM
        2. binding:
          1. instance : 组件实例
          2. value: 值
          3. oldValue: 先前值
          4. arg: 参数
          5. modifiers: 修饰符
          6. dir:definition 对象
        3. vnode:当前节点
        4. prevNode: 上一个虚拟节点(在beforeUpdate 和 updated 中可用)
  4. mixin : 注册mixin对象
    1. mixin
  5. mount : 挂载到 DOM 节点,innerHTML 变为组件的渲染结果
    1. rootContainer: DOM 的根节点
    2. isHydrate
  6. provide: 供应服务,由注入(inject)使用(子组件,插件等),可替代 globalProperties
    1. key
    2. value
  7. unmount: 卸载
  8. use: 插件
    1. plugin: {install(){...}...} 的插件对象或者 install() 方法
    2. ...options
  9. version : 版本号

属性:

  1. $data : 数据对象
  2. $props:入口数据对象
  3. $el:根 DOM 节点
  4. $options:自定义属性
  5. $parent:父实例
  6. $root:根组件(没有父实例时就是自己)
  7. $slots:具名插槽(defalt 默认插槽)
  8. $refs: 具有 ref 属性的标签
  9. $attrs: 非 props 和 emits 的其他标签属性

方法:

  1. $watch: 侦听器
    1. 数据对象或函数
    2. (value,oldvalue)=> 回调函数
    3. {选项}
      1. deep: true : 深度侦听对象(和数组)
      2. immediate: true : 立即触发回调
      3. flush: pre/post/sync : 渲染前/渲染后/同步
    4. 返回值:停止侦听函数
  2. $emit : 触发事件
  3. $forceUpdate : 强制重新渲染自身和内容
  4. $nextTick : 更新完毕后运行。
    1. callbak : 回调

插件

vue 插件种类:

  1. 具有 install 方法的对象
  2. 函数
// 插件对象
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 成员:

  1. errorHandler : (err,vm, info)=> 默认错误处理
  2. warnHandler: (msg,vm, trace)=> 默认警告处理
  3. globalProperties : 全局属性对象(可添加自定义属性到此)
  4. isCustomElement: tag=>识别自定义组件
  5. optionMergeStrategies: (parent, child, vm)=>自定义选项合并策略
  6. performance:性能分析

代码模板

组件字段布局顺序

<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>

路由

状态管理

服务端渲染

工具

动画

无障碍访问

参考

1
https://gitee.com/deepinwiki/wiki.git
git@gitee.com:deepinwiki/wiki.git
deepinwiki
wiki
wiki
master

搜索帮助