1.mvvm 框架是什么?
定义:M:Model(服务器上的业务逻辑操作) V:View(页面)VM:ViewModel(Model与View之间核心枢纽,比如Vue.js)
大体上:vm层(视图模型层)通过接口从后台m层(model层)请求数据,vm层继而和v(view层)实现数据的双向绑定。
三者关系:Model与ViewModel之间的双向关系
Model通过Ajax通信,发送数据给ViewModel。
ViewModel也可以通过Ajax通信,发送请求给Model。
ViewModel与View之间的双向关系
ViewModel中的数据改变,可以同时改变View上的显示内容。
View上的内容改变(比如输入框中的内容),也可以同时改变ViewModel中对应的数据。
前端框架MVVM出现的最大意义是什么:
mvvm层实现了前后端更好的分离(前端需要的数据只需要请求后端的接口即可)
MVVM 的出现促进了 GUI 前端开发与后端业务逻辑的分离,极大地提高了前端开发效率。
MVVM用接口实现了前后端数据的通信,这样可以使前后端之间的业务逻辑没有什么关系。
MVVM在感觉上要比mvc模式前后端要分的更开
前端框架MVVM中的vm层是干嘛的
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示),而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新,真正实现数据驱动开发。看到了吧,View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
View一般就是我们平常说的HTML文本的Js模板,里面可以嵌入一些js模板的代码,比如Mustache,比如jstl类似的模板伪代码
ViewModule层里面就是我们对于这个视图区域的一切js可视业务逻辑,举个例子,比如图片走马灯特效,比如表单按钮点击提交,这些自定义事件的注册和处理逻辑都写在ViewModule里面了
Module就更简单了,就是对于纯数据的处理,比如增删改查,与后台CGI做交互
2.vue-router 是什么?它有哪些组件
路由就是SPA(单页应用)的路径管理器,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质 就是建立起url和页面之间的映射关系。
SPA(single page application):单一页面应用程序,只有一个完整的页面;它在加载页面时,不会加载整个页面,而是只更新某个指定的容器中内容。单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面;vue-router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式;根据mode参数来决定采用哪一种方式。
1、Hash模式:
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。 hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中也不会不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置;所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据
2、History模式:
由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
//main.js文件中const router = new VueRouter({ mode: 'history', routes: [...]})
要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
exportconstroutes = [
{path:"/", name:"homeLink", component:Home}
{path:"/register", name:"registerLink", component: Register},
{path:"/login", name:"loginLink", component: Login},
{path:"*", redirect:"/"}]
此处就设置如果URL输入错误或者是URL 匹配不到任何静态资源,就自动跳到到Home页面
常用的形式:
<router-link :to='' class='active-class'> //路由声明式跳转 ,active-class是标签被点击时的样式
<router-view> //渲染路由的容器
<keep-alive> //缓存组件
3.active-class 是哪个组件的属性?
active-class属于vue-router的样式方法 当routerlink标签被点击时将会应用这个样式 使用有两种方法routerLink标签内使用
<router-link to='/'active-class="active">首页</router-link>
首页的active有时会有bug,会一直被应用
为了解决上面的问题,还需加入一个属性exact,类似也有两种方式:
在router-link中写入exact
<router-link to='/'active-class="active"exact>首页</router-link>
4.怎么定义 vue-router 的动态路由? 怎么获取传过来的值
可以通过query ,param两种方式
区别: query通过url传参,刷新页面还在 params刷新页面不在了
params的类型:
配置路由格式:/router/:id
传递的方式:在path后面跟上对应的值
传递后形成的路径:/router/123
<!-- 动态路由-params -->
//在APP.vue中
<router-link :to="'/user/'+userId" replace>用户</router-link>
//在index.js
{
path: '/user/:userid',
component: User,
},
跳转方法:
// 方法1:
<router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link>
// 方法2:
this.$router.push({name:'users',params:{uname:wade}})
// 方法3:
this.$router.push('/user/' + wade)
可以通过$route.params.userid 获取你说传递的值
query的类类型
配置路由格式:/router,也就是普通配置
传递的方式:对象中使用query的key作为传递方式
传递后形成的路径:/route?id=123
<!--动态路由-query -->
//01-直接在router-link 标签上以对象的形式
<router-link :to="{path:'/profile',query:{name:'why',age:28,height:188}}">档案</router-link>
/*
02-或者写成按钮以点击事件形式
<button @click='profileClick'>我的</button>
*/
//点击事件
profileClick(){
this.$router.push({
path: "/profile",
query: {
name: "kobi",
age: "28",
height: 198
}
});
}
跳转方法:
// 方法1:
<router-link :to="{ name: 'users', query: { uname: james }}">按钮</router-link>
// 方法2:
this.$router.push({ name: 'users', query:{ uname:james }})
// 方法3:
<router-link :to="{ path: '/user', query: { uname:james }}">按钮</router-link>
// 方法4:
this.$router.push({ path: '/user', query:{ uname:james }})
// 方法5:
this.$router.push('/user?uname=' + jsmes)
可以通过$route.query 获取你说传递的值
5.vue-router 有哪几种导航钩子?
1、全局守卫:router.beforeEach
使用router.beforeEach注册一个全局前置守卫:
constrouter=newVueRouter({...}) router.beforeEach((to,from, next) =>{// ...})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve 完之前一直处于等待中。
每个守卫方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来resolve这个钩子。执行效果依赖 next 方法的调用参数。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed(确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next 传递任意位置对象,且允许设置诸如replace: true、name: 'home' 之类的选项以及任何用在router-link的to prop或router.push中的选项。
next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给router.onError()注册过的回调。
确保要调用next方法,否则钩子就不会被 resolved。
2、全局解析守卫:router.beforeResolve
你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
3、全局后置钩子:router.afterEach
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
router.afterEach((to,from) =>{// ...})
4、路由独享的守卫:beforeEnter
constrouter=newVueRouter({routes:[{path:'/foo',component: Foo,beforeEnter:(to,from, next) =>{// ...}}]})
这些守卫与全局前置守卫的方法参数是一样的。
5、组件内的守卫:beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
const Foo={ template: `...`,
beforeRouteEnter(to, from,next){
//在渲染该组件的对应路由被 confirm 前调用
//不!能!获取组件实例 `this`//因为当守卫执行前,组件实例还没被创建
},
//不过,你可以通过传一个回调给next来访问组件实例。
//在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter(to, from,next){next(vm=>{//通过 `vm` 访问组件实例})},
beforeRouteUpdate(to, from,next){
//在当前路由改变,但是该组件被复用时调用//举例来说,对于一个带有动态参数的路径/foo/:id,在/foo/1和/foo/2之间跳转的时候,
//由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
//可以访问组件实例 `this`},
beforeRouteLeave(to, from,next){
//导航离开该组件的对应路由时调用
//可以访问组件实例 `this`}}
注意:beforeRouteEnter是支持给next 传递回调的唯一守卫。对于beforeRouteUpdate和beforeRouteLeave来说,this已经可用了,所以不支持传递回调,因为没有必要了:
beforeRouteUpdate(to,from, next){// just use `this`this.name= to.params.name
next()}
离开守卫beforeRouteLeave:通常用来禁止用户在还未保存修改前突然离开。该导航可以通过next(false)来取消:
beforeRouteLeave(to, from,next){
constanswer= window.confirm('Do you really want to leave? you have unsaved changes!')
if(answer){next()}else{next(false)}}
6.route和router 的区别
1.router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。
举例:history对象
$router.push({path:'home'});本质是向history栈中添加一个路由,在我们看来是 切换路由,但本质是在添加一个history记录
方法:
$router.replace({path:'home'});//替换路由,没有历史记录
2.route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
$route.path
字符串,等于当前路由对象的路径,会被解析为绝对路径,如"/home/news"。
$route.params
对象,包含路由中的动态片段和全匹配片段的键值对
$route.query
对象,包含路由中查询参数的键值对。例如,对于/home/news/detail/01?favorite=yes,会得到$route.query.favorite == 'yes'。
$route.router
路由规则所属的路由器(以及其所属的组件)。
$route.matched
数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
$route.name
当前路径的名字,如果没有使用具名路径,则名字为空。
$route.path, $route.params, $route.name, $route.query这几个属性很容易理解,主要用于接收路由传递的参数
7.vue-router响应路由参数的变化
当使用路由参数时,例如从 /user/aside导航到 /user/foo,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
注意:
(1)从同一个组件跳转到同一个组件。
(2)生命周期钩子created和mounted都不会调用。
可以使用router的组件内钩子函数
beforeRouteUpdate(to,from,next){
//在这个钩子函数中:to表示将要跳转的路由对象,from表示从哪个路由跳转过来,next多数就是需要调用
//created和mounted不调用,无法拿到需要的动态值,就通过to.path,to.params等
//可以在这个函数中打印to,具体看to对象有什么可以使用的属性
}
添加watch监听
watch: {
// 方法1 //监听路由是否变化
'$route' (to, from) {
if(to.query.id !== from.query.id){
this.id = to.query.id;
this.init();//重新加载数据
}
}
}
//方法 2 设置路径变化时的处理函数
watch: {
'$route': {
handler: 'init',
immediate: true
}
}
为了实现这样的效果可以给router-view添加一个不同的key,这样即使是公用组件,只要url变化了,就一定会重新创建这个组件。
<router-view :key="$route.fullpath"></router-view>
8.vue-router实现路由懒加载( 动态加载路由 )
1. vue异步组件技术
vue-router配置路由,使用vue的异步组件技术,可以实现按需加载。
但是,这种情况下一个组件生成一个js文件。
举例如下:
{path:'/promisedemo',name:'PromiseDemo',component:resolve=>require(['../components/PromiseDemo'],resolve)}
ts文件下需要设置
2. es提案的import()
推荐使用这种方式(需要webpack > 2.4)
webpack官方文档:webpack中使用import()
vue官方文档:路由懒加载(使用import())
vue-router配置路由,代码如下:
// 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。
const ImportFuncDemo1 = () => import('../components/ImportFuncDemo1')
const ImportFuncDemo2 = () => import('../components/ImportFuncDemo2')
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。
// const ImportFuncDemo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo')
// const ImportFuncDemo2 = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo2')
export default new Router({
routes: [
{ path: '/importfuncdemo1', name: 'ImportFuncDemo1', component: ImportFuncDemo1 },
{ path: '/importfuncdemo2', name: 'ImportFuncDemo2', component: ImportFuncDemo2 } ]
})
3、webpack提供的require.ensure()
vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。
这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
举例如下:
{path:'/promisedemo',name:'PromiseDemo',component:resolve=>require.ensure([],()=>resolve(require('../components/PromiseDemo')),'demo')},{path:'/hello',name:'Hello',// component: Hellocomponent:resolve=>require.ensure([],()=>resolve(require('../components/Hello')),'demo')