Vue学习笔记
🍸🍸🍸
1.前置条件
- 了解命令行(不会的再百度)
- 安装最新版本Node.js
2.引入vue
2.1自己引入到html中
1 | <!--2.1.1本地引入--> |
2.2通过HBuilder X创建vue项目
HBuilder X默认生成index.html如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<meta charset="utf-8" />
<title></title>
<!-- <script src="https://unpkg.com/vue@next"></script> -->
<script src="js/v3.2.8/vue.global.prod.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
{{ counter }}
</div>
<script>
const App = {
data() {
return {
counter: 0
}
}
};
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
2.3使用官方脚手架构建项目环境
命令行输入:1
npm install @vue/cli -g
下载官方脚手架到全局,可以快速搭建Vue开发环境以及对应的webpack配置。
- npm:Node Package Manager,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准,经常使用NPM来安装一些开发过程中依赖包。
- CLI:Command-Line Interface, 译为命令行界面, 但是俗称脚手架。
- Webpack:用于现代 JavaScript 应用程序的静态模块打包(Bundle)工具,便于模块化开发。
1 | vue create 项目名称 |
接下来会让你选择一些默认要生成的工具,比如:
([Vue 2]babel、eslint)
([Vue 3]babel、eslint)
按需选择即可。
进入项目文件夹:1
cd 项目名称
启动:1
npm run serve
2.4HBuilder X编辑器创建vue-cli项目
(见2.2图)
2.5自己构建vue脚手架、可视化项目管理方式……
(还没试过……)
3.Vue内容(按序学习)
3.1 vue基础
1. 双向数据绑定
双向数据绑定,是指视图 View 的变化能实时地让数据模型 Model 发生变化。写在标签属性里,v-bind为单向绑定属性,即修改data中的数据可以实现标签内容的变化,一般写法为v-bind:name1=”name2”,简写为:name1=”name2”,name1为元素本身的属性,name2为data中的数据;v-model为双向绑定属性,元素必须是表单元素,可以通过data中的数据修改该属性值,也可以通过页面表单数据填写修改data数据。
v-model.lazy:不用实时收集,失去焦点的时候才收集
2. 数据代理
数据代理:通过一个对象代理另一个对象中的属性的操作,在vue中就是通过vm中的data中的对象来代理元素属性值,通过Object.defineProperty()方法,为每个添加到data中的对象添加一个getter/setter,借此方法改变与此绑定的标签属性值。
Vue.set(this.data1,’prop1’,’value1’)可以为data中的某个对象增加属性。
3. 事件 & 事件修饰符
事件:用v-on:click=”name1(a,b)”,简写为@click=”name1(a,b)”。事件修饰符的使用如@click.once=”name1”。
Vue共有6个事件修饰符:
1.prevent:阻止默认事件(常用)。如:阻止a标签跳转默认超链接。
2.stop:阻止事件冒泡(常用)。如:添加在内层标签上,只执行内层事件,阻止外层事件被触发。
3.once:事件只触发一次(常用)
4.capture:使用事件的捕获模式。 如:内外标签都有同一事件,为外层标签添加capture修饰符,在内标签触发该事件,则先处理外标签事件,再处理内标签事件。在标签层级中,先捕获后冒泡,捕获是从外往内,冒泡是从内往外,正常是在冒泡阶段处理事件。capture在捕获阶段就处理事件了。
本质是用e.stopPropagation()阻止冒泡,vue用事件修饰符封装了这个方法。没有capture的话是先打印2然后1,有了capture则是先打印1然后2。
5.self:只有e.target是当前操作的元素时才触发事件。(也可用于阻止冒泡)如:内外标签都有同一事件,为外层标签添加self修饰符,在内标签触发该事件,则只处理内标签事件。
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕。如:@scroll.passive=”demo”,无须等待demo执行完毕,先执行滚动操作。
4. 插值语法
插值语法:data中的对象可以放到标签体双花括号内作为内容使用,也可以放在标签v-model属性中作为属性值。
5. 计算属性(computed)
1 计算属性相比方法(methods)的优势:计算一次就有缓存,方法每调用一次就计算一次。
2.计算属性的完整写法包含get和set,从set获取参数并对变量赋值,到get计算并返回;简写只要从this中获取data中的数据直接计算返回。
3.计算属性表面上是一个函数,实际上是给vm加了一个属性,它的值是这个方法调用的结果。
6. 监视属性
监视属性(watch):在vm对象中,直接配置watch:{…data1:{handler()…}(data1改变时调用)…}来监视;或在vm对象外,vm.$watch(‘data1’,immediate:true,handler(newValue,oldValue){…}),两种写法。也能监视计算属性。
1.当被监视的属性变化时,回调函数handler自动调用,进行相关操作
2.监视的属性必须存在,才能进行监视
3.不使用immediate和deep时可以简写,简写时可省略handler和其它属性
4.watch可以很畅快的开启一个异步任务,计算属性不行
5.v-for+watch+filter箭头函数可以实现列表过滤(v-for+computed+filter箭头函数也可实现)
7. 深度监视
1.监视多级结构中某个属性的变化:在new Vue的watch配置中用’data1.a’表示要监视的属性;
2.监视多级结构中所有属性的变化:在new Vue的watch配置中添加deep:true。
8. 绑定样式
绑定样式:写为:class=”data1”,data1记录在data中,可以是字符串,也可以是数组,也可以是对象(但要动态决定用不用);也可以写为:style=”data1”,在data中书写样式。
9. 条件渲染
1.v-if:判断元素是否显示。可用v-if/v-else/v-elseif,但条件多时建议用计算属性。v-if在首次渲染时,如果条件为false,页面上不生成该元素;v-if本质是新增和删除元素。
2.v-show:切换元素是否显示。原理是修改元素的display,实现显示隐藏。写法:v-show=”data1” (data1=true;(or false))。v-show不管条件真假,第一次都会渲染出来;v-show本质是操作元素的style属性。
10. 基本列表(li)
1.数组写法:v-for=”d in data1” :key=”d.id”(id是每一项的唯一标识)
数组的七个修改方法:push,pop,shift,unshift,splice,sort,reverse
2.对象写法:‘v-for=”(d,index) of data1” :key=”index”’(data1的位置可以是对象,可以是字符串,可以是数字(遍历次数))。
3.index是默认的key,但最好用d.id作为key,避免数据更新后index发生变化。
11. 过滤器
1.局部过滤器:在vue实例对象中添加filters属性,写法和methods类似,第一个参数接收前面传过来的返回结果,第二个及以上可以接收调用时从标签中传给自己的数据,可以为该形参赋默认值,传过来的参数优先。1
2
3
4
5
6
7
8
9
10
11new Vue({
el:'#root',
data(){
msg:'hhh5555'
},
filters:{
mySlice(value){
return value.slice(0,4)
}
}
})
2.全局过滤器:写在vue实例对象前面1
2
3Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
3.使用:过滤器仅用于插值语法和v-bind,不能用于v-model1
2
3
4<div id="root">
<h2>{{msg|mySlice}}</h2>
<h3 :x="msg|mySlice"></h3>
</div>
12. 内置指令
v-text
v-html
v-cloak:在使用{{}}展示或更新页面数据时,可能因为网速延迟显示数据,比如先出现{{msg}}在页面上,可用以下样式解决问题;也可用v-text代替{{}},v-text没有延迟。
1
2
3 [v-cloak]{
display: none ;
}v-once
v-pre:跳过这个元素和它的子元素的编译过程,跳过大量没有指令的节点会加快编译(比如跳过不需要vue编译的标签)。
13. 自定义指令
1.全局自定义指令:用Vue.directive来注册。
2.局部自定义指令:在组件内设置directives属性。
自定义指令的执行时间:指令与元素成功绑定(在元素中绑定指令后,一上来就会自动执行);指令所在的模板被重新解析时。
14. 生命周期
1.beforeCreate:刚初始化了一个空的Vue实例对象(new Vue()),data和methods中的数据还没初始化,一般在这个阶段不操作。
2.created:data和methods都被初始化好,但此时还是虚拟DOM,真实DOM还没生成,可以最早调用methods中的方法,或调用data中的数据。
3.beforeMount:模板已在内存中编译好(渲染),但尚未挂载到页面中去。
4.mounted:最早通过某些插件操作页面上的DOM节点;执行完则Vue实例初始化完毕;组件脱离创建阶段,进入运行阶段。
5.beforeUpdate:(data发生变化后)此时data是最新的,但还没有更新到页面上取去。
6.updated:已完成data(Model层)->view(视图层)的更新,页面和data中的数据都是最新的。
7.beforeUnmount:Vue实例从运行阶段进入销毁阶段,但实例身上的所有data、methods、过滤器、指令……仍可用。
8.unmounted:此时组件已被完全销毁,组件中的data、methods、过滤器、指令……都已不可用。
15. 非单文件组件
1 | //1.创建组件 |
可以定义一个父组件app,然后使用components属性将其它组件嵌套在内。
可以将vm中的组件作为html标签使用,也可以在vm中定义template属性,该组件会直接出现在root的div内。
16. 单文件组件
1.准备一个html容器,包含root div,只需引入vue.js和main.js。
2.在main.js中引入(import)父组件App,并创建vue实例vm。
3.在App.vue中引入其它嵌套组件并使用……
3.2 vue-cli
1. 定义
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统。
2. 安装:
- 首次全局安装@vue/cli
npm install -g @vue/cli
。 - 在自定义目录下,可以通过
vue create xxx
快速搭建一个新项目。 - 启动新项目:
npm run serve
。
3. 项目组成
项目组成:gitignore用于存储需要git管理的组件;babel.config.js用于ES6=>ES5的转化;package.json和package-lock.json只要是npm管理下的包都会生成,存储webpack的配置。
脚手架文件结构:
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
4. 组件结构
vm->App(一人之下,万人之上😎)->其它组件。
5. 脚手架主页面文件内容:
1 |
|
Q1:为什么主页面没有引入main.js,id=app的div却能使用?
A1:脚手架配置好了。Q2:为什么在main.js中Vue实例对象要用render引用App?
render: h => h(App)
A2:如果用原本的属性template和component引入App,会报错“缺少模板解析器(引用了残缺版的Vue)”。解决报错的办法:①把需要编译的模板交给render函数;②使用包含编译器的Vue版本(完整版)。
import Vue from 'vue'
这句话中的vue来自“/node_modules/vue”,取的是vue文件夹这一目录,根据ES6模块化语法,找到vue下的package.json,”module”对应的值即从vue文件夹下真正获取的文件,而它的值”dist/vue.runtime.esm.js”是残缺版的Vue,这个文件缺少模板解析器,所以读不了template属性。- 完整版是“vue/dist/vue.js”,可以读取template属性。
- render函数的完整写法:
1
2
3
4
5
6
7 render(createElement){
return createElement('h1','你好啊');
};
//render没用到this,所以可以写成箭头函数:
render:createElement=>createElement('h1','你好啊')
//进一步简化:
render:q=>q('h1','你好啊')- render没用到this,所以可以写成箭头函数。
- render可以帮忙渲染模板(render函数接收到的createElement函数去指定内容)。
- Vue的两个组成部分:核心功能+模板解析器。
- /vue/dist中,带有runtime的vue文件就是运行时Vue,都不带模板解析器,体积更小,且模板解析器不应该出现在打包好的文件中,所以借用了render而不用自带解析器的完整版的Vue。
- 带有esm的vue文件代表ES6的modules模块化。
- Vue为vue文件里的template标签找了”vue-template-compiler”进行编译,见全局的package.json文件。
6. 脚手架默认配置
1.项目下执行vue inspect > output.js
可以得到一个一千多行显示所有脚手架隐藏配置的js文件,只输出显示,修改对实际配置无效。
不能改的默认配置:public文件夹(文件夹内网站图标仅名字不能改),src文件夹名不能改,main.js名字不能改。
2.如何修改默认配置(个性化定制😉):所有出现在Vue CLI官网左侧的配置参考都能改(“查字典”😲),配置时新建vue.config.js文件放在和package.json同级的目录下。Vue会将vue.config.js输送给webpack,webpack基于Node.js,Node.js采用的模块化是common.js,Vue会将vue.config.js里的配置和webpack里的做对比和合并,所以vue.config.js使用的是common.js的语法。
3.vue.config.js内,同一属性内不能没有值,但可以只修改一个,其它默认项还是会使用原来的。
Q1:报错’Component name “Aaa” should always be multi-word’如何解决?
A1:在vue.config.js文件中module.exports下添加lintOnSave: false
;或者将组件名按大驼峰命名。
1
2
3
4 module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false
})
7. ref属性
1.methods里的this是vc(VueComponent)(App的实例对象),vc下面有$refs属性,只要在标签内增加ref属性就会在这里显示(显示所有加了ref属性的标签)。如:为h1标签增加属性ref="title"
,在methods中输入this.$refs.title
即可得到该h1标签,相当于取代了id的作用,避免直接操作DOM。
2.如果为组件标签添加ref属性,能得到的是该组件的vc实例对象。如果为组件标签添加id属性并获取,id将赋给该组件内部最外层html标签,并输出组件内html全内容。
8. props配置:父组件向子组件传参
1 | <!-- 父组件: --> |
props的优先级比data更高。
在子组件中,props只读。
子组件向父组件传参
- 方法一:父先给子传一个methods(放在v-bind),子通过props接收,再调用该方法并传参,父就会收到参数数据。
“父亲给儿子一个收集器,儿子去用,用完还给父亲,数据也在父亲那。”
“老子偷偷给儿子一个锦囊,让他在有困难的时候再打开。”这两个方法结合起来就是“组件间通信”,可以实现简易的兄弟组件间传参,案例见ToDoList小项目的输入框回车给列表增加选项功能。
- 方法二:不使用props,在父文件使用@符传递方法
@method1="method1"
,在子文件调用this.$emit('method1',value)
实现传值。 - 方法三:全局事件总线。
9. mixin混入
mixin混入:两个组件共享一个配置(内容相同,复用)。
1.在main.js同一层级内新建js存放复用内容,例如某个methods:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16export const hunru={ //默认暴露(export default)是暴露全部,这里是分别暴露
methods:{
showName(){
alert(this.name);
}
}
//也可配置data,会和组件文件的data进行整合,以组件文件中的data优先。(methods同理)
//但是对于生命周期钩子,两者都执行,先组件文件,后混入文件。
}
//在使用该methods的组件中引入:
import {hunru} from "..."
export default{
name:'School',
data(){...},
mixins:[hunru]
}
2.也可以在main.js中进行全局配置:1
2
3import {hunru,hunru2} from "..."
Vue.mixin(hunru)
Vue.mixin(hunru2)
10. 插件
插件:Vue中插件的本质是包含install方法的对象,第一个参数是Vue,第二个及以后的参数是插件使用者传递的数据。
在main.js的同一层级创建插件js(标准命名是plugins.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
39export const obj={
install(Vue){
//console.log("@@@123install",Vue)//这个形参是Vue的构造函数
//可以为vm上添加全局的过滤器、指令、混入、实例方法:
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
//全局指令
Vue.directive('fbind',{
bind(element.binding){
element.value=binding.value
},
inserted(element,binding){
element.focus()
},
updated(element,binding){
element.value=binding.value
}
})
//全局混入
Vue.mixin({
data(){
return{
x:100,
y:200
}
}
})
//给Vue原型上添加一个方法
Vue.prototype.hello=()=>{alert('你好啊')}
}
}
//步骤:先应用插件,再创建vm。
//在main.js中:
import plugins from "./plugins"
Vue.use(plugins)
一个优秀的插件能提供很多强大的功能(增强💪)。
11. scoped样式
添加在style标签体内,限制样式在同一组件内。
一般不给App.vue添加scoped,一般App.vue里的样式用于全局,加了scoped就只限制在App.vue文件内了。
- 可以给style标签添加属性lang=””(css/less,默认是css)
- 脚手架需要安装less-loader:
npm i less-loader@7
(装6/7版本,以对应webpack的版本) - 检查webpack版本:
npm view webpack versions
- 检查less-loader版本:
npm view less-loader versions
12. 自定义事件
1.<Student v-on:nihao="haha"/>
,简写为<Student @nihao="haha"/>
给Student组件绑定了一个事件nihao,在使用Student组件的页面增加回调methods:haha;
在Student组件页面增加一个可以触发html事件的标签,为这个标签绑定事件,在methods中通过this.$emit('nihao')
触发自定义事件。
增加一个参数可将数据子传父this.$emit('nihao',this.name)
。
2.如果有三秒后再触发的需求,使用ref属性(比如<Student ref="student">
),添加mounted事件:1
2
3
4
5mounted:{
setTimeout(()=>{
this.$refs.syudent.$on('nihao',this.haha) //haha是写在methods里的函数,或者直接在这个位置写箭头函数。
});
}
加上props方法就一共有三种子传父传参的方法了。
3.自定义事件的解绑:在被使用的组件文件内,对html元素事件增加this.$off('nihao')
。
写成数组可以解绑多个事件。
4.自定义事件的销毁:this.$destroy()
,销毁的是该组件的实例,其子组件和子组件的自定义事件也被销毁了。同样可以写在mounted里定时销毁。
5.组件上也可以绑定原生DOM事件,但需要使用native修饰符。
13. 全局事件总线
在main.js中添加:1
2
3
4
5
6
7new Vue({
...
beforeCreate(){
Vue.prototype.$bus=this //全局所有的vc和vm都能看到$bus
},
...
})
总线在组件文件中的调用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18export default{
name:'Compo1',
data(){...},
methods:{
demo(data){
...
}
},
mounted(){
this.$bus.$on('nihao',this.demo)
this.$bus.$on('nihao2',(data)=>{ //绑定自定义事件nihao
console.log('我是Compo1组件,收到了数据',data)
})
},
beforeDestroy(){
this.$bus.$off(['nihao','nihao2']) //$off后一定要加内容(解绑部分自定义事件),否则就把整个总线都删掉了
}
}
14. 消息的订阅与发布
1.一种组件间通信的方式,适用于任意组件间通信。
2.安装:npm i pubsub-js
3.引入:import pubsub from 'pubsub.js'
4.接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。1
2
3
4
5
6
7methods(){
demo(data){...}
},
...
mounted(){
this.pid=pubsub.subscribe('xxx',this.demo)
}
5.提供数据:pubsub.publish('xxx',数据)
6.最好在beforeDestory钩子中,用PubSub.unsubscribe(pid)
去取消订阅。
15.nextTick
可以在DOM解析完后再执行的方法:this.$nextTick(function(){...})
16.动画
Vue封装的过度与动画:在插入、更新和移除DOM元素时,在合适的时候给元素添加样式类名。
1.为添加动画的元素外面包一层<transition>
标签,如果transition没有name属性,则添加默认动画css:1
2
3
4
5
6
7
8
9
10.v-enter-active{/*表示元素进入*/
animation:donghua 1s;
}
.v-leave-active{/*表示元素离开*/
animation:donghua 1s reverse;
}
@keyframes donghua{
...
}
2.如果transition添加了属性name="hello"
,则css写为:1
2
3
4
5
6
7
8
9
10.hello-enter-active{/*表示元素进入*/
animation:donghua 1s;
}
.hello-leave-active{/*表示元素离开*/
animation:donghua 1s reverse;
}
@keyframes donghua{
...
}
3.transition多个元素的情况,配置新的标签,且children要配key:1
2
3
4<transition-group>
<h1 key="1">你好1</h1>
<h1 key="2">你好2</h1>
</transition-group>
4.可集成的第三方库:animate.css
17.配置代理
借助Vue-CLI巧妙地解决ajax请求跨域的问题
常用的发送ajax请求的方式:
- xhr: new XMLHttpRequest() xhr.open() xhr.send()
不常用,一般都将xhr封装起来使用,可能是公司内部自己封装,也可能采用第三方的库。- jQuery: $.get $.post (xhr的封装)
jQuery的核心是封装DOM操作(80%),而Vue是减少DOM操作,不适合在Vue项目中使用- axios: promise风格,支持请求拦截器和响应拦截器,体积小,使用广泛 (xhr的封装)
1 npm i axios ::在对应的项目下安装axios- fetch: 不是xhr的封装,promise风格,但会把返回的数据包两层promise,两次.then才能拿到东西,兼容性不高
- vue-resource:vue里的插件库,用Vue.use(xxx)使用,在vc中显示为属性$http(了解)
1 npm i vue-resource
使用axios发送请求:
先学习一下使用node.js搭建自己的服务器和使用node.js搭建一个简单的web服务器 响应JSON、html
在项目主目录下创建server.js文件,返回的可以是字符串可以是json数据。
同源策略:
- 协议名一致
- 主机名一致
- 端口号一致
以http://localhost:8080
为例,http是协议名,localhost是主机名,8080是端口号。
三个条件同时满足才不会跨域。
跨域时的控制台报错关键词:’CORS’, ‘Access-Control-Allow-Origin’
同源策略是浏览器最核心也最基本的安全功能。为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收 。可以说Web是构建在同源策略基础之上的。解决跨域的方法:
- cors:返回特殊的响应头允许接收,是真正的解决跨域)
- jsonp:很巧妙,借助script标签里的src属性引入外部资源时不受同源策略限制;但是需要前后端配合,只能解决get请求的跨域问题
- 代理服务器:类似中间商,我们和代理服务器之间符合同源策略可以获取数据;代理服务器和目标服务器都是服务器,服务器之间使用http请求,不受同源策略影响。
反向代理服务器如何配置:
3.1 nginx:学习成本高
3.2 vue-cli:在vue.config.js中配置devServer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 //3.2.1 方式一:
//写在vue.config.js中:
module.exports = ({
...
devServer:{
proxy:'请求的目标路径' //比如http://localhost:5000
}
})
//写在请求数据的vue对象中:
methods:{
getStudents(){
axios.get('http://localhost:8080/students').then(
response=>{
console.log('请求服务器1成功了',response.data)
},
error=>{
console.log('请求服务器1失败',error.message)
}
)
}
}缺点:
3.2.1.1 不能配置多个代理
3.2.1.2 不能灵活地控制走不走代理
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 //3.2.2 方式二:
//写在vue.config.js中:
module.exports = ({
...
devServer:{
proxy:{
'/trytry':{
target:'http://localhost:5000',
//ws:true,
//changeOrigin:true
}
/*'/api':{
target:'<url>',
ws:true,
changeOrigin:true
},
target:'<other_url>'
}*/
}
}
})
//写在请求数据的vue对象中:
methods:{
getStudents(){
axios.get('http://localhost:8080/trytry/students').then(
response=>{
console.log('请求服务器1成功了',response.data)
},
error=>{
console.log('请求服务器1失败',error.message)
}
)
}
}
18.插槽
- 默认插槽
组件标签写为普通标签结束格式,不用自结束格式,在标签体中写的内容在组件中显示在什么地方:在组件中用插槽<slot></slot>
安放。slot标签用于占坑。1
2
3
4
5
6
7
8<!-- 父组件: -->
<Fish>①</Fish>
<!-- 子组件: -->
<div>
<h1>fishfish</h1>
<slot>②</slot>
</div>
如果位置①没有内容,会显示位置②内容;
如果位置①有内容,位置①的内容显示在位置②的slot位置,位置②内容隐藏。 - 具名插槽
在组件标签体中添加需要的元素及slot属性1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- 父组件: -->
<Fish>
<div slot="center"></div><!--div往center插槽里放-->
<img slot="footer"><!--img往footer插槽里放-->
<img slot="footer">
<template v-slot:footer>...</template>
</Fish>
<!-- 子组件: -->
<div>
<h1>qqq</h1>
<slot name="center">111</slot>
<slot name="footer">222</slot>
</div> - 作用域插槽
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
3.3 vuex
1.定义
专门在Vue中实现集中式管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
2.使用场景
- 多个组件依赖同一状态
来自不同组件的行为需要变更同一状态
3.原理:
Actions,Mutations,State是对象
- vuex的运行得经过store的管理
- vc可以直接commit到Mutations
4.搭建环境
npm i vuex
vue2中要用vuex3,vue3中要用vuex4import... Vue.use(Vuex)
- 在vm中配置store
1
2
3
4
5new Vue({
...
store,
...
}) - vc ==> store (所有组件的实例对象都能看见store)
在src下创建文件夹store,store下创建index.js,内容:现在vm和所有的vc身上都能看到store了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//该文件用于创建Vuex中最核心的store
//引入Vuex
import Vuex from 'vuex';
//准备actions:用于响应组件中的动作
const actions={};
//准备mutations:用于操作数据(state)
const mutations={};
//准备state:用于存储数据
const state={};
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
}); - 在main.js中引入store
注意:即使写在Vue.use(Vuex)后面:import store from './store'
,也不行,js会扫描整个文件然后把所有import提到最前面;
所以,应该把Vue.use(vuex)放到store/index.js里,引用Vue之前还要再引入一次Vue:再在main.js中引入store1
2
3import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);import store from './store'
5.调用
- 组件中methods里调用
this.$store.dispatch('fn1',this.a)
,前面是方法名,后面是传入的参数 - store中在actions中添加需要调用的方法,并调用commit
1
2
3
4
5
6const actions={
fn1(context,a1){//context是一个mini版的$store
...
context.commit('FN1',a1);//用大写作个区分
}
}; - 调用mutations中的方法
1
2
3
4
5const mutations={
FN1(context,a1){//context是state的内容
state.n += a1;
}
}; - state给n赋初值
1
2
3const state={
n:0;
}; - 在组件中引用state中的值
组件模板能够直接读取vc上的所有东西1
<span>{{$store.state.n}}</span>
- 也可以越过actions这一步,直接在组件方法中调用
this.$store.commit('fn',this.a);
- actions中的一个方法可以继续dispatch第二个方法
- 可以不经过mutations,但是不经过的话vue开发者工具就不活不到数据变化了,不建议跳过mutations
- stores添加getters对象,里面添加的方法可以输出加工后的state中的值,但不改变state
- state类似data,getters类似computed
- 插值语法最好要简化
(1)程序员亲自去写计算属性:$store提取数据的过程最好搬到computed里
(2)借助mapState生成计算属性,从state中读取数据:(对象写法)组件中引入mapStateimport {mapState} from 'vuex'
,computed中引用解构赋值...mapState({aaa:'n'})
;(数组写法)进一步简写为...mapState(['n'])
(3)借助mapGetters生成计算属性,从getters中读取数据:对象写法和数组写法(同上)
(4)mapMutations,mapActions(熟悉vuex使用场景后再学)
3.4 vue-router
1.定义:vue的一个插件库,专门用来实现SPA(single pag web application)应用;整个应用只有一个完整的页面;点击导航链接不会刷新页面,只做页面的局部更新;数据需要通过ajax请求获取。
2.路由
- 一个路由就是一组key-value映射关系
- key为路径,value可为function或component
- 前端路由:value是component,用于展示页面内容;当浏览器路径改变时,显示对应组件
- 后端路由:value是function,用于处理客户端提交的请求;服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回相应数据
3.安装
- 终端引入
npm i vue-router@3
vou-router4版本只能在vue3中使用,vue-router3版本才能在vue@中使用 - 在main.js中引入并使用:
1
2
3
4
5
6
7import VueRouter from 'vue-router'
import router from './router'
Vue.use(VueRouter)
new Vue({
...,
router:router
}) - 在src文件夹中创建router文件夹,在其中创建index.js文件页面url出现‘/#/’说明router开始工作了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import VueRouter from 'vue-router'
import About from '../components/About'
import About from '../components/Category'
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/category',
component:Category
}
]
}) - 在导航页面所在文件(比如App.vue)修改跳转页面链接及展示
将a标签改为router-link标签整个流程不走网络请求1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<head>
<style>
.active{
.../*路由被激活时的样式*/
}
</style>
</head>
...
<router-link class="..." active-class="active" to="/about">About</router-link>
<router-link class="..." active-class="active" to="/category">Category</router-link>
...
<div>
<router-view></router-view><!--指定组件的展示位置-->
</div>
...4.注意
- 路由组建通常放在pages文件夹,一般组件放在components文件夹
- 通过切换,“隐藏”了的路由组建,默认是被销毁掉(destoried)的,需要的时候再去挂载(mounted)
- 每个组件都有自己的$router属性,里面存储着自己的路由信息
- 整个应用只有一个router,可以通过组件的$router属性获取
5.嵌套路由(多级路由)
- 将某个路由文件(About.vue)内需要再路由的内容取出来,创建在新的路由文件(Tom.vue, Jerry.vue)里
- 在router/index.js中添加children:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22...
import Tom from '../pages/Tom'
import Jerry from '../pages/Jerry'
export...({
routes:[
{
path:'/about',
component:About,
children:[
{
path:'tom',
components:Tom
},
{
path:'jerry',
components:Jerry
}
]
},
...
]
}) - 在About.vue中添加vue-router和router-view
1
2
3
4
5
6<div>...
<router-link class="..." active-class="active2" to="/about/tom">Tom</router-link>
<router-link class="..." active-class="active2" to="/about/jerry">Jerry</router-link>
</div>
...
<router-view></router-view>6.路由的query参数
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<!-- About.vue传递参数 -->
<ul>
<li v-for="f in food" :key="t.id">
<!-- to的模板字符串写法: -->
<router-link :to="`/about/tom?id=${f.id}&title=${f.title}`">{{f.title}}</router-link>
<!-- to的对象写法: -->
<router-link :to="{
path:'/about/jerry',
query:{
id:f.id,
title:f.title
}
}">
</router-link>
</li>
</ul>
<script>
export default{
name:'About',
data(){
return{
food:[
{id:001,title:'牛奶'},
{id:002,title:'奶酪'},
{id:003,title:'果派'}
]
}
}
}
</script>
<!-- Tom.vue接收参数 -->
<ul>
<li>食物编号{{$route.query.id}}</li>
<li>食物名称{{$route.query.title}}</li>
</ul>7.命名路由
router/index.js中给路由创建name属性
router-link中的to必须写为对象形式,配置name属性,那么对象中就不用写path了8.路由的params参数
- 模板字符串写法中的to:
:to="/about/tom/${f.id}/${f.title}"
且要在router/index.js中配置占位符:path:'tom/:id/:title'
- 对象写法中的to:只是把query属性名字改一改,改为params
- 接收参数用
{{$route.params.id}}
9.路由的props配置
如果需要接收很多参数,适合用props
在route/index.js中配置: - props的第一种写法:值为对象,该对象中的所有key-value都会以props的形式传给内容组件(Tom);用得少,传递的是死数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16export...({
routes:[
{
path:'/about',
component:About,
children:[
{
path:'tom',
components:Tom,
props:{a:1,b:'hello'}
}
]
},
...
]
})1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 接收: -->
<template>
<ul>
<li>a:{{a}}</li>
<li>b:{{b}}</li>
</ul>
</template>
<script>
export default{
name:'Tom',
props:['a','b']
}
</script> - props的第二种写法:若布尔值为真,就会把该路由收到的所有params参数(父组件About要传params参数),以props的形式传给Detail组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16export...({
routes:[
{
path:'/about',
component:About,
children:[
{
path:'tom/:id/:title',
components:Tom,
props:true,
}
]
},
...
]
})1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 接收: -->
<template>
<ul>
<li>id:{{id}}</li>
<li>title:{{title}}</li>
</ul>
</template>
<script>
export default{
name:'Tom',
props:['id','title']
}
</script> - props的第三种写法:值为函数,用query传入,写在配置里,使组件里面的展示更清晰(不需要繁琐重复的计算属性)
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
31export...({
routes:[
{
path:'/about',
component:About,
children:[
{
path:'tom/:id/:title',
components:Tom,
//写法3.1:
// props(){
// return {id:'666',title:'你好啊'}
// }
//写法3.2:
// props($route){
// return {id:$route.query.id,title:$route.query.title}
// }
//写法3.3:解构赋值
// props({query}){
// return {id:query.id,title:query.title}
// }
//写法3.3:解构赋值的连续写法(两次解构赋值)
props({query:{id,title}}){
return {id:id,title:title}
}
}
]
},
...
]
})1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 接收: -->
<template>
<ul>
<li>id:{{id}}</li>
<li>title:{{title}}</li>
</ul>
</template>
<script>
export default{
name:'Tom',
props:['id','title']
}
</script>10.
作用:控制路由跳转时操作浏览器历史记录的模式<router-link>
的replace属性
依赖浏览器历史记录工作的按钮:前进、后退
push模式:追加历史记录(压栈),默认。前进/后退按钮就是移动指针
replace模式:替换当前记录
replace模式写法一:<router-link replace...></router-link>
replace模式写法二:<router-link :replace="true"...></router-link>
11.编程式路由导航
- vue中的router-link标签最终编译为a标签,如要使用其它标签(比如button),需要通过方法实现。
1
2
3
4
5
6...
<ul v-for="f in food" :key="f.id">
<button @click="pushShow(f)">push查看</button>
<button @click="replaceShow(f)">replace查看</button>
</ul>
...1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22...
methods:{
pushShow(f){
this.$router.push({
name:'nailao',
query:{
id:f.id,
title:f.title
}
})
},
replaceShow(){
this.$router.replace({
name:'nailao',
query:{
id:f.id,
title:f.title
}
})
}
}
... - 按钮实现历史记录前进后退VueRouter上的原型方法go:
1
2
3
4
5
6
7
8methods:{
back(){
this.$router.back();
},
forward(){
this.$router.forword();
}
}this.$router.go(3)
传入一个数字,正数表示前进步数,复数表示后退步数。12.缓存路由组件
切换组件的时候之前的组件会被销毁,防止被销毁的方法:去父组件操作,用keep-alive包裹router-link(组件被缓存了):给keep-alive添加1
2
3<keep-alive>
<router-link></router-link>
</keep-alive>include="nailao"
属性可以指定需要缓存的路由组件,内容为组件名。
官方解释作用:让不展示的路由组件保持挂载,不被销毁。13.路由组件独有的两个生命周期钩子
用于捕获路由组件的激活状态1
2
3
4
5
6
7
8
9...
actived(){//激活
this.timer=setInterval(()=>{
...
},16);
},
deactived(){//失活
clearInterval(this.timer);
}
13.路由守卫
- 全局前置
全局前置路由守卫:初始化的时候被调用,每次路由切换之前被调用。
加在router/index.js中router实例对象后面: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...
const router=new VuewRouter({
routes:[
{
name:'myAbout',
path:'/about',
component:About,
children:[...],
meta:{isAuth:false,title:'news'}
}
]
});
router.beforeEach((to,from,next)=>{
//if(to.path==='/about/jerry'){
if(to.meta.isAuth){//如果有很多路由需要鉴权,添加meta属性更简洁
if(localStorage.getItem('myRouteData')==='mydata'){
next()
}else{
alert('data不对,无权查看')
}
}else{
next()
}
}) - 全局后置
全局后置路由守卫:初始化的时候被调用,每次路由切换之后被调用。1
2
3
4
5
6
7...
router.afterEach((to,from)=>{
document.title=to.meta.title||'xx系统';//有什么用?
});
export default router - 独享路由守卫
只有一个路由需要守卫:把beforeEach配置为beforeEnter放到route的属性里。
独享路由守卫只有前置,没有后置;但是可以配合全局的后置路由守卫一起用。1
2
3
4
5
6
7
8
9
10
11
12
13const router=new VuewRouter({
routes:[
{
name:'myAbout',
path:'/about',
component:About,
meta:{isAuth:true,title:'news'},
beforeEnter:(to,from,next)=>{
...
}
}
]
}) - 组件内路由守卫
在About.vue中:1
2
3
4
5
6
7
8
9
10
11export default{
name:'About',
//通过路由规则,进入该组件时被调用
beforeRouteEnter(to,from,next){
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave(to,from,next){
}
}
To be continued...
element-ui
vue3
4.学习Vue前要掌握的JavaScript内容
ES6语法规范
ES6模块化
包管理器
原型、原型链★
数组常用方法
axios
promise
……