文章目录
- 1. 简介
- 2. 第一个Vue程序
- 3. 指令
- 3.1 判断循环
- 3.2 操作属性
- 3.3 绑定事件
- 3.4 表单中数据双向绑定
- 3.5 其他内置指令
- 3.6 自定义指令
- 4. 组件
- 4.1 全局注册
- 4.2 局部注册
- 4.3 组件通讯
- 4.4 单文件组件
- 5. 组件插槽
- 5.1 单个插槽
- 5.2 具名插槽
- 5.3 作用域插槽
- 6. 内置组件
- 6.1 component组件
- 6.2 keep-alive组件
- 6.3 transition组件
- 7. axios网络通信
- 7.1 第一个Axios
- 7.2 各种请求方式
- 7.3 跨域问题
- 8. Vue的其他配置
- 8.1 计算属性
- 8.2 监视属性
- 8.3 过滤器
- 9. 生命周期
- 10. vue-cli
- 10.1 简介
- 10.2 创建项目
- 10.3 项目目录结构
- 11. webpack
- 11.1 简介
- 11.2 安装使用
- 12. vuex
- 12.1 vuex原理
- 12.2 基本使用
- 12.2 四个map方法
- 12.3 模块化+命名空间
- 13. vue-router
- 13.1 简介
- 13.2 简单使用
- 13.3 单文件注册路由
- 13.4 路由跳转/传递接受参数
- 13.5 两个钩子
- 13.6 路由守卫
- 13.7 路由两种模式
- 14. 组件库
1. 简介
Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
Vue是个MVVM框架,使用了模块化+虚拟DOM
MVVM:Model-View-ViewModel ,核心是ViewModel层,负责转化Model中的数据对象来让数据变得更容易管理和使用
作用:
- ViewModel层向上与视图层View进行双向绑定
- ViewModel层向下与Model层通过接口请求进行数据交互
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有一下好处:
- 低耦合
- 可复用
- 独立开发
- 可测试
⭐解释(思想很重要!):
双向数据绑定 <–(JSON)
View
(HTML,CSS,Templates) <- -> ViewModel
(JavaScrip,Runtime,Complier) -->(AJAX) Model
(Java业务逻辑层->数据库)
前端页面 即时运行即时编译 后端
和JSP本质不一样了,如果前端给我们页面还要${key},这个key还要我们Java程序员写上去,而Vue我们只需要用JSON把数据返回给前端就行了,前端根据模板{{ }}进行显示就行了,这个模板我们不需要我们Java程序员去改去写!!
完全解耦了View层 和Model层,这个是至关重要的,前后端分离的重要一环!!!
Vue.js就是MVVM的实现者,核心就是实现类DOM监听和数据绑定
笔记:前端笔记
2. 第一个Vue程序
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Java</h1> <!-- 而这里改变就需要使用JS操作DOM对象重写DOM对象,而且需要刷新页面才能看到改变!!-->
<!-- View层 模板-->
<div id="app">
{{ message }} <!-- 4.从模板取数据,这里不需要刷新页面就可以根据数据变化响应前端-->
</div>
<!-- 1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
// 2.new Vue对象
var vm = new Vue({
el: "#app", // 3.绑定元素
data: { // Model 数据
message: "Hello,Vue!"
}
});
</script>
</body>
</html>
Vue对象7个常用属性:
- el
- data
- template
- methods
- rander
- computed
- watch
3. 指令
3.1 判断循环
指令带有前缀v,代表他们是Vue提供的特殊特征
v-if
,v-else
,v-else-if
,根据表达式的真假切换元素的显示和隐藏(操作DOM元素,直接把DOM元素移除了)
<div id="app">
<h1 v-if="flag">yes</h1> <!-- 判断显示那个-->
<h1 v-else>No</h1>
<p v-if="show === 'A'">A</p> <!-- 类似JSP里面的JSTL标签-->
<p v-else-if="show === 'B'">B</p>
<p v-else>C</p>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
flag: true,
show: 'A'
}
});
</script>
v-for
<div id="app">
<!-- <li v-for="item in items"> -->
<li v-for="(item, index) in items"> <!-- 反着写,item是数组里面的每个元素,index是下标,0开始,注意是()括号-->
{{item}} ---> {{index}}
</li>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
// 数组里面还可以是对象 items: [ {message:"Java"}, {message:"C++"} ],模板里面直接item.message
items: [1,2,3,4]
}
});
</script>
3.2 操作属性
v-bind
设置元素是属性, 冒号后面代表属性名 v-bind:属性名=表达式
<div id="app">
{{ message }} <!-- 模板中支持JS语法,但只能一条语句{{ message.reverse() }} -->
<span v-bind:title="message"> <!-- 绑定鼠标在字上面显示的内容-->
鼠标悬停几秒查看此处动态绑定提示信息!
</span>
<a v-bind:href="url">图片</a>
<!-- 动态添加class,isActive是data里面的一个数据-->
<p v-bind:class="isActive:'active':''">..</p>
<p v-bind:class="{active:isActive}">..</p> <!-- 推荐对象这种-->
</div>
<a v-bind:href="url">图片</a>
简写:
<a :href="url">图片</a>
3.3 绑定事件
v-on
绑定事件,监听DOM事件
<div id="app">
<button v-on:click="sayHi">点击我</button> <!-- 想要绑定什么事件冒号后面写就行了-->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
message: "Hello!"
},
methods: { // 方法必须定义到Vue的methods对象中!!
sayHi: function () {
alert(this.message); // this指vm这个对象
}
}
});
</script>
<button v-on:click="sayHi">点击我</button>
v-on:都是相同的,所以可以简写:
<button @click="sayHi">点击我</button>
v-on
还可以传递参数,事件修饰符等
事件修饰符
.prevent
阻止标签的默认行为.stop
阻止事件的冒泡.once
事件只触发一次
<div id="app">
<button @click="say('haha')">自定义参数</button>
<!-- .prevent阻止a标签的默认行为-->
<a href=".." @click.prevent="sayHi"></a>
<!-- 键盘事件,keyup,keydown.enter按下回车就会执行sayHi()方法,hdelete,esc,space,tab,up,down-->
<input type="text" @keyup.enter="sayHi"/>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
methods: {
say: function (str) {
alert(str);
},
sayHi: function () {
alert("Hi");
}
}
});
</script>
还有audio标签里面的play(播放) pause(暂停) 事件等
3.4 表单中数据双向绑定
v-model
获取和设置表单元素的值!!
v-model
在表单是input,textarea,select…元素上创建双向数据绑定,会根据控件类型自动选取正确的方式来更新元素
v-model
会忽略所有表单元素的value,checked,selected特性的初始值而总是将Vue实例的数据作为数据源,你应该在JavaScrip在组件date中设置初始值!!!
修饰符:v-model.lazy
v-model.number
v-model.trim
<div id="app">
<input type="text" v-model:value="message"> <br> <!-- 输入的和后面的显示双向动态绑定-->
你输入的文本是:{{message}} <br>
性别:
<input type="radio" name="sex" value="boy" v-model="str"> 男 <!-- 简写可以去掉v-model后的value-->
<input type="radio" name="sex" value="girl" v-model="str"> 女 <br>
你选中的性别的:{{str}}
爱好:
<select name="bobby" v-model="temp">
<option disabled="disabled" value="">请选择</option>
<option value="Java">Java</option>
<option value="Python">Python</option>
<option value="C++">C++</option> <br>
</select>
你选中的爱好是:{{temp}} <!-- 神奇,temp就是value-->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
message: "",
str: "", // 绑定的是value属性值吧!
temp: "Java" // 这里设置为""这默认选“请选择”,设置为Java默认选“Java”,而在option中使用selected没用!
}
});
</script>
3.5 其他内置指令
-
v-show
根据表达式的真假切换元素的显示和隐藏(和v-if效果一样,但这个v-show是操作disabled属性,DOM元素始终都在)频繁切换建议使用
v-show
,因为频繁操作DOM消耗大。<img src="地址" v-show="age>=18"/> <!-- age是data里面的数据-->
-
v-test
设置标签的文本值。例如<h1 v-test="message"></h1>
,缺点会全部替换掉。解决:可以使用{{ }}
语法 -
v-html
设置标签的innerHTML,可能会有XSS攻击 -
v-cloak
没有值,配合css可以防止vue介入慢,而引起的页面闪烁 -
v-once
没有值,在初始动态渲染后,都视为静态内容了 -
v-pre
没有值,跳过所在结点的编译过程(可以利用它跳过没有使用指令差值语法的结点,加快编译) -
ref
属性,这个可以简洁操作DOM(id的替代者。如果写在组件标签上,则拿到的是ref属性所在标签对应子组件的实例对象)<h1 ref="title"></h1> <button @click="showDom">点我展示上面的DOM元素</button> <script> new Vue({ methods: { showDom() { console.log(this.$refs.title); // 类似元素的:document.getElementById("id") } } }) </script>
3.6 自定义指令
directives
,注意:自定义函数里面的this不是vm了,而是window!!
<div id="app">
<h1 v-big="n"></h1>
</div>
<script>
new Vue({
el: "#app",
data: {
n: 10
},
directives: {
big: { // 自定义指令v-big,就需要手动操作DOM
bind() {}, // 指令和元素成功绑定时(这三个函数都能收到那两个参数)
inserted() {}, // 指令所在元素被插入页面时
update() {} // 指令所在的模板被重新解析时
},
// 简写什么时候被调用呢?
// 1.指令和元素成功绑定时(一上来) 2.指令所在的模板被重新解析时(即上面的bind和update)
big(element, binding) { // 简写,
element.innerText = binding.value * 10;
}
}
})
Vue.directive('', {}); // 全局的自定义指令 或者Vue.directive('', 回调函数);
</script>
4. 组件
组件:是可复用Vue实例,就是可以重复使用的模板(局部功能代码和资源的集合)
4.1 全局注册
简单的:
<div id="app">
<my-com></my-com> <!-- 这里就会显示列表Hello-->
</div>
<script>
// 全局定义一个组件(在哪个组件中都可以使用)
// my-com就是组件的名字,如果这里使用驼峰命名法时,上面标签使用要转化为小写加-的形式
Vue.component("my-com", {
template: `<li>Hello</li>`
});
new Vue({
el: "#app"
});
</script>
动态数据:
<div id="app">
<!-- v-bind:接受参数=遍历参数 -->
<my-com v-for="item in items" v-bind:temp="item"></my-com>
</div>
<script>
// 在组件中,改变props中变量变化根组件中不会变,
// 特殊的:数组对象可以改变其内容。父子组件的所有prop都是单向向下绑定的
Vue.component("my-com", {
props: ['temp'],
template: `<li>{{temp}}</li>` // 这里不能直接访问到下面的data数据,要使用props接受上面绑定是,动态显示
});
new Vue({
el: "#app",
data: {
items: ["Java", "Linux", "C++"]
}
});
</script>
4.2 局部注册
(注册的时候是什么名,标签里面用就得用什么名)
new Vue({
el: "#app", // 局部注册只能在这个根实例中使用
components: {
'my-com-a': { // 横线命名要加单引号
template: `<h1>{{ title }}</h1>`,
data() { // data: function{}== data(){}后面是ES6的写法。写成方法是为了多个组件实例数据不相互影响
return { title: '我是标题A' }
}
},
MyComB: { // 这样可以不用加单引号,html中使用时会自动映射为上面那种命名
template: `<h1>{{ title }}</h1>`,
data() {
return { title: '我是标题A' }
}
}
}
})
// 单独配置组件的选项对象
// 每次调用Vue.extend就是调用VueComponent的构造函数,每次返回的都是一个全新的VueComponent,即组件实例对象
// var MyComA = Vue.extend({ }) 简写: var MyComA = { },简写的话注册组件的时候框架会帮我们调用Vue.extend()
var MyComA = {/* */}
var MyComB = {/* */}
new Vue({
el: "#app",
components: {
'my-com-a' : MyComA, //或者MyComA: MyComA (推荐单词首字母都大写,标签里使用也是,但需要在脚手架里面使用)
'my-com-b' : MyComb,
}
});
// 简写
new Vue({
el: "#app",
components: {
MyComA, // 当组件名和属性变量变量的话,可以简写, 这样是ES6的写法
MyComb,
}
});
4.3 组件通讯
-
父组件向子组件传值⭐
通过props选项接受父组件的传值
父组件 <div id="app"> <!-- 父组件设置传值--> <my-com title="哈哈"></my-com> 传递静态的值 <my-com :title="'哈哈'"></my-com> 动态绑定,绑定静态字符串值需要加单引号 <my-com :title="msg"></my-com> msg父组件中data的值 </div> <script> //子组件 Vue.component('my-com', { props: ['title'], // 命名推荐驼峰命名法,父组件绑定时需要使用带横线的命名 template: '<h3>{{ title }}</h3>' props: { // 类型限制,传来的title属性是字符串类型的话才进行接受 title: String } props: { // 完整写法 title: { type: String, required: false, default: '默认值' } } }) // 父组件... </script>
props是只读的,如果强转修改Vue有警告。父子组件的所有prop都是单向向下绑定的!!
如果真的想要改的话,请复制一份到data中,然后再去修改data中的数据。(因为先接受props,再去加载data)
-
子组件向父组件传值
通过自定义事件实现⭐
绑定自定义事件:
-
第一种方式,在父组件中,
<Demo @事件名="回调方法">
-
第二种方式,在父组件中,
this.$refs.demo.$on('事件名',回调方法)
注意第二种方式,绑定自定义事件时,回调函数要么配置在
methods
中,要么用箭头函数,否则this指向会出问题触发自定义事件:在子组件中,
this.$emit('事件名',数据)
解绑自定义事件:
this.$off('事件名')
<div id="app"> <!-- 父组件设置传值--> <h3>购物车</h3> <product-item v-for="product in products" :key="product.id" 下面不用接受key参数,只是个标识 :title="product.title" @count-change="onCountChange" 绑定自定义事件 ></product-item> <p> 总数为: {{totalCount}} </p> <product-item ... ref="productItem"></product-item> <!-- 或者这样绑定自定义事件,这样更灵活点--> <product-item ... @click.native=".."></product-item> <!-- 如果给组件绑定元素的事件需要native修饰符--> </div> <script> // 子组件 Vue.component('product-item', { props: ['title'], template: ` <div> <span>商品名称:{{ title }}。商品个数:{{ count }}</span> <button @click="countIns1">+1</button> <button @click="countIns5">+5</button> </div> `, data() { return {count: 0} }, methods: { countIns1() { //this.$emit('count-change'); // 通知父组件,命名建议采用带横线的方式 this.$emit('count-change', 1); // 触发自定义事件!!!可以带多个参数 this.count++; }, countIns5() { this.$emit('count-change', 5); this.count += 5; // 这样可以解绑自定义事件,再怎么触发都没用了(解绑多个需要传数组),解绑全部可以不传参数! // this.$off('count-change') } } }); // 父组件(回调是留在父组件这里的,例如onCountChange) new Vue({ el: "#app", data: { products: [ {id: 1, title: '苹果'}, {id: 2, title: '香蕉'}, {id: 3, title: '梨子'} ], totalCount: 0 }, methods: { onCountChange(productCount) { this.totalCount += productCount; } }, mouted() { // 或者这样绑定自定义事件,这样更灵活点,比如延迟几秒在进行绑定... this.$refs.productItem.$on('count-change', this.onCountChange); this.$refs.productItem.$once('count-change', this.onCountChange); // (只能用一次,一次性) } }); </script>
-
-
非父子组件传值
-
通过父组件进行数据的中转 (兄弟组件)
如果只有一层的话使用父组件进行中转很简单,如果组件层数很多的话,将会变得非常繁琐!不建议
-
全局事件总线 ⭐: 独立的事件中心,用于管理不同组件间的传值操作 ,任意两个组件都可以通信,开发中用的最多。
安装全局事件总线:
// 标准的写法,在src/main.js中: // 在其他组件中直接this.$eventBus.$emit(),this.$eventBus.$on(),所有的组件实例对象,即vc,vm都能看到!! new Vue({ el: '#app', render: h => h(App), beforeCreate() { // 安装全局事件总线(往Vue的原型对象上放的,所有的组件实例对象都有),this就是根vm Vue.prototype.$eventBus = this; } })
使用事件总线(最好用完在
beforeDestory
钩子中,解绑当前组件所用到的事件!):<div id="app"> <h3>购物车</h3> <product-item title="苹果"></product-item> <product-total></product-total> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script> // 子组件 Vue.component('product-item', { props: ['title'], template: ` <div> <span>商品名称:{{ title }}。商品个数:{{ count }}</span> <button @click="countIns1">+1</button> </div> `, data() { return {count: 0} }, methods: { countIns1() { // 触发自定义事件,注意不是this,而是this.$eventBus,可以被任意组件访问,从而实现了数据的中转 this.$eventBus.$emit('count-change', 1); this.count++; } } }); // 进行商品统计的 子组件 Vue.component('product-total', { template: `<p>总数为:{{ totalCount }}</p>`, data() { return {totalCount: 0} }, mounted() { // mounted钩子函数,最好被挂载后进行绑定自定义事件 // 给this.$eventBus绑定对应事件 (相当于接受上面的数据了),回调函数如果直接写的话要用箭头函数!! this.$eventBus.$on('count-change', (productCount) => { this.totalCount += productCount; }) }, beforeDestory() { this.$eventBus.$off('count-change'); } }); </script>
-
4.4 单文件组件
组件配置中,data,methods,watch中的函数…它们的this都是VueComponent实例对象
Test.vue
:(表示一个独立的组件,一般是MyTest用单词首字母都大写的这种命名)
<!-- 下面是组件的单文件写法。(使用Vue.component()注册全局组件容易命名冲突,而且没有语法提示,使用css不方便) -->
<template> <!-- 模板区-->
<h2 class="test-a">
TEST {{msg}}
</h2>
</template>
<script> /* 脚本区 */
export default {
name: "Test", // 一般和文件名保持一致
props: ['xxx'],
data() { msg: "示例文本" }, // 该组件的数据
methods: { }, // 注册对应的方法
computed: { } // 计算属性
}
</script>
<style scoped> /* 样式区,scoped防止组件间样式冲突问题 */
.test-a {
color: red;
}
</style>
App.vue
:(App一般是根组件,一人之上万人之下,管理所有的子组件)
vm -> App.vue -> Student.vue -> Person.vue..
-> School.vue -> ...
<template>
<div>
<h2>哈哈</h2>
<Test></Test>
</div>
</template>
<script>
import Test from './Test.vue' // ES6的模板导入方案
export default {
name: 'App',
components: { // 局部注册组件,上面模板就能使用导入的子组件了
Test
}
}
</script>
main.js
,人口文件(创建vm)
import App from './App.vue'
new Vue({
el: "#root", // 指定为哪个容器服务
template: `<App></App>`, // 这个不写也行,或者在下面的id为root的div中写<App></App>,或者render: h => h(App)
components: {App} // 只注册APP主组件即可,因为App管理所有的子组件
})
⭐在脚手架中,自动引入的vue是残缺版的vue,即
vue.runtime.esm.js
,里面没有模板解析器,因为最终使用webpack打包发布的时候已经解析过了,就不应该有模板解析器了(体积不小),只能用这个方式:render: h => h(App)
,而用template: <App></App>
会报错。因为
vue.runtime.xxx.js
,没有模板解析器,所以不能使用template配置项(template属性),需要rander函数接收到的createElement函数去指定内容。(而vue文件里面的template标签里内容是由一个专门的独立模块vue-template-complier
解析的,这个脚手架帮已经我们引入了
index.html
(单页面应用,就这一共html)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="root"></div> <!-- 准备一个容器-->
<!-- 这两行引入意义不大了,因为使用脚手架会帮我们自动引入执行-->
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
vue单文件开发,js模块化开发需要在脚手架环境下才能执行。
多个组件共享的配置可以抽取出来一个混合对象。例如:
export const hunhe = { data(){...}, methods: {...} }
。局部:
{ mixin: [hunhe] }
, 全局:Vue.mixin(hunhe)
this.$nextTick(function() {})
指定的回调方法会在Dom元素更新完毕后再去执行!!一般用于当数据修改后,要基于更新后新的DOM进行某些操作时,要在nextTick所指定的回调方法中执行。
5. 组件插槽
在Vue中我们可以使用<slot>
元素作为承载分发内容的出口,称为插槽,可以应用在组合组件的场景中。
以前我们是使用props
进行传递,不太繁琐,但也不算简单,使用插槽更简单一点。
5.1 单个插槽
不使用插槽中间的内容会被抛弃,使用插槽的话中间写的内容会替换组件中的slot标签
slot
中可以设置默认值,<slot>这是默认值</slot>
,父组件中如果设置了插槽内容会被替换掉!
(挖个坑,等着组件的使用者进行填充)
<div id="app">
<!-- 不使用插槽中间的内容会被抛弃,使用插槽的话中间写的内容会替换组件中的slot标签-->
<my-com>实例文本</my-com>
<my-com>
<h2>苏瞳</h2>
</my-com>
<my-com>
这里是父组件的视图模板,只能使用父组件的数据!!(因为这部分是父组件来渲染的)
{{ parValue }}
</my-com>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
Vue.component('my-com', {
template: `
<div>
<h2>组件标题</h2>
<slot></slot>
</div>
`
});
new Vue({
el: "#app",
data: {parValue: 666}
})
</script>
5.2 具名插槽
多个位置需要设置插槽,需要给slot
设置name
<div id="app">
<com-a>
<template v-slot:header> <!-- template是个内容占位符,并不是一个真正的标签,在这个标签中才能使用v-slot-->
<h1>组件头部内容</h1> <!-- 或者不写template标签,直接这样:<h1 slot="header">组件头部内容</h1> -->
</template>
<template v-slot:default> <!-- 不写name,默认是default,或者不用template,直接写就行-->
<p>内容1</p>
<p>内容2</p>
</template>
<template v-slot:footer> <!-- 可以简写:template #footer-->
<p>组件尾部</p>
</template>
</com-a>
</div>
<script>
Vue.component('my-com', {
template: `
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
});
</script>
5.3 作用域插槽
作用域插槽:用于让插槽可以使用子组件中的数据
组件将需要被插槽使用的数据通过v-bind
绑定给<slot>
,这种用于给插槽传递数据的属性称为插槽prop
组件绑定数据后,插槽中需要使用v-slot
接受数据
(数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定)
<div id="app">
<com-a>
<!-- dataObj:是包含了所有插槽prop的对象!!并不是直接接受value和num,而是接受对象!!!-->
<template v-slot:default="dataObj">
{{ dataObj.value }}
{{ dataObj.num }}
</template>
</com-a>
<!-- 简写:只有一个<slot>的话,template标签可以省略,v-slot属性写在com-a标签上,default也可以省略掉!!-->
<com-a v-slot="dataObj"> <!-- 或者这样也可以,但不能都省略 <my-com #default="dataObj"> -->
{{ dataObj.value }}
{{ dataObj.num }}
</com-a>
<!-- ES6的解构操作进行数据接受!!-->
<com-a v-slot="{ value, num }"> <!-- 接受的是整个对象,但我们希望只用整个对象里面的num属性, { num }也行-->
{{ num }}
</com-a>
</div>
<!-- 注意:这种语法是从2.6版本开始的!!-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
var ComA = {
template: `
<div>
<p>组件A的内容:</p>
<slot :value="childValue" :num="childNum">默认值</slot>
</div>
`,
data() {
return {
childValue: "子组件内部数据",
childNum: 666
}
}
};
new Vue({
el: "#app",
components: {
ComA
}
});
</script>
6. 内置组件
6.1 component组件
适用于多个组件进行频繁切换的处理,例如选项卡操作
<component>
用于将一个元组件渲染为动态组件,以is
属性值决定渲染那个组件。类似v-if
,v-else-if
的结构。
<div id="app">
<button v-for="title in titles" @click="curCom = title">
{{ title }}
</button>
<component :is="curCom"></component> <!-- is后面是要显示的组件名-->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
var ComA = { template: `<div>A组件内容</div>` }
var ComB = { template: `<div>B组件内容</div>` }
var ComC = { template: `<div>C组件内容</div>` }
new Vue({
el: "#app",
data: {
titles: ['ComA', 'ComB', 'ComC'],
curCom: 'ComA'
},
components: {
ComA, ComB, ComC
}
})
</script>
is属性在每次进行切换时,Vue都会创建一个新的组件实例 (组件的状态无法进行保留)
// 下面三个组件进行切换时,前一个输入框的内容就会消失!! var ComA = { template: `<div>A组件内容,<input type="text"></div>` } var ComB = { template: `<div>B组件内容,<input type="text"></div>` } var ComC = { template: `<div>C组件内容,<input type="text"></div>` }
6.2 keep-alive组件
用于保留组件状态或避免组件重新渲染。
使用很简单:
<div id="app">
<button v-for="title in titles" @click="curCom = title">
{{ title }}
</button>
<keep-alive> <!-- 加上这个标签就行-->
<component :is="curCom"></component>
</keep-alive>
</div>
include
属性,可以知道哪些组件进行保留状态,哪些不保留!!
<keep-alive include="ComA,ComB,ComC"> <!-- 静态,是组件名-->
<component :is="curCom"></component>
</keep-alive>
<keep-alive :include="['ComA','ComB','ComC']"> <!-- 动态绑定,可以是数组/正则表达式(可以在data中声明)-->
<component :is="curCom"></component>
</keep-alive>
6.3 transition组件
在Vue插入,更新,移除DOM时,提供多种不同的方式的应用过渡动画效果。<transition>
,<transition-group>
标签
<style>
/* 进场类名(v-enter 进入的起点样式, v-enter-active 来的整个过程激活的样式, v-enter-to 进入的终点样式),
离场类名(v-leave, v-leave-active, v-leave-to),官网有详细说 */
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
<div id="app">
<button @click="isShow = !isShow">切换</button>
<transition name="fade"> <!-- 用这个包起来即可,Vue会帮我们再合适的时机自动加入过渡和l(动画主要靠CSS)-->
<p v-show="isShow">哈哈哈哈</p>
</transition>
</div>
<script>
new Vue({
el: "#app",
data: {
isShow: true
}
})
</script>
7. axios网络通信
Axios是一个开源的可以用在浏览器端和NodeJS的异步通信框架,主要作用的实现AJAx异步通信,体积小
特点:
- 从浏览器创建
XMLHttpRequests
- 从 node.js 创建
http
请求 - 支持
Promise
API - 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防御
XSRF
Vue.js
由于是视图层框架,所以不支持AJAX
的通信功能,为了解决,单独开发了一个vue-resource
的插件,但这个进入2.0后就停止了改插件对的维护并**推荐了Axios
框架,少用JQuery
,**因为操作DOM太繁琐!!
7.1 第一个Axios
安装:
- cdn :
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
- npm:
npm install axios --save
data.json
{
"name":"苏瞳java",
"url": "http://baidu.com",
"address": {
"street": "含光门",
"city":"陕西西安",
"country": "中国"
}
}
test.html
<div id="vue">
<div>{{info.name}}</div>
<div>{{info.address.street}}</div>
<a v-bind:href="info.url">点我</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var vm = new Vue({
el: "#vue",
data() { // 注意:是data方法,把数据return到data属性中
return {
info: { }
}
},
mounted() {
// 发送get请求,把获得的数据赋给info (类似JQuery的$.get)
axios.get('data.json').then(response => {this.info = response.data})
}
});
</script>
test02.html
<div id="app">
<button @click="showJoke">点击显示笑话</button>
<button @click="showJoke02">点击显示笑话02</button>
<p>{{ joke }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
joke: ""
},
methods: {
showJoke() {
// 把this保存下来,否则在then里面的方法this就变了
var that = this;
axios.get("https://autumnfish.cn/api/joke").then(function (response) {
that.joke = response.data;
});
},
showJoke02() {
axios.get("https://autumnfish.cn/api/joke").then(response => {
// 使用箭头函数没有this,就会往外找,就找到了vm
this.joke = response.data;
});
}
});
</script>
7.2 各种请求方式
get/delete请求:
第一个参数:url,第三个参数:config
这里最好使用箭头函数,这样this才是组件实例对象
// 向给定ID的用户发起请求
axios.get('/user?id=12345')
.then(function (response) {
// 处理成功情况
console.log(response);
})
.catch(function (error) {
// 处理错误情况
console.log(error);
})
.then(function () {
// 总是会执行
});
// 上述请求也可以按以下方式完成(可选)!! 第二个参数和Post不太一样哦
axios.get('/user', {
params: {
id: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// 总是会执行
});
post/put请求:
第一个参数:url,第二个参数:data,第三个参数:config(后两个参数可选)
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
四种请求方式的参数:
axios.get(url[, config])
axios.delete(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
7.3 跨域问题
自己开发情况下会出现跨域问题,解决:
-
cors
后端设置响应头(使用多)
-
jsonp
利用
<script>
的src属性引用外部资源不受同源策略(协议,主机号,端口号三者相同)的限制,但是只能解决get请求跨域的问题, 前后端配合。(使用极少) -
代理服务器⭐(nginx,vue-lic)。前端和后端之间才有跨域问题,后端和后端之间没有同源策略,没有跨域问题
http://localhost:8080
前端 ----http://localhost:8080
代理服务器 ----http://localhost:5000
真正服务器// 使用vue-cli的配置:src/vue.config.js / 第一种,只能配置一个代理,而且如果本服务器已有资源就不能向另一服务器发起请求。 module.exports = { devServer: { // 告诉代理服务器将请求转发给谁,代理服务器的端口不用我们指定会和我们当前项目保持一致! proxy: "http://localhost:5000" } } // 请求: 发送ajax请求,向8080请求,而不要向真正服务器5000端口请求了。 axios.get('http://localhost:8080/students').then(response => {}) / 第二种,可以开启多个代理服务器(项目中常用) module.exports = { devServer: { proxy: { //请求前缀,可自定义 '/sutong': { target: 'http://localhost:5000', //url地址,被请求服务器的地址 ws: true, //是否支持websocket(默认true) //用空代替请求前缀,代理往后端服务器的请求去掉 /sutong 前缀 pathRewrite:{'^/sutong', ''}, //是否‘说谎’,即是不是表明请求来自代理服务器,true: 隐瞒(一般设为true,默认true) //控制请求头中的Host(端口号)值,如果为true,则和被请求服务器的端口号相同,反之则请求服务器的Host(端口号)值相同 changeOrigin: true }, '/foo': { target: '<other_url>' } } } } /* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080 changeOrigin默认值为true */ // 请求:发起axios请求 (不加请求前缀则请求本服务器中的资源) axios.get('http://localhost:8080/sutong/students').then(reponse => {})
8. Vue的其他配置
8.1 计算属性
能够将计算结果缓存起来的属性(将行为转化成了静态的属性)。把data中的属性经过计算再去使用,但相对于方法有缓存机制。
<div id="app">
<input type="text" v-model:value="firstName"></input>
<input type="text" v-model:value="lastName"></input>
全名(方法):{{fullName1()}}
全名(计算属性):{{fullName2}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
firstName: '张',
lastName: '三'
},
methods: { // 方法实现,但调用几次执行几次,没有缓存
fullName1() {
return this.firstName + '-' + this.lastName;
}
},
computed: { // 计算属性,最终会出现在vm身上,直接读取就行
fullName2: {
// getter什么使用调用?
// 1.初次读取计算属性时 2.所依赖的数据发送变化时 (把返回值作为fullName2的值)
get() {
return this.firstName + '-' + this.lastName;
},
// fullName2不允许改的话,setter可以没有(一般是不做需改的,看下面简写)
set(value) {
const arr = value.split('-');
this.firstName = arr[0];
this.lastName = arr[1];
}
},
fullName2() { // 简写,这个函数就当getter用
return this.firstName + '-' + this.lastName;
}
}
});
</script>
调用发放每次都要计算,都有开销,那这个结果是不经常变化的呢?此时就可以考将这个结果缓存起来,采用计算属性可以很方便的做到这一点,计算属性的特性就是将不经常变化的的计算结果进行缓存,以节约我们的系统开销
什么使用更新缓存? 1.初次读取计算属性时 2. 所依赖的数据发送变化时
8.2 监视属性
监视相比较计算属性能开启异步任务
<div id="app">
<buttrn @click="flag=!flag">切换</buttrn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
flag: true
},
watch: {
flag: { // 监视属性,监视falg属性,监视计算属性也行
immediate: true, // 初始化的时候让handler调用一下。默认false
deep: true // 深度监视,如果flag是个对象,加上这个才会监视flag对象里面属性的变化,默认false
handler(newVal, oldVal) {
console.log('flag被修改了', newVal, oldVal);
}
},
flag(newVal, oldVal) { // 当只需要有一个handler方法的时候可以简写这样,函数名就是监视的谁。
console.log('flag被修改了', newVal, oldVal);
}
}
});
// 或者这样
vm.$watch('flag', {
handler(newVal, oldVal) {
console.log('flag被修改了', newVal, oldVal);
}
})
</script>
⭐⭐⭐⭐
- 被Vue管理的函数,最好写成普通函数,这样this才是vm对象 或 组件实例对象
- 所有不被Vue所关闭的函数(定时器的回调函数,ajax的回调函数,Promis的回调函数),最好写成箭头函数(监听函数没有this,就会往外找),这样这样this才是vm对象 或 组件实例对象。
8.3 过滤器
对要显示的数据进行特定格式化后再显示,(适用于一些简单逻辑的处理)(Vue3已经移除)
(其实用方法,计算属性都能完成)
<div id="root">
<h3>当前时间戳:{{time}}</h3>
<h3>转换后时间:{{time | timeFormater()}}</h3>
<h3>转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script>
<script type="text/javascript">
Vue.config.productionTip = false
// 全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,11)
})
new Vue({
el: '#root',
data: {
time: 1626750147900,
},
// 局部过滤器 (第一个参数是管道符|前面的值)
filters: {
timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){ // ES6中参数可以有默认值
return dayjs(value).format(str)
}
}
})
</script>
9. 生命周期
生命周期函数里面的this都是vm 或者 组件实例对象
所有的函数看图片!
-
beforeCreate
:此时无法通过vm访问data中的数据和methods中的方法(即数据检测,数据代理创建之前) -
created
⭐:此时就可以通过vm访问data中的数据和methods中的方法 -
beforeMount
:挂载之前,页面呈现未经Vue编译的DOM结构(在此时刻自己操作DOM不会生效,因为Vue在下一步会替换你的操作) -
mounted
** ⭐:挂载完毕,页面呈现经过Vue编译的DOM结构。完成了模板的解析并把初始的真正的DOM放入(即把虚拟DOM转化为真实DOM后,只调用一次), 至此初始化过程结束。一般再此开启定时器,发送请求,绑定自定义事件,订阅消息…等初始化工作** -
beforeUpdate
:此时数据是新的,但页面是旧的- 它两个之间:会生成新的虚拟DOM,和旧DOM比较是否可以复用,最终完成页面的渲染,Model -> View
-
updated
:此时数据是新的,页面也是新的 -
beforeDestroy
⭐:此时vm的data,motheds,指令都还可以用,马上执行销毁过程(但再次操作数据页面不在触发更新了),一般进行关闭定时器,取消订阅消息,解除自定义事件…等收尾工作(销毁可以调用vm.$destroy()) -
destroyed
:已经销毁
10. vue-cli
10.1 简介
vue-cli
官方提供的一个脚手架,可以快速生成一个vue项目模板。
主要作用:统一的目录结果,本地调试,热部署,单元测试,集成打包上线(类似后端的Maven
)
先安装
node.js
环境(npm随着nodejs自动安装),一路next
就行,会自动配置Path
环境变量,node -v/npm -v
可查看是否成功。(npm就是一个软件包管理工具,和Linux下的apt软件管理差不多)安装
node.js
淘宝镜像加速器cnpm
,这样后续下载会更快,注意:安装都要用管理员运行cmd
npm install -g cnpm --registry=https://registry.npm.taobao.org
(下载前可以修改一下
npm
默认的下载路径,防止占用C盘空间。-g代表全局安装)安装
vue-cli
cnpm install @vue-cli -g
:vue-cli最新版,输入vue ui
即可进入图形化创建项目界面
vue list
可查看可以基于哪些模板创建vue应用程序
10.2 创建项目
vue-cli3版本以上创建项目:
- 打开命令行,进入你想在哪创建的目录
vue create 项目名
- 选择Vue.js的版本 ,选完等待即可
cd 项目名
进入项目,npm run serve
运行项目- 浏览器访问
http://localhost:8080/
即可看到脚手架帮我们写好的HelloWord(ctrl+c停止)
或者进入你要创建到的目录,执行vue ui
图形化界面创建项目:
10.3 项目目录结构
文件目录
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件 (ES6 -> ES5)
├── package.json: 应用包配置文件
├── README.md: 应用描述文件,怎么使用
└── package-lock.json: 包版本控制文件
可以在项目目录下创建vue.config.js文件,修改一些默认的配置。配置项官网:配置参考 | Vue CLI (vuejs.org)
11. webpack
11.1 简介
webpack
:一个现代JavaScript应用程序的静态模块打包器(module bundler)。当webpack处理应用程序时,他会递归的构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有的这些模块打包成一个或多个bundle。当下最热的前端资源模块化管理工具和打包工具,可以将许多松散耦合的模板按照依赖和规则打包成符合生产环境部署的前端资源。
⭐ES6规范,EcmaScirpt6标准增加了JavaScirpt语言层面的模块体系定义。尽量静态化,使编译时就能确定模板依赖关系,以及输入输出的。容易进行静态分析,但有些原生浏览器还没正常这个。可以使用webpack把ES6规范打包成ES5规范。
import "jquery" export dunction doStuff() {} module "localModele" {}
11.2 安装使用
安装
webpack
:
cnpm install webpack -g
cnpm install webpack-cli -g
webpack -v
,webpack-cli -v
查看是否成功配置:创建
webpack.config.js
配置文件,有entry,output,module,plugins,resolve,watch等属性…(了解一下)
使用webpack
:
- 创建一个项目(目录),例如
D:\webpack-study
- 创建
modules
目录写js代码,要有这个主入口,main.js - 工程下创建
webpack.config.js
(看下面) - 运行
webpack
命令 - 打包后
html
页面引入用就行了
// webpack-study/webpack.config.js文件
module.exports = {
entry: "./modules/main.js", // 主程序入口,自动把入口所需要的东西全部打包进来
output: { // 打包输出到那?
filename: "./js/bundle.js"
}
....
};
然后运行webpack
命令,就会生成一个bundle.js
打包后的文件(打开看一下是个很长的一行)
可以使用webpack --watch
: 一直监听变化,js变化就重新打包(ctrl+c停止)
12. vuex
12.1 vuex原理
概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
- 全局事件总线,如果很多组件的话,实现共享数据(读/写)比较乱,麻烦
- vuex,不属于任何一个组件,要共享的数据都放到vuex中,读写两个api就能实现
什么时候使用vuex:
- 多个组件依赖同一状态
- 来自不同组件的行为需要变更同一状态
原理图:
12.2 基本使用
安装:
Vue 2 匹配 Vuex 3 。Vue 3 匹配 Vuex 4 。
我用的vue2,安装vuex3版本:
npm install vuex@3 --save
基本使用:
-
初始化数据state,配置actions,mutations,操作文件store.js
-
组件中读取vuex中的数据
$store.state.数据
-
组件中修改vuex中的数据
$store.dispatch('action中的方法名',数据)
,或者$store.commit('mutations中的方法名',数据)
若没有网络请求或其他业务逻辑,组件中也可越过actions,即不写
dispatch
直接调用commit
Count.vue相当于Controller,action相当于Service,mutatisons相当于Dao
src/store/index.js
该文件用于创建Vuex中最为核心的store,⭐⭐⭐
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex) // 应用Vuex插件
// 准备actions —— 用于响应组件中的动作 (可以复用)
// 一般把业务逻辑和发送ajax请求写在actions里面
const actions = {
/* 若没有网络请求或其他业务逻辑,组件中也可越过actions
jia(context,value){
console.log('actions中的jia被调用了')
context.commit('JIA',value)
},
jian(context,value){
console.log('actions中的jian被调用了')
context.commit('JIAN',value)
}, */
jiaOdd(context, value) { // context 相当于精简版的 $store
console.log('actions中的jiaOdd被调用了')
if (context.state.sum % 2) {
context.commit('JIA', value)
// context.diapath('demo', value) 如果业务复杂的话还可以继续,调diapath(),找actions的方法继续处理
}
},
jiaWait(context, value) {
console.log('actions中的jiaWait被调用了')
setTimeout(() => {
context.commit('JIA', value)
}, 500)
}
}
// 准备mutations —— 用于操作数据(state)
// 名字一般大写,便于和actions区分
const mutations = {
JIA(state, value) {
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state, value) {
console.log('mutations中的JIAN被调用了')
state.sum -= value
}
}
// 准备state —— 用于存储共享数据
const state = {
sum: 0 // 当前的和
}
// 准备getters —— 用于将state中的数据进行加工(类似计算属性,但这个可以复用,可以不写这个配置)
const getters = {
bigSum(state) {
return state.sum * 10
}
}
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
src/main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store, // vm和所有的组件实例对象有个$store对象了
render: h => h(App)
}).$mount('#app')
Count.vue
:单个组件,案例使用(多个组件操作$store.state都一样操作即可)
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1>
<h1>当前求和方法十倍为:{{ $store.getters.bigSum }}</h1> <!-- 注意不是在state里面了-->
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name:'Count',
data() {
return {
n:1, // 用户选择的数字
}
},
methods: {
increment(){
// this.$store.dispatch('jia',this.n)
this.$store.commit('JIA',this.n)
},
decrement(){
// this.$store.dispatch('jian',this.n)
this.$store.commit('JIAN',this.n)
},
incrementOdd(){
this.$store.dispatch('jiaOdd',this.n)
},
incrementWait(){
this.$store.dispatch('jiaWait',this.n)
},
}
}
</script>
<style lang="css">button{margin-left: 5px;}</style>
12.2 四个map方法
-
mapState
方法:用于帮助映射state
中的数据为计算属性例如:
$store.state.sum -> sum
,就不用写这么长了import {mapState} from "vuex" computed: { // 借助mapState生成计算属性:从state中读取数据,sum、school、subject(对象写法一) // ...是ES6语法,把mapState返回的对象每组k-v展开放到computed对应对象中 ...mapState({sum:'sum', school:'school', subject:'subject'}), // 借助mapState生成计算属性:sum、school、subject(数组写法二) ...mapState(['sum', 'school', 'subject']) }
-
mapGetters
方法:用于帮助映射getters
中的数据为计算属性import {mapGetters} from "vuex" computed: { //借助mapGetters生成计算属性:bigSum(对象写法一) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法二) ...mapGetters(['bigSum']) }
-
mapMutations
方法:用于帮助生成与mutations
对话的方法,即$store.commit(xxx)
的函数import {mapMutations} from "vuex" methods: { //靠mapActions生成:increment方法、decrement方法(对象形式) ...mapMutations({increment:'JIA', decrement:'JIAN'}), //(数组形式),这样的话使用方法就得<button @click="JIA(n)">+</button> ...mapMutations(['JIA','JIAN']), } // 生成的是这样的。和我们自己写的还是有一点区别的, // 自动生成的话就要传参了<button @click="increment(n)">+</button> methods: { increment(value) { this.$store.commit('JIA', value) } }
-
mapActions
方法:用于帮助生成与actions
对话的方法,即$store.dispatch(xxx)
的函数import {mapActions} from "vuex" methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd', incrementWait:'jiaWait'}) //(数组形式) ...mapActions(['jiaOdd','jiaWait']) } // 生成的样式和上面的一样也要传数据
mapActions,mapMutations使用时,若需要传递参数,在模板中绑定事件时传递好参数,否则参数是事件对象。
12.3 模块化+命名空间
目的:让代码更好维护,让多种数据分类更加明确
修改store/index.js
文件,为了解决不同模块命名冲突的问题,将不同模块的namespce: true
,之后在不同页面中引入getter, actions, mutations
时,需要加上所属的模块名。
// 求和相关的配置 (可以把这个countAbout写到一个文件中,然后暴露)
const countAbout = {
namespaced: true, // 开启命名空间(默认false)
state: {x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state) { return state.sum * 10 }
}
}
// 人员相关的配置
const personAbout = {
namespaced: true, // 开启命名空间
state: { ... },
mutations: { ... },
actions: { ... }
}
// 总配置
const store = new Vuex.Store({
modules: {
countAbout: countAbout, // 简写countAbout
personAbout: personAbout
}
})
获取数据:
// 自己写:
this.$store.state.countAbout.sum
this.$store.commit('countAbout/JIA',this.n)
this.$store.getters['countAbout/bigSum']
// 自动生成:
computed: {
// 第一个参数可以指定分类,从countAbout中去后面的state中数据进行映射
...mapState('countAbout', ['sum', 'school', 'subject']),
...mapState('personAbout', ['personList'])
},
methods: {
// 这个也要指定命名空间(其他两个map方法一样)
...mapMutations('countAbout', {increment:'JIA', decrement:'JIAN'})
}
13. vue-router
13.1 简介
vue-router
:官方的路由管理器,它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌
专门用来构建SPA(single page web application)单页面应用,而且点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
功能:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式,在 IE9 中自动降级
- 自定义的滚动条行为
Vue 2 匹配 Vue-Router 3 。Vue 3 匹配 Vue-Router 4 。
安装路由插件:当前项目下安装
vue-router
,进入当前项目目录 (我用的vue2,先安装vue-router3版本)
- nmp:
cnpm install vue-router@3 --save-dev
- cdn:
<script src="https://unpkg.com/vue-router@3.0.0/dist/vue-router.js"></script>
13.2 简单使用
单HTML路由演示:
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用router-link组件来导航,通过传入to属性指定链接,<router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口,路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
<script>
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
const Foo = { template: '<div>foo</div>' } // 可以从其他文件 import 进来
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器或者,只是一个组件配置对象。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
// 4. 创建和挂载根实例。记得要通过 router 配置参数注入路由,从而让整个应用都有路由功能
new Vue({
router // router: router
}).$mount('#app')
</script>
13.3 单文件注册路由
src/router/index.js
:(配置路由)
// 该文件专门用来创建整个应用的路由器
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue' // 一般组件放到commponents文件夹里,路由组件放到views或pages里
import Test from '../views/Test.vue'
import Abc from '../views/Abc.vue'
Vue.use(VueRouter) // 应用路由插件
const routes = [
{
path: '/',
name: 'home', // name可以简化路由跳转,如果是二级路由就不用带一级路由的路径了,直接写name即可
component: HomeView
},
{
path: '/test',
name: 'test',
component: Test,
children: [ // 嵌套路由,记得在Test组件中指定Abc组件的渲染位置,即使用<router-view>
{
path: 'abc' // 注意不用加/了,只需要一级路由加
component: Abc
}
]
},
{
path: '*', // 上面匹配不到就去下面指定的
redirect: '/'
}
]
const router = new VueRouter({
mode: 'history', //代表路由模式,默认是hash,带#号的那种
base: process.env.BASE_URL, //应用的基路径,process.env.BASE_URL指从环境进程中根据运行环境获取的api的base_url
routes //具体的路由配置列表
})
export default router // 导出
src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 默认是: import router from './router/index.js'
Vue.config.productionTip = false
new Vue({
router, // 安装了路由就有router这个配置项
render: h => h(App)
}).$mount('#app')
实现切换使用<router-link>
标签,active-class
属性可以设置高亮样式,vue帮我们自动切换高亮。
指定展示位使用<router-view>
标签
当切换路由时,路由组件是被频繁销毁挂载的,可以使用keep-alive标签把要缓存组件包起来,让不展示的路由组件保持挂载,不被销毁
13.4 路由跳转/传递接受参数
⭐⭐⭐⭐
-
<router-link to="/path"></router-link>
,本质是个a标签,a标签的属性都可以写。<router-link to="/path"></router-link> <!-- 不传递参数可以这么写--> <!-- 传递数据to字符串写法,obj.val是data里面的数据,注意有个``号--> <router-link :to="`/path/?val=${obj.val}`"></router-link> <!-- to的对象写法!可以配置name属性,params参数..--> <router-link :to="{ path: '/path', query: {val: obj.val} }"></router-link>
-
this.$router.push()
这种一般是按钮之类的使用这种跳转方式
this.$router.push({ path: "/lujing", // name: "lujing" 也行,前提得配置好路由名 query: { value: 2 } // 传递参数,会用&拼接到url中,即 /lujing?value=2 // 传递参数,会用/拼接到url中,即 /lujing/2 (router路由器中需要配置占位符,即 path: '/lujing/:value') // 注意:使用params传参不能用path,只能用name! name: "lujing", params: { value: 2 } })
-
this.$router.replace()
用法和上面的push一样,只是history
栈中不会有记录(一般做404页面) -
this.$router.go(n)
n为1,即在浏览器记录中前进一步,
this.$router.forward()
n为 -1,即后退一步记录,等同于
this.$router.back()
this.$router对象是全局路由的实例(整个应用的路由器),是router构造方法的实例。 有push
,replace
,go
方法
this.$route对象表示当前的路由信息/路由规则,包含了当前 URL 解析得到的信息。
// 接受参数:
this.$route.query.xxx // 取query参数
this.$route.params.xxx // 取params参数
// 怎么模板中减少重复写this.$route.query.xxx?? 下面只是为了让路由组件更方便的收到参数!
// 如果是params传参,在路由规则中把props设置为true,使用组件中props接受,直接写{{xxx}}来获取数据
{
name:'test',
path:'test/:id',
component: Test,
// 第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Test组件
// Test组件中页需要props接受, props: ['a'],就可以直接使用了(使用少,传递的是死数据)
// props: {a: 900}
// 第二种写法:props值为布尔值,为true时,则把路由收到的所有params参数通过props传给Test组件
// Test组件中页需要props接受,props: ['id'],这样使用的时候就不用写this.$route.params.xxx了,直接使用id即可
// props: true
// 第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Test组件
// Test组件中页需要props接受,props: ['id']
props($route) {
return {
id: $route.query.id
}
}
// ES6的解构赋值操作
props({query}) {
return {
id: query.id
}
}
}
//
// this.$route对象属性:
fullPath: "/“ // 全路径
hash: ""
matched: [] // 返回一个数组,包含当前路由的所有嵌套路径片段的路由记录
meta: {} // 可以附加一些数据
name: null // 路径名name
params: {} // params参数
path: "/" // 当前的路径path
query: {} // query参数
这两个每个组件实例对象都有,router每个组件都相同是一个对象。route每个组件都不同,存放这自己的路由信息。
13.5 两个钩子
如果组件里面有定时器,并且我们写了在beforeDestory()
里面进行关闭定时器,销毁的时候会关闭定时器。
但如果我们使用keep-alive
缓存路由组件的话,就不会销毁组件,所以定时器也不会关闭,一直开着。所以有了下面这两个钩子:
路由组件所特有的两个生命周期钩子函数(类似:mounted -> activated, beforeDestory -> deactivated)
- activated 激活
- deactivated 失活
<template>
<ul>
<li :style="{opacity}">欢迎学习vue</li>
<li>news001 <input type="text"></li>
<li>news002 <input type="text"></li>
</ul>
</template>
<script>
export default {
name:'News',
data(){
return{
opacity:1
}
},
activated(){
console.log('News组件被激活了')
this.timer = setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
deactivated(){
console.log('News组件失活了')
clearInterval(this.timer)
}
}
</script>
13.6 路由守卫
对路由进行权限控制,类似拦截跳转
全局全局守卫:
- 全局前置路由守卫
- 全局后置路由守卫
src/router/index.js
:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Test from '../views/Test.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/test',
name: 'test',
component: Test,
meta: { // 一个一个判断path是否需要守卫太麻烦,可以标记一个值,来判断是否需要守卫,里面可以自定义一些属性
title: '测试',
isAuth: true
}
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
暴露之前加入一些守护规则 !!
// 全局前置路由守卫 -> 每次路由切换之前被调用(初始化的时候也会被调用一次)
// to是要跳转取路由信息, from的当前路由信息,这两个都是路由信息对象$route。
router.beforEach((to, from, next) => {
if (to.path == '/test' && sessionStorage.getItem('user') !== 'admin') {
alert("你需要admin身份才能访问");
} else {
next(); // 放行
}
// 如果自己配置类路由元信息即meta可以这么写!
if (to.meta.isAuth && sessionStorage.getItem('user') !== 'admin') {
alert("你需要admin身份才能访问");
} else {
next();
}
});
// 全局后置路由守卫 -> 每次路由切换之后被调用(初始化的时候也会被调用一次)
// 没有next(用的不多,可以用来改变一些网页的title)
router.aferEach((to, from) => {
alert("666");
})
export default router // 暴露
独享路由守卫:(注意没有后置路由守卫!!!)
import Vue from 'vue'
import VueRouter from 'vue-router'
import Test from '../views/Test.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/test',
name: 'test',
component: Test,
beforeEnter: (to, from, next) => {
// ...
}
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router // 导出
组件内守卫:
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
<template>
<div>666</div>
</template>
<script>
export default {
name: "Test",
// 通过路由规则,进入当前组件前被调用
// 当调用这个方法,to永远是当前Test组件路由相关信息
beforeRouteEnter(to, from, next) {
console.log("beforeRouteEnter");
if (to.meta.isAuth && sessionStorage.getItem('user') !== 'admin') {
alert("你需要admin身份才能访问");
} else {
next();
}
},
beforeRouteUpdate(to, from, next) { // 反复进入这个组件,参数变化时
console.log("beforeRouteUpdate");
next();
},
// 离开当前组件时被调用,即切换走当前组件才调用(和全局后置守卫有点区别,全局后置守卫是切换成功后立刻调用)
// 当调用这个方法,from永远是当前Test组件路由相关信息
beforeRouteLeave(to, from, next) {
console.log("beforeRouteLeave");
if (confirm("你确定要离开吗?")) {
next();
} else {
next(false);
}
}
}
</script>
13.7 路由两种模式
-
对于一个url什么是hash值?
#
及其后面的内容就是hash值 -
hash值不会包含到http请求中,即hash值不会带给服务器
hash模式:
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
history模式:
- 地址干净,美观
- 兼容性相比hash模式略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
切换:
const router = new VueRouter({
mode:'history',
routes:[...]
})
export default router
14. 组件库
移动端:Vant,Cube UI,Mint UI
PC端:Element UI,IView UI
ElementUI插件安装:
npm i element-ui -S
i
是install
的缩写
-S
是--save
的缩写,局部安装,写入到dependencies
对象。
-D
是--save-dev
的缩写,局部安装,模块写入到devDependencies
对象。总结:运行时需要用到的包使用–S,否则使用–D
安装SASS加载器
cnpm install sass-loader --save-dev
cnpm install node-sass --save-dev
src/main.js
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'; // 1. 引入ElementUI组件库
import 'element-ui/lib/theme-chalk/index.css'; // 2. 引入ElementUI全部样式
Vue.config.productionTip = false
Vue.use(ElementUI) // 3. 使用ElementUI(所有组件,有点大)
new Vue({
el:"#app",
render: h => h(App),
})
src/App.vue
<template>
<div>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
</div>
</template>
<script>
export default {
name:'App',
}
</script>
按需引入⭐⭐:
安装:
npm install babel-plugin-component -D
修改
src/babel.config.js
文件:module.exports = { presets: [ '@vue/cli-plugin-babel/preset', // 这个是一开始就有的,其他是配置的 ["@babel/preset-env", { "modules": false }] ], plugins: [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
src/mian.js
import Vue from 'vue' import App from './App.vue' import { Button,Row } from 'element-ui' // 按需引入组件(样式会根据用的组件自动引入!!) Vue.config.productionTip = false Vue.component(Button.name, Button); // Vue.use(Button) Vue.component(Row.name, Row); // Vue.use(Row) new Vue({ el:"#app", render: h => h(App), })