此文记录一下自己学习vue的知识点整理
🍸🍸🍸

1.前置条件

  • 了解命令行(不会的再百度)
  • 安装最新版本Node.js

2.引入vue

2.1自己引入到html中

1
2
3
4
5
<!--2.1.1本地引入-->
<script src="./src/vue.js"></script><!--或压缩版vue.min.js-->

<!--2.1.2在线引入-->
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.js"></script>

2.2通过HBuilder X创建vue项目

创建vue项目
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
<!DOCTYPE html>
<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
11
new Vue({
el:'#root',
data(){
msg:'hhh5555'
},
filters:{
mySlice(value){
return value.slice(0,4)
}
}
})

2.全局过滤器:写在vue实例对象前面
1
2
3
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})

3.使用:过滤器仅用于插值语法和v-bind,不能用于v-model
1
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 !important;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//1.创建组件
const school=Vue.extend({
template:`
<div>
<h1>在这里写html组件{{nihao}}</h2>
</div>
`,
data(){
return{
nihao:'哈哈哈'
}
}
})
//2.注册组件(局部注册)
const vm=new Vue({
el:'#root',
components:{
nihao
}
})

可以定义一个父组件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/clinpm 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面: -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口: -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标: -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题: -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持js时noscript中的元素就会被渲染: -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器: -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

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
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
<!-- 父组件: -->
<template>
<Student name="大白" :age="18" /><!--①传递-->
<!-- 给age加了冒号,相当于加了v-bind,运行的是冒号里的js表达式,避免了传字符串"18"给子组件。 -->
</template>

<!-- 子组件: -->
<template>
<h2>学生:{{name}}</h2><!--③使用-->
<h2>年龄:{{age+1}}</h2>
<h2>年龄2:{{age2+1}}</h2>
</template>
<script>
export default {
name:'Student',
data(){
return{
age2:this.age+1
}
},
//②接收
//props:['name','age']//简单声明接收
// props:{//限制类型的接收
// name:String,
// age:Number
// }
props:{//进一步明细条件的接收(总共三种条件)
name:{
type:String,
required:true
},
age:{
type:Number,
default:99
}
}
}
</script>

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
16
export 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
3
import {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
39
export 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
5
mounted:{
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
7
new 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
18
export 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
7
methods(){
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请求的方式

  1. xhr: new XMLHttpRequest() xhr.open() xhr.send()
    不常用,一般都将xhr封装起来使用,可能是公司内部自己封装,也可能采用第三方的库。
  2. jQuery: $.get $.post (xhr的封装)
    jQuery的核心是封装DOM操作(80%),而Vue是减少DOM操作,不适合在Vue项目中使用
  3. axios: promise风格,支持请求拦截器和响应拦截器,体积小,使用广泛 (xhr的封装)
    1
    npm i axios ::在对应的项目下安装axios
  4. fetch: 不是xhr的封装,promise风格,但会把返回的数据包两层promise,两次.then才能拿到东西,兼容性不高
  5. vue-resource:vue里的插件库,用Vue.use(xxx)使用,在vc中显示为属性$http(了解)
    1
    npm i vue-resource

使用axios发送请求:
先学习一下使用node.js搭建自己的服务器使用node.js搭建一个简单的web服务器 响应JSON、html
在项目主目录下创建server.js文件,返回的可以是字符串可以是json数据。

同源策略

  1. 协议名一致
  2. 主机名一致
  3. 端口号一致
    http://localhost:8080为例,http是协议名,localhost是主机名,8080是端口号。
    三个条件同时满足才不会跨域。
    跨域时的控制台报错关键词:’CORS’, ‘Access-Control-Allow-Origin’
    同源策略是浏览器最核心也最基本的安全功能。为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收 。可以说Web是构建在同源策略基础之上的。

解决跨域的方法

  1. cors:返回特殊的响应头允许接收,是真正的解决跨域)
  2. jsonp:很巧妙,借助script标签里的src属性引入外部资源时不受同源策略限制;但是需要前后端配合,只能解决get请求的跨域问题
  3. 代理服务器:类似中间商,我们和代理服务器之间符合同源策略可以获取数据;代理服务器和目标服务器都是服务器,服务器之间使用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.插槽

  1. 默认插槽
    组件标签写为普通标签结束格式,不用自结束格式,在标签体中写的内容在组件中显示在什么地方:在组件中用插槽<slot></slot>安放。
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 父组件: -->
    <Fish></Fish>

    <!-- 子组件: -->
    <div>
    <h1>fishfish</h1>
    <slot></slot>
    </div>
    slot标签用于占坑。
    如果位置①没有内容,会显示位置②内容;
    如果位置①有内容,位置①的内容显示在位置②的slot位置,位置②内容隐藏。
  2. 具名插槽
    在组件标签体中添加需要的元素及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.3 vuex

1.定义

专门在Vue中实现集中式管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2.使用场景

  1. 多个组件依赖同一状态
  2. 来自不同组件的行为需要变更同一状态

    3.原理:

  3. Actions,Mutations,State是对象

  4. vuex的运行得经过store的管理
  5. vc可以直接commit到Mutations

    4.搭建环境

  6. npm i vuex vue2中要用vuex3,vue3中要用vuex4
  7. import... Vue.use(Vuex)
  8. 在vm中配置store
    1
    2
    3
    4
    5
    new Vue({
    ...
    store,
    ...
    })
  9. vc ==> store (所有组件的实例对象都能看见store)
    在src下创建文件夹store,store下创建index.js,内容:
    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
    });
    现在vm和所有的vc身上都能看到store了
  10. 在main.js中引入store
    注意:即使写在Vue.use(Vuex)后面:import store from './store',也不行,js会扫描整个文件然后把所有import提到最前面;
    所以,应该把Vue.use(vuex)放到store/index.js里,引用Vue之前还要再引入一次Vue:
    1
    2
    3
    import Vue from 'vue';
    import Vuex from 'vuex';
    Vue.use(Vuex);
    再在main.js中引入storeimport store from './store'

    5.调用

  11. 组件中methods里调用this.$store.dispatch('fn1',this.a),前面是方法名,后面是传入的参数
  12. store中在actions中添加需要调用的方法,并调用commit
    1
    2
    3
    4
    5
    6
    const actions={
    fn1(context,a1){//context是一个mini版的$store
    ...
    context.commit('FN1',a1);//用大写作个区分
    }
    };
  13. 调用mutations中的方法
    1
    2
    3
    4
    5
    const mutations={
    FN1(context,a1){//context是state的内容
    state.n += a1;
    }
    };
  14. state给n赋初值
    1
    2
    3
    const state={
    n:0;
    };
  15. 在组件中引用state中的值
    组件模板能够直接读取vc上的所有东西
    1
    <span>{{$store.state.n}}</span>
  16. 也可以越过actions这一步,直接在组件方法中调用this.$store.commit('fn',this.a);
  17. actions中的一个方法可以继续dispatch第二个方法
  18. 可以不经过mutations,但是不经过的话vue开发者工具就不活不到数据变化了,不建议跳过mutations
  19. stores添加getters对象,里面添加的方法可以输出加工后的state中的值,但不改变state
  20. state类似data,getters类似computed
  21. 插值语法最好要简化
    (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.路由

  1. 一个路由就是一组key-value映射关系
  2. key为路径,value可为function或component
  3. 前端路由:value是component,用于展示页面内容;当浏览器路径改变时,显示对应组件
  4. 后端路由:value是function,用于处理客户端提交的请求;服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回相应数据

    3.安装

  5. 终端引入npm i vue-router@3
    vou-router4版本只能在vue3中使用,vue-router3版本才能在vue@中使用
  6. 在main.js中引入并使用:
    1
    2
    3
    4
    5
    6
    7
    import VueRouter from 'vue-router'
    import router from './router'
    Vue.use(VueRouter)
    new Vue({
    ...,
    router:router
    })
  7. 在src文件夹中创建router文件夹,在其中创建index.js文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import 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
    }
    ]
    })
    页面url出现‘/#/’说明router开始工作了
  8. 在导航页面所在文件(比如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.注意

  9. 路由组建通常放在pages文件夹,一般组件放在components文件夹
  10. 通过切换,“隐藏”了的路由组建,默认是被销毁掉(destoried)的,需要的时候再去挂载(mounted)
  11. 每个组件都有自己的$router属性,里面存储着自己的路由信息
  12. 整个应用只有一个router,可以通过组件的$router属性获取

    5.嵌套路由(多级路由)

  13. 将某个路由文件(About.vue)内需要再路由的内容取出来,创建在新的路由文件(Tom.vue, Jerry.vue)里
  14. 在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
    }
    ]
    },
    ...
    ]
    })
  15. 在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>&nbsp;&nbsp;
    <!-- 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参数

  16. 模板字符串写法中的to::to="/about/tom/${f.id}/${f.title}"
    且要在router/index.js中配置占位符:path:'tom/:id/:title'
  17. 对象写法中的to:只是把query属性名字改一改,改为params
  18. 接收参数用{{$route.params.id}}

    9.路由的props配置

    如果需要接收很多参数,适合用props
    在route/index.js中配置:
  19. props的第一种写法:值为对象,该对象中的所有key-value都会以props的形式传给内容组件(Tom);用得少,传递的是死数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    export...({
    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>
  20. props的第二种写法:若布尔值为真,就会把该路由收到的所有params参数(父组件About要传params参数),以props的形式传给Detail组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    export...({
    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>
  21. 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
    31
    export...({
    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>
    作用:控制路由跳转时操作浏览器历史记录的模式
    依赖浏览器历史记录工作的按钮:前进、后退
    push模式:追加历史记录(压栈),默认。前进/后退按钮就是移动指针
    replace模式:替换当前记录
    replace模式写法一:<router-link replace...></router-link>
    replace模式写法二:<router-link :replace="true"...></router-link>

    11.编程式路由导航

  22. 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
    }
    })
    }
    }
    ...
  23. 按钮实现历史记录前进后退
    1
    2
    3
    4
    5
    6
    7
    8
    methods:{
    back(){
    this.$router.back();
    },
    forward(){
    this.$router.forword();
    }
    }
    VueRouter上的原型方法go:this.$router.go(3)
    传入一个数字,正数表示前进步数,复数表示后退步数。

    12.缓存路由组件

    切换组件的时候之前的组件会被销毁,防止被销毁的方法:去父组件操作,用keep-alive包裹router-link(组件被缓存了):
    1
    2
    3
    <keep-alive>
    <router-link></router-link>
    </keep-alive>
    给keep-alive添加include="nailao"属性可以指定需要缓存的路由组件,内容为组件名。
    官方解释作用:让不展示的路由组件保持挂载,不被销毁。

    13.路由组件独有的两个生命周期钩子

    用于捕获路由组件的激活状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ...
    actived(){//激活
    this.timer=setInterval(()=>{
    ...
    },16);
    },
    deactived(){//失活
    clearInterval(this.timer);
    }

13.路由守卫

  1. 全局前置
    全局前置路由守卫:初始化的时候被调用,每次路由切换之前被调用。
    加在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()
    }
    })
  2. 全局后置
    全局后置路由守卫:初始化的时候被调用,每次路由切换之后被调用。
    1
    2
    3
    4
    5
    6
    7
    ...

    router.afterEach((to,from)=>{
    document.title=to.meta.title||'xx系统';//有什么用?
    });

    export default router
  3. 独享路由守卫
    只有一个路由需要守卫:把beforeEach配置为beforeEnter放到route的属性里。
    独享路由守卫只有前置,没有后置;但是可以配合全局的后置路由守卫一起用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const router=new VuewRouter({
    routes:[
    {
    name:'myAbout',
    path:'/about',
    component:About,
    meta:{isAuth:true,title:'news'},
    beforeEnter:(to,from,next)=>{
    ...
    }
    }
    ]
    })
  4. 组件内路由守卫
    在About.vue中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default{
    name:'About',
    //通过路由规则,进入该组件时被调用
    beforeRouteEnter(to,from,next){

    },
    //通过路由规则,离开该组件时被调用
    beforeRouteLeave(to,from,next){

    }
    }


🐧🐧🐧
To be continued...

element-ui

vue3

4.学习Vue前要掌握的JavaScript内容

ES6语法规范
ES6模块化
包管理器
原型、原型链★
数组常用方法
axios
promise
……