构建用户界面的渐进式js框架
1、组件化模式,提高代码复用率
2、采用声明式编码,方便维护
3、虚拟dom+diff算法,复用dom节点(通过diff算法对比更新虚拟dom,而不是直接对真实dom操作)
容器和vue实例是一一对应的,双大括号里面不光是data的,还能写js表达式(左边能用变量接收的)
插值语法{{}}这个适用于标签体里面的内容,指令语法比如 v-bind(简写为 :),这个可以改变标签,比如标签的内容、属性,添事件
像上面这个url就是js的表达式
v-bind是单向数据绑定,v-model是双向数据绑定。v-model只适用于能交互的元素
el第二种:
vm.$mount(“#id”) // 这个是用实例挂载el
data第二张写法:
组件化必须用函数式写法,要用一般函数,箭头函数的this不是指向vue实例,指向window,会有问题
mvvm模型
model对应data中的数据
view 模板,页面结构
ViewModel vue实例
vue模板可以用vue实例和原型上的所有属性,data属性出现在vue实例上
数据代理
通过一个对象代理操作另一个对象的属性
Object.defineProperty中有get和set函数,vm实例有_data属性,通过这两个方法代理改变
options(new vue的时候传入的配置对象)中的date,方便代码的书写
$event占位event,通过括号传参数
<script type="text/javascript">
Vue.config.productionTip = false;
var vm = new Vue({
el: "#container02"
methods:{
one(event){
event.target.innerHTML;
//target是指向事件触发的元素节点也就是button
alert("ss");
},
two(){
alert("ssss")
}
}
});
</script>
capture这个将事件在冒泡时候触发变成捕获阶段触发。捕获由外向内,触发由内向外,说白了就是改变子元素和父元素事件触发的顺序
<a :href="url" @click.prevent="jump">跳转</a>
@wheel=““和@scroll=””,滚轮和滚动条,区别是滚轮触发,函数执行完成才显示,而滚动条变化是先在界面显示然后调用函数。可以用passive来让界面先显示
键盘按键
tab键需要配合@keydown,ctrl、shift、alt、windows这几个配合@keyup需要和别的按键配合使用,@keydown就不用,直接触发
methods: {
press(e){
alert("触发"); //注意需要将e传入
}
}
计算属性
定义:通过已有的属性计算得来
<script type="text/javascript">
Vue.config.productionTip = false;
var vm = new Vue({
el: "#container01",
data() {
return {
one:"姓",
two:"名"
};
},
methods: {},
computed:{
and:{
get(){
return this.one+" "+this.two;
//这个通过别人为自己赋值
},
set(value){
let array=value.split(" ");
this.one=array[0];
this.two=array[1];
//这个通过自己的val改变别人的值
this都指向vue的实例对象,data中的属性,methods中的函数
实例中的属性监视
watch:{
one: { //属性名
immediate:true, //直接执行handler一次,false则不用管他
handler(newVal,oldVal){ //属性变化执行一次函数
alert("执行一次");
}
}
}
vm.$watch("one", {
immediate: true, //直接执行handler一次
handler(newVal, oldVal) {
alert("执行一次");
}
});
vue实例的属性是一个对象的话,vue的watch默认无法检测里面数据的改变,智能检测它对应的地址值的变化。可以在里面加deep:true
来让vue检测对象内部值的变化。
单一检测对象内部某一个属性的变化,可以用引号包裹obj.key:{ handler(){}}
watch: {
one(){
alert("Ss");//简写,
}
watch比计算属性来说可以实现异步任务
定时器、ajax等函数要用箭头函数。普通函数的话,默认执行是浏览器引擎调用,this就变成了wi 对象。
样式绑定
calss=“”
:calss=“实例中data属性,比如arr”
arr:【“string”,“string”,“string”】 //不加引号去data中 找属性了
<div id="change" :class="classOne">{{and}}</div>
css中有点calss01这个类,true代表加上,false代表不用
:style=“obj”,在date中 obj={fontSize:“40px”}
条件渲染
v-if 和 v-show 对比
如果需要频繁切换用show,if的话页面dom元素节点都会删除
<di v-if="1==1">1</di>
<di v-else-if="1==1">2</di>
<di v-else> 3</di>
上面三个中间不能加别的,if和show右边都是表达式,要返回boolean值
这个模板不改变页面的结构,没有template标签
<ul>
<li v-for="(val,index) in array" :key="index">
{{val}}+{{index}}
</li>
</ul>
<ul>
<li v-for="(val,index) in obj" :key="index">
{{val}}+{{index}}
</li>
</ul>
array:[{id:1,address:"1"},{id:2,address:"3"},{id:3,address:"2"}],
obj:{name:"xiaomig",age:12,address:"ssss"}
key作用
数据的变化,vue生成新的虚拟dom,通过新的虚拟dom的key来寻找原来的虚拟dom的key,如果内容相同就直接采用原来的真实dom,内容改变替换原来真实dom,原来没key就创建新的真实dom。index作为key的问题:数据进行逆序添加删除等产生没有必要的真实dom的更新。效率低,如果包含输入类的dom那页面就有问题。
var newArray=[].filter(value => {
return value.indexOf("s")>-1;
})
上面这个数组过滤方法,value是每个数组的元素,返回结果为真,将该元素保留。这个方法不改变原数组。
data() {
return {
array: [
{id: 1, address: "1",name:"张三"},
{id: 2, address: "3",name:"李四"},
{id: 3, address: "2",name:"王五"}],
showArray:[],
change:""
};
},
methods: {},
watch: {
change:{
immediate:true,
handler(){
this.showArray=this.array.filter(value => {
return value.name.includes(this.change);
})
}
}
}
当watch和计算属性都能实现肯定优先用计算属性
初始赋值和依赖数据改变赋值。由于非空字符串的includes方法对空的字符串返回是true,所以初始执行的时候数据是全的
sort改变原数组
vue检测数据改变
给对象增加能响应的属性
methods: {
addSex(){
Vue.set(this.student,"sex","男");
}
},
这个局限,只能给data中对象加属性,不能是data或vm
修改数组的某一个数据,7个方法让vue更新模板
push pop shift unshift reverse sort splice。使用其他方法的时候,newArray=oldArray就可以让模板重新解析。不能是数组第几项,然后每个对象都有set和get方法
set不能用于vm根节点和vm_data
数据劫持:遍历数据让其获得set和get方法,数据改变调用set可以让模板更新
v-model
label
元素不会向用户呈现任何特殊效果,但可以为鼠标用户改进可用性。当用户在label元素内点击文本时,就会触发该控件,浏览器就会自动将焦点转到和标签相关的表单控件上
v-model绑定输入框的value值
单选按钮和复选框都需要自己写value=“”,然后v-model=“data”。复选框没value的话绑定checked。注意的是v-model="data”中的数据,data中对应的要是个数组,不能是字符串
过滤器
<div>{{times | timeFormate}}</div>
v-bind可以,model不行
v-text
替换所在节点所有文本内容 v-text=“data”,不解析html标签
v-html
cookie:向浏览器发送请求输入用户名密码正确后,服务器向浏览器返回信息时带了cookie,(这个是key:val的形式)。浏览器再次发送请求会携带该cookie来表明自己的身份。这个cookie不能跨浏览器。
这个有安全性问题,可以解析浏览器标签,容易导致xss攻击
v-cloak
引入js文件加载过慢,一些东西需要等加载完成,vue接管后展示,可以加v-cloak属性。然后css属性选择器【v-cloak】{display:none}。vue接管会取消该属性
v-once
这个初始化只加载一次,
v-pre
加这个不会vue解析标签,但是可以提高浏览器解析效率
自定义指令
出现大写字母和-都要引号
n是data中的数据
Vue.directive("fbind", {
bind(el, binding) {
console.log("指令与元素绑定时调用");
el.value = binding.value;
},
inserted(el, binding) {
console.log("指令所在元素被插入页面时调用");
el.focus();
},
update(el, binding) {
console.log("指令所在元素被vue模板重新解析");
el.value = binding.value;
}
});
全局的自定义指令如上,要写在new Vue 前面
Vue生命周期
<h1 :style="{opacity:op}">我初始dom加载完成执行</h1>
第一个函数:
beforeCreate,无法通过vm访问data中的数据和methods中的方法,初始化插件
数据代理和数据监测完成
created:可以访问data中的数据和methods中的方法,dom没有生成,用来执行异步请求
beforeMount:虚拟dom有了但是未呈现页面
mounted:经过vue编译,一般在这里开启定时器,发送网络请求,订阅消息,绑定自定义事件等。但是异步请求会导致dom更新也就是页面闪动
beforeUpdate:数据改变调用beforeUpdate方法,该方法中数据是新的,页面没更新,数据与页面没同步。
之后新生成虚拟dom通过diff算法比较确认新的真实dom完成更新
updated:数据与页面保持一致,完成渲染
vm.$destroy()调用销毁vm
beforeDestroy:vm中所有的data、methods、指令都可用,在此阶段关闭定时器,取消订阅、解绑自定义事件
destroyed:销毁完成(原生的事件需要提前关闭,比如定时器)
两个销毁方法中的数据更新不会引起页面的重新渲染
组件化编程
实现应用局部功能代码和资源的集合
data函数形式是防止其他人使用改变data中的值,自己这边的值也被改变
创建组件简写 const component=Vue.extend({options}) // const component={options}
全局注册组件vue.component(“名称”,组件)
es6模板字符串,template只能有一个根节点,这个非单文件组件,不适用于项目
组件本质是一个构造函数,是Vue.extend生成的。Vue解析组件标签会创建实例对象,相当于new VueComponent(options)。每次调用extend都会创建全新的VueComponent构造函数
组件中data、methods、computed等中的this指向该组件的实例对象,而不是vm
cv没有vm的el
vue和VueComponent关系
$ mount和$watch等在Vue的显式原型对象上
VueComponent.prototype.__proto__=Vue.prototype
组件实例对象就是一个小型的vm,它没有el而且data需要以函数形式返回对象。原因就是在其他地方使用data,如果是对象的话,对他修改可能改变原来的data。
单文件组件
npm config set registry https://registry.npm.taobao.org //配置淘宝下载镜像路径
vue脚手架全局安装:
npm install -g @vue/cli //全局安装vue
选好文件用命令vue create xxx // 创建一个vue项目,自带helloword
项目文件用命令运行npm run serve
build是写完项目后将所有代码编译html\css\js文件,而不是vue文件
ctrl +c 关闭服务器
代码完成需要打包,只保留vue核心,不包括模板解析器(解析器336kb占vue三分之一)
在vueconfig.js中lintOnSave用于语法检查,详细见配置参考
ref
在vue中取代id,写在子组件上可以得到组件的实例而不是组件的结构
<Student ref="st"/>
<div ref="ss">inner</div>
this指的是当前界面的vc
组件通信
<Student ref="st" name="xiaoming" :age="12"/>
父组件用属性向子组件传值,v-bind将里面变成js表达式,那age的格式就是数字而不是属性的字符串。
父组件传入的值优先于本组件的值,
props优先级更高
简单格式用数组即可,规定类型等用对象
混合
将多个组件共用的配置提取成对象
export const mixin={
methods:{}
}
mixins:【“min?”】 和props差不多,局部混合
下面是全局混合,加载到vm上
外面引入和自己冲突以自己为准,生命周期函数叠加
vue插件
功能就是增强vue,让所有东西都可以通过vc直接获取
创建plugins.js
install函数可以获得传入的Vue构造函数和传入的参数,并在use的时候调用
use使用插件和传入参数
scoped
npm i less-loader@7 //7版本对应webpack4,less编译器
npm install --save-dev less-loader less
<style scoped lang="less">
scoped意思是该style中只能控制该组件的样式,一般App不会有,它都是其他组件所用的公共样式
案例
npm i nanoid
npm install --save nano
import {nanoid} from "nanoid
nanoid() 返回一个唯一的id
子组件向父组件通信,父组件传一个函数过来,子组件调用函数要传的数据放在函数的参数中
vue检测props改变,检测不到对象中的属性
reduce 数组中的统计方法
array.reduce((pre,cur)=>{
return pre;
},0)
统计方法,pre代表上次的返回结果,整个方法的返回结果就是pre,0代表pre默认从0开始计数。cur代表遍历数组的每一个元素,只读
props不能用于v-model,它的值不能修改。但props如果是对象,里面的属性修改vue检测不到
浏览器本地存储
localStorage和sessionStorage
自定义事件
子组件向父组件传数据可以通过父组件给子组件一个函数或者如下
school:
<Student @getStu="getStu"></Student>
student:
<button @click="getStu">点我发送data</button>
另一种
<Student ref="getStu"></Student>
on=>once
this.$off("事件") 也可以不传参数,那就是取消所有该实例对象上的自定义事件
<Student ref="getStu" @click.native="cli"></Student>
this.$emit("click",this.stu01);
上面这两行click必须加native修饰符,意思是给组件绑定原生的dom事件,否则按自定义事件来处理
全局事件总线
因为vue的实例对象和vueComponent的实例对象都能访问到Vue的显示原型,所以将它起名$bus,然后利用自定义事件实现组件之间的数据通信
接收数据的组件:
发送数据的组件:
this.$bus.$emit("getStu",this.stu01); //传入事件名和数据
消息订阅与发布
npm i pubsub-js
npm install --save pubsub-js
import pubsub from “pubsub-js”;
组件a
组件b:
@blure和@focus
修改完数据后要依靠新解析的模板来实现某些功能,可以在方法中开一个定时器,但一般用官方的
this.$nextTick(function () { 下一轮,dom节点更新后才执行
this.$refs.?.focus();
})
过渡和动画
appear:出现动画要执行,值是boolean
默认v-enter-xx可以用name代替v-
动画两个类
请求跨域问题
协议,域名,端口号要一致,否则就是跨域
cnpm install --save axios
import axios from "axios" 在组件中
jsonp只能实现get请求
cors跨域
配置代理服务器或者用nginx
服务器之间用http协议通信,前端和后端交互才有跨域问题
1、:搞一个vue.config文件,可以看vue文档,然后用devServer.proxy的配置修改端口号,但是如果前端页面与发送的请求有相同名称的文件,则浏览器不会发送请求,直接用前端的。还有只能配置一个代理
2、
module.exports = {
devServer: {
proxy: {
'/api': { //请求前缀,请求路径localhost://8080/api/?
target: 'http://localhost:8000', //代理服务器地址
ws: true, //支持websorcket
changeOrigin: true,//用于修改请求头中的host的值,
// false代表自己的真实端口而不是虚拟目标服务器的端口
pathRewrite:{"/api":""} //发送ajax等请求在路径多加的/api会被去掉
//由代理服务向真实服务器发送要去掉字段路径
},
'/api2': { //请求前缀,请求路径localhost://8080/api/?
target: 'http://localhost:8001', //代理服务器地址
ws: true, //支持websorcket
changeOrigin: true,//用于修改请求头中的host的值
pathRewrite:{"/api":""} //发送ajax等请求在路径多加的/api会被去掉
//由代理服务向真实服务器发送要去掉字段路径
}
}
}
}
resource
发送ajax
vue-resource
插槽
默认插槽
<Student><h1>你好我是插槽</h1></Student>
具名插槽
template包裹标签有一个新的写法v-slocat:footer
作用域插槽
stu组件
类似props传参数
解构赋值
<template scope="{stu01}">
<h1>{{stu01.name}}</h1>
</template>
vue-x
实现集中式状态(数据)管理的插件
imporet 引入的文件执行顺序优先于本文件的内容,和书写顺序无关
npm install --save vuex
index配置
import Vuex from 'vuex'
import Vue from "vue";
Vue.use(Vuex);
const actions = {} //用于响应组件中的动作
const mutations = {} //用于操作组件中的数据
const state = {} //用于存储数据
export default new Vuex.Store({
actions,
mutations,
state
})
main.js引入
import store from './store/index'
index.js
import Vuex from 'vuex'
import Vue from "vue";
Vue.use(Vuex);
const actions = {
add(context, value) {
setTimeout(() =>{
context.commit("ADD",value)
},2000)
}
} //用于响应组件中的动作
const mutations = {
ADD(state, value) {
state.sum += value;
},
JIAN(state,value) {
state.sum -= value;
}
} //用于操作组件中的数据
const state = {
sum: 12
} //用于存储数据
const getters = {
bigNumber(state){
return state.sum*10;
}
}//用于公共数据的处理
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
组件
<template>
<div>
<button @click="add">数据增长</button>
{{$store.state.sum}}
{{sum}}
{{bigNumber}}
<button @click="jian">数据减少</button>
{{$store.getters.bigNumber}}
</div>
</template>
<script>
import {mapState,mapGetters} from "vuex"
export default {
name: "functionText",
data(){
return {
n:1
}
},
methods:{
add(){
this.$store.dispatch("add",this.n);
},
jian(){
this.$store.commit("JIAN",this.n);
}
},
computed:{
...mapState(["sum"]),
//相当于sum(){return this.$store.state.sum}
...mapGetters(["bigNumber"])
}
}
</script>
<style scoped>
</style>
context不光可以commit,还可以接着dispatch,他还能点state找数据,不过修改的话vue工具失效
组件 简写
<template>
<div>
<button @click="add(n)">数据增长</button>
{{$store.state.add.sum}}
{{sum}}
{{bigNumber}}
<button @click="jian(n)">数据减少</button>
{{$store.state.minous.sum2}}
{{sum2}}
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from "vuex"
export default {
name: "functionText",
data(){
return {
n:1
}
},
methods:{
...mapActions("add",{add:"add"}), add是函数,直接在页面小括号传参数
...mapMutations("minous",{jian:"JIAN"})
},
computed:{
...mapState("add",["sum"]),
...mapState("minous",["sum2"]),
//相当于sum(){return this.$store.state.sum}
...mapGetters("add",["bigNumber"])
}
}
</script>
<style scoped>
</style>
store 下的index.js
import Vuex from 'vuex'
import Vue from "vue";
Vue.use(Vuex);
const add={
namespaced: true,
actions :{
add(context, value) {
setTimeout(() =>{
context.commit("ADD",value)
},2000)
}
} ,
mutations:{
ADD(state, value) {
state.sum += value;
},
},
state:{
sum: 12
},
getters:{
bigNumber(state){
return state.sum*10;
}
}};
const minous={
namespaced: true,
actions :{
} ,
mutations:{
JIAN(state, value) {
state.sum2 -= value;
},
},
state:{
sum2:10
}
}
export default new Vuex.Store({
modules:{
add,
minous
}
})
路由
npm i vue-router@3 --save
vue-router 插件专门实现spa单页面的应用
多个key和value的对应,由路径对应组件
index.js
import Vue from "vue"
import VueRouter from 'vue-router';
import About from "../components/About";
import Home from "../components/Home";
Vue.use(VueRouter)
export default new VueRouter({
routes:[
{
path: "/about", //页面跳转路径
component:About //路径改变对应组件改变
},
{
path:"/home",
component:Home
}
]
})
main.js引入
app组件
<router-link class="list-group-item"
active-class="active" to="/about">About</router-link>
<router-link class="list-group-item"
active-class="active" to="/home">Home</router-link>
link取代a标签,但是插件底层还是转成a标签
用<router-view></router-view>
来代替要切换的组件
二级路由
一般路由组件在pages文件夹
路由传数据
然后通过 <li>{{$route.query.id}}</li>
获得
命名路由
query可以改成params,但是只能用name不能用path
<li>{{$route.params.id}}</li>
组件代码简写
index组件
props:true
上面这个只针对params参数有效,让params中的参数以props的形式展示
浏览器历史记录
默认是push,留下所有浏览的记录,replace只留下最后一个浏览的记录
编程式路由导航
不使用router-link
标签实现跳转,比如按钮或定时器
replace同上
第三个go可以传正数和负数,代表前进和后退的步数
缓存路由组件
默认切换路由原来的组件会被销毁,用上面这个方法就可以缓存组件
:include=“[]” 缓存多个组件
路由组件独有生命周期函数
写在子组件上的两个钩子函数
activated和deactivated两个函数,前面在组件出现时候调用,后面在组件切换走的时候调用,区别去beforeDestroyed方法,路由独有用于缓存路由组件
路由守卫
前置路由守卫:
index.js中的方法,写在routes下面,export 上
调用next方法允许跳转
meta专门提供能自定义放入的东西,
authenticated//认证简称,在meta对象中可以定义标识
if (to.meta.isAuth){ next()} 判断操作,然后调next跳转
后置路由守卫:
跳转完成可以用与改变标题
独享路由守卫
beforeEnter:(to,from,next)=>{
//前置独享路由守卫
}
非全局,写在vueRouter的index.js中。写在路径和组件下面就可
mounted下面函数:
进入该组件和离开该组件都需要next而且必须是通过路由方式进入该组件,否则无法跳转
hash
#和后面的内容就是hash值
hash值不会带给服务器
Element-ui
npm install --save element-ui
npm install babel-plugin-component -D
babelconfig.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
["@babel/preset-env", { "modules": false }]
],
plugins:[
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
npm install babel-plugin-component -D
vue3
创建项目:npm create vue@latest
优势:组合式api和ts更容易维护
更快的速度
更小的体积
更优都数据响应式
vue3需要node16版本以上
ref如果生成引用类型数据,底层还是转换reactive生成
基本数据类型的响应式还是通过object.definedProperty,引用数据类型是通过Proxy代理和反射实现
计算属性
watch
<script>
import {ref,watchEffect,watch} from 'vue'
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup() {
let name = ref('hello')
watch(name, (newval,oldval) => {
alert(newval+'change'+oldval)
},{immediate:true,deep:true})
function butEvent(){
name.value+='ss'
}
watchEffect(() => {
const x1=name.value
console.log(x1)
})
return {
name,
butEvent
}
}
}
</script>
watch监视ref定义基本数据类型不能 点value ,监视reactive生成的deep强制生效
person是reactive定义的,监视其中一个属性,所以有效,而且要用箭头函数返回
watch检测reactive定义的数据需要点value才可以通过对象中属性的变化而检测到,如下
否则就需要深度监视才可以检测到对象中属性的变化
watchEffect
生命周期
左边的可以写在setup外面,右边写在里面,右边先执行
对比vue2,beforeDestroy变成了上面倒数第二个beforeUnmount
组建通信
父子组件
通过props传参,子组件写法如下
子传父组
父组件定义自定义事件@stu=‘stu’ , 子组件编写如下
顶层组件向底层组件通信
provide写在祖先元素,inject写在子元素或后代元素
第一个参数是字符串的name,第二个是ref定义的数据,也可以只有一个参数是非ref类型
通过ref获取dom元素
pinia
pinia的定义引入,函数箭头函数也可以,不要忘记ref定义的数据是reactive类型的数据,需要点vallue获取
持久化
toRef
customRef
自定义ref,详见162
Fragment
就是vue3在组件中不需要跟标签
Teleport
传送
v-if不显示直接在dom元素中移除
将组件的html结构移动到指定位置
dialog 对话框组件
将对话框组件放在里面,在页面移动到body中,出了防止改变所有父级组件
的宽度,还能方便设置绝对定位
Suspense
setup可以异步返回promise
新特性
templete标签里面不再只能有一个根节点
setup
ref
reactive readonly watcheffect
生命周期函数
defineOptions用于配置组件名称等
defineModel用于简化emit
$slots
v-enter v-enter-from
keep-alive
适用于性能优化
,在组件切换都时候保存状态到内存避免dom刷新
通过name确定组件名称,匿名组件不会匹配
匹配成功会多两个函数:actived和deactived