Vue基础知识部分总结

了解单页面组件

vue初始化生成的文件:

├── README.md                       项目介绍
├── index.html                      入口页面
├── build                           构建脚本目录
│   ├── build-server.js                 运行本地构建服务器,可以访问构建后的页面
│   ├── build.js                        生产环境构建脚本
│   ├── dev-client.js                   开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新
│   ├── dev-server.js                   运行本地开发服务器
│   ├── utils.js                        构建相关工具方法
│   ├── webpack.base.conf.js            wabpack基础配置
│   ├── webpack.dev.conf.js             wabpack开发环境配置
│   └── webpack.prod.conf.js            wabpack生产环境配置
├── config                          项目配置
│   ├── dev.env.js                      开发环境变量
│   ├── index.js                        项目配置文件
│   ├── prod.env.js                     生产环境变量
│   └── test.env.js                     测试环境变量
├── mock                            mock数据目录
│   └── hello.js
├── package.json                    npm包配置文件,里面定义了项目的npm脚本,依赖包等信息
├── src                             源码目录    
│   ├── main.js                         入口js文件
│   ├── app.vue                         根组件
│   ├── components                      公共组件目录
│   │   └── title.vue
│   ├── assets                          资源目录,这里的资源会被webpack构建
│   │   └── images
│   │       └── logo.png
│   ├── routes                          前端路由
│   │   └── index.js
│   ├── store                           应用级数据(state)
│   │   └── index.js
│   └── views                           页面目录
│       ├── hello.vue
│       └── notfound.vue
├── static                          纯静态资源,不会被wabpack构建。
└── test                            测试文件目录(unit&e2e)
    └── unit                            单元测试
        ├── index.js                        入口脚本
        ├── karma.conf.js                   karma配置文件
        └── specs                           单测case目录
            └── Hello.spec.js

接下来我们就分析下这些初始化代码(main.js):

Vue的核心架构,主要在于组件和路由两大模块;
先从vue中默认生成的代码开始:
入口js文件:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'/*引入App这个组件*/
import router from './router'/*引入路由配置*/

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',/*最后效果将会替换页面中id为app的div元素*/
  router,/*使用路由*/
  template: '<App/>',/*告知页面这个组件用这样的标签来包裹着,并且使用它*/
  components: { App }/*告知当前页面想使用App这个组件*/
})

代码解析:

import部分

import Vue from 'vue'

如果写全的话是import vue from '../node_modules/vue/dist/vue.js'
在webpack.base.conf.js中进行了定义,内置了一些选项(extenions:['.js','.vue','.json']),意思是省略后面的后缀,由webpack来自动为我们加上。
如果名字比较长,还可以起个别名:
alias:{
‘@’:resolve(‘src’), //它的意思是在vue项目中,引入路径的时候使用@即代表src文件夹
}
注:关于import的知识点:https://blog.csdn.net/mirror_Mx/article/details/85098316

import App from './App'

引入了./App文件(就是目录中和main.js同级的App.vue文件)。

import router from './router'

引入一段路由配置

new Vue实例化

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

其实就相当于平时我们写js时候常用的init啦,然后声明el:'#app',意思是将所有视图放在id值为app这个dom元素中,components表明引入的文件,即上述的App.vue文件,这个文件的内容将以这样的标签写进去#app中,总的来说,这段代码意思就是将App.vue放到#app中,然后以来指代我们的#app。

单页面组件:

打开我们的App.vue文件,在Vue中,官网叫它做组件;
单页面的意思是:结构,样式,逻辑代码都写在同一个文件中;
当我们引入这个文件后,就相当于引入对应的结构、样式和JS代码;
这不就是我们做前端组件化最想看到的吗,从前的asp、php也有这样的文件思想。

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <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>

node端之所以能识别.vue文件,是因为前面说的webpack在编译时将.vue文件中的html,js,css都抽出来合成新的单独的文件。

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view></router-view>
  </div>
</template>

回看我们的浏览器页面中,图片是有了,可下面的文本和链接的代码写在哪里了呢?这里就要开始涉及路由了。
在这里插入图片描述

路由

前面说的src/main.js中有一句引入路由器的代码。

import router from ‘./router’

现在就让我们打开router目录下的js文件:

import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'
import About from '@/components/about'
import Recruit from '@/components/recruit'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
}, {
      path: '/about',
      name: 'about',
      component: About
}, {
      path: '/recruit',
      name: 'recruit',
      component: Recruit
}]
})

注:通过若存在多个路由的时候,则通过name属性加以区分。

小结

从初始化到页面展示,Vue的页面架构流程大概是这样的
在这里插入图片描述

通过项目 进一步解析组件的应用

先来尝试以下页面
页面 效果
这是我们组件库的首页,包含三个子页面,按钮页面、列表页面、导航页面;
点击进去子页面会由路由来配置。先看我们的目录结构:
组件库页面-初始化目录结构
pages目录存放我们的页面,包括首页和三个子页面;
components目录存放我们的具体组件,包括按钮组件,箭头组件,列表组件和导航组件(组件和页面其实是一样的文件类型,只是由于功能不一样,我们就叫不同的称呼)

路由配置

import Vue from 'vue'
import Router from 'vue-router'
// 引用页面模板->供路由使用
import index from '../pages/index.vue'
import pageQuiButton from '../pages/pageQuiButton.vue'
import pageQuiList from '../pages/pageQuiList.vue'
import pageQuiNav from '../pages/pageQuiNav.vue'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'index',
      component: index
    },
    {
      path: '/btn',
      name: 'btn',
      component: pageQuiButton
    },
    {
      path: '/list',
      name: 'list',
      component: pageQuiList
    },
    {
      path: '/nav',
      name: 'nav',
      component: pageQuiNav
    }
  ]
})

由上面代码可得,几个配置完成的路由地址为:
首页:http://localhost:8080/#/
按钮子页:http://localhost:8080/#/btn
列表子页:http://localhost:8080/#/list
导航子页:http://localhost:8080/#/nav
具体的每一页内容对应每一页的.vue文件,不知大家是否记得入口页App.vue,这个文件承载着一些公有的元素,还有就是一个路由容器,我们的首页index.vue到时候也是挂在路由容器中的,看看App.vue的代码。

入口页App.vue

<template>
  <div id="app">
    <h1 class="page-title"><a href="#/">开发组件库</a></h1>
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'app'
}
</script>

<style scoped>
  @import './assets/css/App.css';
</style>

简单分析一下这个入口函数,h1标签是一个公有元素,也就是说到时候每个子页面都会带着这个h1,他的作用就是方便我们快速回到首页,子页面的内容会注入到router-view中。这里值得关注的地方是style标签,我们可以在style标签里面直接写样式,也可以直接引入一个样式文件,scoped关键字表示这个样式是私有的,即使两个组件写着一样的#app{}样式也不会冲突,程序会加上命名空间,这也就是为什么在script标签中有个name参数。

首页index.vue

<template>
  <div class="mod-module">
    <div class="img-list type-full">
      <div class="img-box">
        <p class="img-item">
          <a class="page-link" href="#/btn">按钮</a>
        </p>
      </div>
      <div class="img-box">
        <p class="img-item">
          <a class="page-link" href="#/list">列表</a>
        </p>
      </div>
      <div class="img-box">
        <p class="img-item">
          <a class="page-link" href="#/nav">导航</a>
        </p>
      </div>
    </div>
  </div>
</template>

<style scoped>
 @import './css/index.css';
</style>

首页的代码非常简单,就是几个链接跳转到对应的子页面,程序运行的时候,会将<template>标签的内容注入到router-view标签中,从而实现无刷新的路由跳转。
我们从组件开始入手,子页面只是引用对应的组件。

按钮组件quiButton.vue

<template>
  <button class="qui-btn">
    <span>{{msg}}</span>
  </button>
</template>

<script>
  export default {
        data:function(){
            return {
                msg:'下载'
            }
        }
  }
</script>
<style scoped>
  @import './css/reset.import.css';
  @import './css/qui-btn.import.css';
</style>

按钮组件很简单,就是正常的button标签,script标签中暴露这个组件的data属性(data是Vue的属性值,不是乱写的)。当按钮组件被初始化的时候,msg的自定义属性会被绑定到<span>标签中的{{msg}}中。
这里需要注意一个地方: 如果这里不是组件的话,正常data的写法可以直接写一个对象,比如说data:{msg:'下载'},但由于组件是会在多个地方引用的,JS中直接共享对象会造成引用传递,也就是说修改了msg后所有的按钮的msg都会跟着修改,所以这里用function来每次返回一个对象实例。

使用同个button组件创建不同的按钮

先在quiButton.vue给默认的属性值:

<script>
  export default {
    props: {
      msg: {
        default: '下载'
      }
    }
  }
</script>

在组件中,prop是专门用来暴露组件的属性接口的,这里给了一个默认值‘下载’,后面要使用这个组件,只需<btn msg="确认"></btn>就可以修改按钮的默认文案了。

按钮事件

<template>
  <button class="qui-btn" v-on:click="btnClickEvent">
    <span>{{msg}}</span>
  </button>
</template>

<script>
  export default {
    props: {
      msg: {
        default: '下载'
      }
    },
    methods: {    //绑定事件的关键代码
      btnClickEvent: function(){
        alert(this.msg);
      }
    }
  }
</script>

methods属性中可以写任何的自定义函数,通过在元素中添加v-on:click,再把对应的事件写上就可以了,以上代码实现的就是点击按钮弹出按钮中的文案。

使用按钮组件pageQuiButton.vue

//pageQuiButton.vue
<template>
  <div id="pageQuiButton">
    <!--使用-->
    <qui-btn msg="确定" class="small"></qui-btn>
  </div>
</template>
<script>
  import quiBtn from '../components/quiButton.vue' /*引用*/
  export default {
    name: 'pageQuiButton',
    components: {
      'qui-btn': quiBtn /*注册自定义标签*/
    }
  }
</script>

从script开始解析,首先引入我们的组件赋值给变量quiBtn,使用时候直接将quiBtn作为对象的一部分写进components属性,这是Vue用来存储引用组件的关键字,同时对应我们自定义的标签 “qui-btn”,完成这些操作之后,我们就可以在template中使用自定义的按钮组件上面也说了用msg属性来自定义按钮的文案。完成之后,我们就可以在页面中看到具体效果,点击按钮弹出对应的文案。
按钮页面效果

msg属性自定义(按钮异化)

//pageQuiButton.vue
//监听子组件的事件
<qui-btn v-on:btnClickEvent="doSth" msg="我可以点击" ></qui-btn>

上面的代码在引用组件的时候,注册了一个事件,这个btnClickEvent事件是之前我们在按钮组件中绑定到按钮的click事件中的,然后我们给这个事件一个自定义的方法doSth,同时,在script中声明这个自定义的方法如下:

//pageQuiButton.vue
//页面中引用子组件并监听子组件的事件
<script>
  import quiBtn from '../components/quiButton.vue'
  export default {
    name: 'pageQuiButton',
    components: {
      'qui-btn': quiBtn
    },
    methods: {
      doSth: function(){
        alert('你点击了组件的click:btnClickEvent');
      }
    }
  }
</script>

专业一点的说,这种做法叫做监听,由引用方(暂且叫做父组件)监听子组件的内置方法;同时在子组件中,需要触发这个事件,以下是在子组件中的关键代码:

//quiButton.vue
//子组件中的代码
<script>
  export default {
    props: {
      msg: {
        default: '下载'
      }
    },
    methods: {
      btnClickEvent: function(){
        alert("先弹出默认的文案");
        this.$emit('btnClickEvent');//关键代码父组件触发自定义事件
      }
    }
  }
</script>

这里的关键代码就是$emit,也叫触发机制,父组件监听,子组件触发。
完成这步之后,引用方(父组件)就可以给不同子组件调用不同的事件处理了:

<qui-btn v-on:btnClickEvent="doSth1" msg="确定" ></qui-btn>
<qui-btn v-on:btnClickEvent="doSth2" msg="取消" ></qui-btn>
<script>
/*这里只是简单展示*/
    methods: {
      doSth1: function(){
        alert('111');
      },
      doSth2: function(){
        alert('222');
      }
    }
</script>

给按钮添加图标

下面给我们的按钮组件加上一段结构

//quiButton.vue
<template>
  <button class="qui-btn" v-on:click="btnClickEvent">
    <slot name="icon"></slot><!--重点在这里-->
    <span>{{msg}}</span>
  </button>
</template>

加入了关键字slot并赋予一个name值之后,我们再看看引用如何使用

//pageQuiButton.vue
<qui-btn msg="下载" class="with-icon">
  <img slot="icon" class="ico" src="xxx.png" />
</qui-btn>

img上有个关键字slot="icon"对应组件中的name=“icon”,渲染的时候,会将img整个替换掉组件中的对应name的标签,其实很好理解,slot的翻译是插槽的意思,相当于把img这块内容插到一个名叫icon的插槽里面去。

小总结

学到这里,我们已经学会了用props给按钮自定义文案,用on和emit给按钮自定义点击触发,用slot给按钮添加一些自定义结构。
上述我们已经讨论了如何制作一个按钮组件,以及如何使用我们的按钮组件。
在这里插入图片描述

导航组件quiNav.vue

在这里插入图片描述
先来看看关键代码:

//quiNav.vue
<template>
  <div class="qui-nav nav-type-1">
    <a v-for="(item, index) in items" ><!--关键代码v-for-->
      <span class="nav-txt">{{item.text}}</span>
    </a>
  </div>
</template>

<script>
  export default {
    data:function(){
      return {
        items:[
          {
            text: '首页',
            active : true
          },
          {
            text: '列表',
            active : false
          },
          {
            text: '关于',
            active : false
          },
          {
            text: '招聘',
            active : false
          }
        ]
      }
    }
    }
  }
</script>

动态类名

//quiNav.vue
<template>
  <div class="qui-nav nav-type-1">
    <a v-for="(item, index) in items" :class="[commonClass,item.active ? activeClass : '']" >
      <span class="nav-txt">{{item.text}}</span>
    </a>
  </div>
</template>

<script>
  export default {
    data:function(){
      return {
        commonClass:'nav-item',
        activeClass:'active',
        items:[
            ...//数据
        ]
      }
    }
  }
</script>

:class给组件绑定一个class属性(类似jQuery中的attr方法),这里的写法是缩写,他的全拼应该是v-bind:。注意到最前面有个冒号,:class="XXX"class="XXX"的区别在于不带冒号的是静态的字符串绑定,带冒号的是动态的变量绑定。我们给class绑定了一个数组,这个数组带有变量,先看commonClass,这个变量在data中定义了,然后数组的第二个元素是一个JS的三元运算符:item.active?activeClass:'',当每个item中的active值为true时,绑定activeClass变量对应的类,如果为false,则为空。最后的结果是当item.active为true时候,tab的class值为'nav-item active',当为false,就只有'nav-item'

上面的代码可以理解的话,那么我们切换tab的active类,就转换为修改每个item里面的active的值(再次体现数据驱动)。

那么问题来了,怎么去修改每个item里面的active值呢?没错,给每个tab绑定一个点击事件,当点击事件触发的时候,修改当前tab对应item的active值。于是代码变成了如下:

<template>
  <div class="qui-nav nav-type-1">
    <a v-for="(item, index) in items" :class="[commonClass,item.active ? activeClass : '']" v-on:click="navClickEvent(items,index)" >
      <span class="nav-txt">{{item.text}}</span>
    </a>
  </div>
</template>

<script>
  export default {
    data:function(){
      return {
        commonClass:'nav-item',
        activeClass:'active',
        items:[
          {
            text: '首页',
            active : true
          },
          ......
        ]
      }
    },
    methods:{
      navClickEvent:function(items,index){
        /*默认切换类的动作*/
        items.forEach(function(el){
          el.active = false;
        });
        items[index].active = true;
        /*开放用户自定义的接口*/
        this.$emit('navClickEvent',items,index);
      }
    }
  }
</script>

使用quiNav组件

//pageQuiButton.vue
<template>
  <div id="pageQuiNav">
    <!--使用-->
    <qui-nav v-on:navClickEvent="dosth"></qui-nav>
  </div>
</template>
<script>
  import quiNav from '../components/quiNav.vue' /*引用*/
  export default {
    name: 'pageQuiNav',
    components: {
      'qui-nav': quiNav /*注册自定义标签*/
    },
    methods:{
      dosth:function(items,index){
        console.log(items[index].text + index);
      }
    }
  }
</script>

列表组件quiList.vue

本节我们主要要完成这样一个列表功能,每一行的列表是一个组件,列表内可能出现按钮组件或者箭头组件,点击按钮组件可以自定义事件,同时可以根据不同的参数来决定当前列表是带按钮的列表or带箭头的列表。
在这里插入图片描述

//quiList.vue
<template>
  <div class="qui-list">
    <span class="list-tips">{{tipsText}}</span>
    <qui-btn v-on:btnClickEvent="btnClickEvent" :msg=msg class="small">
    </qui-btn>
  </div>
</template>

<script>
  import quiButton from '../components/quiButton.vue'
  export default{
    props:{
      msg: {
        default: '下载'
      },
      tipsText: {
        default: '默认的文案'
      }
    },
    components: {
      'qui-btn': quiButton
    },
    methods:{
      btnClickEvent:function(){
          alert('按钮点击事件')
      }
    }
  }
</script>

上面的知识点基本上就是我们之前学过的,只不过记住quiList本身是一个组件,而在这个组件里面,我们又引入了按钮组件quiButton,也就是组件内引用组件,实际上就是组件的嵌套,注意到这里:msg=msg的使用,这里冒号表示绑定的是一个变量msg,然后这个属性通过props暴露出去(本身在按钮中就暴露了msg给列表组件使用),借用下面一张图理解下:
在这里插入图片描述
至于点击事件,也是我们之前学习过的事件的绑定。现在引入一个新问题,是否有一个参数,可以决定列表组件的右侧是放置按钮组件呢?还是箭头组件。

动态组件

新增一个箭头组件(quiArrow.vue)

<template>
  <a class="qui-arrow"></a>
</template>

<script>
  export default {
  }
</script>
<style scoped>
  .qui-arrow{
    font-size: 18px;
    color: #aaa;
    margin-right: 10px;
  }
</style>

在quiList中使用动态组件:

//quiList.vue
<template>
  <div class="qui-list">
    <span class="list-tips">{{tipsText}}</span>
    <component :is="currentView" v-on:btnClickEvent="btnClickEvent" :msg=msg class="small" keep-alive></component>
  </div>
</template>

<script>
  import quiArrow from '../components/quiArrow.vue'  
  import quiButton from '../components/quiButton.vue'
  export default{
    props:{
      msg: {
        default: '下载'
      },
      tipsText: {
        default: '默认的文案'
      },
      currentView:{
        default: 'qui-btn'
      }
    },
    components: {
      'qui-btn': quiButton,
      'qui-arrow':quiArrow
    },
    methods:{
      btnClickEvent:function(){
      }
    }
  }
</script>
<style scoped>
  .qui-list{
    padding: 10px 0;
    margin-left: 10px;
    overflow: hidden;
    border-bottom: 1px solid #e0e0e0;
    vertical-align: middle;
    display: flex;
    justify-content:center;
    align-items:Center;
    min-height: 30px;
  }
  .qui-list .qui-btn{
    float: right;
    margin-right: 10px;

  }
  .qui-list .list-tips{
    padding: 0 5px;
    color: #00A0D8;
    float: left;
    vertical-align: middle;
    flex:1;
    text-align: left;
    font-size: 14px;
  }
</style>

代码分析:
component标签,这是vue自带的一个标签,可以把它当做一个容器,这个容器可以用来装按钮,也可以用来装箭头。决定这个容器装的是哪个组件的关键代码在于:is="currentView",当currentView的值为qui-btn,这个容器就是按钮组件,反之,则是箭头组件。
keep-alive关键字保持这个组件在内存中是常驻的,由于动态组件可能需要动态切换,这样保持组件活跃可以减少组件变化时候的内存消耗。
可以看到我们的component上还保留着按钮的点击事件和msg信息,这些没有关系,只要箭头组件中不出现同样的变量就不会发生冲突。

在pageQuiList.vue中使用list组件

<template>
	<div id="pageQuiList">
		<qui-list tipsText="自定义文案,默认右边是按钮" msg="弹层"></qui-list>
		<qui-list v-on:btnClickEvent="test"></qui-list>
		<qui-list ref="child1" tipsText="最右边是箭头" currentView="qui-arrow"></qui-list>
	</div>
</template>

<script>
	import quiList from '../components/quiList.vue'
  export default {
    name: 'pageQuiList',
    components: {
      'qui-list': quiList
    }
  }
</script>
<style>
</style>

使用列表组件的时候,只需要给暴露出来的currentView指定一个值,就可以决定右侧是按钮还是箭头了。

猜你喜欢

转载自blog.csdn.net/mirror_Mx/article/details/85097815