VUE
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。 Vue中文网址为:Vue.js - 渐进式 JavaScript 框架 | Vue.js
1. VUE概述
1.1 MVVM模式
MVVM(Model-View-ViewModel)是一种软件架构设计模式,它是一种简化用户界面的事件驱动编程方式。
Model: 数据模型,泛指后端各种业务逻辑处理和数据操控,围绕数据库系统展开的 JavaScript对象。 View: 视图层,主要由HTML和CSS来构成的用户界面,为了更方便地展现ViewModel或者Model层的数据。 ViewModel:连接视图和数据的中间件, 由前端开发人员组织生成和维护的视图数据层。前端开发者从后端获取得到Model数据进行转换出来,二次封装成符合View层使用预期的视图数据模型。视图状态和行为都封装在这里。这样的封装使得ViewModel可以完整地去描述View层。 Vue.js就是MVVM中的ViewModel层的实现者。
在MVVM架构中,是不允许数据和视图直接通信的,只能通过ViewModel来通信,而ViewModel就是定义了一个Observer观察者。ViewModel是连接View和Model的中间件。
-
ViewModel能够观察到数据的变化,并对视图对应的内容进行更新。
-
ViewModel能够监听到视图的变化,并能够通知数据发生变化。
到此,我们就明白了,Vue.js就是一个MVVM的实现者,它的核心就是实现了DOM监听与数据绑定。
1.2 Vue的特点
-
轻量级,体积小。Vue.js压缩后只有30多kb(Angular压缩后56kb以上,React压缩后44kb以上)
-
更适合移动端,比如移动端的Touch事件
-
易上手,文档齐全
-
吸取了Angular(模块化)和React(虚拟DOM)的长处,并拥有自己独特的功能,如计算属性
-
开源,社区活跃度高
1.3 Vue的核心
-
数据驱动
Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 Javascript 库,专注于View 层。它让开发者省去了操作DOM的过程,只需要改变数据。Vue会通过Dircetives指令,对DOM做一层封装,当数据发生改变会通知指令去修改对应的DOM,数据驱动DOM变化,DOM是数据的一种自然映射。
Vue还会对操作进行监听,当视图发生改变时,vue监听到这些变化,从而改变数据,这样就形成了数据的双向绑定。
Vue是一种MVVM框架。而DOM是数据的一个种自然映射。传统的模式是通过Ajax请求从model请求数据,然后手动的触发DOM传入数据修改页面。Vue中,Directives对view进行了封装,当model里的数据发生变化是,Vue就会通过Directives指令去修改DOM。同时也通过DOM Listener实现对视图view的监听,当DOM改变时,就会被监听到,实现model的改变,实现数据的双向绑定。
当你把一个普通的Javascript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,并将这些属性全部转换为getter&setter,期间使用了Object.defineProperty,这是ES5中一个无法shim的特性,这也是Vue不支持IE8以及更低版本浏览器。
这些getter&setter对用户来说是不可见的,但在内部他们让Vue追踪依赖,在属性被访问和修改时通知变化。注意,浏览器控制台在打印数据对象时getter&setter的格式化不同,所以你可能需要安装vue-devtools来获取更友好的检查接口。
每个组件实例都有相应的watcher实例对象,它会在组件渲染过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
-
组件化
页面上每个独立的可交互区域都视为一个组件。
每个组件对应一个工程目录,组件所属的各种资源在这个目录下就近维护。
页面不过是组件的容器,组件可以嵌套自由组合(复用)形成完整的页面。
2. 第一个Vue应用程序
2.1 下载地址
<!-- 开发环境版本,包含了有帮助的命令行警告 初学者使用--> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
或者:
<!-- 生产环境版本,优化了尺寸和速度 --> <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
2.2 建立项目
编写html页面代码如下
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>第一个Vue</title> <!-- 开发环境版本,包含了有帮助的命令行警告 初学者使用--> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> </head> <body> <div id="app"> <!-- View层 --> {{msg}} <!-- 在下面Vue代码中定义了msg变量 --> </div> <script> //声明式渲染 var first = new Vue({ //View Model层 el: "#app", //挂载#app页面元素 //View Model层中的 View data:{ //View Model层中的model msg:"hello first vue" //定义挂载元素中可以使用的变量 } }) </script> </body> </html>
运行效果:
2.3 Vue实例生命周期
下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
创建期间的生命周期函数:
-
beforeCreate:实例刚在内存中被创建之前,还没有初始化好 data 和 methods 属性。
-
created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始编译模板。
-
beforeMount:此时已经完成了模板的编译,但是还没有渲染到页面中。
-
mounted:此时,已经将编译好的模板渲染到到了页面并在的容器中显示给用户。
运行期间的生命周期函数:
-
beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点。
-
updated:实例更新完毕之后调用此函数,此时 data 中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
销毁期间的生命周期函数:
-
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
-
destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
注意:这里有一些钩子,其实也是函数,可以这样理解:生命周期钩子 = 生命周期函数 = 生命周期事件。
3. 条件渲染
-
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
-
v-else 元素必须紧跟在带
v-if
或者v-else-if
的元素的后面,否则它将不会被识别。 -
v-else-if,顾名思义,充当
v-if
的 “else-if 块”,可以连续使用 。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>条件渲染</title> <!-- 开发环境版本,包含了有帮助的命令行警告 初学者使用--> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> </head> <body> <div id="app"> <span v-if="isShow">可以看见Vue的v-if</span> <span v-else>--什么也没有--</span> </div> <script> //声明式渲染 var first = new Vue({ el: "#app", //挂载#app页面元素 data:{ isShow: true //定义挂载元素中可以使用的变量 } }) </script> <div id="appifelse"> <div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div> </div> <script> //声明式渲染 var first = new Vue({ el: "#appifelse", //挂载#app页面元素 data:{ type: "C" //定义挂载元素中可以使用的变量 } }) </script> </body> </html>
运行结果如下:
-
在
template
元素上使用v-if
条件渲染分组因为
v-if
是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个元素当做不可见的包裹元素,并在上面使用v-if
。最终的渲染结果将不包含template
元素。<div id="apptemp"> <template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template> <template v-else> <h1>noTitle</h1> <p>Paragraph 3</p> <p>Paragraph 4</p> </template> </div> <script> //声明式渲染 var first = new Vue({ el: "#apptemp", //挂载#apptemp页面元素 data:{ ok: false //定义挂载元素中可以使用的变量 } }) </script>
4. 列表渲染
4.1 v-for使用数组
可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。
<div id="app"> <ul> <li v-for="item in items" :key="item.message"> {{ item.message }} </li> </ul> </div> <script> //声明式渲染 var first = new Vue({ el: "#app", //挂载#app页面元素 data:{ items: [ { message: 'Foo' }, { message: 'Bar' } ] } }) </script>
运行如下:
在 v-for
块中,我们可以访问所有父作用域的 property。v-for
还支持一个可选的第二个参数,即当前项的索引。。v-for
指令需要使用 (item,index) in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。
<div id="appIndex"> <ul> <li v-for="(item,index) in items" :key="item.message"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul> </div> <script> //声明式渲染 var first = new Vue({ el: "#appIndex", //挂载#app页面元素 data:{ parentMessage: 'Parent', items: [ { message: 'Foo' }, { message: 'Bar' } ] } }) </script>
运行结果:
4.2 v-for使用对象
可以使用 v-for 来遍历一个对象的 property 。。v-for
指令需要使用 value in objet
形式的特殊语法,其中 object
是源数据中的对象,而 value
则是遍历对象中属性的别名。
<div id="appObject"> <ul> <li v-for="value in object"> {{ value }} </li> </ul> </div> <script> //声明式渲染 var first = new Vue({ el: "#appObject", //挂载#app页面元素 data: { object: { title: 'How to do lists in Vue', author: 'Jane Doe', publishedAt: '2016-04-10' } } }) </script>
运行结果如下:
当然,如果对象在数组里面,v-for
指令需要使用 todo in todos
形式的特殊语法,其中 todos
是源数据数组,而 todo
则是被迭代的数组元素的别名。
<div id="app-4"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> </div> <script> var app4 = new Vue({ el: '#app-4', data: { todos: [ { text: '学习 JavaScript' }, { text: '学习 Vue' }, { text: '整个牛项目' } ] } }) </script>
5. 事件处理
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。v-on
还可以接收一个需要调用的方法名称 ,该方法在绑定的 Vue 实例中定义。比如我们用 v-on
指令添加一个事件监听器 :
<div id="appevent"> <button v-on:click="testClick">点击事件测试</button> <!-- v-on:click="方法名" --> </div> <!-- 该方法名在Vue实例中methods定义 --> <script> var app4 = new Vue({ el: '#appevent', methods:{ /* Vue实例中定义的方法 */ testClick:function(){ alert("点击事件"); } } }) </script>
在看一个实现内容反转的例子:
<div id="appReverse"> <p>{{message}}</p> <button v-on:click="reverseMessage">反转文本</button> </div> <script> var app5 = new Vue({ el: '#appReverse', data: { message: 'Hello Vue.js!' }, methods: { reverseMessage: function () { this.message = this.message.split('').reverse().join('') } } }) </script>
注意在 reverseMessage
方法中,我们更新了应用的状态,但没有触碰 DOM——所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可。
6. Axios异步通信
Axios是一个基于 promise 的 HTTP 库,开源的可以用在浏览器端和NodeJS
的异步通信框架,主要作用就是实现AJAX异步通信,特点如下:
-
从浏览器中创建
XMLHttpRequests
-
从
node.js
创建http请求 -
支持
Promise
API -
拦截请求和响应
-
转换请求数据和响应数据
-
取消请求
-
自动转换 JSON 数据
-
客户端支持防御
Github:GitHub - axios/axios: Promise based HTTP client for the browser and node.js
6.1 第一个Axios应用
6.1.1 创建JSON测试数据
由于接口大部分都是采用JSON格式,可以先在项目中创建一个data.json文件模拟JSON数据,内容如下:
{ "message": "ok", "num": "JT0004301991791", "ischeck": "0", "com": "jtexpress", "status": "200", "data": [ { "time": "2021-12-14 21:08:34", "context": "【上海市】快件离开【上海浦西转运中心】已发往【杭州转运中心】", "ftime": "2021-12-14 21:08:34", "areaCode": "CN310118000000", "areaName": "上海,上海,青浦区", "status": "干线", "location": "上海 上海市 青浦区", "areaCenter": "121.124178,31.150681", "areaPinYin": "qing pu qu", "statusCode": "1002" }, { "time": "2021-12-14 20:54:22", "context": "【上海市】 快件到达【上海浦西转运中心】", "ftime": "2021-12-14 20:54:22", "areaCode": "CN310118000000", "areaName": "上海,上海,青浦区", "status": "干线", "location": "上海 上海市 青浦区", "areaCenter": "121.124178,31.150681", "areaPinYin": "qing pu qu", "statusCode": "1002" }, { "time": "2021-12-14 17:25:58", "context": "【上海市】快件离开【上海杨浦黄兴路网点】已发往【上海浦西转运中心】", "ftime": "2021-12-14 17:25:58", "areaCode": "CN310110000000", "areaName": "上海,上海,杨浦区", "status": "干线", "location": "上海 上海市 杨浦区", "areaCenter": "121.526077,31.259541", "areaPinYin": "yang pu qu", "statusCode": "1002" }, { "time": "2021-12-14 09:03:58", "context": "【上海市】【上海杨浦黄兴路网点】已取件。", "ftime": "2021-12-14 09:03:58", "areaCode": "CN310110000000", "areaName": "上海,上海,杨浦区", "status": "揽收", "location": "上海 上海市 杨浦区", "areaCenter": "121.526077,31.259541", "areaPinYin": "yang pu qu", "statusCode": "1" } ], "state": "5", "condition": "00", "routeInfo": { "from": { "number": "CN310110000000", "name": "上海,上海,杨浦区" }, "cur": { "number": "CN330102000000", "name": "浙江,杭州市,上城区" }, "to": null }, "isLoop": false }
6.1.2 引入Axios
<!-- 引入Axios框架 --> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <div id="appAxs"> 订单号:{{info.num}} <br/> 消息:{{info.message}} </div> <script> //Vue实例 var app4 = new Vue({ el: '#appAxs', data(){ //data是一个函数 return { info:{ message:'测试1', num:'测试2' } } } }) </script>
6.1.3 mounted函数发送请求
<div id="appAxs"> 订单号:{{info.num}} <br/> 消息:{{info.message}} <br/> </div> <script> var app4 = new Vue({ el: '#appAxs', data( ){ return { info:{ message:'测试1', num:'测试2' } } }, mounted( ){ //alert(11); axios.get("data.json") .then(response => this.info = response.data) // } }) </script>
再试试读取一下JSON格式中的数组:
<div id="appAxs"> 订单号:{{info.num}} <br/> 消息:{{info.num}} <br/> <!-- 增加 v-for 遍历数组 --> <ul> <li v-for="his in info.data" > {{his.time}} {{his.context}} </li> </ul> </div> <script> var app4 = new Vue({ el: '#appAxs', data(){ return { info:{ message:'测试1', num:'测试2', data:[] //新增JSON中的数组属性 } } }, mounted(){ //alert(11); axios.get("data.json") .then(response => this.info = response.data) } }) </script>
7. 表单输入绑定
Vue是个MVVM框架,具有数据双向绑定的特点,即当数据发生变化时,视图也会发生变化。反之当视图发生变化时,数据也会同步变化。但这一特定时对于UI控件来说的,非UI控件并不会涉及到数据双向绑定。
在Vue中,双向数据绑定对于我们处理表单来说非常容易。 可以用 v-model
指令在表单 input
、textarea
及 select
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
注意:v-model
会忽略所有表单元素的 value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
-
text 和 textarea 元素使用
value
property 和input
事件; -
checkbox 和 radio 使用
checked
property 和change
事件; -
select 字段将
value
作为 prop 并将change
作为事件。
对于需要使用输入法(如中文、日文、韩文等) 的语言,你会发现 v-model
不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input
事件。
7.1 文本
双向绑定的文本框信息:
<div id="apptext"> <input v-model="message" placeholder="edit me"> <p>Message is: {{ message }}</p> <span>多行文本:</span> <p style="white-space: pre-line;">{{ multiplemessage }}</p> <br> <textarea v-model="multiplemessage" placeholder="add multiple lines"></textarea> </div> <script> var app4 = new Vue({ el: '#apptext', data:{ message : "", multiplemessage : "" } }) </script>
使用v-model
绑定message以及multiplemessage,在视图和数据模型层同步输入的内容。
7.2 复选框
单个复选框,绑定到布尔值
<div id="appCheckBox"> <input type="checkbox" id="checkbox" v-model="checkState"> <label for="checkbox">{{ checkState }}</label> </div> <script> var app4 = new Vue({ el: '#appCheckBox', data:{ checkState : "" } }) </script>
多个复选框,绑定到同一数组
<div id="appMulCheckBox"> <input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> <br> <span>Checked names: {{ checkedNames }}</span> </div> <script> var app4 = new Vue({ el: '#appMulCheckBox', data:{ checkedNames : [] //必须是数组 } }) </script>
7.3 单选框
<div id="appRadio"> <input type="radio" id="one" value="One" v-model="picked"> <label for="one">One</label> <br> <input type="radio" id="two" value="Two" v-model="picked"> <label for="two">Two</label> <br> <span>Picked: {{ picked }}</span> </div> <script> var app4 = new Vue({ el: '#appRadio', data:{ picked : "" } }) </script>
7.4 选择框
单选时:
<div id="appSelect"> <select v-model="selectValue"> <option disabled value="">请选择</option> <option value="AA">A</option> <option value="BB">B</option> <option value="CC">C</option> </select> <span>Selected: {{ selectValue }}</span> </div> <script> var app4 = new Vue({ el: '#appSelect', data:{ selectValue : "" } }) </script>
注意: 如果 v-model
表达式的初始值未能匹配任何选项,select
元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项 。
多选时绑定到一个数组:
<div id="appMulSelect"> <select v-model="selectMul" multiple style="width: 50px;"> <option disabled value="">请选择</option> <option value="AA">A</option> <option value="BB">B</option> <option value="CC">C</option> </select> <span>Selected: {{ selectMul }}</span> </div> <script> var app4 = new Vue({ el: '#appMulSelect', data:{ selectMul : [] } }) </script>
用v-for
渲染一个动态选项:
<div id="appForSelect"> <select v-model="selectFor" style="width: 50px;"> <option disabled value="">请选择</option> <option v-for="option in options" v-bind:value="option.value"> {{ option.text }} </option> </select> <span>Selected: {{ selectFor }}</span> </div> <script> var app4 = new Vue({ el: '#appForSelect', data:{ selectFor : "B2", //默认选中 options: [ { text: 'One', value: 'A1' }, { text: 'Two', value: 'B2' }, { text: 'Three', value: 'C3' }, { text: 'four', value: 'D4' } ] } }) </script>
8. 组件
组件是可复用的 Vue 实例,且带有一个名字,我们把这个组件作为自定义元素来使用。 通常一个应用会以一棵嵌套的组件树的形式来组织,也就是说组件中可以嵌套子组件,子组件中也可以嵌套子组件: 组件是可复用的 Vue 实例,所以它们与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
8.1 注册组件
这里我们演示一种注册组件的方式,在实际开发采用的是vue-cli
创建.vue
模板文件的方式。 为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 Vue.component
全局注册的,比如注册一个自定义li组件:
Vue.component("my-first-li",{ template:"<li>Hello Vue-component</li>" })
然后在页面使用时,就可以在ul
标签中使用了:
<div id="app"> <ul> <my-first-li></my-first-li> <!-- 自定义组件代替li --> </ul> </div>
完整代码如下:
<div id="app"> <ul> <my-first-li></my-first-li> <!-- 自定义组件代替li --> </ul> </div> <script> //注册一个组件 Vue.component("my-first-li",{ template:"<li>Hello Vue-component</li>" }) //声明式渲染 var first = new Vue({ el: "#app", //挂载#app页面元素 }) </script>
8.2 组件数据绑定
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。为了给组件传递一个数据,我们可以用一个 props
选项将其包含在该组件可接受的 prop 列表中,再使用v-bind
绑定该数据。
比如要使用自定义列表展示如下data
中的数组 :
var first = new Vue({ el: "#app", //挂载#app页面元素 data:{ names:["孙权","曹操","刘备"] } })
自定义组件中需要使用v-for
来循环渲染:
<div id="app"> <ul> <my-first-li v-for="str in names">{{str}}</my-first-li> </ul> </div>
这样代码看起来并没有什么错误,但运行效果却并没有显示数组names中的数据,这是因为my-first-li
这个自定义组件的显示内容是由注册组件中的template
定义的。
Vue.component("my-first-li",{ template:"<li>Hello Vue-component</li>" //组件的显示内容由这里决定 })
因此我们需要改变自定义组件中的显示内容:
Vue.component("my-first-li",{ template:"<li>{{x_data}}</li>" })
此时组件中的x_data
需要两步操作
-
将组件上的自定义attribute即
x_data
使用props
选项将其包含在该组件可接受的 prop 列表中Vue.component("my-first-li",{ props:["x_data"] template:"<li>{{x_data}}</li>" })
-
在html上使用时通过
v-bind:attribute
绑定运行时的数据<my-first-li v-for="str in names" v-bind:x_data="str"</my-first-li>
完整代码如下:
<div id="app"> <ul> <my-first-li v-for="strin names" v-bind:x_data="str"></my-first-li> </ul> </div> <script> //注册一个组件 Vue.component("my-first-li",{ props:["x_data"], template:"<li>{{x_data}}</li>" }) //声明式渲染 var first = new Vue({ el: "#app", //挂载#app页面元素 data:{ names:["孙权","曹操","刘备"] } }) </script>
运行效果如下:
如果需要下标,我们再添加一个自定义属性x_ind
并使用v-bind:attr
绑定:
<div id="app"> <ul> <my-first-li v-for="(str,index) in names" v-bind:x_data="str" v-bind:x_ind="index"> </my-first-li> </ul> </div> <script> //注册一个组件 Vue.component("my-first-li",{ props:["x_data","x_ind"], template:"<li>{{x_ind}}-{{x_data}}</li>" }) //声明式渲染 var first = new Vue({ el: "#app", //挂载#app页面元素 data:{ names:["孙权","曹操","刘备"] } }) </script>
最终运行效果如下:
8.3 插槽
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 slot
元素作为承载分发内容的出口。 也可以这样理解:插槽就是子组件中的提供给父组件使用的一个占位符,用slot
元素表示,父组件可以使用任何代码(html,组件等)替换子组件slot
的位置。
比如我们定义一个组件my-second
,内置一个插槽slot
占位,名为abcd
:
<div id="app"> <my-second></my-second> </div> <script> //组件 Vue.component("my-second",{ template:"<div>" +"<h1>看看今天的天气</h1>" +"<slot name='abcd'></slot>" +"</div>" //上面的写法可读性比较强,也可更多地使用下面的写法: //template:"<div><h1>看看今天的天气</h1><slot name='abcd'></slot></div>" }) </script>
此时我们对插槽abcd
并没有填充内容,运行后只显示“看看今天的天气“。
下面我们定义两个不同的组件sub-one
和sub-two
分别展示两种天气:
<script> //子组件1 Vue.component("sub-one",{ template :"<div style='color:red'>5~17度<br/>暴雨<br/>东北风7级</div>" }) //子组件2 Vue.component("sub-two",{ template :"<div style='color:green'>10~25度<br/>多云转晴<br/>东北风小于3级</div>" }) </script>
然后子组件填入指定name的插槽中,就可以了:
<div id="app"> <my-second> <sub-one slot="abcd"></sub-one> <!-- 可切换成观察运行效果 <sub-two slot="abcd"></sub-two> --> </my-second> </div>
效果如下:
如果需要传递数据,还是使用Vue实例的data
和标签中的v-bind:attr
来进行绑定,例如我们定义一个sub-three
来进行天气各项数据的传递:
<div id="app"> <my-second> <sub-three slot="abcd" v-bind:clr="color" v-bind:tpr="temperature" v-bind:wth="weather" v-bind:wfc="wind_force" /> </my-second> </div> <script> //组件 Vue.component("my-second",{ template:"<div>" +"<h1>看看今天的天气2</h1>" +"<slot></slot>" +"</div>" //template:"<div><h1>看看今天的天气</h1><slot name='abcd'></slot></div>" }) //子组件3 Vue.component("sub-three",{ props:["clr","tpr","wth","wfc"], template :"<div :style='clr'>{{tpr}}度<br/>{{wth}}<br/>{{wfc}}</div>" }) //声明式渲染 var second = new Vue({ el: "#app", //挂载#app页面元素 data:{ color:{"color":"orange"}, temperature: "8~20", weather: "小雨", wind_force:"东北风小于5级" } }) </script>
其中:style
是Vue中利用样式对象color:{"color":"orange"}
渲染html样式的方法,当前代码运行效果如下:
这种有name属性的插槽也叫具名插槽,如果只有一个插槽的话就可以不添加name属性,叫做默认插槽,大家可以自行测试,代码如下:
<div id="app"> <my-second> <sub-two></sub-two> <!-- 只有一个插槽填入时不需要指定插槽name --> </my-second> </div> <script> //组件 Vue.component("my-second",{ template:"<div>" +"<h1>看看今天的天气</h1>" +"<slot></slot>" //定义时去掉name属性 +"</div>" }) //子组件1 Vue.component("sub-one",{ template :"<div style='color:red'>5~17度<br/>暴雨<br/>东北风7级</div>" }) //子组件2 Vue.component("sub-two",{ template :"<div style='color:green'>10~25度<br/>多云转晴<br/>东北风小于3级</div>" }) //子组件3 Vue.component("sub-three",{ props:["clr","tpr","wth","wfc"], template :"<div :style='clr'>{{tpr}}度<br/>{{wth}}<br/>{{wfc}}</div>" }) //声明式渲染 var second = new Vue({ el: "#app", //挂载#app页面元素 data:{ color:{"color":"orange"}, temperature: "8~20", weather: "小雨", wind_force:"东北风小于5级" } }) </script>
8.4 自定义事件
自定义事件主要用来解决子组件和父组件数据传递,触发的事件名需要完全匹配监听这个事件所用的名称。即子组件如果要触发一个cameCase
名字的事件:
this.$emit('myEvent')
则监听这个名字的事件名要完全一致:
<sub-component v-on:myEvent="fatherEvent"></sub-component>
比如下面这个例子:
<div id="app"> <my-second> <sub-one v-bind:clr="color" v-on:facherbig="big" ></sub-one> </my-second> </div> <script> //组件 Vue.component("my-second",{ template:"<div>" +"<h1>看看今天的天气</h1>" +"<slot></slot>" +"</div>" }) //子组件1 Vue.component("sub-one",{ props:["clr"], template :"<div :style='clr' @mouseover='subover'>5~17度<br/>暴雨<br/>东北风7级</div>", methods:{ subover : function(){ console.log(1); this.$emit('facherbig'); } } }) //声明式渲染 var second = new Vue({ el: "#app", //挂载#app页面元素 data:{ color:{"color":"orange"}, temperature: "8~20", weather: "小雨", wind_force:"东北风小于5级" }, methods:{ big : function( ){ //console.log( 2 ); this.color={"font-size":"20px"} } } }) </script>
代码的18行@mouseover='subover'
就是子组件的事件,也可以写成v-on:mouseover='subover'
,在子组件的定义中第20行就是该方法的执行代码;其中第22行this.$emit('facherbig')
在子组件嵌入插槽时绑定监听到Vue实例的big
方法,在第3行v-on:fatherbig="big"
这里明确了该子组件事件调用Vue实例中的big
方法。运行结果大家可以通过观察控制台就会发现先执行的时子组件的subover
方法,然后才是父组件的big
方法。这就是自定义事件的基本用法。
9. 计算属性
计算属性是用来声明式的使用其他数据算出一个新数据,并能把新数据缓存下来,当其依赖数据发生改变时,它会自动更新缓存的数据,这就极大的提高了我们程序的性能。
计算属性通常声明Vue在computed
中,声明在methods
中也可以但没有缓存功能。比如计算小计时:
<div id="app"> <p>{{testTotal()}}</p> <p>{{myTotal}}</p> <hr/> <p>{{testTotal()}}</p> <p>{{myTotal}}</p> </div> <script> //声明式渲染 var first = new Vue({ el: "#app", //挂载#app页面元素 data:{ count: 10, price: 5.5 }, methods:{ testTotal( ){ console.log("--testTotal--"); return this.count * this.price; //结果没有缓存,每次都要计算 } }, computed:{ myTotal( ){ console.log("--myTotal--"); return this.count * this.price; //结果有缓存 } } }) </script>
注意计算属性和方法的区别,一是计算属性调用时不需要小括号,而传统方法一定是需要小括号的;二是计算属性第一次计算后会缓存起来,以后调用时不再计算直接使用缓存中的值。因此上面代码在执行时,控制台会输出两次--testTotal--
,--myTotal--
只会输出一次。只有当依赖的属性值发生改变时才会再次计算并缓存起来,如上个例子中的this.count
或this.price
有一个发生了变化,会再输出一次--myTotal--
。
10. VScode安装
(1)下载安装 :Visual Studio Code - Code Editing. Redefined 选择操作系统版本,直接下载安装
安装时下面的选项其实可以都选上
(2)安装中文插件包
安装好后会提示:
如果这一步忽略了的话,还可以点击插件安装:
10.1 创建项目
(1)在磁盘上建立项目文件夹test01,然后右键选择 “通过Code打开”,然后进入VScode自己创建文件夹和文件
(2)在磁盘上建立项目文件夹test01,然后在文件夹地址栏输入cmd,然后进入VScode自己创建文件夹和文件
编写html可以使用!默认模板。
10.2 主题和插件
我们可以选择自己喜欢的主题和风格:
还有文件图标主题也可以选择和删除
下面我们看看可以安装的插件:
(1)Auto Rename Tag 自动对称标签,安装好就可以使用
<!-- 修改标签头自动更新标签尾 --> <divcccc></divcccc> <divaaaa></divaaaa>
(2)Prettier 格式化标签,安装好需要设置:
设置如下:
点击右下角,弹出窗口中选择设置,进入文本编辑器面板,选择 Format On Save :
首次使用时在页面点击右键,选择 "使用...格式化文档":
弹出页面上选择:“配置默认格式化程序”,然后再选择 Prettier ,配置完毕,保存页面时,页面上的 html js 和 css 都可以格式化。
(3)Live Server节点应用程序,安装好就可以在页面右键弹出菜单中使用。
(4)Vetur 组件高亮显示代码插件,安装好项目中 .vue 文件会有代码着色,否则全是白色代码:
后期如果代码有红色波浪线,可以点击上图中的小齿轮,点击扩展设置,下拉到最底下,去掉5个validation :
(5)Emmet语法 ,大家可以参考官网 Cheat Sheet ,提高代码效率。
11. Vue-CLI
vue-cli(俗称:vue 脚手架)是 vue 官方提供的、快速生成 vue 工程化项目的工具。Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:
-
通过
@vue/cli
实现的交互式的项目脚手架。 -
通过
@vue/cli
+@vue/cli-service-global
实现的零配置原型开发。 -
一个运行时依赖 (
@vue/cli-service
),该依赖:
-
可升级;
-
基于 webpack 构建,并带有合理的默认配置;
-
可以通过项目内的配置文件进行配置;
-
可以通过插件进行扩展。
-
-
一个丰富的官方插件集合,集成了前端生态中最好的工具。
-
一套完全图形化的创建和管理 Vue.js 项目的用户界面。
11.1 使用步骤
(1)安装Nodejs ,网址:下载 | Node.js 中文网
默认都是next,碰到这里勾选即可,安装完成后检测,两个版本都能正常打印出来就代表安装完成。
如果有同学碰见报错 :npm WARN config global --global
, --local
are deprecated 的情况,进入nodejs安装目录,找到npm和npm.cmd修改两个文件中的 prefix -g 为 prefix --location=global 即可。初次打开可能没有修改权限,管理员授权在属性里面改一下就行了。
(2)安装 Vue-Cli ,打开 cmd ,输入下列命令:
#设置淘宝镜像下载 npm config set registry https://registry.npm.taobao.org #安装vue脚手架 npm install -g @vue/cli
等待安装成功后,输入 vue -V检测版本:
(3) 使用脚手架创建项目:
建立一个工作目录test02,右键"通过Code打开"进入VScode界面,在test02下新建项目hello01,hello01上右键''在集成终端中打开"进入终端界面:
这里其实和 cmd 一样,接下来输入创建项目的命令 :
vue create hello01
弹出选择项目 vue 版本2还是3,按下回车,就开始创建项目了。
创建完成之后如图:
创建项目时,有碰到 无法加载文件" C:\Users\86136\AppData\Roaming\npm\browserify.ps1,因为在此系统上禁止运行脚本。有关详细信息, 请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies" 这样的错误请参照下述步骤解除:
(4)运行脚手架项目:
在终端界面,进入项目目录,输入指令运行:
#进入项目目录 cd hello01 #运行 npm run serve
这里对应的是项目中的 package.json 中 scripts 中的 serve 项,脚手架自己提供的一款服务器:
编译完成后,点击链接即可显示界面:
如果要终止项目,在终端界面里输入 Ctrl + C 就好了。
也可以使用 vue ui 来创建项目,在终端界面输入 vue ui ,点击 链接即可进入:
vue ui
(5)项目打包
Vue-CLI打包后的目录在 dist 中,使用在hello01中使用命令 npm run build即可。
#如果没有进入hell01项目,先进入 #cd hello01 #打包到dist中, npm run build
11.2 项目结构说明
node_modules 通过 npm 下载的项目中使用的依赖包 public 存放网站标签favicon.ico
和index
首页,打包时打包到dist
文件夹下
src 存放业务相关的代码和资源 src -> assets 存放图片之类的资源文件,这个目录中放的都是想被 webpack 的各种 loader 当作模块处理的文件 src -> components 存放项目中想被复用的公共 UI 组件 src -> App.vue 项目根组件 src -> main.js 项目打包入口文件
static 存放图片之类的资源文件,这个目录中的文件打包时会被 CopyWebpackPlugin 插件复制到出口根目录
.gitIgnore Git管理需要忽略的文件
babel.config.js ES6模块化转换文件
jsconfig.json 项目工具文件,提供了大量能使我们快速便捷提高代码效率的方法
package.json 记录当前项目所依赖模块的版本信息,更新模块时锁定模块的大版本号(版本号的第一位),不能锁定后面的小版本
package-lock.json 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题
vue.config.js 可选的配置文件,其实也可以使用package.json中的vue字段,但我们还是单独将 vue 的配置文件写在这里。
//vue.config.js const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, //改一下项目启动端口 devServer : { port: 9000 } })
main.js 入口文件:
import Vue from 'vue' //引入node_modules中的依赖 import App from './App.vue' //引入同目录下的App.vue模块 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示减少运行时开销 new Vue({ //创建Vue实例,没有el时的创建格式 render: h => h(App), //渲染App代表的模块(同目录下的App.vue模块) }).$mount('#app') //使用$mount函数挂载到容器#app中(public/index.htmls)
App.vue 单文件组件:
一个vue页面通常由三部分组成:模板(template)、js(script)、样式(style),另外当VUE文件中代码都是白色时,可以通过安装vetur插件来解决问题:
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> //导入组件(./components/HelloWorld.vue) import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', //注册组件 components: { HelloWorld } } </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>
-
template : 模板只能包含一个父节点,也就是说顶层的div只能有一个
-
js : 通常用es6来写,用export default导出,其下面可以包含数据data,生命周期(mounted等),方法(methods)等
-
style : 样式默认是影响全局的,如需定义作用域只在该组件下起作用,需在标签上加scoped,<style scoped><\/style>。如要引入外部 css文件,首先需给项目安装css-loader依赖包,打开cmd,进入项目目录,输入npm install css-loader,回车。安装完成后,就可以在style标签下import所需的css文件,例如:
<style> import './assets/css/public.css' </style>
11.3 ES6模块化
ES6 引入了模块化,浏览器端和服务器端都支持的规范,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。之前的规范都只有一端支持(AMD浏览器端模块化规范,CommonJS服务器端模块化规范)。
在ES6的模块系统中,每个JS文件可以理解为一个模块,模块代码以严格模式执行,所以模块中的变量、函数不会添加全局作用域中。
ES6 的模块化分为导出(export) @与导入(import)两个模块。常见的导入导出方式如下:
可以将需要导出的变量放入一个对象中,然后通过默认暴露进行导出,我们在src下建立一个test1.js,在src的main.js中引入:
/*** test1.js **********导出 test1.js *****************/ export default { myFn(){ return "默认导出一个方法" }, myName:"hp" } /*** main.js **********引入 test1.js *****************/ import myObj from "./test1"; console.log(myObj.myFn(),myObj.myName);//默认导出一个方法 hp
可以使用名称导出,然后通过名称默认导入:
/*** test1.js **********导出 test1.js *****************/ let myName = "hp" let myFun = function(){ return "用名称导出一个方法" } export { myName, myFn } /*** index.js **********引入 test1.js *****************/ import {myName,myFn} from "./test1"; console.log(myFn(),myName);//使用名称导出一个方法 hp
还可以使用as别名导出和导入:
/*** test1.js **********导出 test1.js *****************/ let myName = "hp" let myFn = function(){ return "用名称导出一个方法" } export { myName as name, myFn as fn } /*** index.js **********引入 test1.js *****************/ import {name,fn} from "./test1.js"; console.log(fn(),name);//使用名称导出一个方法 hp /* 通过*来批量接收,用as来指定接收的名字 */ /*** index.js **********引入 test.js *****************/ import {name,fn} from "./test1"; console.log(fn(),name);//使用名称导出一个方法 hp
如果只想执行模块的代码,只需编写
import "./test2";
11.4 路由
由于Vue在开发时对路由支持的不足,于是官方补充了vue-router插件。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,实际上就是组件的切换。路由就是SPA(单页应用)的路径管理器。再通俗的说,vue-router就是我们WebApp的链接路径管理系统。
安装
我们新建一个项目:hello02,然后进入该项目,添加router插件:
#进入项目 cd hello02 #安装路由 @3指安装v3版本 npm install vue-router@3
准备页面
在src下创建一个views文件夹,新建3个模块,分别为movie,music,tv:
<!-- 其他三个都一样 ,只是频道汉字不同 --> <template> <div>电影频道</div> </template> <script> export default { } </script> <style> </style>
配置路由
在要使用的项目中配置路由,在src下建立目router,建立配置文件index.js,在里面配置:
import Vue from 'vue' //导入路由插件 import VueRouter from 'vue-router' //安装路由插件 Vue.use(VueRouter) //导入路由组件 import Movie from '../views/movie.vue' import Music from '../views/music.vue' import Tv from '../views/tv.vue' //定义路由对象 let router = new VueRouter({ //routers加载具体的路由信息 routes:[ { path:"/mv",component: Movie }, { path:"/ms",component: Music }, { path:"/tv",component: Tv } ] }) //导出路由对象 export default router
接下来需要在main.js中引入:
import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false import router from './router' //简写:index.js 可以不写 new Vue({ render: h => h(App), router //简写:router:router 名称相同可以简写 }).$mount('#app')
最后需要在使用页面用内置组件 router-link 和 router-view 链接和显示,我就偷懒加在了原项目页面:
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <!-- 内置组件:路由链接 --> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/ms">音乐</router-link> <router-link to="/tv">电视剧</router-link> <router-link to="/mv">电影</router-link> <!-- 内置组件:路由输出位置 --> <!-- 路由出口 路由匹配到的组件将渲染在这里 --> <router-view></router-view> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template>
期间如果有同学报错:Component name "music" should always be multi-word,是因为eslint验证比较严谨,这里我们配置一下 vue.config.js :
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, lintOnSave:false //否在开发环境 下每次保存代码时都启用 eslint 验证 })
12. ElementUI
Elementui是由饿了么前端团队推出的基于vue封装的UI组件库,提供了丰富的PC端组件,简化了常用组件的封装,大大降低了开发难度。它一套为开发者、设计师和产品经理准备的基于Vue 2.0的桌面端组件库。 Elementui是饿了么团队根据MVVM结构Vue封装的UI组件库。中文网站:Element - The world's most popular Vue UI framework
12.1 安装
#进入项目 cd hello03 #根据官网安装 npm i element-ui -S
下载完成后在package.json中可以看见 element-ui 的版本:
12.2 布局
还是在src中建立views,创建MyContainer.vue,template粘贴官网的代码,大致如下:
在src中添加router路由,配置path目录直接到MyContainer.vue中:
import Vue from 'vue' //导入路由插件 import VueRouter from 'vue-router' //安装路由插件 Vue.use(VueRouter) //定义路由对象 let router = new VueRouter({ //routers加载具体的路由信息,这里我们换一种写法,不在外部import...from了 routes:[ { path:"/",component:()=> import('../views/MyContainer.vue') } ] }) //导出路由对象 export default router
在 main.js 中按需导入ElementUI提供的组件 container 及其相关组件,以及我们的路由设置:
//import { from } from 'core-js/core/array' import Vue from 'vue' import App from './App.vue' //Element-ui css 导入css import 'element-ui/lib/theme-chalk/index.css' //Element-ui 按需导入 import { Container, Header, Aside, Main, Footer } from 'element-ui' //Element-ui 按需注册 Vue.use(Container) Vue.use(Header) Vue.use(Aside) Vue.use(Main) Vue.use(Footer) Vue.config.productionTip = false import router from './router' //简写:index.js 可以不写 new Vue({ render: h => h(App), router //简写:router:router 名称相同可以简写 }).$mount('#app')
修改App.vue,直接使用 router-view :
<template> <div id="app"> <router-view/></router-view> </div> </template> <script> export default { name: "App" }; </script> <style></style>
运行查看效果:
12.3 后台布局
我们这次使用3个组件查看效果:link,button, select 。分别建立MyLink,MyButton,MySelect 三个vue文件,每个文件拷贝官方代码放入template的div标签中:
修改上个例子中的 MyContainer ,加入 router-link 和 router- view , 注意 router-link 在 el-aside ,router-view 在 el-main 中:
编辑路由文件,index.js ,这里我们使用子路由,否则内容会覆盖整个窗体因为默认的路由“/”就是整个窗体。
import Vue from 'vue' //导入路由插件 import VueRouter from 'vue-router' //安装路由插件 Vue.use(VueRouter) //定义路由对象 let router = new VueRouter({ //routers加载具体的路由信息 routes:[ { path:"/", component:()=> import('../views/MyContainer.vue') , //再换一种写法,子路由,再次不使用 import...from 的语法 children:[ { path:"/lnk",component:()=> import('../views/MyLink.vue') }, { path:"/btn",component:()=> import('../views/MyButton.vue') }, { path:"/sel",component:()=> import('../views/MySelect.vue') } ] } ] }) //导出路由对象 export default router
最后在 main.js 中按需引入三个组件及其相关组件:
//Element-ui 按需导入 import { Container, Header, Aside, Main, Footer, Link, Button, Select, Option } from 'element-ui' //Element-ui 按需注册 Vue.use(Container) Vue.use(Header) Vue.use(Aside) Vue.use(Main) Vue.use(Footer) Vue.use(Link) Vue.use(Button) Vue.use(Select) Vue.use(Option)
完成后效果如图所示,容器布局和路由可实现左边点击链接右边变化的后台管理界面效果:
vue-axios
13. vue-axios
安装:
#安装 axios npm install axios -S
引入main.js:
// step1: 引入 import axios from 'axios' // step2:把axios挂载到vue的原型中,在vue中每个组件都可以使用axios发送请求, // 不需要每次都 import一下 axios了,直接使用 $axios 即可 Vue.prototype.$axios = axios
此时在其他组件均可以使用:
this.$axios.get("http://www.juhe.cn/simpleWeather/query").then((response) => { console.log(response.data); });
下面我们来解决跨域问题(ip和端口任意一个不一致都不是当前项目):
main.js中加入
//step3:使每次请求都会带一个 /api 前缀 axios.defaults.baseURL = '/api'
vue.config.js中加入跨域配置:
module.exports = defineConfig({ transpileDependencies: true, devServer:{ proxy:{ '/api':{ // 此处的写法,目的是为了 将 /api 替换成 http://apis.juhe.cn/ target:'http://apis.juhe.cn/', // 允许跨域 changeOrigin: true, // https时需要配置此参数 //secure:true, //是否代理websockets ws:true, //重写 pathRewrite: { '^/api': '' } }, //'xyz':{ target:'http:192.168.0.1:8899/'//其他跨域配置 } } /* /api: 请求的url使用/api开始时,才会调用代理。eg:请求url为/api/admin/login实际对应的请求地址为http://apis.juhe.cn/api/admin/login。 但我们的请求的接口url可能并不含/api。所以就需要使用pathRewrite:{ '^/api':'' }来将url中的/api替换为空。得到正确的请求url:http://apis.juhe.cn/admin/login 如果你的请求的所有的url恰好都含有/api,那么可以选择不写pathRewrite或者写成pathRewrite:{ '^/api':'/api' } */ } })