Vue - 基础笔记梳理

备战秋招,复习基础。若有错误,欢迎批评指正,共同进步!html

基本概念

构建用户界面的渐进式框架前端

只关注图层,自底向上增量开发(增量是什么TBC!!!)vue

代码只须要关注逻辑层,DOM操做由VUE处理。node

经过尽量简单的API实现响应的数据绑定和组合的组件视图。ios

实现原理

资料参考:Vue原理解析——本身写个Vuevue-router

资料参考:剖析Vue原理&实现双向绑定MVVMvuex

  1. 经过创建虚拟dom树document.createDocumentFragment(),方法建立虚拟dom树。
  2. 一旦被监测的数据改变,会经过Object.defineProperty定义的数据拦截,截取到数据的变化。
  3. 截取到的数据变化,从而经过订阅 — 发布者模式,触发Watcher(观察者),从而改变虚拟dom的中的具体数据。
  4. 最后,经过更新虚拟dom的元素值,从而改变最后渲染dom树的值,完成双向绑定

虚拟DOM

vue的vnode代码部分位于项目的src/core/vdom文件夹下,vue的Virtual DOM是基于snabbdom修改的,vnode类的数据结构以下npm

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
  }
}
复制代码

数据双向绑定

vue原理

  1. 实现一个数据监听器Observer,可以对数据对象的全部属性进行监听,若有变更可拿到最新值并通知订阅者
  2. 实现一个指令解析器Compile,对每一个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. 实现一个Watcher,做为链接Observer和Compile的桥梁,可以订阅并收到每一个属性变更的通知,执行指令绑定的相应回调函数,从而更新视图
  4. mvvm入口函数,整合以上三者

实现observer

Object.defineProperty

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 Object.defineProperty(obj, prop, descriptor)json

将须要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。axios

对其更底层对象属性的修改或获取的阶段进行了拦截

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之间通讯的桥梁,主要作的事情是:

  1. 在自身实例化时往属性订阅器(dep)里面添加本身
  2. 自身必须有一个update()方法
  3. 待属性变更dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

实现MVVM

MVVM做为数据绑定的入口,整合Observer、Compile和Watcher三者,经过Observer来监听本身的model数据变化,经过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通讯桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变动的双向绑定效果。

!!!太复杂了!!!还要再研究研究!!!QAQ

4种状况不触发vue响应式更新

  • 不能检测到的数组变更是:
    • 一、当利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue;
    • 二、当修改数组的长度时,例如:vm.items.length = newLength;
  • 不能检测到的对象变更是:
    • 三、向响应式对象添加属性;
    • 四、向响应式对象删除属性;
  • 解决方法总结:
    • 一、建立新的数组替换原有数组值
    • 二、使用JavaScript的数组操做函数,这些方法都会返回一个新数组,也是数组替换原理;
    • 三、使用vue自带的 vue.set(object , key , value );向响应式对象添加属性;
    • 四、使用vue自带的 vue.delete(object , key );向响应式对象删除属性;
    • 五、对象添加属性还可使用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']

$:

  1. 表示属性和方法存在于vue实例的原型上
  2. 字符串中插入变量 ${...}

只有当实例被建立时data中存在的属性才是响应式的!即:要先初始化,哪怕为空也行

实例方法

vm.$el === document.getElementById('example')
vm.$watch('a',function(new,old){在vm.a改变时调用}
复制代码

不要在选项属性或回调函数中使用箭头函数!

兄弟组件传值

  • 子传父,而后父传子
  • vuex
  • 事件总线:以新建一个Vue实例看成事件总线,触发on和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)
复制代码
  • 区别:

    1. pushState()设置的新URL能够是与当前URL同源的任意URL;而hash只可修改#后面的部分,所以只能设置与当前URL同文档的URL;
    2. pushState()设置的新URL能够与当前URL如出一辙,这样也会把记录添加到栈中;而hash设置的新值必须与原来不同才会触发动做将记录添加到栈中;
    3. pushState()经过stateObject参数能够添加任意类型的数据到记录中;而hash只可添加短字符串;
    4. pushState()可额外设置title属性供后续使用。
    5. hash 模式下,仅hash符号以前的内容会被包含在请求中,如http://www.abc.com,所以对于后端来讲,即便没有作到对路由的全覆盖,也不会返回404错误。
    6. 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~或用setget双向绑定!

相关文章
相关标签/搜索