MVVM思想
M: 即Model 模型,包括数据和一些基本的操作
V: 即View视图,页面的结构,页面渲染
VM:即View-Model,模型与视图层的数据交换,即数据与页面间的双向操作
在MVVM中VM要做的就是,只要我们的Model中的数据发生了改变,View中就会显示改变后的数据,当修改了View的时候,Model中的数据也会发生改变,不在需要自己通过获取DOM元素来进行复杂的操作
封装组件 SPA
SPA单页面是只有一个主页面的应用,一开始就加载页面所需要的html css js ,所有的内容都包含在这个主页面中,但是在写的时候还是会写许多的模板,然后通过路由的方式动态载入,进行页面的跳转
优点:用户的体验好,在用户点击切换页面的时候速度快,减轻服务器的压力,
缺点:初次加载耗时多,页面不能前进后退,因为web应用在一个页面中显示所有的内容,所以不能使用浏览器的请进和后退
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SPA页面</title>
<script src="../js/vue.js"></script>
<script src="../js/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
<router-view></router-view>
</div>
<script>
var Login = {
template:'<h1>这是登录组件</h1>'
}
var Register = {
template:'<h1>这是注册组件</h1>'
}
var home = {
template:'<h1>SPA首页</h1>'
}
var router = new VueRouter({
routes: [
// 从首页重定向到登录页
// {path:'/',component:home,redirect:{path:'/login',component:Login}},
{path:'/',component:home},
{path:'/login',component:Login },
{path:'/register',component:Register},
]
})
var vm = new Vue({
el:'#app',
router
})
</script>
</body>
</html>
computed和watch区别
计算属性computed:
computed会对data对象中的数据做计算处理,并且它会把计算结果进行缓存,在页面刷新的时候不会重新进行计算,只有当data对象中的数据发生变化的时候才会重新计算,计算属性中虽然以方法的形式进行定义,但是在使用的时候会以属性的方式使用
<body>
<div id="app">
<h1>{{get}}</h1>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
msg1 :'first',
msg2:'last'
},
computed:{
get(){
return this.msg1 +'-'+ this.msg2 ;
}
}
})
</script>
</body>
watch监听
watch是监听某个值的变化,然后去执行相对应的函数,如果需要在某个值发生变化的时候做什么事情,这时候使用watch来监听这个数据的变化。
watch中的函数名称必须要和data中的属性名保持一致,因为watch是监听data中的属性,当data中的属性发生变化时,想对应的函数才会执行
watch中的函数有两个参数,第一个是新传的值(newvalue),第二个为之前的旧值(oldval)
<body>
<div id="app">
<h1>{{msg1}}</h1>
<h1>{{msg2}}</h1>
<h1>{{msg3}}</h1>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
msg1 :'first',
msg2:'last',
msg3:"fff"
},
watch:{
msg1:function(newval,oldval){
this.msg2 = oldval + this.msg3 ;
},
msg2:function(newval,oldvalue){
this.msg3 = newval + this.msg1 ;
}
}
})
</script>
</body>
测试结果:
hh
firstfff
firstfffhh
测试方法:在控制台测试
vm.msg1='hh'
"hh"
区别:computed是在多条数据发生变化的时候使用computed来进行计算(一个属性受多个属性的影响 常用于购物车总价的计算),watch是监听data中的某一条数据的变化的(一条数据的变化影响多条数据 常用于搜索框)
vue中的插槽的理解
插槽用来定义,插槽就是在定义子组件的时候起到了一个占位的作用,当父组件在用这个子组件的时候,想要在里面添加自己的html或者组件时,添加的内容就会添加到定义的标签里面进行替换
插槽分为匿名插槽,具名插槽,和作用域插槽
具名插槽和匿名插槽
<body>
<div id="app">
<com>
<ul slot='ul'>
<li v-for="(item , index) in list" :key='index'>{{item}}</li>
</ul>
<!-- 此时的span标签不被执行,缺少名字 -->
<span>hhh</span>
<span slot="span">这是具名插槽</span>
</com>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
list:['java','javascript','jQuery']
},
components:{
com:{
template:`
<div>
<slot name="ul">插槽</slot>
<slot name="span">插槽</slot>
</div>
`
}
}
})
</script>
</body>
作用域插槽:在子组件的slot上面绑定数据的插槽
作用域插槽在使用的时候,在自定义组件里面要使用template包裹,template有一个属性slot-scope携带子组件传递过来的值,父组件只需要做的就是样式的变化。
<template slot-scope='change'>
<li>{{change.items}}</li>
</template>
</con>
<con>
<template slot-scope='change'>
<span style="margin-left: 20px;">{{change.items}}</span>
</template>
</con>
</div>
<script>
Vue.component('con',{
template:`
<div>
<ul>
<slot v-for = 'item in list' :items = item>{{item}}</slot>
</ul>
</div>
`,
data(){
return {
list:['java','c++','php','javascript','c#']
}
}
})
var vm = new Vue({
el:'#app',
data:{
}
})
</script>
</body>
v-if和v-show的区别,适合那种情境
这两个都是用来控制元素的显示与隐藏的。
v-show是通过使用css的display:none来控制元素的显示与隐藏的。
v-if是vue通过操作虚拟DOM树来删除和创建真实的DOM树来达到元素的显示和隐藏
如果需要频繁的显示和隐藏元素选择v-show
什么是vuex,在什么场景下使用
vuex是一个为vuejs提供的状态管理模式,如果一个状态需要在多个组件中使用,就需要将这个状态集中管理到store中,方便组件之间的共享,一般管理登录状态,购物车等需要在多个页面中用的的状态
vuex的结构
vuex使用:先引入->在Vue.use(Vuex)中挂载->在生成store对象->将store对象在全局的vue实例中注册
const store = new Vuex.Store({
state:{},//state中存储数据,在组件中使用this.$store.state
mutations:{},//mutations是唯一可以修改state中数据的对象,里面的每个方法都有一个回调函数,回调函数有一个形参state就是state中的数据,在组件中通过this.$store.commit('mutations中定义的方法名',参数(也可以是对象))触发mutations中的方法
actions:{},//actions中定义异步方法来修改state的状态值,异步方法接收两个参数(context,payload)但是不能直接修改,只能通过context.commit('mutations中的方法名',参数)来修改state中的状态值 在组件中使用this.$store.dispath('actions中的方法名',参数)来触发actions中的方法
getters:{},//getters类似于计算属性 可以针对state中的数据做一些逻辑计算, 在组件中使用this.$store.getter.方法名访问
modules: {
a: moduleA
}
})
modulA = { // vuex允许我们将store分割成多个store模块
state: {},
mutations: {},
getters: {},
actions: {},
modules:{}
}
Vuex的工作流程:
在vue的组件中通过dispath来调用actions中的异步方法,在actions中通过commit来调用Mutations中的方法,在mutations中可以直接操作state中的数据,只要state中的数据发生改变就会立即响应到组件中
Vuex的store仓库中对象的操作方式
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const moduleA = {
state: {
name: 'zhangsan'
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
getters: {
fullname(state) {
return state.name + '11111'
},
fullname2(state, getters) {
return getters.fullname + '2222'
},
fullname3(state, getters, rootState) { // rootState访问跟上state中的数据
return getters.fullname2 + rootState.counter
}
},
actions: {
aUpdateName(context) {
console.log(context);
setTimeout(() => {
context.commit('updateName', 'wangwu')
}, 1000)
}
}
}
const store = new Vuex.Store({
state: {
counter: 1000,
students: [
{id: 1, name: 'zs', age: 16},
{id: 2, name: 'ls', age: 17},
{id: 3, name: 'ww', age: 18},
{id: 4 name: 'zl', age: 19}
],
info: {
name: 'zhangsanfen',
age: 20,
height: 1.76
}
},
mutations: {
decrement(state) {
state.counter--
},
incrementCount(state, payload) {
state.counter += payload.count
},
addStudent(state, stu) { //stu接收组件中传递过来的对象
state.students.push(stu)
},
updateInfo(state) {
state.info.name = 'zhaosi'
}
},
actions: {
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo');
console.log(payload);
resolve('1111111')
}, 1000)
})
}
},
getters: {
powerCounter(state) {
return state.counter * state.counter
},
more20stu(state) {
return state.students.filter(s => s.age > 20)
},
more20stuLength(state, getters) {
return getters.more20stu.length
},
moreAgeStu(state) {
return age => {
return state.students.filter(s => s.age > age)
}
}
},
modules: {
a: moduleA
}
})
路由使用的步骤
1、下载路由模块 npm install vue-router -S
2、在router.js中引入路由 import VueRouter from ‘vue-router’
3、通过Vue.use(VueRouter)在vue全局注册使用
4、接着创建路由对象并导出 export detault touter = new VueRouter({routes:[]})
5、在main.js全局的vue实例中进行注册,然后就能在全局中通过this.$router进行操作了
常用的组件
渲染一个元组件为动态组件。绑定is来决定哪个组件被渲染
元素作为单个元素/组件的过渡效果,只会把过渡效果用到其包裹的内容上面,而不会额外渲染元素,也不会出现在可被检查的组件层级中。
元素作为多个元素/组件的过渡效果。<transition-group>
渲染一个真实的 DOM 元素。默认渲染 <span>
,可以通过 tag
=‘标签名’ 配置哪个元素应该被渲染。
组价包裹着的组件能够被缓存,当页面切换的时候不会持续的创建和销毁组件,它里面有三个属性
。include 值是一个字符串或者正则表达式。只有名称匹配的组件才会被缓存
。exclude 值是一个字符串或者正则表达式。任何名称匹配的组价都不会被缓存 exclude 的优先级高于 include的优先级
。max 值是数字 最多可以缓存多少组件实例
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<view-router></view-router>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<view-router></view-router>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<view-router></view-router>
</keep-alive>
<keep-alive :max="10">
<view-router></view-router>
</keep-alive>
keep-alive相对应的两个生命周期钩子 activated 和deactivated
当组件在 <keep-alive>
内被切换,它的 activated
(活跃的钩子) 和 deactivated
(不活跃的钩子) 这两个生命周期钩子函数将会被对应执行。
vue的生命周期
生命周期就是一个组件从创建、数据初始化、挂载、更新、销毁的这么一个阶段每个周期都有相对应的钩子函数来进行处理
beforeCreate :在实例创建之后,数据和方法初始化之前进行调用,此时获取不到this. e l ( 受 控 区 域 ) 和 t h i s . el(受控区域) 和 this. el(受控区域)和this.data(实例中的数据)
Create:实例创建完成,此时可以操作data中的数据,但是受控区域还未绑定
beforeMount:el挂载之前,此时获取不到dom元素
mounted:此时是给vue实例对象添加$el成员,并且替换掉挂载的DOM元素
beforeUpdata:当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,渲染前调用beforeUpdata,还未同步到页面
Updated:当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,渲染后调用updata
beforeDestroy:在实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed:在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
组件中的data为什么必须是一个函数
因为一个组件被创建后会被用到多个地方,如果data还是一个对象的话,就会造成在一个地方修改了data中的数据其它地方的数据也会被修改,因为这个js本身 对象没有作用域的特点造成的,如果将data定义为一个函数的话它就会有自己的作用域,相当于每个地方调用的时候都会实例化一个对象,然后各自操作各自作用域里面的属性。
vue中的组件传值实现的各种方式
父子组件之间的传值
父向子传值:
父组件通过属性绑定的方式获取父组件的值(:item = ‘message’),然后传递到子组件中的props:[‘item’]中/props:{
item:{type:string , detault:'默认值 '}
}
通过this.$children[0]拿到子组件中的值
使用this.$refs获取指定的组件
子向父传值:
使用this.$emit(‘事件’,参数) 传递事件给父组件,父组件监听该事件来进行传值
使用this.$parent获取父组件中的对象
非父子组件之间的传值
使用数据总线的方式,用来触发和接收事件,来起到父子组件之间的通信
Vue.prototype.bus = new Vue()
给其中一个组件中绑定事件触发一个方法,改方法中通过this.bus. e m i t ( ′ f u n ′ , 参 数 ) 的 形 式 将 数 据 传 出 , 在 接 收 数 据 的 那 个 组 件 中 使 用 t h i s . b u s . emit('fun',参数)的形式将数据传出,在接收数据的那个组件中使用this.bus. emit(′fun′,参数)的形式将数据传出,在接收数据的那个组件中使用this.bus.on(‘fun’(参数)=>{})来达到传值
<body>
<div id="app">
<com></com>
<con></con>
</div>
<script>
Vue.prototype.bus = new Vue() ;
Vue.component ('com',{
template:`
<div>
<button @click='getDate'>点击com组件</button>
</div>
`,
data() {
return {
msg:'com组件中的值'
}
},
methods: {
getDate(){
this.bus.$emit('fun',this.msg) ;
}
},
})
Vue.component ('con',{
template:`
<div>
<h1>这是con组件----{{msgg}}</h1>
</div>
`,
data() {
return {
msgg:''
}
},
// mounted是vue中的一个钩子函数,一般在初始化页面完成后,再对dom节点进行相关操作。
mounted() {
this.bus.$on('fun',(msg)=>{
console.log(msg)
this.msgg = msg ;
}) ;
},
})
var vm = new Vue({
el:'#app',
data:{
}
})
</script>
</body>
vue中有几种路由模式
vue路由中有hash和history两种路由模式,默认使用hash模式,hash模式在URL地址中会出现一个#号,而history模式的url是正常url的结构。
如果想要将hash模式转换为history模式需要在路由实例中加mode:'history’来改变
hash值是由前端路由处理,所以改变hash时不会刷新页面,也不会向服务器发送请求
history是整个地址都要加载,可以保存历史记录,方便前进和后退