框架入门及 Vue
MVC、MVP 与 MVVM
MVC
M(Model):数据保存
V(View):用户页面
C(Controller):业务逻辑
所有通信都是单向的。
- View 传指令到 Controller
- Controller 完成业务逻辑后,要求 Model 改变状态
- Model 将新的数据发送到 View,用户得到反馈
MVP
传统前端开发过程使用的 MVP 设计模式(eg.jQuery)。Model 数据层,View 视图层(DOM 式,负责绘制 UI 元素、与用户进行交互),Presenter 呈现层(处理与用户交互的逻辑)。
- 各部分之间的通信是双向的,View 与 Model 不发生联系,都通过 Presenter 传递,所以 P 是 MVP 模式的核心,P 里的业务逻辑是 M 和 V 之间的中转站。
- 在 MVP 设计模式中,M 层很远;在编写代码的时候其实都在写 P,而 P 中的代码大部分都是在操作 DOM。写大型项目的时候,其实 P 层中百分之七八十的代码都是在操作 DOM
MVVM
P 层变成了 VM 层,并且 VM 层不需要我们去编写(是 Vue 自带的)
- 在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的,因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上(VM 层一方面会监听数据的变化来改变视图,另一方面会监听 V 层的事件触发,然后帮我们调用我们写的逻辑代码来改变 M 层的数据)。用 MVVM 开发的时候,最重的一层其实是 M 层。所以可以这么理解,用 jQuery 开发的时候我们是面向 DOM 进行编程,而用 Vue 是面对数据进行开发,这样在编写大型项目的时候能节省很多代码。
- ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
- 对于 MVVM 来说,其实最重要的并不是通过双向绑定或者其他的方式将 View 与 ViewModel 绑定起来,而是通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM 的精髓。
多页面应用 and 单页面应用
- 多页面应用:每一次页面跳转的时候,后台的服务器(后端)都会返回一个新的 html 文档。只经过一个 http 请求,速度快;搜索引擎排名效果好。但是每次切换页面都需要一次 http 请求,所以切换速度慢。
- 单页面应用:在 Vue 项目中, 实现页面跳转不用
<a>
,而是<router-link to=xxx>
,页面跳转是通过 JS 感知 url 的变化,来先清除页面的组件,再渲染新组件实现的。SEO 只识别 html 的内容,所以 SEO 效果会比较差。但是仍有其他方法解决些缺点
基础知识
- Vue 实例、模板(在项目中的 App.vue 是一个 Vue 实例,其他的页面和组件其实是模板。Vue 实例的属性可用 $+属性名 加以区分)
- Vue 实例的数据用 data 属性保存,在 template 中可用插值表达式插入;时间用 v-on;方法用 method 属性保存
- template 内不要放逻辑,可用计算属性 computed 代替;侦听某一个数据的变化并对此作出反应用侦听器 watch
- v-if,v-show 用于数据的显示;v-for 用于数据做循环展示
- 样式绑定 v-bind(有多种语法,eg. 数组语法,对象语法)
watch 的用法
监听多个变量
监听单个字符串或数字变量的话就直接写就可以了,如果是监听多个,要结合计算属性,把要监听的多个变量封装成一个对象
1 | // realName 和 idCard 是data中的两个变量 |
监听对象
监听变量是字符串或数字的话就直接写就可以了,如果监听的是对象,要 计算属性 + 用 deep: true 实现对对象内部属性的监听,也见深度监听
1 | watch:{ |
watch 和 updated 的区别
- updated 是数据发生变化且界面更新完毕执行;不能监听路由数据;所有数据发生变化都会调用(耗费性能)
- 【网易雷火】所以想实现监听一个变量的变化并在这个变量变化导致的页面更新后做一些事情,只用 updated 是不够的。所以怎么实现到现在我仍旧不知道
Vue 生命周期
Vue 实例从创建到销毁的过程,就是生命周期(有创建、初始化数据、挂载 Dom、渲染、更新销毁等一系列过程)。分为 8 个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。
- 在 beforeCreate 阶段,vue 实例的挂载元素 el 和数据对象 data 都为 undefined,还未初始化。在 created 阶段,vue 实例的数据对象 data 有了,el 还没有
- 在 beforeMount 阶段,vue 实例的$el 和 data 都初始化了,但还是挂载之前为虚拟的 dom 节点,data.message 还未替换。在 mounted 阶段,vue 实例挂载完成,data.message 成功渲染。
- beforeUpdate:数据发生改变页面还没被重新渲染之前
- 销毁前/后:执行 destory()
它的生命周期有多个生命周期钩子,即是某一个时间点会自动执行的函数,借用这些钩子我们能在 Vue 实例的生命周期中写一些逻辑完善我们的需求
组件
组件注册
子组件 export default 中写 name 属性;父组件用 import 并写 components 属性即可引用
传值
- 父子组件间的传值
- 父传给子:父绑定属性的方式传给子,子用 props 方法接收
- 子传给父:$emit 来传递自定义事件和参数,父通过监听这个自定义的事件接收
- 非父子组件间的传值(兄弟组件传值)
- 小型项目用 enentBus(发布订阅模式),就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。在 Vue.prototype.bus=new Vue()。 在 Vue 的原型加一个 bus 属性,指向一个 Vue 实例。这样 Vue 实例都会有这个 bus 属性,而且指向的都是同一个实例。然后在一个组件的 bus 去$emit(), 另一个组件的 bus 的去 on 监听事件
- 大型项目用 Vuex
插槽
使用场景:父组件向子组件传递 DOM,可用插槽。即是在父组件中用某个子组件标签,标签之间就可以写插槽,可以写任何模板代码(可以是一点 html 代码,甚至可以引用另外一个组件)。然后在子组件中这部分内容就用<slot>
代替
具名插槽:父组件中写一个 slot 属性。子组件写成<slot name = '该属性'>
即可。就能实现传递多个插槽
作用域插槽:子组件 v-for 一个<slot>
,则这个插槽必须是作用域插槽。在父组件写法会有不同
Vue 中操作 DOM
先在 DOM 元素节点/组件上加 ref 属性和属性值,然后 Vue 实例中用 vm.$refs.属性值 就能引用这个 DOM 节点。
Vue 中的动画
不太熟练
CSS 动画
- transition
- animation
- 自定义
- 用 Animate.css 库。ink src 引入 css 库,在
中写 enter-active-class=类名,类名开头必须是 animated,然后再写具体哪个动画
JS 动画:用 Velocity.js
Vue-cli
在真实的 Vue 项目开发过程中,我们会借助 webpack 构建大型项目的开发目录,然后在开发完成之后进行打包,把代码打包生成一个线上可运行的最终代码。每个开发人员自己去配置 webpack 的开发环境是挺难的,所以有 Vue-cli 脚手架工具,用来快速构建 Vue 项目,这个脚手架自带了 webpack 的配置
Vue-router
路由就是根据网址的不同,返回不同组件给用户。组件中的<router-view>
显示的是当前路由地址所对应的内容。在 router/index.js 中定义当用户访问某个路径 path 时候,给用户展示某个组件
基本用法
在 router/index.js 中定义当用户访问某个路径 path 时候,给用户展示某个组件
在组件中用标签<router-link to="/" tag="div">
动态路由匹配
例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果。 把配置文件中的 path 写成path: '/user/:id' // 动态路径参数 以冒号开头
编程式导航
页面跳转还可以用编程式导航。在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push(location, onComplete?, onAbort?)
命名路由
通过 name 来标识一个路由,而不是路径。在 index.js 配置中加 name 属性后,可用传对象访问<router-link :to="{ name: 'user', params: { userId: 123 }}" tag="div">
重定向和别名
“重定向”的意思是,当用户访问 /a 时,URL 将会被替换成 /b,然后匹配路由为 /b,在配置中加 redirect 属性即可
“别名”的意思是, /a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。加 alias 属性即可
路由懒加载
在组件中引用组件的时候,如果子组件很多,我们可以用懒加载实现按需加载。
在引用组件的时候写成const Foo = () => import('./Foo.vue')
,router 的配置文件不用改
导航守卫
导航守卫主要用来通过跳转或取消的方式守卫导航,其实是vue 路由的钩子函数,是写在 router 的配置文件中
首页可以控制导航跳转,beforeEach,afterEach 等,一般用于页面 title 的修改。一些需要登录才能调整页面的重定向功能。
- beforeEach 主要有 3 个参数 to,from,next。
- to:route 即将进入的目标路由对象。
- from:route 当前导航正要离开的路由。
- next:function 一定要调用该方法 resolve 这个钩子。执行效果依赖 next 方法的调用参数。可以控制网页的跳转
keep-alive
在<router-view>
外包裹<keep-alive>
,能实现路由的内容(几个组件)被加载过一次之后,就把路由中的内容放在内存之中,下次再进路由的时候不需要重新渲染组件,在内存中并显示就可以。
此外该标签可以加个 exclude=”Detail”,就是进入 Detail 组件的时候 kepp-alive 不会生效
Vuex(看到官方文档项目结构结束就差不多了)
Vuex 是 Vue 官方推荐的数据框架,能用于不同组件间的数据共享(一般用来非父子组件的数据共享)
可以理解为储存公用数据的仓库(store),各个组件都能一起共享数据。
- actioin 是放异步操作
- mutations 是放同步的对数据的改变
- state 是驱动应用的数据源
使用
在/src 目录下新建 store/index.js(可以继续新建 mutations.js, state.js 导入 index.js 中),在 src/main.js import 这个文件
此时在组件中可以不用绑定属性不用 props 了,直接用 Vuex 的 mapState,mapMutations 等简化写法
可以结合 localStorage 保存一些数据
- store.Vuex 使用单一状态树,即每个应用将仅仅包含一个 store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据
- mutations 定义的方法动态修改 Vuex 的 store 中的状态或数据
- actions 可以理解为通过将 mutations 里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action
- getter.类似 vue 的计算属性,主要用来过滤一些数据
- module.项目特别复杂的时候,可以让每一个模块拥有自己的 state、mutation、action、getters,使得结构非常清晰,方便管理
Mock 数据
- static 目录建立 mock 文件夹(因为在整个工程里,只有 static 目录能被外部访问)
- proxy 转发。让对 api/xx.json 的请求转到 mock/xx.json
- Vue 是推荐用axios第三方模块来获取 Ajax 数据,安装之后就可以用 axios.get()方法(其实类似于 fetch)。写好方法后让方法在 mounted 的时候执行
一些功能的原理
Vue 实现数据双向绑定的原理:Object.defineProperty()
- vue 实现数据双向绑定主要是:采用数据劫持结合观察者模式的方式。Vue 会遍历数据对象的属性,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调
- vue 的数据双向绑定 将 MVVM 作为数据绑定的入口,整合 Observer,Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 的数据变化,通过 Compile 来解析编译模板指令(vue 中是用来解析 插值表达式),最终利用 watcher 搭起 observer 和 Compile 之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据 model 变更双向绑定效果
手写实现双向数据绑定
1 | //双向数据绑定 |
缺点
- 监听不到对象属性的增删、数组元素和长度的变化
- 同时会在 vue 初始化的时候把所有的观察者都建立好,才能观察到数据对象属性的变化
Vue3.0 的改进
- 采用了 ES2015 的 Proxy 来代替 Object.defineProperty,
- 可以做到监听对象属性的增删和数组元素和长度的修改,还可以监听 Map、Set、WeakSet、WeakMap,
- 同时还实现了惰性的监听,不会在初始化的时候创建所有的 Observer,而是会在用到的时候才去监听。
- 但是,虽然主流的浏览器都支持 Proxy,ie 系列却还是不兼容,所以对 ie 还是 Object.defineProperty()
v-for 的原理
computed 的原理
- 初始化 data, 使用 Object.defineProperty 把这些属性全部转为 getter/setter。
- 初始化 computed, 遍历 computed 里的每个属性,每个 computed 属性都是一个 watch 实例。每个属性提供的函数作为属性的 - getter,使用 Object.defineProperty 转化。
- Object.defineProperty getter 依赖收集。用于依赖发生变化时,触发属性重新计算。
- 若出现当前 computed 计算属性嵌套其他 computed 计算属性时,先进行其他的依赖收集
杂
v-if 与 v-show 的区别
v-if 按照条件是否渲染,v-show 是 display 的 block 或 none;
v-el 作用
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标.可以是 CSS 选择器,也可以是一个 HTMLElement 实例
vue 的优点是什么?
- 低耦合。视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的”View”上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变
- 可重用性。你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 view 重用这段视图逻辑
- 可测试。界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写
Vue 组件 data 为什么必须是函数
每个组件都是 Vue 的实例。组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一个会影响其他
为什么官方建议数据异步请求在 mounted 事件进行
请求是需要时间的,而且这个时间具有不稳定性,很可能 vue 的虚拟 DOM 准备好了,你的数据才请求到,然后又得更新一遍虚拟 DOM,再渲染,极大地延长了白屏时间,用户体验很不好。而在 mounted 事件请求数据呢,静态页面会先渲染好,等数据好了,再更新部分 DOM 即可