学习Vue课程的收获

 Vue.js是一款流行的开源JavaScript框架,用于构建用户界面和单页面应用程序。它由尤雨溪(Evan You)创建,并于2014年首次发布。Vue.js的设计目标是简单、灵活,易于上手,同时也具有强大的功能和性能。

Vue.js的特点包括:

响应式数据绑定:Vue.js采用了响应式设计的思想,可以轻松地将数据和DOM进行绑定,实现数据的变化自动更新视图。

组件化开发:Vue.js鼓励将应用程序划分为多个可重用的组件,每个组件包含自己的模板、逻辑和样式,使得开发和维护变得更加简单和高效。

简洁的模板语法:Vue.js提供了简洁、灵活的模板语法,使开发者能够更直观地编写HTML模板,并结合JavaScript表达式和指令。

Vue Router:Vue.js配备了官方的路由库Vue Router,用于管理应用程序的路由和导航,支持嵌套路由、过渡效果等功能。

丰富的生态系统:在Vue.js周边有许多优秀的工具和库,比如Vue CLI用于快速搭建项目、Vue Devtools用于调试等,以及大量社区贡献的插件和组件。

学习路线:
学习Vue.js的路线可以根据你的经验和学习风格来定制,以下是一个常见的学习路线:

HTML、CSS和JavaScript基础:

如果你对前端开发已经有一定了解,可以跳过这一步。
否则,先学习HTML、CSS和JavaScript的基础知识,了解它们的语法、特性和常用技术。

组件化开发

  • 学习如何创建和使用Vue组件,掌握组件的模板、样式和逻辑。
  • 了解Vue组件之间的通信方式,包括props、$emit和事件总线等。
  • 实践编写简单的Vue组件,并将其组合成更复杂的应用程序。
  • Vue Router

    • 学习使用Vue Router来管理应用程序的路由和导航。
    • 掌握如何定义路由、配置路由表和导航到不同的页面。
    • 实践构建具有多个路由的单页面应用(SPA)。
    • Vuex状态管理

      • 学习使用Vuex来管理应用程序的状态。
      • 了解Vuex的核心概念,包括state、getters、mutations和actions。
      • 实践在Vue.js应用程序中集成和使用Vuex,管理和共享数据。
      • API通信

        • 学习如何与后端API进行通信,获取和提交数据。
        • 掌握发送HTTP请求、处理响应和管理异步操作的技巧。
        • 实践与后端服务器进行数据交互的实例。
        • Webpack和构建工具

          • 学习使用Webpack等构建工具来组织、打包和优化Vue.js项目。
          • 了解如何配置和使用构建工具,以便在开发过程中提高效率和性能。

Vue.js环境配置:
Vue.js的环境配置包括以下几个步骤:

安装Node.js

Vue.js是基于Node.js开发的,因此需要先安装Node.js。
在Node.js官网下载对应系统的安装包,并进行安装。
安装Vue CLI(命令行界面)

Vue CLI是Vue.js的脚手架工具,可以快速创建Vue.js项目。
在命令行中运行以下命令进行全局安装:
npm install -g @vue/cli

创建Vue.js项目

  • 在命令行中运行以下命令创建Vue.js项目:
vue create my-project
  • my-project是项目名称,也可以根据自己的需要进行修改。
  • 创建完成后进入项目目录:
cd my-project

  1. 启动开发服务器
  • 在项目目录下运行以下命令启动开发服务器:
    npm run serve
  • 运行成功后在浏览器中输入http://localhost:8080即可访问项目。

Vue.js组件:
Vue.js是一个组件化的框架,组件是Vue.js应用的基本构建块。Vue组件具有以下特点:

复用性:组件可以被多次使用,通过封装HTML、CSS和JavaScript代码,可以将一些功能或UI元素封装成独立的组件,以便在不同的地方重复使用。

独立性:每个组件都是相互独立的,组件内部的逻辑和样式与其他组件隔离开来,便于开发和维护。

可组合性:组件可以嵌套组合,形成更大的组件或应用程序。父组件可以通过props属性向子组件传递数据,子组件可以通过事件向父组件发送消息。

可维护性:组件化开发使得代码更加模块化,每个组件负责自己的功能,易于单独测试和修改,提高了代码的可维护性。

Vue组件的基本结构包括template、script和style三个部分,其中template部分定义了组件的HTML模板,script部分定义了组件的逻辑和数据,style部分定义了组件的样式。

1.2.2 入门程序

创建一个vuetest目录, 并且在目录下创建 01_vue入门程序.html 文件. 代码编写步骤: 1、定义html,引入vue.js 2、定义app div,此区域作为vue的接管区域 3、定义Vue实例,接管app区域。 4、定义model(数据对象) 5、在app中展示数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js" ></script>
</head>
<body>
     <!-- 2. 定义t1 div,此区域作为vue的接管区域 -->
    <div id="t1">
     <!-- {{}} 双括号是VUE中的插值表达式,将表达式的值输出到HTML页面 -->
        <h2>{{message}}</h2>
        <span>{{school.id}}:{{school.name}}</span>
        <ul>
            <li>{{teacher[0].id}}:{{teacher[0].name}}</li>
             <li>{{teacher[1].id}}:{{teacher[1].name}}</li>
        </ul>
    </div>
​
</body>
  
<script>
    //3. 创建vue实例
  var VM = new Vue({
    var obj = new Vue({
         //定义 Vue实例挂载的元素节点,表示vue接管该div
        el: "#t1",
        //4.定义model模型数据对象
        data: {
            message: "hello",
            school: {"id": 1,"name": "希望小学", },
            teacher: [{"id":1 , "name": "张三"},{"id": 2 , "name":"王五"} ]
        }
    });
    
</script>
</html>

效果如下:

  1. {{}}:差值表达式
    插值表达式的作用:
    通常用来获取Vue实例中定义的数据(data)
    属性节点中不能够使用插值表达式
  2. el:挂载点
    1. el的作用?
      定义Vue实例挂载的元素节点,表示Vue接管该区域
    2. Vue的作用范围?
      Vue会管理el选项命中的元素,及其内部元素
    3. el选择挂载点时,是否可以使用其他选择器?
      可以,但是建议使用ID选择器
    4. 是否可以设置其他的dom元素进行关联?
      可以,但是建议选择DIV,不能使用HTML和Body标签
  1. data:数据对象
    1. Vue中用到的数据定义在data中
    2. data中可以写复杂类型
    3. 渲染复杂类型数据的时候,遵守js语法

1.2.3 声明式渲染的好处

Vue中声明式渲染,简单理解就是我们声明数据,Vue帮我们将数据渲染到HTML

1.2.4 Vue常用指令

根据官网的介绍,指令是带有v-前缀的特殊属性。通过指令来操作DOM元素

1. v-text指令:

作用:获取data数据,设置标签的内容。

注意:默认写法会替换全部内容,使用插值表达式{{}}可以替换指定内容。

实例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        <!-- v-text 获取data数据,设置标签内容,会覆盖之前的内容体-->
        <h2 v-text="msg">天天好心情</h2> 
        <!-- 使用插值表达式,不会覆盖 -->
        <h2>阿红:{{msg}}</h2>
        <!-- 拼接字符串 -->
        <h2 v-text="msg+1"></h2>
        <h2 v-text="msg+'abc' "></h2>
    </div>
</body>
<script>
    var obj = new Vue({
        el: "#app",
        data: {
            "msg": "学习vue的第一天!"
        }
    });
</script>
</html>

效果展示:

2. v-html指令

作用:设置元素的innerHTML(可以向元素中写入新的标签)

代码实例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        <!-- 普通获取文本 -->
        <h2>{{msg}}</h2>
        <h2 v-html="msg"></h2>
        <!-- 设置元素的innerHTML -->
            <!--  v-text只能获取该值 -->
        <h2 v-text="url"></h2>
            <!-- v-html内联html标签 -->
        <h2 v-html="url"></h2>
​
    </div>
</body>
<script>
    var obj = new Vue({
        el: "#app",
        data:{
            "msg": "java程序员",
            "url": "<a href='https://www.baidu.com'>百度一下</a>",
        } ,
    })
</script>
</html>

效果展示:

3. v-on指令

作用:为袁术绑定事件,比如:v-on:click,可以简写为@click="方法名"

绑定的方法定义在vue实例的methods属性中。

代码实例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        <!-- 使用v-on 绑定click点击事件 -->
        <input type="button" value="点击按钮" v-on:click="show1" />
        <!--简写方式: 使用@符号也可以绑定 -->
        <input type="button" value="点击按钮2" @click="show2" />
        <!-- 双击事件 -->
        <input type="button" value="双击事件" @dblclick="show3" />
​
        <h2>{{food}}</h2>
    </div>
​
</body>
<script>
    var obj =  new Vue({
        el:"#app",
        data: {
            "food": "麻婆豆腐",
        },
        methods: {
            show1:function() {
                alert("今天不学习,明天变垃圾!");
            },
            show2:function(){
                alert("天天好心情");
            },
            show3:function(){
                console.log(this.food);
                this.food += "真好吃";
            },
        },
    });
</script>
</html>

效果展示:

案例总结:

  • 创建VUE实例时: el(挂载点) , data(数据) , methods(方法)
  • v-on 指令的作用是绑定事件,简写为 @
  • 方法中使用this关键字,获取data中的数据
  • v-text 与 {{}} 的作用都是用来 设置元素的文本值

4.v-show指令

作用:v-show指令,根据真假值,切换元素的显示状态

代码实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app" >
        <input type="button" value="切换" @click="change" />
        <img v-show="flag" src="../img/car.gif">
    </div>
</body>
    <script>
        var obj = new Vue({
            el: "#app",
            data: {
                flag: true,
            },
            methods:{
                change:function(){
                    this.flag = !this.flag;
                },
            }
        });
    </script>
</html>

v-show指令总结

  • 原理是修改元素的display属性,实现显示或者隐藏
  • 指令后面导入内容,最终会解析为布尔值
  • 值为true显示,为false则隐藏
  • 数据改变之后,显示的状态会同步更新

5. v-if 指令

作用:根据表达式的真假,切换元素的显示和隐藏(操纵dom元素)

代码实例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        <input type="button" value="切换" v-on:click="changeShow" />
        <img v-if="isShow" src="../img/car.gif"/>
    </div>
</body>
    <script>
        var obj = new Vue({
            el: "#app",
            data: {
                isShow: true,
            },
            methods:{
                changeShow: function(){
                    this.isShow = !this.isShow;
                },
            }
        });
    </script>
</html>

v-if="isShow"就是根据值来删除和增加dom元素

  • 如该下图就是动态的删除img元素。

v-if指令总结

  • v-if指令的作用:根据表达式的真假切换元素的显示状态
  • 本质就是通过dom元素,来切换显示状态
  • 表达式为真,dom元素存在,为假从dom树种删除
  • 频繁切换使用v-show,反之使用v-if

6. v-bind指令

作用:设置元素的属性(比如:src,title,class)

代码实例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js" ></script>
</head>
<body>
    <div id="app">
        <img src="../img/lagou.jpg" />
        <!-- 使用v-bind设置src属性值 -->
        <img v-bind:src="imgSrc" alt="" />
         <!-- 简写 设置title -->
        <img :src="imgSrc" :title="imgTitle" />
        <!-- 设置class -->
        <div :style="{fontSize: size + 'px'}">v-bind指令</div>
    </div>
</body>
    <script>
        var obj = new Vue({
            el: "#app",
            data: {
                imgSrc: "../img/lagou.jpg",
                imgTitle: "我好看吗",
                size: 50,
            },
        });
    </script>
</html>

v-bind指令总结:

  • v-bind指令的作用是:为元素绑定属性
  • 完整写法v-bind:属性名,可以简写:属性名

7. v-for指令

作用:根据数据生成列表结构

  • 相当于java里的for循环

代码示例:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../js/vue.js"></script>
  </head>
  <body>
    <div id="app">
        <input type="button" value="增加人员信息" v-on:click="add" />
        <input type="button" value="删除人员信息" v-on:click="remove" />
      <h2 v-for="item in city">城市:{{item}}</h2>
      <h2 v-for="(item,index) in city">序号:{{index}} 城市:{{item}}</h2>
      <ul>
        <li v-for="(item,index) in people">{{index}}编号:{{item.num}}姓名:{{item.name}}年龄:{{item.age}}</li>
      </ul>
    </div>
  </body>
  <script>
    var obj = new Vue({
      el: "#app",
      data: {
        city: ["上海", "北京", "贵州"],
        people: [
          { num: "007", name: "小李飞刀", age: "22" },
          { num: "003", name: "马艳三", age: "18" },
          { num: "010", name: "王菲", age: "33" },
        ],
      },
      methods: {
          add:function(){
              //push 添加
              this.people.push({ num: "001", name: "马云", age: "52" })
          },
          remove:function(){
              this.people.shift();//从第一个元素开始删除
          }
      }
    });
  </script>
</html>

效果展示:

  • v-for 指令的作用: 根据数据生成列表结构
  • 数组经常和 v-for结合使用,数组有两个常用方法:
    • push() 向数组末尾添加一个或多个元素
    • shift() 把数组中的第一个元素删除

  • 语法是: (item,index) in 数据
  • item和index 可以结合其他指令一起使用
  • 数组的长度变化,会同步更新到页面上,是响应式的

8. v-on指令的补充说明

  1. 传递自定义参数 : 函数调用传参
  2. 事件修饰符: 对事件触发的方式进行限制

代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
         <!-- 函数传参 -->
        <input type="button" value="带参按钮" v-on:click="show(700,'老铁666')" />
        <!-- 事件修饰符 :只要input输入就触发函数-->
        <input type="text" @keyup="hi" />
        <!-- 时间修饰符:只有按enter键才触发函数 -->
        <input type="text" @keyup.enter="hi2" /> 
    </div>
</body>
<script>
    var obj = new Vue({
        el: "#app",
        data: {

        },
        methods: {
           show:function(p1,p2){
            console.log(p1);
            console.log(p2);
           },
           hi:function(){
            alert("就是这样的!");
           },
           hi2:function(){
               alert("我按了enter键!");
           }
        },
        
    });
</script>
</html>

效果展示:

总结

  • 事件绑定方法,可以传入自定义参数
  • 定义方法时,需要定义形参,来接收实际的参数
  • 事件的后面跟上 .修饰符 可以对事件进行限制
  • .enter 可以限制触发的按键为回车
  • 事件修饰符有许多 使用时可以查询文档

Vue为我们提供了很多高级特性,学习和掌握它们有助于提高你的代码水平。

一、watch进阶

从我们刚开始学习Vue的时候,对于侦听属性,都是简单地如下面一般使用:

watch:{
    a(){
     //doSomething
    }
}

实际上,Vue对watch提供了很多进阶用法。

handler函数

以对象和handler函数的方式来定义一个监听属性,handler就是处理监听变动时的函数:

watch:{
    a:{
        handler:'doSomething'
    }
},
methods:{
    doSomething(){
        //当 a 发生变化的时候,做些处理
    }
}

handler有啥用?是多此一举么?用途主要有两点:

  • 将处理逻辑抽象出去了,以method的方式被复用
  • 给定义下面两个重要属性留出了编写位置

deep属性

不知道你注意到了没有?

当watch的是一个Object类型的数据,如果这个对象内部的某个值发生了改变,并不会触发watch动作!

也就是说,watch默认情况下,不监测内部嵌套数据的变动。但是很多情况下,我们是需要监测的!

为解决这一问题,就要使用deep属性:

watch:{
    obj:{
        handler:'doSomething',
        deep:true
    }
},
methods:{
    doSomething(){
        //当 obj 发生变化的时候,做些处理
    }
}

deep属性默认为false,也就是我们常用的watch模式。

immediate属性

watch 的handler函数通常情况下只有在监听的属性发生改变时才会触发。

但有些时候,我们希望在组件创建后,或者说watch被声明和绑定的时候,立刻执行一次handler函数,这就需要使用immediate属性了,它默认为false,改为true后,就会立刻执行handler。

watch:{
    obj:{
        handler:'doSomething',
        deep:true,
        immediate:true
    }
},
methods:{
    doSomething(){
        //当 obj 发生变化的时候,做些处理
    }
}

同时执行多个方法

使用数组可以设置多项,形式包括字符串、函数、对象

watch: {
    // 你可以传入回调数组,它们会被逐一调用
    a: [

      'handle1',

      function handle2 (val, oldVal) { /* ... */ },

      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }

    ],

  }

二、$event的不同表现

$event 是事件对象的特殊变量,在两种场景下,它有不同的意义,代表不同的对象。

  • 在原生事件中表示事件本身。可以通过$event.target获得事件所在的DOM对象,再通过value进一步获取具体的值。
<template>
    <div>
        <input type="text" @input="inputHandler('hello', $event)" />
    </div>
</template>


export default {
    methods: {
        inputHandler(msg, e) {
            console.log(e.target.value)
        }
    }
}
  • 而在父子组件通过自定义事件进行通信时,表示从子组件中传递出来的参数值

看下面的例子:

//blog-post组件的模板

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

在父级组件监听这个事件的时候,可以通过 $event 访问到blog-post子组件传递出来的0.1这个值:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"
></blog-post>

此时,$event的值就是0.1,而不是前面的事件对象。

三、异步更新队列

  • Vue 在更新 DOM 时是异步执行的。
  • 只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
  • 如果同一个 watcher 被多次触发,只会被推入到队列中一次。

这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。

多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。

虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)

这样回调函数将在 DOM 更新完成后被调用。例如:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '未更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})

因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

methods: {
  updateMessage: async function () {
    this.message = '已更新'
      //在这里可以看出,message并没有立刻被执行
      //要理解页面刷新和代码执行速度的差别
      //通常我们在页面上立刻就能看到结果,那是因为一轮队列执行其实很快,感觉不出DOM刷新的过程和所耗费的时间
      //但对于代码的执行,属于即刻级别,DOM没更新就是没更新,就是会有问题
    console.log(this.$el.textContent) // => '未更新'

    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}

通俗的解释

  • Vue的DOM刷新机制是个异步队列,并不是你想象中的立刻、马上、即时更新!
  • 这个异步队列是一轮一轮的执行并刷新
  • 上面带来的问题是,一些依赖DOM更新完毕才能进行的操作(比如对新增加的DOM元素进行事件绑定),无法立刻执行,必须等待一轮队列执行完毕
  • 最容易碰到上面问题的地方:created生命周期钩子函数中对DOM进行操作
  • 解决办法:使用this.nextTick(回调函数)方法,将对DOM的操作作为它的回调函数使用。

四、函数式组件

因为传统编写模板的能力不足,我们引入了渲染函数createElement。我们又希望获得更多的灵活度,于是引入了JSX。最后,我们发现有些简单的模板可以更简单更小巧的实现,于是引入了函数式组件。Vue总是试图为每一种场景提供不同的能力。

有这么一类组件,它的特点是:

  • 比较简单
  • 没有管理任何状态,也就是说无状态,没有响应式数据
  • 没有监听任何传递给它的状态
  • 没有写生命周期方法
  • 本质上只是一个接收一些prop的函数
  • 没有实例,没有this上下文

那么这个组件可以定义为函数式组件。与普通组件相比,函数式组件是无状态的,无法实例化,没有任何的生命周期和方法,适合只依赖于外部数据的变化而变化的组件,因其轻量,渲染性能会有所提高。

创建函数式组件

  • 以定义全局组件的方式
Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

注意其中的functional: true,

在 Vue 2.3.0 或以上的版本中,你可以省略  props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。
当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的。
  • 对于单文件组件,创建函数式组件的方式是在模板标签内,添加functional属性
<template functional>
...
</template>

<script>
...
</script>

<style>
...
</style>

最重要的context参数

因为无状态,没有this上下文,所以函数式组件需要的一切都是通过 context 参数来传递,它是一个包括如下字段的对象:

  • props:提供所有 prop 的对象
  • children:VNode 子节点的数组
  • slots:一个函数,返回了包含所有插槽的对象
  • scopedSlots:(2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
  • data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
  • parent:对父组件的引用
  • listeners:(2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
  • injections:(2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的 property。

应用场景

函数式组件的一个典型应用场景是作为包装组件,比如当你碰到下面需求时:

  • 程序化地在多个组件中选择一个来代为渲染;
  • 在将 childrenpropsdata 传递给子组件之前操作它们。

下面是一个 smart-list 组件的例子,它能根据传入 prop 的值来代为渲染更具体的组件:

var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }

Vue.component('smart-list', {
  functional: true,
  props: {
    items: {
      type: Array,
      required: true
    },
    isOrdered: Boolean
  },
  render: function (createElement, context) {
    function appropriateListComponent () {
      var items = context.props.items

      if (items.length === 0)           return EmptyList
      if (typeof items[0] === 'object') return TableList
      if (context.props.isOrdered)      return OrderedList

      return UnorderedList
    }

    return createElement(
      appropriateListComponent(),
      context.data,
      context.children
    )
  }
})

五、监听子组件的生命周期

假如我们有父组件Parent和子组件Child,如果在父组件中需要监听子组件的mounted这个生命周期函数,并做一些逻辑处理,常规写法可能如下:

// Parent.vue
<Child @mounted="doSth" />

//Child.vue
mounted(){
    this.$emit('mounted');
}

但是,Vue给我们提供了一种更简便的方法,子组件无需做任何处理,只需要在父组件引用子组件时使用@hook事件来监听即可,代码如下:

// Parent.vue

<Child @hook:mounted="doSth" />  

methods:{
    doSth(){
        //some codes here
    }
}

核心是@hook:mounted="doSth"的写法!

当然这里不仅仅可以监听mounted,其他生命周期都可以监听,例如created、updated等。

六、样式穿透

我们知道,在单文件组件的style中使用 scoped 属性后,父组件的样式将不会渗透到子组件中。

不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

如果你希望父组件的 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,可以使用深度选择器: >>> 操作符。

<style scoped>
.a >>> .b { /* ... */ }
</style>

上述代码将会编译成:

.a[data-v-f3f3eg9] .b { /* ... */ }

但是,有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/ 或 ::v-deep 操作符,这两者都是 >>> 的别名,实现同样的功能。

我们都知道,通过 v-html 创建的 DOM 内容不受 scoped 样式影响,可以通过深度作用选择器>>>来为他们设置样式。

七、路由的props属性

一般在组件内使用路由参数,大多数人会这样做:

export default {
    methods: {
        getParamsId() {
            return this.$route.params.id
        }
    }
}

当你随便用用,临时凑手,这没什么问题,毕竟解决了需求。

可我们要随时谨记:组件是用来复用的!组件应该有高度的封闭性!

在组件中使用 $route 会使它与路由系统形成高度耦合,从而使组件只能在使用了路由功能的项目内,或某些特定的 URL 上使用,限制了其灵活性。

试想一下,如果你的组件被人拿去复用了,但是那个人并没有使用路由系统,而是通过别的方式传递id参数,那么他该怎么办?

正确的做法是通过 props 解耦

首先,为组件定义一个叫做id的prop:

export default {
    props: ['id'],
    methods: {
        getParamsId() {
            return this.id
        }
    }
}

如果组件没有对应路由,那么这个id也可以通过父组件向子组件传值的方式使用。

如果使用了路由,可以通过路由的prop属性,传递id的值:

const router = new VueRouter({
    routes: [{
        path: '/user/:id',
        component: User,
        props: true
    }]
})

将路由的 props 属性设置为 true 后,组件内可通过 props 接收到 params 参数

另外,你还可以通过函数模式来返回 props

const router = new VueRouter({
    routes: [{
        path: '/user/:id',
        component: User,
        props: (route) => ({
            id: route.query.id
        })
    }]
})

其实,上面的技巧,在VueRouter的官档都有说明。

八、异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。

为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。

你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是为了演示用的,如何获取组件取决于你自己。

一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})

你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:

Vue.component(
  'async-webpack-example',
  // 这个 `import` 函数会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

当使用局部注册组件的时候,你也可以直接提供一个返回 Promise 的函数:

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

如果你想实现异步加载组件的功能,提高首屏显示速度,那么可以使用上面例子中的定义组件的方法,也就是:箭头函数+import语句!

处理加载状态

2.3.0+ 新增

异步组件的工厂函数也可以返回一个如下格式的对象,用来灵活定制异步加载过程:

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})
注意如果你希望在 Vue Router 的路由组件中使用上述语法的话,必须使用 Vue Router 2.4.0+ 版本。

九、批量导入组件

很多时候我们会编写一些类似输入框或按钮之类的基础组件,它们是相对通用的组件,称为基础组件,它们会在更大一些的组件中被频繁的用到。

这很容易导致大的组件里有一个很长的导入基础组件的语句列表,例如:

import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
//更多导入

export default {
  components: {
    BaseButton,
    BaseIcon,
    BaseInput
  }
}

当你的基础组件很多的时候,这个过程将非常重复、麻烦和无聊。

require.context()

如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 方法批量导入这些组件,然后将它们注册为全局组件,这样就可以在任何地方直接使用它们了,再也不用为导入的事情烦恼了!

下面是一个示例代码:

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // 其组件目录的相对路径
  './components',
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式
  /Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
  // 获取组件的配置,也就是具体内容,具体定义,组件的本身代码
  const componentConfig = requireComponent(fileName)

  // 获取组件的 PascalCase 命名,用来规范化组件名
  const componentName = upperFirst(
    camelCase(
      // 获取和目录深度无关的文件名
      fileName
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )

  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  )
})

记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生

更多内容请访问: https://www.liujiangblog.com

更多视频教程请访问: https://www.liujiangblog.com/video/

编辑于 2020-05-10 10:24

 

 

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值