Vue

1. 概述

官网

  • Vue是一套用于构建用户界面的额渐进式JavaScript框架。被设计为可以自底向上逐层应用。

  • Vue的核心库只关心视图层。

    第三方库(vue-router跳转,vue-resource通信,vuex管理)

    方便与第三方库或既有项目整合。

  • 是MVVM模式的实现者。

2. 几个模式

  • MVC(同步通信为主) :Model、View、Controller

  • MVP(异步通信为主) :Model、View、Presenter

  • MVVM(异步通信为主):Model、View、View Model,是一种简化用户界面的事件驱动编程方式。MVVM源自于经典的MVC(Model-View-Controller)模式。MVVM的核心是ViewModel层,负责转换Model中的数据对象来让数据变得更容易管理和使用。其作用如下:

    • 该层向上与视图层进行双向数据绑定
    • 向下与Model层通过接口请求进行数据交互

    MVVM已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的MVVM框架有Vue.jsAnfular JS

    使用MVVM的好处:

    • 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
    • 可复用:你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
    • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewMode),设计人员可以专注于页面设计。
    • 可测试:界面素来是比较难以测试的,而现在测试可以针对ViewModel来写。
    image-20210917213133182
    Image

随着Node JS的兴起, JavaScript开始有能力运行在服务端。这意味着可以有一种新的研发模式:

image-20210917173209707
Image

在这种研发模式下,前后端的职责很清晰。对前端来说,两个UI层各司其职:

  • Front-end Ul layer处理浏览器层的展现逻辑。通过CSS渲染样式, 通过JavaScript添加交互功能, HTML的生成也可以放在这层, 具体看应用场景。
  • Back-end Ul layer处理路由、模板、数据获取、Cookie等。通过路由, 前端终于可以自主把控URL Design, 这样无论是单页面应用还是多页面应用, 前端都可以自由调控。后端也终于可以摆脱对展现的强关注,转而可以专心于业务逻辑层的开发。

通过Node, WebServer层也是JavaScript代码, 这意味着部分代码可前后复用, 需要SEO的场景可以在服务端同步渲染,由于异步请求太多导致的性能问题也可以通过服务端来缓解。前一种模式的不足,通过这种模式几乎都能完美解决掉。

前后分离的开发思想主要是基于SoC(关注度分离原则)。

数据双向绑定

  • ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新
  • ViewModel能够监听到视图的变化, 并能够通知数据发生改变

3. 入门

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <!-- 引入Vue的js文件 -->
        <script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
    </head>
    <body>
        <!-- View 模版 -->
        <!-- 将数据保定到页面元素 -->
        <div id="app">
      <span>
        {{message}}
      </span>
    </div>        

        <script type="text/javascript">
            var vm = new Vue({
                // 绑定元素,这里使用id绑定
                el: "#app",
                // Model 数据
                data: {
                    message: "hello, world!"
                }
            });
        </script>
    </body>
</html>

只需要在绑定的元素中使用双花括号将Vue创建的名为message属性包裹起来, 即可实现数据绑定功能, 也就实现了View Model层所需的效果。

打开页面可以发现message的属性被替换成了"hello, world!"的内容。控制台可以动态更改vm.message的内容,页面会随之动态更改。这是借助了Vue的数据绑定功能实现的。MVVM模式中要求View Model层使用观察者模式来实现数据的绑定和监听,以做到数据和视图的快速响应。现在数据和DOM已经被建立了关联,所有东西都是响应式的。可以在控制台操作对象属性,界面实时更新。

4. 基础语法

使用v-属性绑定数据是*不需要双花括号包裹的

4.1. v-bind

绑定元素特性。

<!-- 将这个元素节点的title特性和Vue实例的message属性保持一致 -->
<!--可以缩写为<div id="app" :title="message">-->
<div id="app" v-bind:title="message">
  <span>
    鼠标悬停以查看内容
  </span>
</div>

<script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      message: "hello, world!"
    }
  });
</script>\
image-20210917233610518
Image

4.2. v-if, v-else-if, v-else

<div id="app">
  <span v-if="score>90">A</span>
  <span v-else-if="score>80">B</span>
  <span v-else-if="score>70">C</span>
  <span v-else>D</span>
</div>

<script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      score: 80
    }
  });
</script>

4.3. v-for

<div id="app">
  <!-- 在items数组中逐个获取item和下标 -->
  <!-- 获取多个值的时候,需要用,分隔,同时需要用括号括起来 -->
  <li v-for="(item, index) in items">
    [{{index}}]. {{item.message}}
  </li>
</div>

<script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      items: [
        {message: "hello"},
        {message: "world"},
        {message: "!"}
      ]
    }
  });
</script>

4.4. v-on

监听事件。

这里的click是vue的事件,可以绑定到vue的methods中的方法事件。

<div id="app">
  <!--可以缩写为<button type="button" @click="click()">Click Me</button>-->
  <button type="button" v-on:click="click()">Click Me</button>
</div>

<script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      message: "hello world!"
    },
    // 绑定方法,注意这里是methods,不是method
    methods:{
      click: function() {
        console.log(this.message);
      }
    }
  });
</script>

5. 双向绑定

Vue基于MVVM框架。当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化。

如果使用vuex, 实际上数据还是单向的, 之所以说是数据双向绑定,这是用的UI控件来说, 对于我们处理表单, Vue.js的双向数据绑定用起来就特别舒服了。即两者并不互斥,在全局性数据流使用单项,方便跟踪;局部性数据流使用双向,简单易操作。

5.1. 使用

可以用v-model指令在表单、及元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

注意:v-model会忽略所有表单元素的valuecheckedselected特性的初始值而总是将Vue实例的数据作为数据来源。你应该通过JavaScript在组件的data选项中声明初始值!

文本

此后,不管是在视图改变数字或者在数据改变数字,两端都会同步发生变化。

<div id="app">
  <input type="text" name="username" value="" v-model="val"/>
  {{val}}
</div>

<script type="text/javascript">
  let vm = new Vue({
    el: "#app",
    data: {
      val: "root"
    }
  });
</script>

textarea同理。

在文本区域插值不起作用,应该使用 v-model 来代替。

<textarea v-model="text"></textarea>

单复选框

<div id="app">
  <input type="radio" name="gender" value="male" v-model="val"/><input type="radio" name="gender" value="female" v-model="val"/>女
  {{val}}
</div>

<script type="text/javascript">
  let vm = new Vue({
    el: "#app",
    data: {
      // 默认值,相当于在value=female的标签加上checked
      val: "female"
    }
  });
</script>
<div id="app">
  <input type="checkbox" name="hobby" value="sing" v-model="val"/>唱
  {{val}}
</div>

<script type="text/javascript">
  let vm = new Vue({
    el: "#app",
    data: {
      // true选中 flase不选中
      val: false
    }
  });
</script>
<!--单个复选框,绑定到具体值-->
<div id="app">
  <input type="checkbox" name="hobby" value="sing" v-model="val"/><input type="checkbox" name="hobby" value="jump" v-model="val"/><input type="checkbox" name="hobby" value="rap" v-model="val"/>rap
  <input type="checkbox" name="hobby" value="basketball" v-model="val"/>篮球
  {{val}}
</div>

<script type="text/javascript">
  let vm = new Vue({
    el: "#app",
    data: {
      // 默认值为空,后面视图选中的值都会保存到val里面
      val: []
    }
  });
</script>

下拉框

v-model表达式的初始值未能匹配到任何选项的时候,元素会被渲染成“未选中”的状态。在IOS中会使用户无法选择第一个选项,所以需要提供一个值为空的禁用选项。

<select v-model="val">
  <!--值为空的禁用选项-->
  <option value="" disabled>---请选择---</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
  <option>D</option>
</select>
<span>val:{{val}}</span>
</div>

<script type="text/javascript">
  let vm = new Vue({
    el: "#app",
    data: {
      // 默认值为A
      val: 'A'
    }
  });
</script>

6. 组件

组件是可复用的Vue实例, 说白了就是一组可以重复使用的模板, 跟JSTL的自定义标签、Thymelealth:fragment等框架有着异曲同工之妙,通常一个应用会以一棵嵌套的组件树的形式来组织:

Component Tree
Image

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

6.1. 第一个Vue组件

注意:在实际开发中,并不会用以下方式开发组件,而是采用vue-cli创建,vue模板文件的方式开发。

使用Vue.component()方法注册组件。

使用props属性传递参数。注意:默认规则下props属性里的值不能为大写。

<div id="app">
  <!-- 中间商,一边遍历获取vm.items中的值,一边传递给component的props中,进行展示 -->
  <mycomponent v-for="(item,index) in items" v-bind:val="item"></mycomponent>
</div>

<script type="text/javascript">
  Vue.component("mycomponent", 
    // 定义组件的名字为 mycomponent,并设置模版
    props: ['val'],
    template: `<li>{{val}}</li>`
  });

  let vm = new Vue({
    el: "#app",
    data: {
      items: [1, 2, 3, 4, 5]
    }
  });
</script>

说明

  • v-for="item in items":遍历Vue实例中定义的名为items的数组,并创建同等数量的组件
  • v-bind:itemChild="item":将遍历的item项绑定到组件中props定义名为item属性上;= 号左边的itemChildprops定义的属性名,右边的为item in items 中遍历的item项的值

7. Vue实例生命周期钩子

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

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

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted、updated 和 destroyed。生命周期钩子的 this 上下文指向调用它的 Vue 实例。

Vue 实例生命周期
Image

8. Axios异步通信

官网

文档

GitHub

8.1. 概述

Axios是一个开源的可以用在浏览器端和Node JS的异步通信框架, 她的主要作用就是实现AJAX异步通信,其功能特点如下:

  • 从浏览器中创建XMLHttpRequests
  • 从node.js创建http请求
  • 支持Promise API[JS中链式编程]
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防御XSRF(跨站请求伪造)

8.2. 为什么使用Axios

由于Vue.js是一个视图层框架并且作者(尤雨溪) 严格准守SoC(关注度分离原则)所以Vue.js并不包含AJAX的通信功能, 为了解决通信问题, 作者单独开发了一个名为vue-resource的插件, 不过在进入2.0版本以后停止了对该插件的维护并推荐了Axios框架。少用jQuery, 因为它操作Dom太频繁!

8.3. 开始

  1. 引入Axios的js文件

    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    
  2. 模拟JSON数据

    {
      "name": "rootwhois",
      "url": "https://rootwhois.cn",
      "page": 1,
      "isNonProfit": true
    }
    
  3. html

    <div id="app">
      <span></span><br>
      <a v-bind:href="info.url">Click Me</a><br>
      <span></span><br>
      <span></span>
    </div>
    
    <script type="text/javascript">
      var vm = new Vue({
        el: "#app",
        data() {
          return {
            info: {
              name: null,
              url: null,
              page: null,
              isNonProfit: null
            }
          }
        },
        // 编译完成的html挂载到对应的dom时触发的钩子
        mounted() {
          axios.get("data.json")
            .then(response => this.info = response.data);
        }
      });
    </script>
    

    说明:

    1. v-cloak解决页面闪烁问题(不会出现这样的信息)

      <style>
        [v-cloak]{
          display: none;
        }
      </style>
      <div id="app" v-cloak>
      
    2. 在这里使用了v-bind将a:href的属性值与Vue实例中的数据进行绑定

    3. 使用axios框架的get方法请求AJAX并自动将数据封装进了Vue实例的数据对象中

    4. 我们在data中的数据结构必须和Ajax响应回来的数据格式匹配

9. 计算属性

计算属性的重点突出属性两个字上(属性是名词),这个属性有计算的能力,这里的计算就是个函数。它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),可以理解为缓存。

<div id="app">
  <!-- methods中的是方法,所以需要通过()调用 -->
  <span>{{currentTime1()}}</span><br>
  <!-- computed中虽然是一个方法,但是已经变成了一个计算属性,当作属性调用 -->
  <span>{{currentTime2}}</span>
  </div>

  <script type="text/javascript">
  let vue = new Vue({
    el:"#app",
    data:{
      message: "message"
    },
    methods:{
      // 在方法区中的方法,每调用一次就会产生一个新的数值。
      currentTime1: function() {
        return Date.now();
      }
    },
    computed:{
      // 计算属性有缓存机制,只有当方法区内的参数有变动后,才会产生一个新的值。
      currentTime2: function() {
        // this.message是为了能够让currentTime2观察到数据变化而变化
        this.message;
        return Date.now();
      }
    }
  });
</script>

注意点:

  • 方法区和计算属性区的方法不能重名,否则只会调用方法区的函数。

总结

调用方法时,每次都需要讲行计算,既然有计算过程则必定产生系统开销,那如果这个结果是不经常变化的,就可以考虑将这个结果缓存起来,采用计算属性可以很方便的做到这点,计算属性的主要特性就是为了将不经常变化的计算结果进行缓存,以节约我们的系统开销。

10. 插槽(内容分发)

在Vue中使用<slot>元素作为承载分发内容的出口。

<div id="app">
  <language>
    <my-title slot="language-title" :title="title"></my-title>
    <my-item slot="language-items" v-for="(item,index) in items" :item="item" :index="index"></my-item>

  </language>
</div>


<script type="text/javascript">
  Vue.component("language", {
    template: '<div>\
<slot name="language-title"></slot>\
<ul>\
<slot name="language-items"></slot>\
  </ul>\
  </div>'
  });

  Vue.component("my-title", {
    props: ['title'],
    template: '<div>{{title}}</div>'
  });

  Vue.component("my-item", {
    props: ['item', 'index'],
    template: '<li>{{index}}--{{item}}</li>'
  })

  let vm = new Vue({
    el: "#app",
    data: {
      title: "语言",
      items: ["Java", "Python", "C++"]
    }
  });
</script>

11. 自定义事件

数据项在Vue的实例中, 但删除操作要在组件中完成, 那么组件来删除Vue实例中的数据,就涉及到参数传递与事件分发了。Vue为我们提供了自定义事件的功能很好的帮助我们解决了这个问题; 使用this.$emit(‘自定义事件名’, 参数) 。

步骤:

  1. 在对象的methods中定义删除方法
  2. 对应组件添加删除按钮,绑定监听事件
  3. 增加自定义事件,通过自定义事件调用监听事件,实现调用在步骤1中定义的删除方法。
<div id="app">
  <language>
    <my-title slot="language-title" :title="title"></my-title>
    <!-- v-on:remove="removeItems(index)"绑定监听事件 -->
    <my-item slot="language-items" v-for="(item,index) in items" 
             :index="index" :item="item" v-on:remove="removeItems(index)"></my-item>
  </language>
</div>

<script type="text/javascript">
  Vue.component("language", {
    template: '<div>\
<slot name="language-title"></slot>\
<ul>\
<slot name="language-items"></slot>\
  </ul>\
  </div>'
  });

  Vue.component("my-title", {
    props: ['title'],
    template: '<div>{{title}}</div>'
  });

  Vue.component("my-item", {
    props: ['item', 'index'],
    template: `<li>{{index}}--{{item}}<button @click="remove">删除</button></li>`,
    methods:{
      remove: function() {
        // 绑定指定的监听事件,实现调用对象里面的removeItems方法。
        this.$emit('remove');
      }
    }
  });

  let vm = new Vue({
    el: "#app",
    data: {
      title: "语言",
      items: ["Java", "Python", "C++"]
    },
    methods:{
      // 删除items中指定索引的元素
      removeItems: function(index) {
        console.log("删除了" + this.items[index]);
        this.items.splice(index,1);
      }
    }
  });
</script>

12. 创建vue-cli项目

vue-cli官方提供的一个脚手架,用于快速生成一个vue的项目模板。

主要的功能

  • 统一的目录结构
  • 本地调试
  • 热部署
  • 单元测试
  • 集成打包上线

12.1. 需要的环境

  • Nodejs
  • Git

12.2. 安装vue-cli

npm install vue-cli -g
vue list

12.3. 第一个vue-cli程序

  1. 初始化项目

    说明:

    • Project name:项目名称,默认回车即可
    • Project description:项目描述,默认回车即可
    • Author:项目作者,默认回车即可
    • Install vue-router:是否安装vue-router,选择n不安装(后期需要再手动添加)
    • Use ESLint to lint your code:是否使用ESLint做代码检查,选择n不安装(后期需要再手动添加)
    • Set up unit tests:单元测试相关,选择n不安装(后期需要再手动添加)
    • Setupe2etests with Nightwatch:单元测试相关,选择n不安装(后期需要再手动添加)
    • Should we run npm install for you after the,project has been created:创建完成后直接初始化,选择n,我们手动执行;运行结果!
    admin@admindeMacBook-Pro vue % vue init webpack myvue
    
    ? Project name myvue
    ? Project description A Vue.js project
    ? Author rootwhois
    ? Vue build standalone
    ? Install vue-router? No
    ? Use ESLint to lint your code? No
    ? Set up unit tests No
    ? Setup e2e tests with Nightwatch? No
    ? Should we run `npm install` for you after the project has been created? (recom
    mended) no
    
       vue-cli · Generated "myvue".
    
    # Project initialization finished!
    # ========================
    
    To get started:
    
      cd myvue
      npm install (or if using yarn: yarn)
      npm run dev
    
    Documentation can be found at https://vuejs-templates.github.io/webpack
    
  2. 安装依赖

    cd myvue
    npm install
    
  3. 运行

    npm run dev
    

13. webpack

详见这里

14. vue-router

Vue Router是Vue.js官方的路由管理器。它和Vue.js的核心深度集成, 让构建单页面应用变得易如反掌。

包含的功能:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于Vue js过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的CSS class的链接
  • HTML5 历史模式或hash模式, 在IE 9中自动降级
  • 自定义的滚动行为

14.1. 安装

npm install vue-router --save-dev

如果在一个模块化工程中使用它,必须要通过Vue.use()明确地安装路由功能:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter);

14.2. 测试

  1. components下创建组件

    Content.vue

    <template>
      <h1>Content</h1>
    </template>
    
    <script>
      export default {
        name: "Content"
      }
    </script>
    

    HelloWorld.vue

    <template>
        <h1>hello</h1>
    </template>
    
    <script>
    export default {
      name: 'HelloWorld'
    }
    </script>
    
  2. 安装路由,在src目录下新建文件夹router,专门用于存放路由。

    index.js

    import Vue from 'vue';
    // 导入路由插件
    import Router from 'vue-router';
    
    import Hello from '../components/HelloWorld.vue';
    import Content from '../components/Content.vue';
    
    // 安装路由
    Vue.use(Router);
    
    // 配置路由
    export default new Router({
      routes: [
        {
          // 路由路径
          path:'/content',
          // 路由名称
          name:'content',
          // 跳转到的组件
          component: Content
        },
        {
          path:'/hello',
          name:'hello',
          component: Hello
        }
      ]
    });
    
  3. src/main.js 导入路由

    import Vue from 'vue'
    import App from './App'
    
    // 导入路由配置目录,自动扫描里面的路由配置
    import router from './router';
    
    new Vue({
      el: '#app',
      // 配置路由
      router,
      components: { App },
      template: '<App/>'
    })
    
  4. App.vue使用路由

    <template>
      <div id="app">
        <h1>vue-router</h1>
        <!--
         router-link:默认渲染成一个a标签,to属性为指定链接
         router-view:用于渲染路由匹配到的组件
         -->
         <router-link to="/hello">Hello</router-link>
         <router-link to="/Content">Content</router-link>
         <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    
    <style>
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

15. 实战

15.1. 简单的Vue

  1. 创建工程

    admin@admindeMacBook-Pro ~ % mkdir vue
    admin@admindeMacBook-Pro ~ % cd vue
    admin@admindeMacBook-Pro vue % vue init webpack hello-vue
    
    ? Project name hello-vue
    ? Project description A Vue.js project
    ? Author rootwhois
    ? Vue build standalone
    ? Install vue-router? No
    ? Use ESLint to lint your code? No
    ? Set up unit tests No
    ? Setup e2e tests with Nightwatch? No
    ? Should we run `npm install` for you after the project has been created? (recom
    mended) no
    
       vue-cli · Generated "hello-vue".
    
    # Project initialization finished!
    # ========================
    
    To get started:
    
      cd hello-vue
      npm install (or if using yarn: yarn)
      npm run dev
    
    Documentation can be found at https://vuejs-templates.github.io/webpack
    
    cd hello-vue 
    # 安装vue-router
    npm install vue-router --save-dev
    # 安装element-ui
    npm i element-ui -S
    # 安装依赖
    npm install
    # 安装SASS加载器,这里npm报错
    cnpm install [email protected] [email protected] --save-dev
    #启动测试
    npm run dev
    
  2. 在src下创建如下结构:

    • assets:用于存放资源文件
    • components:用于存放Vue功能组件
    • views:用于存放Vue视图组件
    • router:用于存放vue-router配置

    • views/Main.vue

      <template>
        <h1>首页</h1>
      </template>
      
      <script>
        export default {
          name:"Main"
        }
      </script>
      
      <style>
      </style>
      
    • views/Login.vue

      其中el-form的元素为ElementUI组件;

      <template>
        <div>
          <el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
            <h3 class="login-title">欢迎登录</h3>
            <el-form-item label="账号" prop="username">
              <el-input type="text" placeholder="请输入账号" v-model="form.username"/>
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input type="password" placeholder="请输入密码" v-model="form.password"/>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" v-on:click="onSubmit('loginForm')">登录</el-button>
            </el-form-item>
          </el-form>
      
          <el-dialog
            title="温馨提示"
            :visible.sync="dialogVisible"
            width="30%">
            <span>请输入账号和密码</span>
            <span slot="footer" class="dialog-footer">
              <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
            </span>
          </el-dialog>
        </div>
      </template>
      
      <script>
        export default {
          name: "Login",
          data() {
            return {
              form: {
                username: '',
                password: ''
              },
      
              // 表单验证,需要在 el-form-item 元素中增加 prop 属性
              rules: {
                username: [
                  {required: true, message: '账号不可为空', trigger: 'blur'}
                ],
                password: [
                  {required: true, message: '密码不可为空', trigger: 'blur'}
                ]
              },
      
              // 对话框显示和隐藏
              dialogVisible: false
            }
          },
          methods: {
            onSubmit(formName) {
              // 为表单绑定验证功能
              this.$refs[formName].validate((valid) => {
                if (valid) {
                  // 使用 vue-router 路由到指定页面,该方式称之为编程式导航
                  this.$router.push("/main");
                } else {
                  this.dialogVisible = true;
                  return false;
                }
              });
            }
          }
        }
      </script>
      
      <style lang="scss" scoped>
        .login-box {
          border: 1px solid #DCDFE6;
          width: 350px;
          margin: 180px auto;
          padding: 35px 35px 15px 35px;
          border-radius: 5px;
          -webkit-border-radius: 5px;
          -moz-border-radius: 5px;
          box-shadow: 0 0 25px #909399;
        }
      
        .login-title {
          text-align: center;
          margin: 0 auto 40px auto;
          color: #303133;
        }
      </style>
      
  3. router/index.js

    import Vue from "vue"
    import Router from "vue-router"
    
    // 导入视图
    import Main from "../views/Main.vue"
    import Login from "../views/Login.vue"
    
    // 显式声明使用的模块
    Vue.use(Router);
    
    // 进行路由
    export default new Router({
      routes:[
        {
          path: "/login",
          component: Login
        },
        {
          path: "/main",
          component: Main
        }
      ]
    });
    
  4. main.js

    import Vue from 'vue'
    import App from './App'
    
    import router from "./router"
    
    // 导入element-ui
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    
    Vue.use(router);
    Vue.use(ElementUI);
    
    new Vue({
      el: '#app',
      router,
      // ElementUI
      render: h => h(App)
    })
    
  5. App.vue

    <template>
      <div id="app">
        <router-link to="/main">Main</router-link>
        <router-link to="/login">Login</router-link>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
    
    }
    </script>
    
    <style>
    </style>
    
  6. 运行

    npm run dev
    

可能遇到的问题

  1. Module build failed: TypeError: this.getOptions is not a function

    原因:sass-loader版本过高

    解决方法:sass-loader降级

    npm uninstall sass-loader
    cnpm install [email protected] --save-dev
    
  2. Module build failed: Error: Node Sass version 6.0.1 is incompatible with ^4.0.0.

    原因:node-sass版本过高

    解决方法:node-sass降级

    npm uninstall node-sass
    npm install [email protected] --save-dev
    

15.2. 路由嵌套

嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:

/user/foo/profile                     /user/foo/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+
  1. views/user/Profile.vue

    <template>
      <h1>个人信息</h1>
    </template>
    
    <script>
      export default {
        name: "UserProfile"
      }
    </script>
    
    <style>
    </style>
    

    views/user/List.vue

    <template>
      <h1>用户列表页</h1>
    </template>
    
    <script>
      export default {
        name: "UserList"
      }
    </script>
    
    <style>
    </style>
    
  2. index.js

    import Vue from "vue"
    import Router from "vue-router"
    
    // 导入视图
    import Main from "../views/Main.vue"
    import Login from "../views/Login.vue"
    import UserProfile from "../views/user/Profile.vue"
    import UserList from "../views/user/List.vue"
    
    // 显式声明使用的模块
    Vue.use(Router);
    
    // 进行路由
    export default new Router({
      routes:[
        {
          path: "/login",
          component: Login
        },
        {
          path: "/main",
          component: Main,
          // 子路由
          children: [
            {
              path:"/user/profile",
              component: UserProfile
            },
            {
              path:"/user/list",
              component: UserList
            }
          ]
        }
      ]
    });
    
  3. Main.vue

    在元素中配置了用于展示嵌套路由,主要使用个人信息展示嵌套路由内容。

    <template>
        <div>
          <el-container>
            <el-aside width="200px">
              <el-menu :default-openeds="['1']">
                <el-submenu index="1">
                  <template slot="title"><i class="el-icon-caret-right"></i>用户管理</template>
                  <el-menu-item-group>
                    <el-menu-item index="1-1">
                      <router-link to="/user/profile">个人信息</router-link>
                    </el-menu-item>
                    <el-menu-item index="1-2">
                      <router-link to="/user/list">用户列表</router-link>
                    </el-menu-item>
                  </el-menu-item-group>
                </el-submenu>
                <el-submenu index="2">
                  <template slot="title"><i class="el-icon-caret-right"></i>内容管理</template>
                  <el-menu-item-group>
                    <el-menu-item index="2-1">分类管理</el-menu-item>
                    <el-menu-item index="2-2">内容列表</el-menu-item>
                  </el-menu-item-group>
                </el-submenu>
              </el-menu>
            </el-aside>
    
            <el-container>
              <el-header style="text-align: right; font-size: 12px">
                <el-dropdown>
                  <i class="el-icon-setting" style="margin-right: 15px"></i>
                  <el-dropdown-menu slot="dropdown">
                    <el-dropdown-item>个人信息</el-dropdown-item>
                    <el-dropdown-item>退出登录</el-dropdown-item>
                  </el-dropdown-menu>
                </el-dropdown>
              </el-header>
    
              <el-main>
                <router-view />
              </el-main>
            </el-container>
          </el-container>
        </div>
    </template>
    
    <script>
        export default {
            name: "Main"
        }
    </script>
    
    <style scoped lang="scss">
      .el-header {
        background-color: #B3C0D1;
        color: #333;
        line-height: 60px;
      }
    
      .el-aside {
        color: #333;
      }
    </style>
    

15.3. 参数传递

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。此时我们就需要传递参数了。

传递参数有两种方式,一种是$route,一种是props。以下都有进行演示。

  1. 修改路由配置

    /goHome实现组件重定向,访问后会跳转到/main

    // 进行路由
    export default new Router({
      routes:[
        {
          path: "/login",
          component: Login
        },
        {
          path: "/main",
          component: Main,
          // 子路由
          children: [
            {
              // 这里的传参的方式为RestFul风格
              path:"/user/profile/:id/:name",
              // 绑定to值的name属性,传参数的时候需要设置
              name: "UserProfile",
              component: UserProfile,
              // 支持传递参数(第二种)
              props: true
            },
            {
              path:"/user/list",
              component: UserList
            },
            {
              path:"/goHome",
              // 重定向
              redirect: "/main"
            }
          ]
        }
      ]
    });
    
  2. 传递参数

    将 to 改为了 :to,是为了将这一属性当成对象使用,

    注意 router-link 中的 name 属性名称一定要和路由中的name 属性名称 匹配,因为这样 Vue 才能找到对应的路由路径;

    <el-menu-item-group>
      <el-menu-item index="1-1">
        <!-- <router-link v-bind:to="{name:'UserProfile', params:{id:1}}">个人信息</router-link> 带参数的格式-->
        <router-link v-bind:to="{name:'UserProfile', params:{id:1, name:'张三'}}">个人信息</router-link>
      </el-menu-item>
      <el-menu-item index="1-2">
        <router-link to="/user/list">用户列表</router-link>
      </el-menu-item>
      <el-menu-item index="1-3">
        <router-link to="/goHome">回到首页</router-link>
      </el-menu-item>
    </el-menu-item-group>
    
  3. 接收参数并显示

    <template>
      <div>
        <h1>个人信息</h1>
        <!-- 通过直接传递 -->
        <span></span><br>
        <span></span><br>
    
        <!-- 通过传递参数方式(第二种) -->
        <span></span><br>
        <span></span>
    
        </span>
      </div>
    </template>
    
    <script>
      export default {
        // props: true设置后,可以直接接收参数(第二种方式)
        props:["id","name"],
        name: "UserProfile"
    
      }
    </script>
    
    <style>
    </style>
    

15.4. 去除地址栏中的/

默认为带/#,mode: "hash"

如果要更改为不带/#,则mode: "hostory"

export default new Router({
  mode:"history",
  ...
};

15.5. 404

  1. 定制404页面

    <template>
      <div>404 页面不存在</div>
    </template>
    
    <script>
        export default {
          name: "NotFound"
        }
    </script>
    
    <style>
    </style>
    
  2. 添加路由

    import Vue from "vue"
    import Router from "vue-router"
    
    // 导入视图
    // ...
    import NotFound from "../views/NotFound.vue"
    
    Vue.use(Router);
    
    export default new Router
      routes:[
        // ...
        {
          path: "*",
          component: NotFound
        }
      ]
    });
    

15.6. 路由钩子和异步请求

axios

axios依赖安装和导入

npm install axios
npm install vue-axios


cnpm install [email protected] [email protected] --save-dev
// 导入axios
import axios from "axios"
import VueAxios from "vue-axios"
Vue.use(VueAxios, axios)

在static/mock/data.json下准备json文件

{
  "name": "rootwhois",
  "url": "https://rootwhois.cn",
  "page": 1,
  "isNonProfit": true
}

在static目录下的静态资源不会经过路由,可以直接访问。

路由钩子

  • beforeRouteEnter:在进入路由前执行
  • beforeRouteLeave:在离开路由前执行

参数说明:

  • to:路由将要跳转的路径信息
  • from:路径跳转前的路径信息
  • next:路由的控制参数
  • next() 跳入下一个页面
  • next(’/path’) 改变路由的跳转方向,使其跳到另一个路由
  • next(false) 返回原来的页面
  • next((vm)=>{}) 仅在 beforeRouteEnter 中可用,vm 是组件实例

示例:

Profile.vue

<template>
  <div>
    <h1>个人信息</h1>
    </span>
  </div>
</template>

<script>
  export default {
    name: "UserProfile",
    // 使用路由钩子
    beforeRouteEnter: (to, from, next) => {
      console.log("进入路由之前");
      // 通过vm调用当前对象下的方法
      next(vm => {
        vm.getData();
      });
    },
    beforeRouteLeave: (to, from, next) => {
      console.log("进入路由之后");
      next();
    },
    methods:{
      getData: function() {
        // 调用axios异步请求
        this.axios({
          method: 'get',
          url: 'http://localhost:8080/static/mock/data.json'
        }).then(function(response){
          console.log(response);
        });
      }
    }

  }
</script>

<style>
</style>

​ 运行后,可以在控制台看到请求体response被打印出来,此时可以通过上面的Axios异步通信进行更多设置。

16. 值得注意的问题

  1. <template>中,必须要有唯一一个根结点。
  2. 尽量用npm,少用cnpm。但是有些软件必须cnpm才能装得上。
Copyright © rootwhois.cn 2021-2022 all right reserved,powered by GitbookFile Modify: 2023-03-05 10:55:52

results matching ""

    No results matching ""