备战秋招,复习基础。如有错误,欢迎批评指正,共同进步!
基本概念
构建用户界面的渐进式框架
只关注图层,自底向上增量开发(增量是什么TBC!!!)
代码只需要关注逻辑层,DOM操作由VUE处理。
通过尽可能简单的API实现响应的数据绑定和组合的组件视图。
实现原理
资料参考:Vue原理解析——自己写个Vue
资料参考:剖析Vue原理&实现双向绑定MVVM
- 通过建立虚拟dom树
document.createDocumentFragment(),方法创建虚拟dom树。 - 一旦被监测的数据改变,会通过Object.defineProperty定义的数据拦截,截取到数据的变化。
- 截取到的数据变化,从而通过订阅 — 发布者模式,触发Watcher(观察者),从而改变虚拟dom的中的具体数据。
- 最后,通过更新虚拟dom的元素值,从而改变最后渲染dom树的值,完成双向绑定
虚拟DOM
vue的vnode代码部分位于项目的src/core/vdom文件夹下,vue的Virtual DOM是基于snabbdom修改的,vnode类的数据结构如下
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component’s scope
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.functionalContext = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
数据双向绑定
- 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
- 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
- mvvm入口函数,整合以上三者
实现observer
Object.defineProperty
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
Object.defineProperty(obj, prop, descriptor)
将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
对其更底层对象属性的修改或获取的阶段进行了拦截
var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; // 哈哈哈,监听到值变化了 kindeng --> dmq
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
// 取出所有属性遍历
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
function defineReactive(data, key, val) {
observe(val); // 监听子属性
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
return val;
},
set: function(newVal) {
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
}
});
}
发布-订阅者模式
维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法。
在数据变动时发布消息给订阅者,触发相应的监听回调。
// ... 省略
function defineReactive(data, key, val) {
var dep = new Dep();
observe(val); // 监听子属性
Object.defineProperty(data, key, {
// ... 省略
set: function(newVal) {
if (val === newVal) return;
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
dep.notify(); // 通知所有订阅者
}
});
}
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
实现Compile
compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
实现watcher
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己
- 自身必须有一个update()方法
- 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
实现MVVM
MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
!!!太复杂了!!!还要再研究研究!!!QAQ
4种情况不触发vue响应式更新
- 不能检测到的数组变动是:
- 1、当利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue;
- 2、当修改数组的长度时,例如:vm.items.length = newLength;
- 不能检测到的对象变动是:
- 3、向响应式对象添加属性;
- 4、向响应式对象删除属性;
- 解决方法总结:
- 1、创建新的数组替换原有数组值
- 2、使用JavaScript的数组操作函数,这些方法都会返回一个新数组,也是数组替换原理;
- 3、使用vue自带的 vue.set(object , key , value );向响应式对象添加属性;
- 4、使用vue自带的 vue.delete(object , key );向响应式对象删除属性;
- 5、对象添加属性还可以使用Object.assign({},obj1,obj2)返回获取的新对象替换原有对象;
基础语法
var vm = new Vue({
el:'相当于id',
data:{...},
template:{...},
methods:{...},函数调用,不能缓存 → {{methodTest()}}
computed:{...},属性调用,具有缓存功能,依赖于data中的数据,只有在它的相关依赖数据发生改变时才会重新求值 → {{computedTest}}
watch:{...},监测Vue实例上的数据变动
render:{...} 渲染优先级:render → template → outerHTML
})
data() {
return {
demo: {
name: ''
},
value: ''
};
},
computed: {
newName() {
return this.demo.name;
}
},
watch: {
newName(val) {
this.value = val;
}
}
文本插值<p>{{message}}</p>
HTML插值<div v-html:"message></div>
属性v-bind:class="{'class1':use}" → 动态赋值
指令v- → 如:v-if v-else
用户输入v-model → 实现数据双向绑定
事件监听 v-on:click
v-if v-show v-for区别
- v-for: 遍历整个数组 item in items
- v-if:操控一个dom元素的创建与销毁(决定一个元素的存在与否),有更高的切换开销。相当于原生js中的if
- v-show:控制一个dom的显示隐藏(通过操作dom的display:block/none)达到效果,有更高的初始渲染开销。
- 如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
处理边界
| 用处 | 范例 |
|---|---|
| 访问根实例 | this.$root.foo |
| 访问父级组件实例 | this.$parent |
| 访问子组件实例(非响应式) | 子<input ref="input"> 父this.$refs.input.focus() |
| 依赖注入(访问任意上级) | 父 provide:funtion(){ return{getMap:this.getMap}} 子 inject:['getMap'] |
$:
- 表示属性和方法存在于vue实例的原型上
- 字符串中插入变量
${...}
只有当实例被创建时data中存在的属性才是响应式的!即:要先初始化,哪怕为空也行
实例方法
vm.$el === document.getElementById('example')
vm.$watch('a',function(new,old){在vm.a改变时调用}
不要在选项属性或回调函数中使用箭头函数!
兄弟组件传值
- 子传父,然后父传子
- vuex
- 事件总线:以新建一个Vue实例当作事件总线,触发
emit
生命周期
参考资料:手把手教Vue--生命周期
| 方法名 | 状态 | 含义 | 用法 |
|---|---|---|---|
| beforeCreate | creating 状态 | 实例创建之前调用 | 加 Loading 事件 |
| created | creating 状态 | 实例创建成功,此时 data 中的数据显示出来了 | 页面没有加载完成就请求数据,结束 Loading |
| beforeMount | mounting 状态 | 数据中的 data 在模版中先占一个位置 | |
| mounted | mounting 状态 | 模版中的 data 数据直接显示出来了 | 发起异步服务端请求 |
| beforeUpdate | updating 状态 | 当 data 数据发生变化调用,发生在虚拟 DOM 重新渲染和打补丁之前 | |
| updated | updating 状态 | 数据更改导致的虚拟 DOM 重新渲染和打补丁 | |
| beforeDestroy | destroying 状态 | 在 vue 实例销毁之前调用,此时实例任然可用 | 弹出确认删除 |
| destroyed | destroying 状态 | 在 vue 实例销毁之后调用,vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | 关闭定时器,停止监听属性,清除相关数据达到资源的有效利用 |
Vue路由
HTML里
<div id = "app">
<router-link to="/foo">Go to Foo</router-link>
<router-view></router-view>
</div
JS里
1 定义路由组件 const Foo = {template:'<div>foo</div>'}
2 定义路由 const routes = [{path:'/foo',component:Foo}]
3 创建router实例 const router = new VueRouter({routes})
4 创建挂载根实例 const app = new Vue({router}}).$mount('#app')
| 属性 | 用法 |
|---|---|
| to | 目标路由的链接 |
| replace | 点击时调用 |
| append | 添加相对路径的基路径 :to="{path:'relative/path'}" append |
| tag | 渲染成某种标签 如<li> |
| active-class | 链接激活时使用的CSS类名 |
| exact-active-class | 链接精确匹配时的CSS类名 |
| event | 触发导航的事件,如event="mouseover→鼠标移动到上面时导航html内容改变 |
keep-alive 缓存失活的组件。切换页面,切换回来,页面状态不变
hash和history
参考资料:vue-router的两种模式(hash和history)及区别
- hash - 即地址栏URL中的 # 符号(此hash不是密码学里的散列运算)
比如这个URL:
http://www.abc.com/#/hello,hash的值为#/hello.它的特点在于:hash虽然出现在URL中,但不会被包括在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
1 $router.push() //调用方法
2 HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
3 History.transitionTo() //监测更新,更新则调用History.updateRoute()
4 History.updateRoute() //更新路由
5 {app._route= route} //替换当前app路由
6 vm.render() //更新视图
- history - 利用了HTML5 History Interface中新增的
pushState()和replaceState()方法。(需要特定浏览器支持)
1.push
与hash模式类似,只是将window.hash改为history.pushState
2.replace
与hash模式类似,只是将window.replace改为history.replaceState
3.监听地址变化
在HTML5History的构造函数中监听popState(window.onpopstate)
-
区别:
- pushState()设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,因此只能设置与当前URL同文档的URL;
- pushState()设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
- pushState()通过stateObject参数可以添加任意类型的数据到记录中;而hash只可添加短字符串;
- pushState()可额外设置title属性供后续使用。
- hash 模式下,仅hash符号之前的内容会被包含在请求中,如http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误。
- history模式下,前端的URL必须和实际向后端发起请求的URL一致。如htttp://www.abc.com/book/id。如果后端缺少对/book/id 的路由处理,将返回404错误、
Vue过渡
<transition name="动画名称" mode="out-in">
<div></div>
</transition>
过渡模式:in-out / out-in
| 属性 | 用法 |
|---|---|
| v-enter | 进入过渡的开始状态 |
| v-enter-active | 进入过渡生效时的状态,如{transition:opacity 2s} |
| v-enter-to | 进入过渡的结束状态 |
| v-leave | 离开过渡的开始状态 |
| v-leave-active | 离开过渡生效时的状态 |
| v-leave-to | 离开过渡的结束状态 |
混入
mixins:可复用的方法或计算属性,可包含任意组件选项。
待补充!!!
AJAX
vue-resource
通过XMLHttpRequest或JSONP发起请求并处理响应
可使用全局对象方式vue.http或在组件内部使用this.$http来发期请求
get:function(){
this.$http.get('/someUrol').then(
function(res){
document.write(res.body);
},
function(){
console.log('error!')
}
);
}
API:待补充具体含义~
get(url,[options])
head(url,[options])
delete(url,[options])
jsonp(url,[options])
post(url,[body],[options])
put(url,[body],[options])
patch(url,[body],[options])
axios
基于promise的HTTP客户端
npm install axios
import axios from 'axios'
get:
axios.get(url).then(function(response){...}).catch(function(error){...});
post:
axios({
method:'post',
url:'/user',
data:{
firstName:'a',
lastName:'b'
}
});
axios.all([func1(),func2()]).then{...} → 执行多个并发请求
API:待补充具体含义~
axios.request(config)
axios.get(url[,config])
axios.delete(url[,config])
axios.head(url[,config])
axios.post(url[,data,[config]])
axios.put(url[,data,[config]])
axios.patch(url[,data,[config]])
axios拦截器
请求拦截
axios.interceptor.request.use(function(config){
//在发送请求前做的事
return config;
},function(error){
//请求错误时做的事
return Promise.reject(error);
});
响应拦截
axios.interceptors.response.use(function(response){
//对响应数据做的事
return config;
},function(error){
//请求错误时做的事
return Promise.reject(error);
});
Prop验证
- 必填:
required:true - 默认值:
default:100 - 自定义验证函数:
validator:function(value){return ['success,'danger'].indexOf(value)!==-1 - 类型检查
插槽
将子组件中<slot></slot>替换为父组件<></>之间的任意代码
<slot>当父组件中不提供内容时的后备内容</slot>
具名插槽
父组件:<template v-slot:header></template>
子组件:<header></header>
作用域插槽
父组件:<template v-slot:default="slotProps">
{{slotProps.user.firstName}}
</template>
子组件:<slot v-bind:user="user"></slot>
简写父组件:<current-user v-slot:"slotProps">slotProps.user.firstName</current-user>
再简写父组件:<current-user v-slot:"{user}">{{slotProps.user.firstName}}</current-user>
可定义后备值"{user={firstName:'Guest'}}"
Vue-CLI
快速原型开发:对于单个 *.vue文件
vue serve在开发环境模式下零配置为.js或.vue文件启动一个服务器
vue build在生产环境模式下零配置构建一个.js或.vue文件
Preload 预加载首渲内容
Prefetch 预加载未来内容
Vue-cookies
载入
npm install vue-cookies
var Vue = require('vue')
Vue.use(require('vue-cookies')
或
import Vue from 'vue'
import VueCookies from 'vue-cookies'
Vue.use(VueCookies)
| 用法 | 范例 |
|---|---|
| 设置cookie | this.$cookies.set(keyName,value[,expireTime[,path[,domain[,secure]]]]) |
| 获取cookie | this.$cookies.get(keyName) // return value |
| 删除cookie | this.$cookies.remove(keyName[,path[,domain]]) |
| 存在? | this.$cookies.isKey(keyName) //retuen boolean |
| 得到全部 | this.$cookies.keys() |
VueX
状态管理模式 集中式存储管理应用的所有组件的状态
- state:驱动应用的数据源(单一状态树)
- view:以声明方式将state映射到视图
- actions:响应在view上的用户输入导致的状态变化
基本用法
--- main.js ---
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
//配置...
});
new Vue({
el: '#app',
router:router,
store:store,
render:h=>{
return h(App)
}
});
Vuex里的数据都是响应式的!!!
state
--- main.js ---
const store = new Vuex.Store({
state:{
count:0
}
});
--- index.vue ---
{{ $store.state.count}}
或
export default{
computed:{
count (){
return this.$store.state.count;
}
}
}
mapState
辅助函数,获取多个状态,帮助生成计算属性
computed:{
localComputed(){
...
},
...mapState({
...
})
}
Getter
计算store,返回值会缓存。依赖改变时会重新计算。可通过属性(缓存)和方法(不缓存)访问。
--- main.js ---
const store = new Vuex.Store({
state:{list:[1,5,8,10,30,50]},
getters:{
filteredList: state =>{
return state.list.filter(item => item <10); ← 以state为参数
},
listCount: (state,getters) =>{
return getters.filteredList.length; ← 以state和getters为参数
}
}
})
--- index.vue ---
export default{
computed:{
list (){
return this.$store.getters.filteredList;
},
listCount (){
return this.$store.getters.listCount;
}
}
}
mapGetter
辅助函数,映射多个局部计算。
computed:{
...mapGetters([...也可另取名字])
}
mutations
mutation改变store中状态的唯一方法就是提交mutation(同步)
--- main.js ---
const store = new Vues.Store({
state:{list:[1,5,8,10,30,50]},
mutation:{
increment(state,params){
state.count+=params.count; ← 直接传入对象
},
descrease (state){
state.count --;
}
}
});
--- index.vue ---
export default{
methods:{
handleIncrement (){
this.$stroe.commit({
type:'increment',
count:10 ← 直接传入对象
});
},
handleDecrease (){
this.$store.commit('descrease');
}
}
}
Action
提交mutation(而非直接更改状态),支持异步
--- main.js ---
mutation:{
increment (state){
state.count++
}
},
actions:{
increment(与store实例具有相同方法和属性的context对象){
context.commit('increment')
}
异步
asyncIncrement (context){
return new Promise(resolve =>{
setTimeout(()=>{
context.commit('increment');
resolve();
},1000)
});
}
--- index.vue ---
methods:{
handleActionIncrement(){
this.$stroe.dispatch('increment');
},
handleAsyncIncrement (){
this.$store.dispatch('asyncIncrement').then(()=>{ ← 异步调用 返回的是Promise
console.log('...');
});
}
}
mapActions
辅助函数,映射多个dipatch调用
可嵌套:
actionB({dispatch,commit}){
return dispatch('actionA').then(()={
commit('other Mutation')
})
}
可用async / await 组合action! → 一个dispatch触发多个action
涉及改变数据的,就用mutations,存在业务逻辑的,就用actions
Module
将store分割成模块
const moduleA = {
state:{...},
mutations:{...},
actions:{...}, ← 可接收rootState 同getter
getters:{
sumCount (state,getters,rootState){ ← 在模块内部调用根节点状态:rootState
return state.count + rootState.count;
}
}
}
const moduleB = {...}
const store = new Vuex.store({
modules{
a:moduleA,
b:moduleB
}
})
访问:
store.state.a
store.state.b
命名空间:
namespaced:true访问全局需加一些额外操作~- 调用时路径改变
其他注意
vuex中的state用v-model会报错!!应绑定事件回调mutation~或用set和get双向绑定!