Vue官方文档学习笔记 22-2-3

开始Vue,抓紧咯

本文大部分内容来自于Vue官方中文文档,掺杂了自己的浅陋理解,仅用作个人学习使用,各位小伙伴不要被我不严谨的措辞、类比误导了

介绍

Vue.js是什么

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。


开始

将 Vue.js 添加到项目中主要有四种方式:

  1. 在页面上以 CDN 包的形式导入。
  2. 下载 JavaScript 文件并自行托管。
  3. 使用 npm安装它。
  4. 使用官方的 CLI 来构建一个项目,它为现代前端工作流程提供了功能齐备的构建设置 (例如,热重载、保存时的提示等等)。

CDN

<script src="https://unpkg.com/vue@next">script>

这样会直接使用最近版本

但是对于生产环境,最好使用明确的版本号和文件,防止盲目使用最新版本造成破坏


自行托管

下载.js文件,并按照使用普通js那样使用它,具体方式类似于CDN


npm

建议大型应用使用npm,可以配合模块打包器使用

# 最新稳定版
$ npm install vue@next

Vue 还提供了编写单文件组件的配套工具。如果你想使用单文件组件,那么你还需要安装 @vue/compiler-sfc

$ npm install -D @vue/compiler-sfc

命令行工具 CLI


Vite


声明式渲染

核心是一个允许采用模板语法声明式地将数据渲染进DOM的系统

文本插值:

<div id="hello">
    {{msg}}:{{count}}
div>
<script>
    const msg={
        data(){
            return {
                msg:"hello",
                count:0
            }
        },
        mounted() {
            setInterval(() =>{
                this.count++
            },1000)
        }
    }
    Vue.createApp(msg).mount("#hello")
script>

数据和DOM建立了关联,所有东西都是响应式的。

指令绑定:

<div id="bind-attribute">
    <span v-bind:title="message">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
    span>
div>
<script>
    const AttributeBinding = {
        data() {
            return {
                message: 'You loaded this page on ' + new Date().toLocaleString()
            }
        }
    }

    Vue.createApp(AttributeBinding).mount('#bind-attribute')
script>

v-bind attribute 被称为指令。指令带有前缀 v-。它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:“将这个元素节点的 title attribute 和当前活跃实例的 message property 保持一致”。


处理用户输入

为了让用户和应用进行交互,我们可以用 v-on 指令添加一个事件监听器,通过它调用在实例中定义的方法

<div id="bind-attribute">
    <p>{{message}}p>
    <button v-on:click="func">反转button>
div>
<script>
    const AttributeBinding = {
        data() {
            return {
                message: 'You loaded this page on ' + new Date().toLocaleString()
            }
        },
        methods: {
            func(){
                this.message=this.message.split('').reverse().join('')
                //字符串切分-反转数组-字符数组转字符串
            }
        }
    }
    Vue.createApp(AttributeBinding).mount('#bind-attribute')
script>

你没有触碰dom,dom的操作都由Vue处理

v-model实现双向绑定

<div id="hello">
    {{msg}}:{{count}}
    <br>
    <input type="text" v-model="msg">
div>
<script>
    const msg={
        data(){
            return {
                msg:"hello",
                count:0
            }
        },
        mounted() {
            setInterval(() =>{
                this.count++
            },1000)
        }
    }
    Vue.createApp(msg).mount("#hello")
script>

双向绑定,input变化时msg也会同步改变


条件、循环

v-if

<div id="hello">
    {{msg}}:{{count}}
    <br>
    <input type="checkbox" v-model="seen"> 
    <br>
    <span v-if="seen">你看到了我span>
div>
<script>
    const msg={
        data(){
            return {
                msg:"hello",
                count:0,
                seen:true
            }
        },
        mounted() {
            setInterval(() =>{
                this.count++
            },1000)
        }
    }
    Vue.createApp(msg).mount("#hello")
script>

v-for配合列表处理数组

<div id="hello">
    {{msg}}:{{count}}
    <ol>
        <li v-for="todo in todos">
            {{todo.text}}
        li>
    ol>
div>
<script>
    const msg={
        data(){
            return {
                msg:"hello",
                count:0,
                todos:[
                    {text:"1"},
                    {text:"2"},
                    {text:"3"}
                ]
            }
        },
        mounted() {
            setInterval(() =>{
                this.count++
            },1000)
        }
    }
    Vue.createApp(msg).mount("#hello")
script>

组件化应用

页面可以抽象出一颗祖建树

Vue官方文档学习笔记 22-2-3_第1张图片

Vue中,组件本质上是一个具有与定义选项的实例

创建组件:创建一个组件对象,之后定义在父级组件即可。

<div id="todo-list-app">
    <ol>
        
        
        
        <todo-item
                   v-for="item in groceryList"
                   v-bind:todo="item"
                   v-bind:key="item.id"
                   >todo-item>
    ol>
div>
<script>
    const TodoItem = {
        props: ['todo'],
        template: `
  • {{ todo.text }}
  • `
    } const TodoList = { data() { return { groceryList: [ { id: 0, text: 'Veasdgetables' }, { id: 1, text: 'Cheese' }, { id: 2, text: 'Whatever else humans are supposed to eat' } ] } }, components: { TodoItem } } const app = Vue.createApp(TodoList) app.mount('#todo-list-app')
    script>

    应用与组件实例


    创建一个应用实例


    每个应用都是从createApp创建一个应用实例开始的。他用于注册一个“全局”组件。

    const app = Vue.createApp({
      /* 选项 */
    })
    
    const app = Vue.createApp({})
    app.component('SearchInput', SearchInputComponent)
    app.directive('focus', FocusDirective)
    app.use(LocalePlugin)
    

    应用实例暴露的大多数方法都会返回该同一实例,允许链式:

    Vue.createApp({})
      .component('SearchInput', SearchInputComponent)
      .directive('focus', FocusDirective)
      .use(LocalePlugin)
    

    根组件


    传递给 createApp 的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。

    一个应用需要被挂载到一个 DOM 元素中。例如,如果你想把一个 Vue 应用挂载到

    ,应该传入 #app

    const RootComponent = { 
      /* 选项 */ 
    }
    const app = Vue.createApp(RootComponent)
    const vm = app.mount('#app')
    

    与大多数应用方法不同的是,mount 不返回应用本身。相反,它返回的是根组件实例。

    组件实例 property


    在前面的指南中,我们认识了 data property。在 data 中定义的 property 是通过组件实例暴露的:

    const app = Vue.createApp({
      data() {
        return { count: 4 }
      }
    })
    
    const vm = app.mount('#app')
    
    console.log(vm.count) // => 4
    

    还有各种其他的组件选项,可以将用户定义的 property 添加到组件实例中,例如 methodspropscomputedinjectsetup。我们将在后面深入讨论它们。组件实例的所有 property,无论如何定义,都可以在组件的模板中访问。

    Vue 还通过组件实例暴露了一些内置 property,如 $attrs$emit。这些 property 都有一个 $ 前缀,以避免与用户定义的 property 名冲突。

    生命周期钩子


    每个组件在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

    比如created钩子就可以在实例创建后执行代码。代码中使用this指向当前活动实例

    Vue.createApp({
      data() {
        return { count: 1}
      },
      created() {
        // `this` 指向 vm 实例
        console.log('count is: ' + this.count) // => "count is: 1"
      }
    })
    

    不要使用箭头函数,因为他无法使用this。

    个人理解:类似于aop或者代理模式,给你在封装好的组件各个时期添加代码的功能,编程的本质是套衣服。

    生命周期


    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IPeXxIT-1643967326448)(https://v3.cn.vuejs.org/images/lifecycle.svg)]

    模板语法


    Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层组件实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。

    在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应性系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

    ## 插值


    文本

    双大括号,最基本最常见

    会去寻找实例property中对应的值,并且自动绑定。

    你可以使用 v-once指令插一个不会更新的值。需要注意这个对其他数据的绑定影响

    原始HTML

    双大括号不会将内容解释为HTML,假如想输出HTML,请使用v-html指令

    只对可信内容进行HTML插值,不要用户提供的内容进行插值

    Attribute

    使用v-bind指令对属性插值,大括号不起作用

    <div v-bind:id="dynamicId">div>
    

    如果绑定的值是 nullundefined,那么该 attribute 将不会被包含在渲染的元素上。

    ### 使用js表达式

    事实上,我们的插值可以是一个js表达式

    但是要注意语句与表达式的区别

    {{var x = 1}}  //语句 不可以
    {{x = 1}}      //表达式 可以
    

    指令


    指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 (v-forv-on 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

    参数

    一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute,下面例子中的href就是v-bind的一个参数

    <a v-bind:href="url"> ... a>
    

    还有一个v-on的例子,它对应的是DOM的事件名

    <a v-on:click="doSomething"> ... a>
    

    动态参数

    可以使用js表达式代替参数

    <a v-bind:[attributeName]="url"> ... a>
    

    假如你有一个attributeName的值为"href",那么等价于v-bind:href

    修饰符

    修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

    <form v-on:submit.prevent="onSubmit">...form>
    

    1

    在接下来对 v-onv-for 等功能的探索中,你会看到修饰符的其它例子

    缩写


    不写v-bind和v-on

    
    <a v-bind:href="url"> ... a>
    
    
    <a :href="url"> ... a>
    
    
    <a :[key]="url"> ... a>
    
    
    <a v-on:click="doSomething"> ... a>
    
    
    <a @click="doSomething"> ... a>
    
    
    <a @[event]="doSomething"> ... a>
    

    tips

    对动态参数值约定

    动态参数预期会求出一个字符串,null 例外。这个特殊的 null 值可以用于显式地移除绑定。任何其它非字符串类型的值都将会触发一个警告。

    对动态参数表达式约定

    动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:

    
    <a v-bind:['foo' + bar]="value"> ... a>
    

    变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。

    在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:

    
    <a v-bind:[someAttr]="value"> ... a>
    
    JavaScript 表达式

    模板表达式都被放在沙盒中,只能访问一个受限的全局变量列表,如 MathDate。你不应该在模板表达式中试图访问用户定义的全局变量。

    Data Property 和方法


    Data Property


    组件的data是一个函数,会在创建时调用,他需要返回一个对象

    他们只会在首次创建时添加,因此你需要保证你用的proerty都在data返回值里,可以考试使用null等占位值。

    假如在后续时添加data的新property,vue的响应系统不会跟踪它。

    不要使用_和$开头的变量,这会导致和内部API冲突

    方法


    使用methods为组件添加方法,他是一个包含所需方法的对象

    定义时也不要使用箭头函数,这会导致VUE无法注入this

    注意了,这里函数内部假如使用axios等库再写函数时,vue就无法保证能将this注入进去了,所以需要使用匿名函数

    methods: {
        getAnswer() {
            this.answer = 'Thinking...'
            axios
                .get('https://yesno.wtf/api')
                .then(response => {//这里一定要使用箭头函数!不然this无法获取到vue对象
                this.answer = response.data.answer
            })
                .catch(error => {
                this.answer = 'Error! Could not reach the API. ' + error
            })
        }
    }
    

    模版中也支持js表达式,你可以在那些地方调用方法

    <span :title="toTitleDate(date)">
      {{ formatDate(date) }}
    span>
    

    防抖与节流


    是什么

    防抖:发生事件x秒后执行y方法,每次重新触发事件清零计时,也就是在最后一次触发后的x秒执行y方法。

    比如搜索联想,在文本改变后1s进行联想。假如你在1s内持续输入,则不会联想。当你停止输入1s以上时才会联想。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKSuiwqS-1643967326449)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220203003254704.png)]

    节流:发生事件x秒后执行y方法,x秒内重复触发事件不会重复执行,也就是每x秒最多进行一次操作

    搜索联想就不能用这个了,不然持续输入的话,会每一秒联想一次。

    常见场景比如即时查询,不管用户查询多少次,你定一个额度,一段时间最多返回一次结果即可。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O13H0rdn-1643967326450)(C:\Users\光球层上的黑子\AppData\Roaming\Typora\typora-user-images\image-20220203003719241.png)]

    Vue解决方案

    没有内置,可以使用Lodash等库来实现

    如果一个组件只用一次,你可以直接在methods实现自己的防抖

    但是复用时会导致防抖共享。需要彼此独立时,可以在生命周期钩子实现

    app.component('save-button', {
      created() {
        // 使用 Lodash 实现防抖
        this.debouncedClick = _.debounce(this.click, 500)
      },
      unmounted() {
        // 移除组件时,取消定时器
        this.debouncedClick.cancel()
      },
      methods: {
        click() {
          // ... 响应点击 ...
        }
      },
      template: `
        
      `
    })
    

    计算属性,侦听器


    计算属性


    模版里的表达式比较复杂时,会失去其简单和声明式的特征。应当使用计算属性代替

    <div id="hello">
        {{msg}}:{{count}}
        <br>
        <span v-if="countCheck"> count < 20 span>
        <span v-else> count &rt; 20 span>
    div>
    <script>
        const msg={
            data(){
                return {
                    msg:"hello",
                    count:0,
                    countBool:false
                }
            },
            mounted() {
                setInterval(() =>{
                    this.count++
                },100)
            },
            computed:{
                countCheck(){
                    return this.count<20
                }
            }
        }
        Vue.createApp(msg).mount("#hello")
    script>
    

    这里声明了countCheck计算属性。

    在上面的例子中countCheck计算属性依赖于count,当count修改时也会动态的修改所有绑定的countCheck

    countCheck是一个计算属性的getter,不要混淆计算属性与函数。调用时,不要使用function()的函数调用方式,而要使用function这样的函数名。

    计算属性PK方法

    你也可以在methods里定义一个方法,之后调用方法来实现计算属性。这在实现上来看是一样的,但是计算属性有它自己的缓存(类似于记忆化搜索)。计算属性只会在相应的响应式依赖发生时重新求值。

    computed: {
      now() {
        return Date.now()
      }
    }
    

    Date.now () 不是响应式依赖,这个计算属性也就不会执行,假如你真的要实现这样的操作的话,建议使用函数。

    Setter

    可以显性的声明一个Setter。

    <div id="hello">
        <input type="text" v-model="inputV" @change="change"/>
        <br><span>{{TestSetter}}span>
    div>
    <script>
        var vm=Vue.createApp({
            data(){
                return {
                    inputV:0,
                    Value :0
                }
            },
            methods: {
                change(){
                    this.TestSetter = this.inputV
                }
            },
            computed:{
                TestSetter : {
                    get(){
                        return this.Value
                    },
                    set(newValue){
                        this.Value=newValue
                    }
                }
            }
        }).mount("#hello")
    script>
    

    上面这堆东西其实可以简化成:

    <div id="hello">
        <input type="text" v-model="TestSetter"/>
        <br><span>{{TestSetter}}span>
    div>
    <script>
        var vm=Vue.createApp({
            data(){
                return {
                    Value :0
                }
            },
            computed:{
                TestSetter : {
                    get(){
                        return this.Value
                    },
                    set(newValue){
                        this.Value=newValue
                    }
                }
            }
        }).mount("#hello")
    script>
    

    可见,计算属性其实是一个很灵活的东西,他大体上就是一个数据一样使用,而非把它当作函数。

    侦听器


    侦听器顾名思义,目前看起来侦听器和一个定义了setter的计算属性用法类似

    当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

    侦听器需要定义在watch里,和需要监听的属性同名。

    可以带0、1、2个参数,第一个参是新值,第二个参是旧值。

    可以声明为对象类型,可以带immediately,deep等参数,详情百度

    <div id="watch-example">
        <p>
            Ask a yes/no question:
            <input v-model="question" />
        p>
        <p>{{ answer }}p>
    div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js">script>
    <script>
        const watchExampleVM = Vue.createApp({
            data() {
                return {
                    question: '',
                    answer: 'Questions usually contain a question mark. ;-)'
                }
            },
            watch: {
                // 每当 question 发生变化时,该函数将会执行
                question(newQuestion, oldQuestion) {
                    if (newQuestion.indexOf('?') > -1) {
                        this.getAnswer()
                    }
                }
            },
            methods: {
                getAnswer() {
                    this.answer = 'Thinking...'
                    axios
                        .get('https://yesno.wtf/api')
                        .then(response => {//这里一定要使用箭头函数!不然this无法获取到vue对象
                        this.answer = response.data.answer
                    })
                        .catch(error => {
                        this.answer = 'Error! Could not reach the API. ' + error
                    })
                }
            }
        }).mount('#watch-example')
    script>
    

    计算属性PK侦听器


    看着用…差不多

    用错了顶多就是丑点,丑就丑吧,怕啥…

    Class 与 Style 绑定


    操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

    绑定HTML Class


    对象语法

    我们可以传给 :class (v-bind:class 的简写) 一个对象,以动态地切换 class:

    <div :class="{ active: isActive }">div>
    

    上面的语法表示 active 这个 class 存在与否将取决于对象属性isActive 的真假

    你可以在对象中传入更多字段来动态切换多个 class。此外,:class 指令也可以与普通的 class attribute 共存。当有如下模板:

    <div
      class="static"
      :class="{ active: isActive, 'text-danger': hasError }"
    >div>
    

    和如下 data:

    data() {
      return {
        isActive: true,
        hasError: false
      }
    }
    

    渲染的结果为:

    <div class="static active">div>
    
    

    isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为 true,class 列表将变为 "static active text-danger"

    你也可以直接将这个对象对象在data中定义

    <div :class="classObject">div>
    
    data() {
      return {
        classObject: {
          active: true,
          'text-danger': false
        }
      }
    }
    

    渲染的结果和上面一样。

    我们也可以在这里绑定一个返回对象的计算属性,通过这个计算属性的getter为模版返回一个具体对象。这是一个常用且强大的模式:

    <div :class="classObject">div>
    
    data() {
      return {
        isActive: true,
        error: null
      }
    },
    computed: {
      classObject() {
        return {
          active: this.isActive && !this.error,
          'text-danger': this.error && this.error.type === 'fatal'
        }
      }
    }
    

    数组语法

    我们可以把一个数组传给 :class,以应用一个 class 列表:

    <div :class="[activeClass, errorClass]">div>
    
    data() {
      return {
        activeClass: 'active',
        errorClass: 'text-danger'
      }
    }
    

    渲染的结果为:

    <div class="active text-danger">div>
    

    如果你想根据条件切换列表中的 class,可以使用三元表达式:

    <div :class="[isActive ? activeClass : '', errorClass]">div>
    

    这样写将始终添加 errorClass,但是只有在 isActive 为真时才添加 activeClass

    不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:

    <div :class="[{ active: isActive }, errorClass]">div>
    

    在组件上使用

    这个章节假设你已经对 Vue 组件有一定的了解。当然你也可以先跳过这里,稍后再回过头来看。

    当你在带有单个根元素的自定义组件上使用 class attribute 时,这些 class 将被添加到该元素中。此元素上的现有 class 将不会被覆盖。

    例如,如果你声明了这个组件:

    const app = Vue.createApp({})
    
    app.component('my-component', {
      template: `

    Hi!

    `
    })

    然后在使用它的时候添加一些 class:

    <div id="app">
      <my-component class="baz boo">my-component>
    div>
    

    HTML 将被渲染为:

    <p class="foo bar baz boo">Hip>
    

    对于带数据绑定 class 也同样适用:

    <my-component :class="{ active: isActive }">my-component>
    

    当 isActive 为 truthy[1] 时,HTML 将被渲染成为:

    <p class="foo bar active">Hip>
    

    如果你的组件有多个根元素,你需要定义哪些部分将接收这个 class。可以使用 $attrs 组件 property 执行此操作:

    <div id="app">
      <my-component class="baz">my-component>
    div>
    
    const app = Vue.createApp({})
    
    app.component('my-component', {
      template: `
        

    Hi!

    This is a child component
    `
    })

    你可以在非 Prop 的 Attribute 小节了解更多关于组件属性继承的信息。

    绑定内联样式


    对象语法

    :style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式或短横线分隔来命名:

    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">div>
    
    data() {
      return {
        activeColor: 'red',
        fontSize: 30
      }
    }
    

    直接绑定到一个样式对象通常更好,这会让模板更清晰:

    <div :style="styleObject">div>
    
    data() {
      return {
        styleObject: {
          color: 'red',
          fontSize: '13px'
        }
      }
    }
    

    同样的,对象语法常常结合返回对象的计算属性使用。

    数组语法

    :style 的数组语法可以将多个样式对象应用到同一个元素上:

    <div :style="[baseStyles, overridingStyles]">div>
    

    自动添加前缀

    :style 中使用需要一个 vendor prefix(浏览器引擎前缀) 的CSS属性时,Vue 将自动侦测并添加相应的前缀。Vue 是通过运行时检测来确定哪些样式的 property 是被当前浏览器支持的。如果浏览器不支持某个 property,Vue 会进行多次测试以找到支持它的前缀。

    多重值

    可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

    <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">div>
    

    这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

    条件渲染


    v-if


    v-if只会在表达式为真是被渲染,也有对应的v-else-if和v-else。

    可以添加一个