Vue
基础
渐进式 JS 框架
MVC
M - Model 数据层(数据库)
V - View 视图层(页面、ejs)
C - Controler 控制层(路由)
MVVM
M - Model 数据层(data、computed…)
V - View 视图层(模板页面)
VM - ViewModel 视图模型层(vm 实例对象)
- 数据能由 ViewModel 操作渲染到视图层上(当数据将来发生修改,自动重新渲染到视图层上-响应式)
- 页面数据发生变化,能由 ViewModel 操作来修改数据层的数据
- 数据 M --> V 也能 V --> M, 叫做双向数据绑定
new Vue(config)
new Vue({ el: '#app', data: {}, methods: {}, computed: {}, watch: {} })
模板语法
-
双大括号表达式(插值语法)
- 语法:{{JS表达式}}
- 作用:用来渲染 JS 动态数据
- 注意:只能用于标签内,不能作为标签属性
指令语法
- 语法: v-xxx:propName=“JS 表达式” / v-xxx=“JS 表达式”
- 作为标签属性使用
- v-model
- 用来双向数据绑定,常用于表单项
v-model="msg"
- v-bind
- 用来单向数据绑定(强制绑定数据)
v-bind:value="msg"
- 简写
:value="msg"
- v-on
- 用来绑定事件
v-on:click="handleClick"
- 简写
@click="handleClick"
- v-if / v-else / v-else-if 条件渲染
- 根据表达式结果选择渲染哪个元素,切换显示)
- v-show 条件渲染
- 区别
- v-if 隐藏时,元素移除 DOM 树
- v-show 隐藏时,通过 display: none 控制隐藏
- 所以,如果元素频繁切换显示、隐藏,用 v-show 较好(因为它不会移除 DOM 树)
- 区别
- v-for 列表渲染
- v-for="(item, index) in xxx" :key=“item.id”
- v-text
- 用于显示元素 textContent、innerText
- v-html
- 用于显示元素 innerHTML
- v-cloak
- 用于防止闪现(从插值语法变成真正数据),配置 [v-cloak]{ display: none; }
- v-pre
- 显示最原始的内容,不会被 Vue 解析
- v-once
- 只被 Vue 解析一次(只渲染一次)
自定义指令
- 自定义局部指令
{
directives: {
'upper-text': function (el, binding) {
el.textContent = binding.value.toUpperCase();
}
}
}
- 自定义全局指令
Vue.directive("upper-text", function (el, binding) {
el.textContent = binding.value.toUpperCase();
});
计算属性
- computed 计算属性
- 只读计算属性
computed: { fullName() { return xxx; } }
- 可读可写计算属性
computed: { fullName: { get() {}, set(newVal) {} } }
- 通常情况下,计算属性内部会使用 data 数据。一旦内部使用的 data 数据发生变化,计算属性就会重新计算结果。如果 data 数据没有变化,就不会重新计算,使用上一次的缓存结果(有缓存)。
监视属性
- watch
watch: { firstName(newVal, oldVal) { } }
- 监视一个已存在的 data 属性,一旦 data 属性发生变化,就会调用相应的函数处理
样式处理
-
class
<p class="red">xxx</p>
<p :class="red">xxx</p>
<p :class="{red: isRed}">xxx</p>
<p :class="[red, 'green']">xxx</p>
-
style
<p style="font-size: 16px;">xxx</p>
<p :style="{fontSize: '16px'}">xxx</p>
-
如果样式是动态(可变的)用 style,如果静态写死就用 class
事件处理
-
v-on:eventName=“handleClick”
- 回调函数的参数:event
-
v-on:eventName=“handleClick()”
- 回调函数的参数:无
-
v-on:eventName=“handleClick(xxx)”
- 回调函数的参数:xxx
-
v-on:eventName=“handleClick(xxx, $event)”
- 回调函数的参数:xxx, event
-
v-on:eventName=“orderType = 1”
- 不需要设置回调函数, orderType = 1 就是要执行的语句
- 当函数语句只有一条时,可以使用
-
事件修饰符
- @click.prevent.stop=“xxx”
- .prevent 禁止事件默认行为
- .stop 阻止事件冒泡
-
按键修饰符
- @keyup.13=“xxx”
- @keyup.enter=“xxx”
生命周期函数
-
组件从创建到销毁过程中自动触发的函数
-
初始化
- beforeCreate 在数据代理之前触发,所以不能通过 this 访问 data、methods 等数据
- created 在数据代理之后
- beforeMount
- mounted 发送请求、设置定时器、绑定自定义事件等一次性任务
-
更新
- beforeUpdate
- updated
-
卸载
- beforeDestroy 清除定时器、解绑事件等收尾工作(防止内存泄漏)
- destroyed
过渡和动画
<transition name="xxx"><p v-show="xxx">xxx</p></transition>
- 默认样式:
- 隐藏到显示 v-enter/v-enter-active/v-enter-to
- 显示到隐藏 v-leave/v-leave-active/v-leave-to
- 加上 name 属性:xxx-enter/xxx-enter-active/xxx-enter-to
过滤器
-
用来格式化数据(时间)
-
局部过滤器
// 定义过滤器
{
filters: {
formatDate(value, str) {
return dayjs(value).format(str);
}
}
}
// 使用过滤器
{{time | formatDate('YYYY-MM-DD HH:mm:ss')}}
- 全局过滤器
// 定义过滤器
Vue.filter('formatDate', function(value, str) {
return dayjs(value).format(str);
})
}
// 使用过滤器
{{time | formatDate('YYYY-MM-DD HH:mm:ss')}}
ref
- 用来获取 DOM 元素或者组件实例对象
// 设置ref
<input ref="name" />;
// 获取ref的值
this.$refs.name;
插件
- 扩展 Vue 的功能
// 定义插件
// 方式一
function MyPlugin(Vue) {}
// 方式二
const MyPlugin = {
install: function (Vue) {},
};
// 使用插件
Vue.use(MyPlugin);
key
-
key 有什么用?
- 能让相同层级节点更新时性能更好
-
key 的值是什么?
- 能用 id 用 id
- 如果没有 id,只有往数组后面添加、删除,或者数据如果不变可以用 index
响应式
-
什么是响应式数据?
- 更新了数据,数据会变化,同时页面也会变化
-
哪些数据是响应式数据?
- data 中定义的数据
- 注意:如果给 data 数据后面新添加属性,这些属性不是响应式
- 如果想变成响应式数据,需要使用
this.$set() / Vue.set()
- 如果想变成响应式数据,需要使用
组件
// 定义组件
const Component = Vue.extend(组件配置对象);
// 注册局部组件
{
components: {
Component: Component,
Component: 组件的配置对象,
}
}
// 注册全局组件
Vue.component(组件名称, Component);
Vue.component(组件名称, 组件的配置对象);
脚手架
-
安装包
- npm i @vue/cli -g
-
创建脚手架
- vue create xxx
-
启动开发环境
- npm run serve
-
启动生产环境
- npm run build
render方法
import Vue from "vue"; --> vun.runtime.esm.js
vun.runtime.esm.js 只包含运行时版本(不具备编译模板能力)
通过render方法,就能具备编译模板能力
new Vue({
render: h => h(App)
}).$mount("#app");
组件间通讯
data为什么要是函数?
data有两种写法:1. 对象 2. 函数
在普通页面中,两种写法都行
但是在组件中,必须使用函数
如果在组件中,data使用对象,复用组件时这多个组件会共享同一份data数据
如果在组件中,data使用函数,复用组件时这多个组件会调用函数得到一份新data数据,每个组件的data数据都会不一样,所以不会互相影响
1.props(父子组件)
当使用props传数据的时候,如果组件声明接受了,就可以直接通过this使用
如果组件没有声明接受,属性会在this.$attrs上,可以通过$attrs访问
-
适用场景:父子组件
-
父传子:将数据通过 props 直接传递
// 父组件传递数据
<Child :name="name" />
// 子组件声明接受props
{
props: ['name'],
props: {name: String},
props: {name: { type: String, required: true, default: xxx, validator() {} }},
}
// 子组件使用
this.xxx
// 如果属性没有通过props声明接受,那么可以通过$attrs来使用
this.$attrs.xxx
- 子传父
- 父组件定义更新数据的方法,以 props 方式传递给子组件,子组件声明接受调用函数,传递数据给父组件
- 子组件调用使用
2.自定义事件(子向父)
作用:用来子组件向父组件通信
给哪个组件绑定自定义事件,就只有那个组件可以触发(使用)
- 适用场景:子传父
方式一
@add="add"
绑定自定义事件
事件名:add
事件回调函数:add
// 绑定自定义事件
<Child @add="add" />
// 触发事件
this.$listeners.add();
this.$emit('add')
方式二
ref 如果设置给普通DOM元素,那么获取到的就是这个真实DOM元素
如果设置给组件,那么获取带的就是组件实例对象
// 绑定自定义事件
<Child ref="child" />
mounted() {
this.$refs.child.$on('add', this.add)
}
// 触发事件
this.$emit('add')
所有组件实例对象都具备以下方法:
$on(eventName, listener) 绑定自定义事件(持久)
$once(eventName, listener) 绑定自定义事件(一次性)
$off(eventName, listener) 解绑事件
$emit(eventName, data) 触发自定义事件
3.全局事件总线(任意组件)
-
适用场景:任意组件
-
原理:
- 本质上就是自定义事件
- 给 Vue 的原型添加一个可以绑定事件的对象(Vue 的实例 vm、组件的实例 this)
- 使用 Vue 的实例 vm 在 main.js 文件中最方便
-
使用方式有两种:
Vue.prototype.$bus = new Vue()
beforeCreate() { Vue.prototype.$bus = this }
- 所有组件实例对象都能通过原型链的方式访问到
$bus
这个对象,所以就能通过它绑定事件或者触发事件
// 绑定事件(接受数据):
this.$bus.$on(eventName, listener)
// 触发事件(发送数据):
this.$bus.$emit(eventName, data)
4.PubSub消息发布订阅(任意组件)
- 使用场景:任意组件
- 发布(发送数据):PubSub.publish(msg, data)
- 订阅(接受数据): PubSub.subscribe(msg, listener)
5.插槽(父给子传递带数据标签)
- 使用场景:父给子传递带数据的标签
默认插槽
组件写成双标签,里面放入标签数据,那么这个标签数据就会以插槽的方式传递给子组件
// 父组件给子组件传递带数据的标签
<AChild>
<p>hello vue000</p>
<p>hello vue11</p>
<p>{{msg}}</p>
</AChild>
// 子组件使用
// 使用父组件以插槽方式传递的标签数据
<slot></slot>
具名、命名插槽
具名、命名插槽: 给每一个插槽取一个名字
// 具名、命名插槽: 给每一个插槽取一个名字
<BChild>
// 旧语法:slot="名称"
<template slot="header">
<header>头部...{{ title }}</header>
</template>
// 新语法:v-slot:名称
<template v-slot:main>
<main>内容区...</main>
</template>
// 新语法可以简写:#名称
<template #footer>
<footer>底部...</footer>
</template>
</BChild>
- 子组件使用
<template>
<div>
// 显示头部
// 通过name属性来决定使用哪个具名插槽
<slot name="header"></slot>
<p>---------</p>
// 显示内容区
<slot name="main"></slot>
<p>---------</p>
// 显示底部
<slot name="footer"></slot>
</div>
</template>
作用域插槽
- 父组件
<CChild>
// 父组件插槽可以接受子组件通过slot传递的props数据
<template #list="slotProps">
<template v-slot:list="slotProps">
// { person } -> 就是对数据进行解构赋值
<template #list="{ person }">
<template #list="{ person: { name, age } }">
<ul>
<li>姓名:{{ name }}</li>
<li>年龄:{{ age }}</li>
</ul>
</template>
</CChild>
- 子组件
<template>
<div>
// 以标签属性(props)方式传递person数据
<slot name="list" :person="person"></slot>
</div>
</template>
<script>
export default {
name: "CChild",
data() {
return {
person: {
name: "jack",
age: 18,
},
};
},
};
</script>
跨域
什么是跨域?
违背了同源策略
什么是同源策略
协议名、域名(ip 地址)、端口号三者必须完全一致
什么请求会有跨域问题?
只有位于客户端的 ajax 请求才可能出现跨域问题,
其他普通的 http 请求是不存在跨域问题的(script、服务器和服务器之间。。。)
解决跨域方式:
jsonp
- 原理:利用 script 标签天然可以跨域的特性进行跨域
- 步骤:
- 创建 script 标签
- 创建全局回调函数
- 设置 script 的 src 属性,往往会添加一个 callback 请求参数
- 将 script 标签添加到 body 中生效
cors
- 原理:http 协议规定,指定响应头可以资源进行跨域访问
- access-control-allow-origin 允许那些地址可以跨域
- access-control-allow-headers 允许哪些请求头可以跨域
- access-control-allow-methods 允许哪些请求方式可以跨域
- access-control-max-age 预检请求缓存多久
- 发送请求的时候,会先发送一个 options 请求,这个请求就叫预检请求
- 默认请求下,每个请求之前会发,max-age 可以缓存预检请求结果,在缓存期间内,预检就不会再发了
服务器代理模式(proxy)
原理:
- 正常情况下,客户端直接访问目标服务器,会存在跨域问题
- 在客户端和目标服务器,设置一个代理服务器。
- 客户端先将请求发送给代理服务器,客户端和代理服务器之间没有跨域问题
- 客户端和代理服务器运行的是同一个服务器
- 代理服务器设置类似于 cors 方法解决了跨域
- 代理服务器接收到请求就会将请求转发到目标服务器上,服务器和服务器之间不存在跨域问题,所以请求 ok
- 目标服务器将响应返回给代理服务器,代理服务器再讲响应返回给客户端
正向代理
- 代理客户端,替客户端去转发请求(隐藏真正客户端)
- 例子:开发中 proxy,xxUtil
- 配置:
- React 脚手架项目中:在 package.json 中配置 proxy
- Vue 脚手架项目中:在项目根目录创建一个配置文件 vue.config.js,在其中
module.exports = {
devServer: {
proxy: {
"/api": {
target: "http://localhost:3000",
pathRewrite: { "^/api": "" },
},
},
},
};
反向代理
- 代理服务器,替服务器先接受请求,然后再将请求分配给某个服务器(隐藏真正的服务器)
- 例子:nginx