本节内容主要包括使用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
更欢迎来被删的前端游乐场边撸猫边学前端噢