> 文档中心 > 二、Vue脚手架工程

二、Vue脚手架工程


一、前言

使用vue,大部分是使用前后端分离技术,前端vue单独打包成项目运行。首先大概介绍一下vue脚手架,nodejs和webpack之间的关系。
vue脚手架:
vue官方提供的vue项目目录结构。无需程序员手动搭建框架,使用脚手架的架构,继续开发项目即可。
webpack:
在vue脚手架项目中,会使用到很多ES6语法和特殊结构。而这些语法和结构,是我们在开发中应用的,最终部署成项目,结构会发生变化,ES6语法浏览器也不能很好的支持。这时webpack就发挥了作用。它会把工程中的语法转换成浏览器认识的语法,并会对项目结构进行整理,打包,最终形成打包后的项目。
nodejs:
js的运行环境。webpack打包工具依赖nodejs环境才能工作。

由上面可知,想使用vue搭建前端项目,需要搭建nodejs环境,webpack工具并安装vue脚手架。

二、脚手架介绍

2.1 脚手架的安装
1.需要首先搭建node.js环境,使用内置的npm命令来搭建脚手架环境。
2. npm install -g @vue/cli --安装vue脚手架环境
3. 在合适目录下创建vue项目:vue create 项目名
在这里插入图片描述
这里,选择Vue2版本。创建完后,一个脚手架工程就好了。这个项目现在就可以启动,输入npm run serve 命令,启动项目。
在这里插入图片描述
此时,这个工程就相当于是一个helloworld。我们自己的工程,在此基础上改造就可以。

2.2脚手架目录结构解析
二、Vue脚手架工程
babel.config.js: ES6转ES5的配置文件
package.json: 类似于包的说明书
package-lock.json: 锁定依赖包版本的文件。
node_modules文件夹:脚手架为我们自动创建的依赖的第三方库,都放到了这个文件夹下。
src文件夹:
二、Vue脚手架工程
assets文件夹:静态资源文件夹,放图片,视频等资源
components:所有程序员定义的组件,都放到这里
App.vue文件:所有组件的总管。
main.js文件:程序的入口,vue对象在这里创建。
public文件夹:放html文件。vue就是在渲染这里的html文件。
二、Vue脚手架工程
2.3 main.js详解
首先,main.js是程序的入口,那这个入口文件,是在哪里配置定义的呢?
这是脚手架默认配置的,并且把配置文件隐藏起来了。输入命令vue inspect >output.js,就可以在项目中生成其默认配置文件。
在这里插入图片描述
需要注意的是,这里生成配置文件只是让你看看,直接改这里的配置,不能生效。
想覆盖脚手架的默认配置,在vue.config.js文件中进行修改。修改项配置可在脚手架官网进行查看。
在这里插入图片描述
2.4 main.js源码详解

import Vue from 'vue'import App from './App.vue'Vue.config.productionTip = falsenew Vue({  render: h => h(App),}).$mount('#app')

首先,import进来vue。这里的vue,是node_modules里的vue类库。在这里引用之后,这里集成的组件,就无需再引入vue类库了。

import App from './App.vue'

这句是引入App组件。App组件是所有子组件的总管。App组件汇总其他子组件后,交给main.js中的Vue对象。

下面看创建Vue对象的代码:

new Vue({  render: h => h(App),}).$mount('#app')

这里使用了render函数,看其官网介绍:
在这里插入图片描述
使用了render函数,就不能再使用el来绑定dom元素了,所以使用了$mount方法来绑定。
其中,render函数有一个参数,是一个函数,函数名叫createElement。用于创建元素用的。这样也能绑定组件。render函数必须写return,否则页面无法加载元素。
render函数的完整写法如下:

render(h){  return h(APP名字)}

因为方法里没有用到关键字this,所以可以使用箭头函数来简写,因为有参数,又有返回值,所以简写后为:

render: h => h(App),

三、组件化编程

3.1 什么是组件
上述脚手架中,带有.vue后缀的文件,都是组件。实现应用中局部功能的代码和资源的集合,就是一个组件。局部功能可以按照功能点儿划分,如搜索组件、列表组件等。也可以根据布局划分,如头部组件,尾部组件等。一个组件里包含了html,css,js,img等等东西。

3.2组件的定义
语法如下:

var Profile = Vue.extend({  data: function () {    return {      firstName: 'Walter',      lastName: 'White',      alias: 'Heisenberg'    }  }});

需要注意的是,data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数,不能写成简写。
还需要注意的是,组件最终都引用在了App.vue组件中,而App.vue组件,也被main.js所引用。所以,定义的所有组件,都要使用export来修饰,才能使其正常在其他地方import进去。
甜点:export的几种写法:
export是ES6的语法。这里简单说一下几种方式。

  1. export default方式:
    这种方式,只能导出一个对象。default只能使用一次。所以,使用export default时需要把导出的内容都放在一个对象中导出。
  2. export 单个对象
    就是每个需要导出的函数,对象,变量前面都加上export关键字,多写几个关键字的事儿。
  3. export {a,b,c}
    也是形成了一个对象,直接导出了这个对象,不用写default关键字了。这种方式导出的内容,在import时,需要import {a,b,c} from xxx来导入,也可以import * from xxx来全部导入。

ES6简写规则:
在这里插入图片描述

需要注意的是,Vue.extend写法,需要在每个组件文件中,都要import进vue才行,否则就会报错。所以,一般使用简写方式来定义组件。简写方式就是直接写{},就是组件,然后定义其属性即可。最后导出组件。最终写法就是:

export default {//组件属性... ...}

3.3 组件对象的本质
组件,可以看作是一个小的vue对象。组件对象的内部,和vue对象的内部结构是一样的。所以,在第一节中讲到的vue对象的所有属性,在组件对象中,都可以定义。
下面,定义一个多属性的组件,如下:

import Vue from 'vue' var a= Vue.extend({  name: 'HelloWorld',  data: function (){    return {      "firstName":'zhang',      "lastName":'san',    }  },  methods:{    method1(){},    method2(){},  }});console.log(new a); export default a;

通过console.log输出一下a对象,如下:
在这里插入图片描述
首先可以看到,插件对象,本质是一个VueComponent对象。
而其内部结果,和第一节中看到的vue对象基本一模一样。有_data属性,且也把data中的属性提出来了,形成了get,set方法。等等。
需要注意的是,每一个组件,都是新new VueComponent。每个组件都是一个新对象。

3.4 VueComponent对象和vm对象的区别与联系
1.vc定义时,不能用el配置项,且data函数不能简写。不能简写主要是为了防止组件复用时一处修改值,而其他复用组件的地方值也修改问题发生。
2.this指向问题:
在vm的data函数、methods函数,watch函数,computed函数中,this指的是vm实例对象。
在vc的data函数,methods函数,watch函数,computed函数中,this指的是vc实例对象。
3.vm实例中的$children属性,是其组件的集合,如下图:
二、Vue脚手架工程
4.vc中如何获取vm上的属性和数据:
js实例对象的_proto_是隐式原型链,在实例对象本身没有的属性或方法,如果在_proto_中有,也 可以直接js对象实例进行调用。
一个重要的内置关系:VueComponent.prototype.proto ===Vue.prototype。
由上面vc的内置关系可知,vc中,直接.属性,可以获取到vm中的属性。其最终走的就是原型链,找到的vm中的属性。

四、组件的用法

4.1组件的结构
组件中,
标签中写html元素,需要一个大的

包裹其中,也就是需要有一个跟元素,而不能是多个根元素。
中定义组件对象,
中写css样式,为了与其他组件的样式区分,加scoped属性,即
中,可以使用插值表达式等等方式,来写页面,并为页面赋值。
4.2组件的用法
定义好组件后,在引用组件的中,import进来该组件,就可以使用这个组件了。使用方式就是在中,写该组件的名字标签,即可。
比如在App.vue中使用HelloWorld组件,那么在App.vue的中,

import HelloWorld from './components/HelloWorld.vue'

在components属性中,加上HelloWorld组件:

export default {  name: 'App',  components: {    HelloWorld  }}

然后在中,写。那么HelloWorld组件中的内容就出现在了App.vue中。

4.3 ref属性
在使用vue后,尽量避免直接操作dom元素,否则就失去了使用vue框架的意义。那么在一个方法中,如何通过id获取到一个元素呢?这就用到了ref属性。
ref用在普通html元素中,那获取到的就是dom元素,ref写在子组件标签中,那获取到的就是VueComponent对象。
通过VueComponent对象的$refs属性,可以获取到定义的ref集合,然后再通过 .属性,来具体拿到某个元素。示例如下:

<template>  <div id="app">    <HelloWorld ref="ceshi" msg="Welcome to Your Vue.js App"/>    <div ref="ceshi1">666</div>  </div></template>

Helloword组件标签定义了ref属性,普通dom元素div定义了ref属性,通过钩子方法mounted函数来输出,如下:

export default {  name: 'App',  components: {    HelloWorld  },  mounted() {    console.log('$refs',this.$refs);    console.log('mounted-ceshi',this.$refs.ceshi);    console.log('mounted-ceshi1',this.$refs.ceshi1);  }}

输出结果如下:
二、Vue脚手架工程
可见$refs属性将我们定义的ref属性作为了key,vaule是相应的dom对象或组件对象。
二、Vue脚手架工程
组件标签上的ref获得的是VueComponent对象。
二、Vue脚手架工程
普通dom元素直接获取的dom元素内容。

4.4props属性
组件是可以多处复用的。而复用时,父组件可能会有重新定义子组件中data中属性值的需求,此时,就需要用props属性了。
props属性用于子组件中,定义父组件中可以赋值的属性。定义在props中的属性就无需再定义在data中了。
简写方式:

props:[‘key1’,‘key2’,‘key3’]

这种写法,key1,ke2,key3就是可以在父组件中赋值的属性。

全写方式:

props:{‘key1’:{type:String, //类型required:false,//是否必传default:123 //默认值}}

在props中定义的属性,也是组件中有的属性,其使用方式等同于在data中定义的属性的使用方式。
props中定义的属性,不支持运算,这样vue会报错。如果想对props属性进行运算,就定义一个中间变量,去对props属性进行运算,而不是直接对props属性进行运算。如下图:
在这里插入图片描述
props中,不仅可以定义属性,还可以定义函数。定义函数的意思就是通过父组件来传递函数,定义子组件中这个函数的具体实现。

子组件定义了props属性后,父组件怎么给其传值呢?
如果是固定值,则直接把props属性当作子组件标签的一个属性传值即可。如果想给子组件的props属性传vue中的数据,那么,需要写成:props属性=(父组件数据)。多加个冒号,也就是v-bind的缩写,这样,才会去vue中找对应的数据。
示例如下:
首先,定义子组件,并定义props属性。

<template>  <div class="hello">  <!--  使用props中定义的属性  -->    <h1>{{ msg }}</h1>    <h2>{{ceshi}}</h2>    <h3>{{hanshu()}}</h3>  </div></template><script>export default {  name: 'HelloWorld',  props: {    msg: String,    ceshi: String,    hanshu:Function //定义函数  }}</script>

然后,在父组件中,对这些属性进行传值,如下:

<template>  <div id="app">    <HelloWorld ref="ceshi"      <!-- 直接赋值,则直接写属性,然后赋值即可    -->  msg="Welcome to Your Vue.js App"     <!-- a是父组件定义的属性,需要在props属性前加冒号-->  :ceshi="a"     <!-- method是父组件定义的函数,加冒号引用此函数   -->  :hanshu="method"    />    <div ref="ceshi1">666</div>  </div></template><script>import HelloWorld from './components/HelloWorld.vue'export default {  name: 'App',  components: {    HelloWorld  },  data:function (){    return {      a:'绑定props属性'    }  },  methods:{    method(){      return '函数props绑定';    }  }}</script>

4.5minix混入技术
一些共用的东西,可以提取出来,形成js,然后供所有组件使用。具体用法如下:
定义共用js:
minix.js:

export default {    data () { return {     name: 'minix',     minixName: 'minixObj',     flag: false,     obj: {     class: 'classtest',     id: 'idtest'     } }    },    mounted() { console.log('minixMounted');    },    methods: { speak() {     console.log('this is minix'); }, getData() {     return '100'; }    }}

在组件中,使用这个js的方法:

import myMinix from './minix';  //引入公用js export default {    data () { return {     name: 'todo',     lists: [1, 2, 3, 4],     obj: {     todoclass: 'todoclasstest',     id: 'idtodotest'     } }    }, mounted() { console.log('todoMounted');    },    minixs: [myMinix], // todo.vue 中声明minix 进行混合。这样引入js中的东西这个组件都有了    methods: { speak () {     console.log('this is todo'); }, submit() {     console.log('submit'); },    }}

五、自定义事件

在组件标签上,可以自定义事件,来进行子组件向父组件数据的传递。注意:上面说的props属性是父组件向子组件传数据。这里讨论子组件向父组件传数据。

这个自定义事件如何触发呢?需要在Demo组件中去定义。如点击Demo组件中的某个按钮触发selfevent事件,就在Demo组件中写@click事件,然后在回调函数中,调用this.$emit(‘selfevent’,)函数,来写selfevent的触发时机。而selfevent的触发函数,就在绑定事件的组件中定义就行。当eventself触发后,自动执行其回调函数。
需要注意的是,在组件标签上绑定事件,即使内置事件,如@click,那么vue也会当成自定义事件去解析。如果想用内置事件,则需要用@click,navite='xxx’来绑定。
自定义事件的作用就是子组件给父组件传递信息通信用的。
因为子组件定义自定义事件的触发时机,在this.$emit中,就可以传递子组件的数据,父组件写子组件的标签时,绑定自定义事件回调函数,那么在回调函数中就可以接收到子组件传递的数据。
示例如下:
首先,子组件定义自定义事件:

<template>  <div class="hello">  <!--  使用props中定义的属性  -->    <h1>{{ msg }}</h1>    <h2>{{ceshi}}</h2>    <h3>{{hanshu()}}</h3>    <div @click="demo">点我触发自定义事件</div>  </div></template><script>export default {  name: 'HelloWorld',  props: {    msg: String,    ceshi: String,    hanshu:Function //定义函数  },  methods:{    demo(){      //触发自定义事件,并向父组件传递信息      this.$emit('selfevent',this.msg,this.ceshi);    }  }}</script>

然后在父组件中,写自定义事件的回调函数,在回调函数里,就可以接收到子组件传来的数据。

<template>  <div id="app">    <HelloWorld ref="ceshi"  msg="Welcome to Your Vue.js App"  :ceshi="a"  :hanshu="method"  @selfevent="method1"    />    <div ref="ceshi1">666</div>  </div></template><script>import HelloWorld from './components/HelloWorld.vue'export default {  name: 'App',  components: {    HelloWorld  },  data:function (){    return {      a:'绑定props属性'    }  },  methods:{    method(){      return '函数props绑定';    },    method1(a,b,c){      console.log('触发自定义事件');      console.log('接收到参数a',a);      console.log('接收到参数b',b);    }  }}</script>

由此可以看到,自定义事件就是在子组件中定义自定义事件的触发时机,并给父组件传递数据,然后在父组件定义回调函数,然后接收子组件传递的数据。

六、全局事件总线

组件间通信方式,适用于任意组件间通信。
6.1 安装全局事件总线
在main.js中,new Vue的beforeCreate函数中,安装全局事件总线

new Vue({  el:'#app',  //将app组件放入#app容器中  render: h => h(App),  beforeCreate() {      Vue.prototype.$bus=this;//安装全局事件总线,$bus就是当前应用的vm。vm中定义的属性,在各个组件中,都可以获得。上面的_proto_隐式链中获得。  }})

6.2 使用事件总线
提供数据:

<template>  <div class="hello">  <!--  使用props中定义的属性  -->    <h1>{{ msg }}</h1>    <h2>{{ceshi}}</h2>    <h3>{{hanshu()}}</h3>    <div @click="demo">点我触发自定义事件</div>  </div></template><script>export default {  name: 'HelloWorld',  props: {    msg: String,    ceshi: String,    hanshu:Function //定义函数  },  methods:{    demo(){      //触发自定义事件,并向父组件传递信息     // this.$emit('selfevent',this.msg,this.ceshi);      //全局事件总线,通过this.$bus隐式链获得vm对象上的$bus属性。      this.$bus.$emit('selfevent',this.msg,this.ceshi)    }  }}</script>

接收数据方:

methods:{    method(){      return '函数props绑定';    },    method1(a,b,c){      console.log('触发自定义事件');      console.log('接收到参数a',a);      console.log('接收到参数b',b);    },    demo(a,b){      console.log('接收到全局事件总线参数a',a);      console.log('接收到全局事件总线参数b',b);    }  },  mounted() {    console.log('vc',this.$bus);//$bus就是vm实例    //vm上绑定自定义事件,接收数据    this.$bus.$on('selfevent',this.demo);  }

在绑定事件的组件上,beforeDestory方法中,解绑事件:

 beforeDestroy() {    //对事件进行解绑    this.$bus.$off('selfevent');  }

由此可见,全局事件总线,在子组件中,还需要通过$emit方法绑定自定义事件。哪里需要接收数据,在哪里定义$bus.$on绑定自定义事件即可。

七、消息订阅与发布

在这里插入图片描述
实际用的不多,还是用Vue原生的消息事件总线多。这里主要有一个意识,可以依赖一些第三方类库,实现某些功能。

八、$nextTick函数

我们修改vue中的数据时,修改的是vue虚拟内存中的数据,而虚拟内存的数据更新到真实dom上,是有延迟的。如果想在真实dom更新后进行一些操作,那么就要用这个函数来执行回调函数了。看如下示例:
data中定义属性:

data:function (){    return {      change:'改变元素'    }  }

html元素中使用这个属性,

<div ref="change">{{change}}</div>    <div @click="demo1">点我改变元素内容</div>

单击事件函数,改变change属性的值:

demo1(){      console.log('change',this.$refs.change.innerHTML);     this.change='change-change';     this.$nextTick(function (){console.log('$nextTick',this.$refs.change.innerHTML);     })      console.log('no-$nextTick',this.$refs.change.innerHTML);    }

输出内容如下:
二、Vue脚手架工程
可见,先输出了没有调用$nextTick函数的旧值,后输出了$nextTick回调函数中改变后的值。

九、Vue发送ajax请求

用axios封装的方法进行发送。之前用的jquery的$.get等方法底层操作的是XMLHttpRequest对象,进行请求的发送。
axios底层也是这个对象,相当于重新对其进行了封装。不同框架进行了不同的封装而已。
axios的使用,可参考https://www.bbsmax.com/A/xl56nop95r/。

跨域的理解:
跨域是浏览器的同源策略做的保护机制。就是发送请求的浏览器的ip,端口号与发送的请求中的ip,端口号不一致时做的限制。需要注意的是,跨域请求是能发送出去的,服务器也能收到请求,并返回数据。但是浏览器收到数据后,会根据同源策略,报错。所以跨域是收到服务器的数据后报的跨域问题。
当后台给请求头加上一些信息后,浏览器就不会报错了,就会返回收到的数据。
vue-cli脚手架提供了一个与前端服务同域名,同端口号的后台服务,可以通过这个服务,在后台转发跨域请求,这样,也就避免了前端跨域的问题。
vue-cli之proxy的配置如下:
在vue.config.js配置文件中,配置:

module.exports = {  devServer:{    // 本地域名    host:'localhost',    // 本地端口    port:'8080',    open:true,    proxy:{ //配置跨域      // 当访问到 api 开头的接口时走下面的内容      '/api':{ // 最终想要访问的地址 target:'http://localhost:8088/resource', changeOrigin:true,//允许跨域 pathRewrite:{//在使用axios发送请求时,用api代表target的路径。但是真实的里面没有api,则在这里配置成'',就自动去掉api了   '^/api':'' }      }    }  }}

在发送请求的组件,引入axios,

import axios from "axios";

在发送请求的函数里定义axios请求:

methods:{    method(){      axios.get('api/login').then(   response=>{     console.log('返回数据',response);   },   error=>{     console.log('错误消息',error)   }      )    }  }

输出的response如下:
二、Vue脚手架工程
可见,data是服务器返回的数据,还有headers请求头信息可以拿到。
注意:在开发中,通过配置proxyTable来实现跨域,在生产环境,使用nginx解决跨域问题。前端静态资源都放到nginx下面。

十、插槽

在上面讲到的props属性中,父组件可以向子组件传递数据,但是不能传递子组件的dom结构。想要在父组件中定义子组件的dom结构,就要用到插槽了。
默认插槽:
子组件中,定义,进行占位,这个标签的意思就是让父组件定义这块的dom元素。
父组件中,在子组件标签内部,定义dom元素,就替换到子组件slot处了。
在这里插入图片描述
具名插槽:
子组件中定义多个slot标签,则需要用name做标识。父组件的子标签中,就要用slot属性指定代替的区域。
在这里插入图片描述
作用域插槽
数据在子组件中,但是展示数据的dom,要在父组件中定义,则使用作用域插槽。
在子组件slot标签,绑定数据:
在这里插入图片描述
父组件中,用scope属性定义作用域:
在这里插入图片描述