vue笔记
Vue.js
Vue介绍
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
Vue是一个渐进式的框架:
渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,带来丰富的交互体验。
vue 安装
方法一:直接CDN引入
1 |
|
方法二:下载和引入
官网下载地址:https://cn.vuejs.org/v2/guide/installation.html
方法三:npm安装
1 |
|
入门案例:
1 |
|
v-for列表循环:
1 |
|
案例:计数器
1 |
|
@click=”方法名”或者v-on:click=”方法名”
MVVM
1.1基本概念MVVM是Model-View-ViewModel的缩写,表示模型-视图-视图模型。模型层主要负责处理交互请示并返回响应的数据;视图层主要负责展示视图;视图-模型层起到前两者的桥梁作用,一方面响应用户事件并向模型层发送请求,另一方面将模型层返回的数据通过数据绑定在视图中展示。
1.2与MVC模式的区别1.MVVM模式本质上是MVC模式的改进版,MVVM有着最为独特的一个特性,那就是数据绑定;2.MVC模式是系统架构级别的,MVVM是用于单页面的,因此,MVVM也更灵活。
MVVM分为三个部分:分别是M(Model,模型层 ),V(View,视图层),VM(ViewModel,V与M连接的桥梁,也可以看作为控制器)
1、 M:模型层,主要负责业务数据相关;
2、 V:视图层,顾名思义,负视图相关,细分下来就是html+css层;
3、 VM:V与M沟通的桥梁,负责监听M或者V的修改,是实现MVVM双向绑定的要点;
MVVM支持双向绑定,意思就是当M层数据进行修改时,VM层会监测到变化,并且通知V层进行相应的修改,反之修改V层则会通知M层数据进行修改,以此也实现了视图与模型层的相互解耦;
Mustache语法(双大括号)
v-once
该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变。
1 |
|
v-html
如果我们希望解析出HTML展示,使用v-html标签
1 |
|
v-text
将数据显示在页面中
1 |
|
v-pre
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
1 |
|
v-cloak
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
1 |
|
1 |
|
v-bind
某些属性我们也希望动态来绑定。
比如动态绑定a元素的href属性
比如动态绑定img元素的src属性这个时候,我们可以使用v-bind指令:
*作用:动态绑定属性
*缩写: :(v-bind语法糖(简写)也就是:)
*预期:any (with argument)| Object (without argument)口参-数:attrOrProp (optional)
1 |
|
v-bind动态绑定class(1)
1 |
|
1 |
|
v-bind动态绑定class
对象语法
数组语法
1 |
|
作业:鼠标点击高亮显示
1 |
|
1 |
|
v-bind绑定style样式
我们可以利用v-bind:style来绑定一些CSS内联样式,
·在写CSS属性名的时候,比如font-size
1.可以使用驼峰式 (camelCase) fontSize
2.横线分隔(kebab-case,记得用单引号括起来)’font-size‘
绑定class有两种方式:
(1)对象语法
1 |
|
(2)数姐语法
1 |
|
计算属性
1 |
|
利用计算属性计算总价格
1 |
|
计算属性的setter和getter
计算属性一般是没有set方法的,只读属性
1 |
|
在控制台输入app.fullName = “li si”,即可调用计算属性的set和get方法
计算属性的缓存
使用函数会调用多次,而计算属性只会调用一次
1 |
|
事件监听
v-on
情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
1 |
|
v-on修饰符
.stop阻止冒泡
1 |
|
.prevent阻止默认行为
1 |
|
.once只调用一次
1 |
|
代码:
1 |
|
条件判断
v-if
1 |
|
v-else
1 |
|
v-else-if
1 |
|
案例:用户登录切换
1 |
|
小问题:
如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入的内容。
但是按道理讲,我们应该切换到另外一个input元素中了。
在另一个input元素中,我们并没有输入内容。为什么会出现这个问题呢?
问题解答:
这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。
在上面的案例中,Vue内部会发现原来的input元素不再使用,直接作为else中的input来使用了。解决方案:
如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key
并且我们需要保证key的不同
v-show
v-show当条件为false时,只是给元素添加了行内样式display:none
1 |
|
v-show和v-if对比
v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
v-if和v-show对比
v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
- v-if当条件为false时,压根不会有对应的元素在DOM中。
v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
开发中如何选择呢? - 当需要在显示与隐藏之间切片很频繁时,使用v-show
当只有一次切换时,通过使用v-if
循环遍历
v-for遍历数组
1 |
|
v-for循环遍历对象
1 |
|
检测数组更新
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。
Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新
push() 数组末尾添加元素
1
2
3//1.push方法,数组末尾添加元素
this.list.push('eee')
this.list.push('eee', 'fff', 'ggg')pop() 删除数组中的最后一个元素
1
2//2.pop()删除数组中的最后一个元素
this.list.pop()shift() 删除数组中的第一个元素
1
2//3.删除数组中的第一个元素
this.list.shift()unshift() 在数组最前面添加元素
1
2
3//4..unshift()在数组最前面添加元素
this.list.unshift('000')
this.list.unshift('000','111','222')splice()删除元素/插入元素/替换元素
1.删除元素:第一个参数:从这个索引后面开始删除
第二个参数:传入要删除几个元素(如果没有传,就删除后面所有元素)
2.替换元素:第二个参数:标识我们要替换几个元素,后面就是用于替换前面的元素
3.插入元素:第二个参数:传入0,并且后面跟上要插入的元素//5.splice()作用:删除元素/插入元素/替换元素 //删除元素:第一个参数:从这个索引后面开始删除 // 第二个参数:传入要删除几个元素(如果没有传,就删除后面所有元素) //替换元素:第二个参数:标识我们要替换几个元素,后面就是用于替换前面的元素 //插入元素:第二个参数:传入0,并且后面跟上要插入的元素 const start = 1 this.list.splice(start) //删除start后面所有的元素 this.list.splice(start,2) //删除start后的两个元素 this.list.splice(start, 2 ,'ffff') //只传一个就只会替换一个 this.list.splice(start, 2 ,'fff', 'fff', 'fff') //会替换两个,再插入一个 this.list.splice(start,0, 'x','y') //插入
1
2
3
4
5
6
7
* sort() 排序
```javascript
this.list.sort()reverse() 反转
1
this.list.reverse()
注意:
1
this.list[0] = '1111111' //通过这个修改方式修改无效,
Vue.set
1 |
|
图书购物车案例
filters过滤器使用
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
v-model
Vue中使用v-model指令来实现表单元素和数据的双向绑定。
- 案例的解析:
当我们在输入框输入内容时
因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message发生改变。
当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变。
所以,通过v-model实现了双向的绑定。
当然,我们也可以将v-model用于textarea元素
1 |
|
v-model原理
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
- 1.v-bind绑定一个value属性
- 2.v-on指令给当前元素绑定input事件
1 |
|
1 |
|
v-model:ratio
1 |
|
v-model:checkbox
- 复选框分为两种情况:单个勾选框和多个勾选框
- 单个勾选框:
- v-model即为布尔值。
- 此时input的value并不影响v-model的值。
1 |
|
- 多个复选框:
- 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
当选中某一个时,就会将input的value添加到数组中。
- 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
1 |
|
v-model结合select类型
- 和checkbox一样,select也分单选和多选两种情况。
- 单选:只能选中一个值。
- v-model绑定的是一个值。
- 当我们选中option中的一个时,会将它对应的value赋值到melect中
1 |
|
- 多选:可以选中多个值。
- v-model绑定的是一个数组。
- 当选中多个值时,就会将选中的option对应的value添加到数组mySelect
1 |
|
:value值绑定
lazy修饰符:
默认情况下,v-model默认是在input事件中同步输入框的数据的。
也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
lazy修饰符可以让数据在失去焦点或者回车时才会更新
1
2<input type="text" v-model.lazy="message" >
{{message}}
number修饰符:
- 默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
- 但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
- number修饰符可以让在输入框中输入的内容自动转成数字类型:
1 |
|
trim修饰符:
- 如果输入的内容首尾有很多空格,通常我们希望将其去除
- trim修饰符可以过滤内容左右两边的空格
1 |
|
组件化开发
注册组件基本步骤
组件的使用分成三个步骤:
创建组件构造器
1
2
3
4
5
6
7
8
9
10// 1.创建组件构造器对象
const cnp = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈哈</p>
<p>我是内容,呵呵呵呵呵</p>
</div>
`
})注册组件
1
2//2.注册组件
Vue.component('my-cpn', cnp)使用组件
1
2<my-cpn></my-cpn>
<my-cpn></my-cpn>
1.Vue.extend():
调用Vue.extend()创建的是一个组件构造器。
通常在创建组件构造器时,传入template代表我们自定义组件的模板。
该模板就是在使用到组件的地方,要显示的HTML代码。
事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。2.Vue.component():
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:1、注册组件的标签名 2、组件构造器3.组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)
我们来看下面我使用了三次
而第三次其实并没有生效:
全局组件和局部组件
调用Vue.component()注册组件时,组件的注册是全局的
1 |
|
父组件与子组件
子组件不能访问父组件
在前面我们看到了组件树:
组件和组件之间存在层级关系
而其中一种非常重要的关系就是父子组件的关系
我们来看通过代码如何组成的这种层级关系:
父子组件错误用法:以子标签的形式在Vue实例中使用
因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
类似这种用法,
1 |
|
注册组件语法糖
注册全局组件
1 |
|
1 |
|
模板的分离写法
刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
Vue提供了两种方案来定义HTML模块内容:
使用
(2)另一种打包方式:(配置入口和出口)
webpack配置(文件名:webpack.config.js需要自己建)
1
2
3
4
5
6
7
8
9
10
11const path = require('path')
module.exports = {
//入口,可以是字符串/数组/对象,这里我们入口只有一个,所以写一个字符串即可
entry: './src/main.js',
//出口,通常是一个对象,里面至少包含两个重要性,path和filename
output: {
path: path.resolve(__dirname, 'dist'), //注意:path是一个绝对路径
filename: 'bundle.js'
}
}输入命令webpack即可打包:
1
webpack
局部安装webpack
目前,我们使用的webpack是全局的webpack,如果我们想使用局部来打包呢?
因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。
所以通常一个项目,都有自己局部的webpack。
第一步,项目中需要安装自己局部的webpack
这里我们让局部安装webpack3.6.01
npm install webpack@3.6.0 --save-dev
Vue CLI3中已经升级到webpack4,但是它将配置文件隐藏了起来,所以查看起来不是很方便。
第二步,通过node_modules/.bin/webpack启动webpack打包
1
node_modules/.bin/webpack
package.json中定义启动
- 但是,每次执行都敲这么一长串有没有觉得不方便呢?
- OK,我们可以在package.json的scripts中定义自己的执行脚本。
- package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
- 首先,会寻找本地的node_modules/.bin路径中对应的命令。
- 如果没有找到,会去全局的环境变量中寻找。
在psckage.json中配置”build”:”webpack”
1
2
3
4"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},以后就可以通过npm run build来导包
1
npm run build
loader
- loader是webpack中一个非常核心的概念。
- webpack用来做什么呢?
- 在我们之前的实例中,我们主要是用webpack来处理我们写的js代码,并且webpack会自动处理js之间相关的依赖。
- 但是,在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。
对于webpack本身的能力来说,对于这些转化是不支持的。 - 那怎么办呢?给webpack扩展对应的loader就可以啦。
- loader使用过程:
- 步骤一:通过npm安装需要使用的loader
- 步骤二:在webpack.config.js中的modules关键字下进行配置
大部分loader我们都可以在webpack的官网中找到,并且学习对应的用法。 - 官网:https://www.webpackjs.com/concepts/loaders/
css文件处理
- 项目开发过程中,我们必然需要添加很多的样式,而样式我们往往写到一个单独的文件中。
- 在src目录中,创建一个css文件,其中创建一个normal.css文件。
- 我们也可以重新组织文件的目录结构,将零散的js文件放在一个js文件夹中。
- normal.css中的代码非常简单,就是将body设置为red
- 但是,这个时候normal.css中的样式会生效吗?
当然不会,因为我们压根就没有引用它。
webpack也不可能找到它,因为我们只有一个入口,webpack会从入口开始查找其他依赖的文件。
main.js导入css
1
2//引入css
require('./css/normal.css')加载css文件必须有对应的loader
1
2npm install --save-dev css-loader
npm install --save-dev style-loader配置webpack.config.js
1
2
3
4
5
6
7module: {
rules: [
{ test: /\.css$/,
use: ['style-loader','css-loader']
},
]
}出现错误:原因版本不匹配
1
2npm install --save-dev css-loader@3.3.0
npm install --save-dev style-loader@1.0.0less文件处理
导入less文件
1
2
3require('./css/index.less')
//或者
import index from './css/index.less'less文件
1
2
3
4
5
6
7@fontSize: 50px;
@fontColor: red;
body {
font-size: @fontSize;
color: @fontColor;
}安装lessloader
1
2
3//这个版本不匹配
npm install --save-dev less-loader less
npm install --save-dev less-loader@4.1.0配置webpack.config.js
1
2
3
4
5
6
7
8
9
10{
test: /\.less$/,
use: [{
loader: "style-loader", // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader", // compiles Less to CSS
}]
}图片文件处理
url-loader
1
2npm install --save-dev url-loader
npm install --save-dev url-loader@1.1.21
2
3
4
5
6
7
8
9
10
11
12
13
14{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
// 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
limit: 8192,
name: 'img/[name].[hash:8].[ext]'
},
}
]
}file-loader
因为大于8kb的图片,会通过file-loader进行处理,但是我们的项目中并没有file-loader
1
2npm install --save-dev file-loader
npm install --save-dev file-loader@1.1.5修改文件名称
我们发现webpack自动帮助我们生成一个非常长的名字
这是一个32位hash值,目的是防止名字重复
但是,真实开发中,我们可能对打包的图片名字有一定的要求
比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复
所以,我们可以在options中添加上如下选项:
- img:文件要打包到的文件夹
- name:获取图片原来的名字,放在该位置
- hash:8:为了防止图片名称冲突,依然使用hash,但是我们只保留8位
- ext:使用图片原来的扩展名
但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确
默认情况下,webpack会将生成的路径直接返回给使用者
但是,我们整个程序是打包在dist文件夹下的,所以这里我们需要在路径下再添加一个dist/
1
2
3
4
5
6
7
8
9
10
11
12
13
14{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
// 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
limit: 8192,
name: 'img/[name].[hash:8].[ext]'
},
}
]
}es6语法处理
- 如果希望将ES6的语法转成ES5,那么就需要使用babel。
- 而在webpack中,我们直接使用babel对应的loader就可以了。
1
npm install --save-dev babel-loader@7 babel-core@6.26.3 babel-preset-es2015@6.24.1
webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13,
{
test: /\.js$/,
// exclude: 排除
// include: 包含
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}引入Vue.js
- 我们希望在项目中使用Vuejs,那么必然需要对其有依赖,所以需要先进行安装
- 注:因为我们后续是在实际项目中也会使用vue的,所以并不是开发时依赖
1
2npm install vue --save
npm install vue@2.5.21 --save安装vue-loader和vue-template-compiler
如果vue-loader超过15以后需要配置VueLoaderPlugin
1
2
3
4npm install vue-loader vue-template-compiler --save-dev
//下面版本可以用
npm install vue-loader@13.0.0 vue-template-compiler@2.5.21 --save-dev修改webpack.config.js的配置文件:
1
2
3
4
5,
{
test: /\.vue$/,
use: ['vue-loader']
}runtime-only ->代码中,不可以有任何的template
runtime-complier ->代码中,可以有template,因为有complier可以编译template
解决方案:
修改webpack.config.js的配置文件:放在module:的外面和这个是兄弟关系
1
2
3
4
5
6
7resolve: {
// alias: 别名
extensions: ['.js', '.css', '.vue'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}补充:el和template区别
Plugin的使用
添加版权的Plugin
- 我们先来使用一个最简单的插件,为打包的文件添加版权声明
- 该插件名字叫BannerPlugin,属于webpack自带的插件。
1
2
3
4const webpack = require('webpack')
plugins: [
new webpack.BannerPlugin('最终版权归aaa所有'),
],打包html的plugin
1
2npm install html-webpack-plugin --save-dev
npm install html-webpack-plugin@3.2.0 --save-dev1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new webpack.BannerPlugin('最终版权归aaa所有'),
new HtmlWebpackPlugin({
template: 'index.html'
}),
new UglifyjsWebpackPlugin()
],
//注意:
output: {
path: path.resolve(__dirname, 'dist'), //注意:path是一个绝对路径
filename: 'bundle.js',
// publicPath: 'dist/' 安装html-webpack-plugin需要删去
},js压缩的Plugin
1
2npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
1
2
3
4
5
6
7
8const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
plugins: [
new webpack.BannerPlugin('最终版权归aaa所有'),
new HtmlWebpackPlugin({
template: 'index.html'
}),
new UglifyjsWebpackPlugin()
],搭建本地服务器
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
不过它是一个单独的模块,在webpack中使用之前需要先安装它
1
2npm install --save-dev webpack-dev-server@2.9.1
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
port:端口号
inline:页面实时刷新
historyApiFallback:在SPA页面中,依赖HTML5的history模式
webpack.config.js文件配置修改如下:(和mudule还有plugins是兄弟关系)
1
2
3
4devServer: {
contentBase: './dist',
inline: true
}我们可以再配置另外一个scripts:
–open参数表示直接打开浏览器1
2
3
4
5"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server --open"
},
以后运行命令直接改为:自动在浏览器打开
1
npm run dev
配置文件的分离
安装:
1
2npm install webpack-merge --save-dev
npm install webpack-merge@4.1.5 --save-dev1
2
3
4
5"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
},base.config.js
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'bundle.js',
// publicPath: 'dist/'
},
module: {
rules: [
{
test: /\.css$/,
// css-loader只负责将css文件进行加载
// style-loader负责将样式添加到DOM中
// 使用多个loader时, 是从右向左
use: [ 'style-loader', 'css-loader' ]
},
{
test: /\.less$/,
use: [{
loader: "style-loader", // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader", // compiles Less to CSS
}]
},
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
// 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
limit: 13000,
name: 'img/[name].[hash:8].[ext]'
},
}
]
},
{
test: /\.js$/,
// exclude: 排除
// include: 包含
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
},
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
resolve: {
// alias: 别名
extensions: ['.js', '.css', '.vue'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
plugins: [
new webpack.BannerPlugin('最终版权归aaa所有'),
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}dev.config.js
1
2
3
4
5
6
7
8
9
10const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
devServer: {
contentBase: './dist',
inline: true
}
})prod.config.js
1
2
3
4
5
6
7
8
9const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
plugins: [
new UglifyjsWebpackPlugin()
]
})package.json
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{
"name": "webpack",
"version": "1.0.0",
"description": "",
"main": "main.js",
"dependencies": {
"vue": "^2.5.21",
"webpack": "^3.6.0"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^3.3.0",
"file-loader": "^1.1.5",
"html-webpack-plugin": "^3.2.0",
"install": "^0.13.0",
"less": "^4.1.3",
"less-loader": "^4.1.0",
"npm": "^8.13.2",
"style-loader": "^1.0.0",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^1.1.2",
"vue-loader": "^13.0.0",
"vue-template-compiler": "^2.5.21",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.5"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
},
"author": "",
"license": "ISC"
}Vue Cli
如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI
使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。
- 如果每个项目都要手动完成这些工作,那无以效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。
CLI是什么意思?
- CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架.
- Vue CLI是一个官方发布 vue.js 项目脚手架
- 使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.
Vue Cli的使用前提-Node
安装NodeJS
可以直接在官方网站中下载安装.
检测安装的版本
默认情况下自动安装Node和NPM
1
node -v
Node环境要求8.9以上或者更高版本
- 什么是NPM呢?
- NPM的全称是Node Package Manager
- 是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。
- 后续我们会经常使用NPM来安装一些开发过程中依赖包.
cnpm安装
由于国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。
你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
1
npm install -g cnpm --registry=https://registry.npm.taobao.org
这样就可以使用 cnpm 命令来安装模块了:
1
cnpm install [name]
Vue Cli的使用前提-webpack
Vue.js官方脚手架工具就使用了webpack模板
- 对所有的资源会压缩等优化操作
- 它在开发过程中提供了一套完整的功能,能够使得我们开发过程中变得高效。
Webpack的全局安装
1
npm install webpack -g
Vue Cli的使用
安装脚手架:
1
npm install -g @vue/cli
注意:上面安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的。
Vue CLI2初始化项目
1
2
3npm install -g @vue/cli-init
# `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同
vue init webpack my-projectVue CLI3初始化项目
1
vue create my-project
创建Vue Cli2项目
目录结构详解
不使用Eslint验证
Runtime-Compiler和Runtime-only的区别
- 如果在之后的开发中,你依然使用template,就需要选择Runtime-Compiler
- 如果你之后的开发中,使用的是.vue文件夹开发,那么可以选择Runtime-only
Vue运行过程
parse:解析
ast:抽象语法树(abstract syntax code,AST)
render函数的使用
main.js
1
2
3
4
5
6
7
8
9
10
11
12new Vue({
el: '#app',
// components: { App },
// template: '<App/>'
render: (createElement) => {
// 使用方式一: return createElement('标签', '相关数据对象(可以不传)', ['内容数组'])
// return createElement('div', {class: 'box'}, ['我是div中的内容'])
// 嵌套render函数
return createElement('div', {class: 'box'}, [createElement('h2', {class: 'h2box'}, ['我是render函数里面嵌套的h2'])])
}
})使用方式二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const cpn = Vue.component('cpn', {
template: '<div>我是cpn组件</div>',
data () {
return {}
}
})
/* eslint-disable no-new */
new Vue({
el: '#app',
// components: { App },
// template: '<App/>'
render: (createElement) => {
// 使用方式一: return createElement('标签', '相关数据对象(可以不传)', ['内容数组'])
// return createElement('div', {class: 'box'}, ['我是div中的内容'])
// 嵌套render函数
// return createElement('div', {class: 'box'}, [createElement('h2', {class: 'h2box'}, ['我是render函数里面嵌套的h2'])])
// 使用方式二: 传入一个组件对象
return createElement(cpn)
}
})最终方式
1
2
3
4
5
6
7
8
9
10
11
12
13import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
new Vue({
el: '#app',
// components: { App },
// template: '<App/>'
render: (createElement) => {
return createElement(App)
}
})运行
1
npm run dev
Vue Cli3
- vue-cli 3 与 2 版本有很大区别
- vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
- vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
- vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
- 移除了static文件夹,新增了public文件夹,并且index.html移动到public中
创建vue cli3项目
1
vue create 项目名
采用手动选择,配置分多个文件
删掉自定义配置:
运行
1
npm run serve
编译
1
npm run build
目录结构详解
图形化管理界面
启动命令
1
vue ui
Vue-Router
路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动. — 维基百科
后端路由
早期的网站开发整个HTML页面是由服务器来渲染的.
- 服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示.
但是, 一个网站, 这么多页面服务器如何处理呢?
一个页面有自己对应的网址, 也就是URL.
URL会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个Controller进行处理.
Controller进行各种处理, 最终生成HTML或者数据, 返回给前端.这就完成了一个IO操作
上面的这种操作, 就是后端路由.
当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户端.
这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化.
后端路由的缺点:
一种情况是整个页面的模块由后端人员来编写和维护的.
另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码.
而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情.
前端路由
- 前后端分离阶段:
- 随着Ajax的出现, 有了前后端分离的开发模式.
- 后端只提供API来返回数据, 前端通过Ajax获取数据, 并且可以通过JavaScript将数据渲染到页面中.
- 这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上.
- 并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可.
- 目前很多的网站依然采用这种模式开发.
- 单页面富应用阶段:
- 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.
- 也就是前端来维护一套路由规则.
- 前端路由的核心是什么呢?
- 改变URL,但是页面不进行整体的刷新。
URL的hash
URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
方法 含义 window.location.href:”url” 在本页跳转到url所指的链接 window.location.replace:”url” 用新的url替换原先的路径 window.location.reload() 强制刷新页面,重新向服务端发送请求 location.href:”url” 在本页跳转到链接地址 parent.location.href:”url” 跳转到上一层页面的指定url链接 top.location.href:”url” 在最外层页面上进行跳转 window.location.href:这种请求方式,需要刷新整个界面,故而用户体验度不好。但是在文件下载的时候,却只能用这种方式发送请求,ajax请求得不到响应。
HTML5的history模式
- history接口是HTML5新增的, 它有五种模式改变URL而不刷新页面.
- history.pushState()
history.replaceState()
history.go()
history.back() == history.go(-1)
history.forword() == history.go(1)
认识Vue-Router
- 目前前端流行的三大框架, 都有自己的路由实现:
- Angular的ngRouter
- React的ReactRouter
- Vue的vue-router
- vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。
- vue-router是基于路由和组件的
- 路由用于设定访问路径, 将路径和组件映射起来.
- 在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
安装和使用Vue-Router
步骤一: 安装vue-router
1
npm install vue-router --save
步骤二: 在模块化工程中使用它(因为是一个插件, 所以可以通过Vue.use()来安装路由功能)
第一步:导入路由对象,并且调用 Vue.use(VueRouter)
1
2
3
4
5import Vue from 'vue'
import VueRouter from 'vue-router'
// 1.注入插件
Vue.use(VueRouter)第二步:创建路由实例,并且传入路由映射配置
1
2
3
4
5
6
7
8
9
10// 2.定义路由
const routers = []
// 3.创建路由实例
const router = new VueRouter({
routers
})
// 4.导出router实例
export default router第三步:在Vue实例中挂载创建的路由实例(main.js)
1
2
3
4
5
6import router from './router'
new Vue({
router, // 路由挂载到Vue实例中
render: h => h(App),
}).$mount('#app')
使用vue-router的步骤:
第一步: 创建路由组件
第二步: 配置路由映射: 组件和路径映射关系1
2
3
4
5
6
7
8
9
10
11// 2.定义路由
const routers = [
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]第三步: 使用路由: 通过
和 (App.vue) 1
2
3
4
5<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
src–>router–>index.js(没有文件可以自己创建)
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
27import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
// 1.注入插件
Vue.use(VueRouter)
// 2.定义路由
const routers = [
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
// 3.创建路由实例
const router = new VueRouter({
routers
})
// 4.导出router实例
export default routerApp.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
components: {
}
}
</script>
<style>
</style>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个标签. : 该标签会根据当前的路径, 动态渲染出不同的组件.
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和处于同一个等级.
在路由切换时, 切换的是挂载的组件, 其他内容不会发生改变. 路由的默认路径
- 配置解析:
- 我们在routes中又配置了一个映射.
- path配置的是根路径: /
- redirect是重定向, 也就是我们将根路径重定向到/home的路径下, 这样就可以得到我们想要的结果了.
改变路径的hash模式
我们前面说过改变路径的方式有两种:
URL的hash
HTML5的history
默认情况下, 路径的改变使用的URL的hash.
如果希望使用HTML5的history模式, 非常简单, 进行如下配置即可:
1
2
3
4
5// 3.创建路由实例
const router = new VueRouter({
routes,
mode: 'history'
})router-link属性
在前面的
中, 我们只是使用了一个属性: to, 用于指定跳转的路径. 还有一些其他属性:
tag: tag可以指定之后渲染成什么组件, 比如上面的代码会被渲染成一个 - 元素, 而不是
1
2<router-link to="/home" tag="button">首页</router-link>
<router-link to="/about" tag="button">关于</router-link>replace: replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中
1
2<router-link to="/home" tag="button" replace>首页</router-link>
<router-link to="/about" tag="button" replace>关于</router-link>active-class: 当
对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称. - 在进行高亮显示的导航菜单或者底部tabbar时, 会使用到该类.
- 但是通常不会修改类的属性, 会直接使用默认的router-link-active即可.
1
2
3
4
5
6
7
8// 3.创建路由实例
const router = new VueRouter({
routes,
mode: 'history',
// 修改linkActiveClass
linkActiveClass: 'active'
})路由代码跳转
1
2this.$router.push('/home')
this.$router.replace('/home')获取当前路由($route)
1
this.$route.path
点击相同路由报错解决方案:https://blog.csdn.net/weixin_44196222/article/details/125565441?spm=1001.2014.3001.5502
动态路由
- 在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:
/user/aaaa或/user/bbbb
除了有前面的/user之外,后面还跟上了用户的ID
这种path和Component的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)。
1
2
3
4{
path: '/user/:userId',
component: User
}获取参数:(上面如果写abc,这里也要写abc)
1
this.$route.params.userId
路由懒加载
- 官方给出了解释:
- 当打包构建应用时,Javascript 包会变得非常大,影响页面加载。
- 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
- 官方在说什么呢?
- 首先, 我们知道路由中通常会定义很多不同的页面.
- 这个页面最后被打包在哪里呢? 一般情况下, 是放在一个js文件中.
- 但是, 页面这么多放在一个js文件中, 必然会造成这个页面非常的大.
- 如果我们一次性从服务器请求下来这个页面, 可能需要花费一定的时间, 甚至用户的电脑上还出现了短暂空白的情况.
如何避免这种情况呢? 使用路由懒加载就可以了.
- 路由懒加载做了什么?
- 路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块.
- 只有在这个路由被访问到的时候, 才加载对应的组件
懒加载的方式
- 方式一: 结合Vue的异步组件和Webpack的代码分析.
1
const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
- 方式二: AMD写法
1
const About = resolve => require(['../components/About.vue'], resolve);
- 方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
1
const Home = () => import('../components/Home.vue')
未使用路由懒加载打包:
使用路由懒加载后打包:(会把每个路由单独打包到一个js文件里)
1
2
3const Home = () => import('../components/Home.vue')
const About = () => import('../components/About.vue')
const User = () => import('../components/User.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// 2.定义路由
const routes = [
{
path: '/',
// 重定向
redirect: '/home'
},
{
path: '/home',
component: Home,
children:[
{
path: '',
redirect: 'news'
},
{
path: 'news',
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
}
]路由传递参数
传递参数的方式:
传递参数主要有两种类型: params和query
params的类型:
- 配置路由格式: /router/:id
- 传递的方式: 在path后面跟上对应的值
- 传递后形成的路径: /router/123, /router/ab
1
2<router-link :to="'/user/'+userId" tag="button" >用户</router-link>
<button @click="userClick">用户</button>1
2
3userClick() {
this.$router.push('/user/'+ this.userId)
},query的类型:
- 配置路由格式: /router, 也就是普通配置
- 传递的方式: 对象中使用query的key作为传递方式
- 传递后形成的路径: /router?id=123, /router?id=abc
1
2<router-link :to="{path: '/profile', query: {name: 'zhangsan', age: 19, height: 188}}" tag="button">档案</router-link>
<button @click="profileClick">档案</button>1
2
3
4
5
6
7
8
9profileClick () {
this.$router.push({
path: '/profile' + 123,
query: {
name: 'zhangsan',
age: 18
}
})
},
获取
1
this.$route.query.参数名
$route和$router的区别
- $route和$router是有区别的
- $router为VueRouter实例,想要导航到不同URL,则使用$router.push方法
- $route为当前router跳转对象里面可以获取name、path、query、params等
导航守卫
我们来考虑一个需求: 在一个SPA应用中, 如何改变网页的标题呢?
- 网页标题是通过
来显示的, 但是SPA只有一个固定的HTML, 切换不同的页面时, 标题并不会改变.
但是我们可以通过JavaScript来修改的内容.window.document.title = ‘新的标题’.
- 网页标题是通过
普通的修改方式:
我们比较容易想到的修改标题的位置是每一个路由对应的组件.vue文件中.
通过mounted声明周期函数, 执行对应的代码进行修改即可.
但是当页面比较多时, 这种方式不容易维护(因为需要在多个页面执行类似的代码).
1
2
3created () {
document.title = "关于"
}
有没有更好的办法呢? 使用导航守卫即可.
什么是导航守卫?
- vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
- vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
导航钩子的三个参数解析:
- to: 即将要进入的目标的路由对象.
- from: 当前导航即将要离开的路由对象.
- next: 调用该方法后, 才能进入下一个钩子.
1
2
3
4
5
6// 路由导航守卫
router.beforeEach((to, from, next) => {
window.document.title = to.matched[0].meta.title
console.log(to)
next()
})keep-alive
- keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
- 它们有两个非常重要的属性:
- include - 字符串或正则表达,只有匹配的组件会被缓存
- exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
- router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
1
2
3
4
5<keep-alive exclude="User,Profile"> <!-- 逗号之间不要空格 -->
<router-view>
<!-- 所有路径匹配到的视图组件都会被缓存 -->
</router-view>
</keep-alive>exclude,里面的属性是这里的name
实现保持默认状态:(保持上一个选中的)
1
2
3
4
5
6
7activated() {
this.$router.push(this.path)
},
beforeRouteLeave (to, from, next) {
this.path = this.$route.path
next()
}TabBar
创建项目
1
vue create 项目名称
- 如果在下方有一个单独的TabBar组件,你如何封装
自定义TabBar组件,在APP中使用
让TabBar出于底部,并且设置相关的样式 - TabBar中显示的内容由外界决定
定义插槽
flex布局平分TabBar - 自定义TabBarItem,可以传入 图片和文字
定义TabBarItem,并且定义两个插槽:图片、文字。
给两个插槽外层包装div,用于设置样式。
填充插槽,实现底部TabBar的效果 - 4.传入 高亮图片
定义另外一个插槽,插入active-icon的数据
定义一个变量isActive,通过v-show来决定是否显示对应的icon - 5.TabBarItem绑定路由数据
安装路由:npm install vue-router —save
完成router/index.js的内容,以及创建对应的组件
main.js中注册router
APP中加入组件 - 6.点击item跳转到对应路由,并且动态决定isActive
监听item的点击,通过this.$router.replace()替换路由路径
通过this.$route.path.indexOf(this.link) !== -1来判断是否是active - 7.动态计算active样式
封装新的计算属性:this.isActive ? {‘color’: ‘red’} : {}
MainTab
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<template>
<div>
<tab-bar>
<tab-bar-item path="/home" activeColor="#FF5777">
<img slot="item-icon" src="~img/tabbar/home.svg">
<img slot="item-icon-active" src="~img/tabbar/home_active.svg"/>
<div slot="item-text">首页</div>
</tab-bar-item>
<tab-bar-item path="/category" activeColor="#FF5777">
<img slot="item-icon" src="~img/tabbar/category.svg">
<img slot="item-icon-active" src="~img/tabbar/category_active.svg"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item path="/cart" activeColor="#FF5777">
<img slot="item-icon" src="~img/tabbar/shopcart.svg">
<img slot="item-icon-active" src="~img/tabbar/shopcart_active.svg"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item path="/profile" activeColor="#FF5777">
<img slot="item-icon" src="~img/tabbar/profile.svg">
<img slot="item-icon-active" src="~img/tabbar/profile_active.svg"/>
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
</div>
</template>
<script>
import TabBar from '../tabbar/TabBar.vue'
import TabBarItem from '../tabbar/TabBarItem.vue'
export default {
name: 'MianTab',
components: {
TabBar,
TabBarItem
}
}
</script>TabBar
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<template>
<div class="tab-bar">
<slot/>
</div>
</template>
<script>
export default {
name: 'TabBar'
}
</script>
<style scoped>
.tab-bar {
display: flex;
background-color: #f6f6f6;
position: fixed;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 -1px 1px rgba(100, 100, 100, .2);
}
</style>TabBarItem
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
47
48
49
50
51
52
53
54<template>
<div class="tab-bar-item" @click="itemClick">
<div v-if="!isActive"><slot name="item-icon"></slot></div>
<div v-else><slot name="item-icon-active"></slot></div>
<div :style="activeStyle"><slot name="item-text"></slot></div>
</div>
</template>
<script>
export default {
name: 'TabBarItem',
props: {
path: String,
activeColor: {
type: String,
default: 'red'
}
},
computed: {
isActive() {
return this.$route.path.indexOf(this.path) !== -1
},
activeStyle() {
return this.isActive ? {color: this.activeColor} : {}
}
},
methods: {
itemClick() {
if (this.path != this.$route.path){
this.$router.push(this.path)
}
}
}
}
</script>
<style scoped>
.tab-bar-item {
flex: 1;
text-align: center;
height: 49px;
font-size: 14px;
}
.tab-bar-item img {
width: 24px;
height: 24px;
margin-top: 3px;
vertical-align: middle;
margin-bottom: 2px;
}
</style>修改文件路径别名
vue.config.js(修改完要重启)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
lintOnSave: false, //去掉EsLint代码规范验证
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
'img': resolve('src/assets/img')
}
}
},
}Vuex
- 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
- 状态管理到底是什么?
- 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
- 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
- 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
- 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
- 等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
- 当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
- 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
- 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
单界面状态管理
多界面状态管理
Vuex状态管理图例
安装和导入Vuex
1
npm install vuex --save
src->store->index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
export default storemain.js
- 挂载到Vue实例中,让所有的Vue组件都可以使用这个store对象
- 来到main.js文件,导入store对象,并且放在new Vue中
- 这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
1
2
3
4
5
6
7
8
9
10
11import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')基本使用
APP.vue
1
2
3
4
5
6
7<div id="app">
<h2>------APP组件------</h2>
<h2>{{this.$store.state.counter}}</h2>
<button @click="addCounter">+</button>
<button @click="subCounter">-</button>
<hello-vuex></hello-vuex>
</div>1
2
3
4
5
6
7
8
9import {INCREMENT, DECREMENT} from './store/motations-types'
methods: {
addCounter() {
this.$store.commit(INCREMENT)
},
subCounter() {
this.$store.commit(DECREMENT)
}
}store->index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
Vue.use(Vuex)
const state = {
counter: 0
}
const store = new Vuex.Store({
state,
mutations,
actions: {
},
modules: {
}
})
export default storemutations.js
1
2
3
4
5
6
7
8
9
10import { INCREMENT, DECREMENT } from "./motations-types.js"
export default {
[INCREMENT](state) { // 默认会传入state
state.counter++
},
[DECREMENT](state) {
state.counter--
}
}motations-types.js
1
2export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'- 1.提取出一个公共的store对象,用于保存在多个组件中共享的状态
- 2.将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
- 3.在其他组件中使用store对象中保存的状态即可
- 通过this.$store.state.属性的方式来访问状态
- 通过this.$store.commit(‘mutation中方法’)来修改状态
- 注意事项:
- 我们通过提交mutation的方式,而非直接改变store.state.count。
- 这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。
State
Vuex提出使用单一状态树, 什么是单一状态树呢?
英文名称是Single Source of Truth,也可以翻译成单一数据源。Getters
app.vue
1
2
3
4
5<h2>------getters基本使用------</h2>
<h2>{{$store.getters.powerCounter}}</h2>
<p>{{$store.getters.more20Age}}</p>
<h2>年龄超过20的数量:{{$store.getters.more20AgeLength}}</h2>
<p>{{$store.getters.moreAgestu(12)}}</p>getters.js
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19export default {
powerCounter(state) {
return state.counter * state.counter
},
more20Age(state) {
return state.students.filter(s => s.age >= 20)
},
more20AgeLength(state, getters){
return getters.more20Age.length
},
moreAgestu(state) {
// return function (age) {
// return state.students.filter(s => s.age > age)
// }
return age => {
return state.students.filter(s => s.age >= 20)
}
}
}Mutation(同步函数)
- Vuex的store状态的更新唯一方式:提交Mutation
- Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state。
- mutation的定义方式:
1
2
3
4
5
6
7
8
9import {INCREMENT, DECREMENT} from './store/motations-types'
methods: {
addCounter() {
this.$store.commit(INCREMENT)
},
subCounter() {
this.$store.commit(DECREMENT)
}
}mutations.js
1
2
3
4
5
6
7
8
9
10import { INCREMENT, DECREMENT } from "./motations-types.js"
export default {
[INCREMENT](state) { // 默认会传入state
state.counter++
},
[DECREMENT](state) {
state.counter--
}
}motations-types.js
1
2export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'Mutation传递参数
1
2
3
4
5
6
7
8
9
10
11
12addCounter1(count) {
// payload: 负载
// 1.普通的提交封装
this.$store.commit('addCounter', count)
},
subCounter1(count) {
// // 2.特殊的提交封装
this.$store.commit({
type: 'subCounter',
count
})
}mutation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { INCREMENT, DECREMENT } from "./motations-types.js"
export default {
[INCREMENT](state) { // 默认会传入state
state.counter++
},
[DECREMENT](state) {
state.counter--
},
addCounter(state, n) {
state.counter += n
},
subCounter(state, payload) {
state.counter -= payload.count
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16updateInfo(state) {
state.info.name = 'modify'
// 错误的代码: 不能在这里进行异步操作
// setTimeout(() => {
// state.info.name = 'modify'
// }, 1000)
//state.info['address'] = '洛杉矶'
//Vue.set(state.info, 'address', '洛杉矶')
// 该方式做不到响应式
// delete state.info.age
//Vue.delete(state.info, 'age')
}- Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
- 这就要求我们必须遵守一些Vuex对应的规则:
- 提前在store中初始化好所需的属性.
- 当给state中的对象添加新属性时, 使用下面的方式:
- 方式一: 使用Vue.set(obj, ‘newProp’, 123)
- 方式二: 用新对象给旧对象重新赋值
通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.
主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.Action(异步函数)
- 我们强调, 不要再Mutation中进行异步操作.
- 但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢?
- Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.
- Action的基本使用代码如下:
- context是什么?
- context是和store对象具有相同方法和属性的对象.
- 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
- 我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
- 事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.
在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch
actions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20export default {
// aUpdateInfo(context, payload) {
// setTimeout(() => {
// context.commit('updateInfo')
// console.log(payload.message)
// payload.success()
// },5000)
// }
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo');
console.log(payload);
resolve('1111111')
}, 1000)
})
}
}Info.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16updateInfo() {
// this.$store.commit('updateInfo')
// this.$store.dispatch('aUpdateInfo', {
// message: '我是携带的信息',
// success: () => {
// console.log('里面已经完成了');
// }
// })
this.$store
.dispatch('aUpdateInfo', '我是携带的信息')
.then(res => {
console.log('里面完成了提交');
console.log(res);
})
}Module
我们在moduleA中添加state、mutations、getters
mutation和getters接收的第一个参数是局部状态对象虽然, 我们的doubleCount和increment都是定义在对象内部的.
但是在调用的时候, 依然是通过this.$store来直接调用的.1
2<h2>{{$store.state.a.counter}}</h2>
<button @click="addInfoCounter">+</button>1
2
3addInfoCounter() {
this.$store.commit('add')
}1
2
3
4
5
6
7
8
9
10const module = {
state: {
counter: 0
},
mutations:{
add(state){
state.counter ++
}
}
}项目结构
axios
1
npm install axios --save
http://123.207.32.32:8000/home/multidata
http://123.207.32.32:8000/home/data?type=sell&page=1
Vue中发送网络请求有非常多的方式, 那么, 在开发中, 如何选择呢?
选择一: 传统的Ajax是基于XMLHttpRequest(XHR)
- 为什么不用它呢?
- 非常好解释, 配置和调用方式等非常混乱.
- 编码起来看起来就非常蛋疼.
- 所以真实开发中很少直接使用, 而是使用jQuery-Ajax
- 为什么不用它呢?
选择二: 在前面的学习中, 我们经常会使用jQuery-Ajax
相对于传统的Ajax非常好用.- 为什么不选择它呢?
- 首先, 我们先明确一点: 在Vue的整个开发中都是不需要使用jQuery了.
- 那么, 就意味着为了方便我们进行一个网络请求, 特意引用一个jQuery, 你觉得合理吗?
- jQuery的代码1w+行.
- Vue的代码才1w+行.
- 完全没有必要为了用网络请求就引用这个重量级的框架.
- 为什么不选择它呢?
选择三: 官方在Vue1.x的时候, 推出了Vue-resource.
Vue-resource的体积相对于jQuery小很多.
另外Vue-resource是官方推出的.- 为什么不选择它呢?
- 在Vue2.0退出后, Vue作者就在GitHub的Issues中说明了去掉vue-resource, 并且以后也不会再更新.
- 那么意味着以后vue-reource不再支持新的版本时, 也不会再继续更新和维护.
- 对以后的项目开发和维护都存在很大的隐患.
选择四: 在说明不再继续更新和维护vue-resource的同时, 作者还推荐了一个框架: axios
axios有非常多的优点, 并且用起来也非常方便.
jsonp
- 在前端开发中, 我们一种常见的网络请求方式就是JSONP
- 使用JSONP最主要的原因往往是为了解决跨域访问的问题.
- JSONP的原理是什么呢?
- JSONP的核心在于通过
- JSONP的原理是什么呢?