本节内容主要包括使用Vue框架过程中需要掌握的一些基本概念,以及怎么使用现有的一些开源库和组件快速创建项目。另外再附赠对状态管理、数据传递的一些方法和理解叭。总而言之,这一节开始会是与Vue紧密相关的内容啦。
Vue基本概念
首先,要快速写出来一个 Vue 项目,要先理解一些基本的概念。概念这样的东西,一个个介绍讲解会很枯燥,那既然这一节内容是快速创建一个 Vue 项目,那我们就一边讲怎么写一边介绍相关概念叭。
这里会主要以管理端这样的页面为最终效果,毕竟这是最常见也是最容易写的一类型页面。
Vue组件
本来想着从指令讲起的,不过既然上一节中介绍了数据驱动的编码思维,那我们就从数据结构设计起,所以直接开始讲 Vue 组件啦。
生命周期
既然要讲 Vue 组件,那生命周期得先了解下。经过上一节内容的讲解,我们知道在 Vue 中要渲染一块页面内容的时候,会有这么几个过程:
1). 解析语法生成 AST。
2). 根据 AST 结果,完成 data 数据初始化。
3). 根据 AST 结果和 data 数据绑定情况,生成虚拟 DOM。
4). 将虚拟 DOM 生成真正的 DOM 插入到页面中,此时页面会被渲染。
当我们绑定的数据进行更新的时候,又会产生以下这些过程:
5). 框架接收到数据变更的事件,根据数据生成新的虚拟 DOM 树。
6). 比较新旧两棵虚拟 DOM 树,得到差异。
7). 把差异应用到真正的 DOM 树上,即根据差异来更新页面内容。
当我们清空页面内容时,还有:
8). 注销实例,清空页面内容,移除绑定事件、监听器等。
所以在整个页面或是组件中,我们会有以下的一些关键的生命周期钩子:
| 生命周期钩子 | 
说明 | 
对应上述步骤 | 
| beforeCreate | 
初始化实例前,data 属性等不可获取 | 
1 之后,2 之前 | 
| created | 
实例初始化完成,此时可获取 data 里数据,无法获取 DOM | 
2 之后,3 之前 | 
| beforeMount | 
虚拟 DOM 创建完成,此时未挂载到页面中 | 
3 之后,4 之前 | 
| mounted | 
数据绑定完成,真实 DOM 已挂载到页面 | 
4 之后 | 
| beforeUpdate | 
数据更新,DOM Diff 得到差异,未更新到页面 | 
6 之后,7 之前 | 
| updated | 
数据更新,页面也已更新 | 
7 之后 | 
| beforeDestroy | 
实例销毁前 | 
8 之前 | 
| destroyed | 
实例销毁完成 | 
8 之后 | 
这些钩子有什么用呢,我们可以在某些生命周期中做一些事情,例如created事件中,可以拿到基础的数据,并根据这些数据可以开始进行后台请求了。
数据
假设我们要做一个管理端的页面,包括常见的增删查改,那会包括菜单、列表、表单这几种内容,如图:

既然要使用数据驱动的方式,那么我们先来设计这个页面的数据包括哪些。每一个都可以抽象成一组数据设计,我们一个个详细来看。
1. 菜单

如图,我们能看到,菜单列表主要包括父菜单列表,每个父菜单包括:
- 图标 icon
 
- 菜单名字 text
 
- (可选)子菜单列表 subMenus
 
所以,我们可以抽象出这么一个数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
  | const menus = [   {     text: "服务管理",      icon: "el-icon-setting",      subMenus: [{ text: "服务信息" }, { text: "新增" }]     },   {     text: "产品管理",     icon: "el-icon-menu",     subMenus: [{ text: "产品信息" }]   },   {     text: "日志信息",     icon: "el-icon-message"   } ]; 
  | 
 
2. 列表

如图,我们能看到,列表里每行内容包括:
- 日期 date
 
- 姓名 name
 
- 电话 phone
 
- 地址 address
 
我们可以先整理到这么一个数据:
1 2 3 4 5 6 
  | const tableItem = {   date: "2019-05-20",    name: "被删",    phone: "13888888888",    address: "深圳市南山区滨海大道 888 号"  }; 
  | 
 
而在列表这样的增删查改的场景下,一般还需要一个唯一标识来作为标记,这里使用 id,用最简单的方式来拷贝出 20 个数据:
1 2 3 4 5 6 7 8 9 10 11 
  | const tableData = Array(20).fill(tableItem).map((x, i) => {return {id: i + 1, ...x};}); console.log(tableData[1]);     address: "深圳市南山区滨海大道 888 号"     date: "2019-05-20"     id: 2     name: "被删"     phone: "13888888888" } */ 
  | 
 
方法
关于 Vue 的 methods 方法,如果说数据是状态机的话,那事件大概可以当成状态机的扭转。这里以列表作为举例吧,例如新增、删除、上移、下移,我们只需要处理数据就好了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 
  | export default {   data() {          return {       menus: menus,        tableData: tableData      };   },   methods: {          addTableItem(item = {}){              this.tableData.push({...item, id: this.tableData.length + 1});     },          deleteTableItem(id){              const index = this.tableData.findIndex(x => x.id === id);       this.tableData.splice(index, 1);     },          moveTableItem(id, direction){       const dataLength = this.tableData.length;              const index = this.tableData.findIndex(x => x.id === id);       switch(direction){                  case 'up':           if(index > 0) {                          const item = this.tableData.splice(index, 1)[0];             this.tableData.splice(index - 1, 0, item);           }           break;                  case 'down':           if(index < dataLength - 1) {                          const item = this.tableData.splice(index, 1)[0];             this.tableData.splice(index + 1, 0, item);           }           break;       }     }   } } 
  | 
 
当我们把数据更新了之后,Vue 会自动帮我们更新到页面里,具体是怎么实现的呢,可以参考上一节的数据绑定的实现、虚拟 DOM 的内容哈。
组件
数据和事件都写好了,接下来就轮到拼页面了。其实前端写样式是一件很蛋疼的事情,但写页面又是一件很有成就感的事情,所以为了不打击大家的学习热情,我们直接跳过学习调节样式的环节,来到组装页面的环节吧~~
组件的自我修养
首先我们理解一下,组件是什么呢,个人的理解是(右侧是举例 Vue 中类似的属性或者 API):
- 组件内维护自身的数据和状态:
data 
- 组件内维护自身的事件:
methods、生命周期钩子 
- 通过提供配置的方式,来控制展示,或者控制执行逻辑:
props 
- 通过一定的方式(事件触发/监听、API 提供),提供与外界(如父组件)通信的方式:
$emit、$on 
如何在一个页面中,抽象出某些组件出来,涉及的篇幅会很长,大家也可以参考前端抽象+配置化系列:《页面区块化与应用组件化》、《一个组件的自我修养》、《组件配置化》、《数据抽离与数据管理》。(真的很多,加油看)
一般来说,我们可以使用所见即所得的方式,例如上面的,菜单就是个组件,或者表格就是个组件,来划分。
Vue 组件
在 Vue 里,页面也好、某块内容也好,都可以定义为一个组件。而关于组件的,前面也说了会包括生命周期、数据状态、事件处理、模板样式等,基本的可以参考一下Vue-组件基础,了解一下下面的内容,避免后面直接使用组件的时候有些不了解:
Element
这套系列的教程,会直接使用 Element 组件。不要误会,没有收取广告费,是因为我们这边大家都要用 Vue + Element 啦,所以教程以自己人为最高优先级。
1. 使用 Element
首先,我们把 Element 装上,很简单:
官方教程也有教我们怎么在 Vue 里使用,也很简单,在 main.js 中写入以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 
  | import Vue from 'vue'; import ElementUI from 'element-ui';  import 'element-ui/lib/theme-chalk/index.css';  import App from './App.vue'; Vue.use(ElementUI);  new Vue({   el: '#app',   render: h => h(App) }); 
  | 
 
2. 使用 Element 组件
在官网中,我们能找到很多的组件,如图:

左侧列表里,全是 Element,接下来就是要拼成一个表单+列表的页面了。
首先我们得去偷个合适的布局,翻到布局容器 Container 这一个组件页面,我们可以看到一个理想的示例:

点开显示代码,然后尽情拷贝吧~~~鉴于上一节我们用 vue-cli 脚手架生成了个 demo,我们就用在这个 demo 里改,由于主页面内容都放在HelloWorld.vue这个文件里,我们就拷进去吧。

粘贴的时候,会发现编辑器有报错?没有比官方代码贴进来直接报错更糟糕的事情了,我们来瞧瞧是因为什么。
上一节我们讲了,浏览器里面会解析 HTML/CSS/Javascript 这三种文件,那.vue是什么鬼来的?.vue文件其实是单文件组件,就是把 HTML/CSS/Javascript 写在一个文件里,对于简单的组件来说其实是件好事情,一眼就能看完它做了什么(不过个人还是喜欢分开几个文件的方式,看个人喜好啦)。我们来看看一个.vue文件包括啥:
1 2 3 4 5 6 7 8 9 10 11 12 13 
  |   .vue 文件里,   使用 <template> 隔离 HTML,   使用 <script> 隔离 Javascript,   使用 <style> 隔离 CSS   --> <template>   <div>This will be pre-compiled</div> </template> <script src="./my-component.js"></script> <style src="./my-component.css"></style> 
  | 
 
所以,原来的示例代码里少了<template></template>,这里包裹起来就好啦:

然后打开页面,发现跟想象的差不多,除了几处需要调整:

1) Vue logo 要去掉 -> 在App.vue文件里,把<img alt="Vue logo" src="./assets/logo.png">去掉,还有 body 自带的 margin 也去掉。
2) 这些滚动条太丑了,干掉! -> 把<el-container>里的height: 500px;去掉,然后我们调整下
然后我们得到一个这个页面:

页面绑定
前面我们给页面抽象了数据和事件,现在要做的是把它们绑定到我们的页面里,我们要先来看看 Element 是怎么设置数据和配置的。
0. Vue 绑定语法
既然我们要把数据绑定到组件或是元素里,我们先了解下 Vue 中与数据绑定相关的,各位也可以参考Vue-模板语法一节内容。
数据绑定
我们先来看看数据绑定有哪些最基本的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 
  | <span>Message: {{ msg }}</span> <span>{{ msg.split('').reverse().join('') }}/span> <p v-html="rawHtml"></p> <div v-bind:id="dynamicId"></div> <div :id="dynamicId"></div> `v-bind`还可用来传参,关于 props 可以参考[Vue-Prop](https://cn.vuejs.org/v2/guide/components-props.html)一节: ``` html <template>      <my-table :data="tableData"></my-table>    </template> <script> export default {   props: {     data: {       type: Array,        default: () => {},      }   },   methods: {     someFunction(){              console.log(this.data);      }   } } </script> 
  | 
 
父子组件间的数据传递,通常通过 props 和事件进行传递(父组件通过 props 绑定数据给到子组件,通过事件监听获取子组件的数据更新),当然也可以自定义一些状态机制来传递,也可以使用Vuex、Rxjs这种状态管理的工具。
事件绑定
我们来看看,在 Vue 里是怎样进行事件绑定的:
1 2 3 4 5 6 
  | <button v-on:click="counter += 1">Add 1</button> <button @click="counter += 1">Add 1</button> <button @click="counterAddOne">Add 1</button> 
  | 
 
事件监听还能用于父子组件的事件传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 
  | <template>      <input v-model="val" />   <button @click="clickDone">done</button> </template> <script> export default {   data(){     return {       val: ''      }   },   methods: {     clickDone(){              this.$emit('done', this.val);     }   } } </script> <template>      <child-component @done="getChildData"></child-component> </template> <script> export default {   methods: {     getChildData(value){              alert(value);     }   } } </script> 
  | 
 
关于 Vue 的事件,还有很多方便的用法噢(例如过滤某个按键等),可以参考Vue-事件处理一节内容,以及Vue-自定义事件一节内容。
表单绑定
Vue 里有个很好用的指令v-model,常常用来绑定表单的值,可以参考Vue-表单输入绑定一节内容。但其实v-model也是语法糖,最终是通过前面的数据和事件绑定结合实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
  | <template>         <input :value="val" @input="updateValue" />      <input v-model="val" /> </template> <script> export default {   data(){     return {       val: ''     }   },   methods: {     updateValue(event){       this.val = event.target.value;     }   } } </script> 
  | 
 
v-model也可以自定义表单绑定,可参考《Vue2使用笔记15–自定义的表单组件》一文。
其他的,还有挺常用的一些指令(例如v-if条件、v-for遍历),可以参考条件渲染和列表渲染,当然你还可以自行开发自定义指令,可参考《Vue2使用笔记16–自定义指令》一文。
1. 菜单绑定
我们先来看看 Elmenet 里的菜单是怎么用的,可以参考Element-NavMenu导航菜单文档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
  | <el-menu :default-openeds="['1', '3']">      <el-submenu index="1">          <template slot="title"><i class="el-icon-message"></i>导航一</template>     <el-menu-item-group>              <el-menu-item index="1-1">选项1</el-menu-item>       <el-menu-item index="1-2">选项2</el-menu-item>     </el-menu-item-group>   </el-submenu>      <el-menu-item index="2">          <i class="el-icon-menu"></i>     <span slot="title">导航二</span>   </el-menu-item> </el-menu> 
  | 
 
绑定数据之后,就会变成这样啦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 
  | <el-menu :default-openeds="['0', '1']" class="el-menu-vertical-demo"    background-color="#545c64" text-color="#fff" active-text-color="#ffd04b"   >      <template v-for="menu in menus">          <el-submenu v-if="menu.subMenus && menu.subMenus.length" :index="menu.index" :key="menu.index">       <template slot="title">                  <i :class="menu.icon"></i>                           <span slot="title">{{menu.text}}</span>       </template>       <el-menu-item-group>                  <el-menu-item v-for="subMenu in menu.subMenus" :key="subMenu.index" :index="subMenu.index">{{subMenu.text}}</el-menu-item>       </el-menu-item-group>     </el-submenu>          <el-menu-item v-else :index="menu.index" :key="menu.index">              <i :class="menu.icon"></i>              <span slot="title">{{menu.text}}</span>     </el-menu-item>   </template> </el-menu> 
  | 
 
我们之前的 menus 并没有index,这里可以顺便遍历生成一下:
1 2 3 4 5 6 7 8 9 10 11 
  | menus = menus.map((x, i) => {   return {     ...x,          subMenus: (x.subMenus || []).map((y, j) => {       return { ...y, index: `${i}-${j}` };     }),          index: `${i}`   }; }); 
  | 
 
看~菜单成功生成了:

2. 列表绑定
Demo 里的列表是不带操作按钮的,我们参考Element-Table表格文档以及Button按钮文档把自定义选项加上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
  | <el-table stripe :data="tableData" style="border: 1px solid #ebebeb;border-radius: 3px;margin-top: 10px;">      <el-table-column prop="id" label="id" width="100"></el-table-column>      <el-table-column prop="date" label="日期" width="200"></el-table-column>      <el-table-column prop="name" label="姓名" width="200"></el-table-column>      <el-table-column prop="phone" label="电话" width="200"></el-table-column>      <el-table-column prop="address" label="地址"></el-table-column>      <el-table-column fixed="right" label="操作" width="300">     <template slot-scope="scope">              <el-button @click="deleteTableItem(scope.row.id)" type="danger" size="small">删除</el-button>              <el-button @click="moveTableItem(scope.row.id, 'up')" size="small">上移</el-button>       <el-button @click="moveTableItem(scope.row.id, 'down')" size="small">下移</el-button>     </template>   </el-table-column> </el-table> 
  | 
 
然后我们就顺利获得了这样一个列表:

3. 表单绑定
有列表的地方,当然也少不了表单啦那么,同样的方法,我们直接去Element-Form表单这里偷代码吧因为这里打算用弹窗的方式来装这个表单的内容,所以我们也抠了Element-Dialog对话框的代码出来~
有了前面数据设计和绑定的基础,这里可以直接给出我们的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 
  | <el-button type="primary" @click="dialogFormVisible = true;form = {};">新增</el-button> <el-dialog title="新增" :visible.sync="dialogFormVisible">   <el-form :model="form">          <el-form-item label="日期" :label-width="formLabelWidth">              <el-date-picker v-model="form.date" value-format="yyyy-MM-dd" type="date" placeholder="选择日期"></el-date-picker>     </el-form-item>     <el-form-item label="姓名" :label-width="formLabelWidth">       <el-input v-model="form.name"></el-input>     </el-form-item>     <el-form-item label="电话" :label-width="formLabelWidth">       <el-input v-model="form.phone" type="tel"></el-input>     </el-form-item>     <el-form-item label="地址" :label-width="formLabelWidth">       <el-input v-model="form.address"></el-input>     </el-form-item>   </el-form>   <div slot="footer" class="dialog-footer">          <el-button @click="dialogFormVisible = false">取 消</el-button>          <el-button type="primary" @click="dialogFormVisible = false; addTableItem(form)">确 定</el-button>   </div> </el-dialog> 
  | 
 
我们需要新增的数据变量包括:
1 2 3 4 5 6 7 8 9 
  | export default {   data() {     return {       dialogFormVisible: false,        form: {},        formLabelWidth: '120px',      };   } } 
  | 
 
Okay,我们的表单就写好了:

课后作业
其实到这里,我们已经成功地东拼西凑成一个带菜单、列表和表单的页面了,这也是我们在管理端里最常见的一种页面类型。
这个页面也有挺多可以完善的地方,例如:
- 左侧菜单可以支持收起。
 
- 列表支持修改。
 
- 列表支持批量删除。
 
- 表单支持校验手机号和其他选项不为空。
 
这些就当作课后作业来完成吧,如果很懒的你,也可以直接看最终结果:
结束语
其实前端发展到现在,已经有很多开源轮子了。所以前端开发的效率在不断提升,会让人有种我很厉害的幻觉。而常常在这样的幻觉消失之后,会发现自己除了会用工具以外,什么都没剩下了。为了避免陷入恐慌的这一天到来,我们应该沉静下来,缺啥补啥,相对于囫囵吞枣,更应该多深入理解和研究下。
  
		
		
			查看Github有更多内容噢:https://github.com/godbasin
			
			更欢迎来被删的前端游乐场边撸猫边学前端噢