快速進階Vue3.0

在2019.10.5日發(fā)布了Vue3.0預(yù)覽版源碼诲宇,但是預(yù)計最早需要等到 2020 年第一季度才有可能發(fā)布 3.0 正式版居灯。

可以直接看 github源碼宿礁。

新版Vue 3.0計劃并已實現(xiàn)的主要架構(gòu)改進和新功能:

  • 編譯器(Compiler)
    • 使用模塊化架構(gòu)
    • 優(yōu)化 "Block tree"
    • 更激進的 static tree hoisting 功能 (檢測靜態(tài)語法墩衙,進行提升)
    • 支持 Source map
    • 內(nèi)置標(biāo)識符前綴(又名"stripWith")
    • 內(nèi)置整齊打诱⒌(pretty-printing)功能
    • 移除 Source map 和標(biāo)識符前綴功能后泌霍,使用 Brotli 壓縮的瀏覽器版本精簡了大約10KB
  • 運行時(Runtime)
    • 速度顯著提升
    • 同時支持 Composition API 和 Options API货抄,以及 typings
    • 基于 Proxy 實現(xiàn)的數(shù)據(jù)變更檢測
    • 支持 Fragments (允許組件有從多個根結(jié)點)
    • 支持 Portals (允許在DOM的其它位置進行渲染)
    • 支持 Suspense w/ async setup()

目前不支持IE11

1.剖析Vue Composition API

可以去看官方地址

  • Vue 3 使用ts實現(xiàn)了類型推斷,新版api全部采用普通函數(shù)烹吵,在編寫代碼時可以享受完整的類型推斷(避免使用裝飾器)
  • 解決了多組件間邏輯重用問題 (解決:高階組件碉熄、mixin、作用域插槽)
  • Composition API 使用簡單

先嘗鮮Vue3.0看看效果

<script src="vue.global.js"></script>
<div id="container"></div>
<script>
    function usePosition(){ // 實時獲取鼠標(biāo)位置
        let state = Vue.reactive({x:0,y:0});
        function update(e) {
            state.x= e.pageX
            state.y = e.pageY
        }
        Vue.onMounted(() => {
            window.addEventListener('mousemove', update)
        })
        Vue.onUnmounted(() => {
            window.removeEventListener('mousemove', update)
        })
        return Vue.toRefs(state);
    }
    const App = {
        setup(){ // Composition API 使用的入口
            const state  = Vue.reactive({name:'youxuan'}); // 定義響應(yīng)數(shù)據(jù)
            const {x,y} = usePosition(); // 使用公共邏輯
            Vue.onMounted(()=>{
                console.log('當(dāng)組掛載完成')
            });
            Vue.onUpdated(()=>{
                console.log('數(shù)據(jù)發(fā)生更新')
            });
            Vue.onUnmounted(()=>{
                console.log('組件將要卸載')
            })
            function changeName(){
                state.name = 'webyouxuan';
            }
            return { // 返回上下文,可以在模板中使用
                state,
                changeName,
                x,
                y
            }
        },
        template:`<button @click="changeName">{{state.name}} 鼠標(biāo)x: {{x}} 鼠標(biāo): {{y}}</button>`
    }
    Vue.createApp().mount(App,container);
</script>

到這里你會發(fā)現(xiàn)響應(yīng)式才是Vue的靈魂

2.源碼目錄剖析

packages目錄中包含著Vue3.0所有功能

├── packages
│   ├── compiler-core # 所有平臺的編譯器
│   ├── compiler-dom # 針對瀏覽器而寫的編譯器
│   ├── reactivity # 數(shù)據(jù)響應(yīng)式系統(tǒng)
│   ├── runtime-core # 虛擬 DOM 渲染器 肋拔,Vue 組件和 Vue 的各種API
│   ├── runtime-dom # 針對瀏覽器的 runtime锈津。其功能包括處理原生 DOM API、DOM 事件和 DOM 屬性等凉蜂。
│   ├── runtime-test # 專門為測試寫的runtime
│   ├── server-renderer # 用于SSR
│   ├── shared # 幫助方法
│   ├── template-explorer
│   └── vue # 構(gòu)建vue runtime + compiler

compiler
compiler-core主要功能是暴露編譯相關(guān)的API以及baseCompile方法
compiler-dom基于compiler-core封裝針對瀏覽器的compiler (對瀏覽器標(biāo)簽進行處理)

runtime
runtime-core 虛擬 DOM 渲染器琼梆、Vue 組件和 Vue 的各種API
runtime-testDOM結(jié)構(gòu)格式化成對象性誉,方便測試
runtime-dom 基于runtime-core編寫的瀏覽器的runtime (增加了節(jié)點的增刪改查,樣式處理等)茎杂,返回render错览、createApp方法

reactivity
單獨的數(shù)據(jù)響應(yīng)式系統(tǒng),核心方法reactive煌往、effect倾哺、 refcomputed

vue
整合 compiler + runtime

到此我們解析了Vue3.0結(jié)構(gòu)目錄刽脖,整體來看整個項目還是非常清晰的

再來嘗嘗鮮:
我們可以根據(jù)官方的測試用例來看下如何使用Vue3.0

const app = {
    template:`<div>{{count}}</div>`,
    data(){
        return {count:100}
    },
}
let proxy = Vue.createApp().mount(app,container);
setTimeout(()=>{
    proxy.count = 200;
},2000)

接下來我們來對比 Vue 2 和 Vue 3 中的響應(yīng)式原理區(qū)別

3.Vue2.0響應(yīng)式原理機制 - defineProperty

這個原理老生常談了羞海,就是攔截對象,給對象的屬性增加setget方法曲管,因為核心是defineProperty所以還需要對數(shù)組的方法進行攔截

3.1 對對象進行攔截

function observer(target){
    // 如果不是對象數(shù)據(jù)類型直接返回即可
    if(typeof target !== 'object'){
        return target
    }
    // 重新定義key
    for(let key in target){
        defineReactive(target,key,target[key])
    }
}
function update(){
    console.log('update view')
}
function defineReactive(obj,key,value){
    observer(value); // 有可能對象類型是多層却邓,遞歸劫持
    Object.defineProperty(obj,key,{
        get(){
            // 在get 方法中收集依賴
            return value
        },
        set(newVal){
            if(newVal !== value){
                observer(value);
                update(); // 在set方法中觸發(fā)更新
            }
        }
    })
}
let obj = {name:'youxuan'}
observer(obj);
obj.name = 'webyouxuan';

3.2 數(shù)組方法劫持

let oldProtoMehtods = Array.prototype;
let proto = Object.create(oldProtoMehtods);
['push','pop','shift','unshift'].forEach(method=>{
    Object.defineProperty(proto,method,{
        get(){
            update();
            oldProtoMehtods[method].call(this,...arguments)
        }
    })
})
function observer(target){
    if(typeof target !== 'object'){
        return target
    }
    // 如果不是對象數(shù)據(jù)類型直接返回即可
    if(Array.isArray(target)){
        Object.setPrototypeOf(target,proto);
        // 給數(shù)組中的每一項進行observr
        for(let i = 0 ; i < target.length;i++){
            observer(target[i])
        }
        return
    };
    // 重新定義key
    for(let key in target){
        defineReactive(target,key,target[key])
    }
}

測試

let obj = {hobby:[{name:'youxuan'},'喝']}
observer(obj)
obj.hobby[0].name = 'webyouxuan'; // 更改數(shù)組中的對象也會觸發(fā)試圖更新
console.log(obj)

這里依賴收集的過程就不詳細描述了,我們把焦點放在Vue3.0

  • Object.defineProperty缺點
    • 無法監(jiān)聽數(shù)組的變化
    • 需要深度遍歷院水,浪費內(nèi)存

4.Vue3.0數(shù)據(jù)響應(yīng)機制 - Proxy

在學(xué)習(xí)Vue3.0之前腊徙,你必須要先熟練掌握ES6中的 ProxyReflect 及 ES6中為我們提供的 Map檬某、Set兩種數(shù)據(jù)結(jié)構(gòu)

先應(yīng)用再說原理:

let p = Vue.reactive({name:'youxuan'});
Vue.effect(()=>{ // effect方法會立即被觸發(fā)
    console.log(p.name);
})
p.name = 'webyouxuan';; // 修改屬性后會再次觸發(fā)effect方法

源碼是采用ts編寫撬腾,為了便于大家理解原理,這里我們采用js來從0編寫橙喘,之后再看源碼就非常的輕松啦时鸵!

4.1 reactive方法實現(xiàn)

通過proxy 自定義獲取、增加厅瞎、刪除等行為

function reactive(target){
    // 創(chuàng)建響應(yīng)式對象
    return createReactiveObject(target);
}
function isObject(target){
    return typeof target === 'object' && target!== null;
}
function createReactiveObject(target){
    // 判斷target是不是對象,不是對象不必繼續(xù)
    if(!isObject(target)){
        return target;
    }
    const handlers = {
        get(target,key,receiver){ // 取值
            console.log('獲取')
            let res = Reflect.get(target,key,receiver);
            return res;
        },
        set(target,key,value,receiver){ // 更改 、 新增屬性
            console.log('設(shè)置')
            let result = Reflect.set(target,key,value,receiver);
            return result;
        },
        deleteProperty(target,key){ // 刪除屬性
            console.log('刪除')
            const result = Reflect.deleteProperty(target,key);
            return result;
        }
    }
    // 開始代理
    observed = new Proxy(target,handlers);
    return observed;
}
let p = reactive({name:'youxuan'});
console.log(p.name); // 獲取
p.name = 'webyouxuan'; // 設(shè)置
delete p.name; // 刪除

我們繼續(xù)考慮多層對象如何實現(xiàn)代理

let p = reactive({ name: "youxuan", age: { num: 10 } });
p.age.num = 11

由于我們只代理了第一層對象初坠,所以對age對象進行更改是不會觸發(fā)set方法的和簸,但是卻觸發(fā)了get方法,這是由于 p.age會造成 get操作

get(target, key, receiver) {
      // 取值
    console.log("獲取");
    let res = Reflect.get(target, key, receiver);
    return isObject(res) // 懶代理碟刺,只有當(dāng)取值時再次做代理锁保,vue2.0中一上來就會全部遞歸增加getter,setter
    ? reactive(res) : res;
}

這里我們將p.age取到的對象再次進行代理,這樣在去更改值即可觸發(fā)set方法

我們繼續(xù)考慮數(shù)組問題
我們可以發(fā)現(xiàn)Proxy默認可以支持數(shù)組半沽,包括數(shù)組的長度變化以及索引值的變化

let p = reactive([1,2,3,4]);
p.push(5);

但是這樣會觸發(fā)兩次set方法爽柒,第一次更新的是數(shù)組中的第4項,第二次更新的是數(shù)組的length

我們來屏蔽掉多次觸發(fā)者填,更新操作

set(target, key, value, receiver) {
    // 更改浩村、新增屬性
    let oldValue = target[key]; // 獲取上次的值
    let hadKey = hasOwn(target,key); // 看這個屬性是否存在
    let result = Reflect.set(target, key, value, receiver);
    if(!hadKey){ // 新增屬性
        console.log('更新 添加')
    }else if(oldValue !== value){ // 修改存在的屬性
        console.log('更新 修改')
    }
    // 當(dāng)調(diào)用push 方法第一次修改時數(shù)組長度已經(jīng)發(fā)生變化
    // 如果這次的值和上次的值一樣則不觸發(fā)更新
    return result;
}

解決重復(fù)使用reactive情況

// 情況1.多次代理同一個對象
let arr = [1,2,3,4];
let p = reactive(arr);
reactive(arr);

// 情況2.將代理后的結(jié)果繼續(xù)代理
let p = reactive([1,2,3,4]);
reactive(p);

通過hash表的方式來解決重復(fù)代理的情況

const toProxy = new WeakMap(); // 存放被代理過的對象
const toRaw = new WeakMap(); // 存放已經(jīng)代理過的對象
function reactive(target) {
  // 創(chuàng)建響應(yīng)式對象
  return createReactiveObject(target);
}
function isObject(target) {
  return typeof target === "object" && target !== null;
}
function hasOwn(target,key){
  return target.hasOwnProperty(key);
}
function createReactiveObject(target) {
  if (!isObject(target)) {
    return target;
  }
  let observed = toProxy.get(target);
  if(observed){ // 判斷是否被代理過
    return observed;
  }
  if(toRaw.has(target)){ // 判斷是否要重復(fù)代理
    return target;
  }
  const handlers = {
    get(target, key, receiver) {
      // 取值
      console.log("獲取");
      let res = Reflect.get(target, key, receiver);
      return isObject(res) ? reactive(res) : res;
    },
    set(target, key, value, receiver) {
      let oldValue = target[key];
      let hadKey = hasOwn(target,key);
      let result = Reflect.set(target, key, value, receiver);
      if(!hadKey){
        console.log('更新 添加')
      }else if(oldValue !== value){
        console.log('更新 修改')
      }
      return result;
    },
    deleteProperty(target, key) {
      console.log("刪除");
      const result = Reflect.deleteProperty(target, key);
      return result;
    }
  };
  // 開始代理
  observed = new Proxy(target, handlers);
  toProxy.set(target,observed);
  toRaw.set(observed,target); // 做映射表
  return observed;
}

到這里reactive方法基本實現(xiàn)完畢,接下來就是與Vue2中的邏輯一樣實現(xiàn)依賴收集和觸發(fā)更新

file
get(target, key, receiver) {
    let res = Reflect.get(target, key, receiver);
+   track(target,'get',key); // 依賴收集
    return isObject(res) 
    ?reactive(res):res;
},
set(target, key, value, receiver) {
    let oldValue = target[key];
    let hadKey = hasOwn(target,key);
    let result = Reflect.set(target, key, value, receiver);
    if(!hadKey){
+     trigger(target,'add',key); // 觸發(fā)添加
    }else if(oldValue !== value){
+     trigger(target,'set',key); // 觸發(fā)修改
    }
    return result;
}

track的作用是依賴收集占哟,收集的主要是effect心墅,我們先來實現(xiàn)effect原理酿矢,之后再完善 tracktrigger方法

4.2 effect實現(xiàn)

effect意思是副作用,此方法默認會先執(zhí)行一次怎燥。如果數(shù)據(jù)變化后會再次觸發(fā)此回調(diào)函數(shù)瘫筐。

let school = {name:'youxuan'}
let p = reactive(school);
effect(()=>{
    console.log(p.name);  // youxuan
})

我們來實現(xiàn)effect方法,我們需要將effect方法包裝成響應(yīng)式effect铐姚。

function effect(fn) {
  const effect = createReactiveEffect(fn); // 創(chuàng)建響應(yīng)式的effect
  effect(); // 先執(zhí)行一次
  return effect;
}
const activeReactiveEffectStack = []; // 存放響應(yīng)式effect
function createReactiveEffect(fn) {
  const effect = function() {
    // 響應(yīng)式的effect
    return run(effect, fn);
  };
  return effect;
}
function run(effect, fn) {
    try {
      activeReactiveEffectStack.push(effect);
      return fn(); // 先讓fn執(zhí)行,執(zhí)行時會觸發(fā)get方法策肝,可以將effect存入對應(yīng)的key屬性
    } finally {
      activeReactiveEffectStack.pop(effect);
    }
}

當(dāng)調(diào)用fn()時可能會觸發(fā)get方法,此時會觸發(fā)track

const targetMap = new WeakMap();
function track(target,type,key){
    // 查看是否有effect
    const effect = activeReactiveEffectStack[activeReactiveEffectStack.length-1];
    if(effect){
        let depsMap = targetMap.get(target);
        if(!depsMap){ // 不存在map
            targetMap.set(target,depsMap = new Map());
        }
        let dep = depsMap.get(target);
        if(!dep){ // 不存在set
            depsMap.set(key,(dep = new Set()));
        }
        if(!dep.has(effect)){
            dep.add(effect); // 將effect添加到依賴中
        }
    }
}

當(dāng)更新屬性時會觸發(fā)trigger執(zhí)行隐绵,找到對應(yīng)的存儲集合拿出effect依次執(zhí)行

function trigger(target,type,key){
    const depsMap = targetMap.get(target);
    if(!depsMap){
        return
    }
    let effects = depsMap.get(key);
    if(effects){
        effects.forEach(effect=>{
            effect();
        })
    }
}

我們發(fā)現(xiàn)如下問題

let school = [1,2,3];
let p = reactive(school);
effect(()=>{
    console.log(p.length);
})
p.push(100);

新增了值驳糯,effect方法并未重新執(zhí)行,因為push中修改length已經(jīng)被我們屏蔽掉了觸發(fā)trigger方法氢橙,所以當(dāng)新增項時應(yīng)該手動觸發(fā)length屬性所對應(yīng)的依賴酝枢。

function trigger(target, type, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  let effects = depsMap.get(key);
  if (effects) {
    effects.forEach(effect => {
      effect();
    });
  }
  // 處理如果當(dāng)前類型是增加屬性,如果用到數(shù)組的length的effect應(yīng)該也會被執(zhí)行
  if (type === "add") {
    let effects = depsMap.get("length");
    if (effects) {
      effects.forEach(effect => {
        effect();
      });
    }
  }
}

4.3 ref實現(xiàn)

ref可以將原始數(shù)據(jù)類型也轉(zhuǎn)換成響應(yīng)式數(shù)據(jù)悍手,需要通過.value屬性進行獲取值

function convert(val) {
  return isObject(val) ? reactive(val) : val;
}
function ref(raw) {
  raw = convert(raw);
  const v = {
    _isRef:true, // 標(biāo)識是ref類型
    get value() {
      track(v, "get", "");
      return raw;
    },
    set value(newVal) {
      raw = newVal;
      trigger(v,'set','');
    }
  };
  return v;
}

問題又來了我們再編寫個案例

let r = ref(1);
let c = reactive({
    a:r
});
console.log(c.a.value);

這樣做的話豈不是每次都要多來一個.value帘睦,這樣太難用了

get方法中判斷如果獲取的是ref的值,就將此值的value直接返回即可

let res = Reflect.get(target, key, receiver);
if(res._isRef){
  return res.value
}

4.4 computed實現(xiàn)

computed 實現(xiàn)也是基于 effect 來實現(xiàn)的坦康,特點是computed中的函數(shù)不會立即執(zhí)行竣付,多次取值是有緩存機制的

先來看用法:

let a = reactive({name:'youxuan'});
let c = computed(()=>{
  console.log('執(zhí)行次數(shù)')
  return a.name +'webyouxuan';
})
// 不取不執(zhí)行,取n次只執(zhí)行一次
console.log(c.value);
console.log(c.value);
function computed(getter){
  let dirty = true;
  const runner = effect(getter,{ // 標(biāo)識這個effect是懶執(zhí)行
    lazy:true, // 懶執(zhí)行
    scheduler:()=>{ // 當(dāng)依賴的屬性變化了滞欠,調(diào)用此方法古胆,而不是重新執(zhí)行effect
      dirty = true;
    }
  });
  let value;
  return {
    _isRef:true,
    get value(){
      if(dirty){
        value = runner(); // 執(zhí)行runner會繼續(xù)收集依賴
        dirty = false;
      }
      return value;
    }
  }
}

修改effect方法

function effect(fn,options) {
  let effect = createReactiveEffect(fn,options);
  if(!options.lazy){ // 如果是lazy 則不立即執(zhí)行
    effect();
  }
  return effect;
}
function createReactiveEffect(fn,options) {
  const effect = function() {
    return run(effect, fn);
  };
  effect.scheduler = options.scheduler;
  return effect;
}

trigger時判斷

deps.forEach(effect => {
  if(effect.scheduler){ // 如果有scheduler 說明不需要執(zhí)行effect
    effect.scheduler(); // 將dirty設(shè)置為true,下次獲取值時重新執(zhí)行runner方法
  }else{
    effect(); // 否則就是effect 正常執(zhí)行即可
  }
});
let a = reactive({name:'youxuan'});
let c = computed(()=>{
  console.log('執(zhí)行次數(shù)')
  return a.name +'webyouxuan';
})
// 不取不執(zhí)行,取n次只執(zhí)行一次
console.log(c.value);
a.name = 'zf10'; // 更改值 不會觸發(fā)重新計算,但是會將dirty變成true

console.log(c.value); // 重新調(diào)用計算方法

到此我們將Vue3.0核心的 Composition Api 就講解完畢了筛璧! 不管是面試還是后期的應(yīng)用也再也不需要擔(dān)心啦逸绎!~

歡迎關(guān)注前端優(yōu)選 webyouxuan 精彩文章,等你來看夭谤!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棺牧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子朗儒,更是在濱河造成了極大的恐慌颊乘,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件醉锄,死亡現(xiàn)場離奇詭異乏悄,居然都是意外死亡,警方通過查閱死者的電腦和手機恳不,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門檩小,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妆够,你說我怎么就攤上這事识啦「何茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵颓哮,是天一觀的道長家妆。 經(jīng)常有香客問我,道長冕茅,這世上最難降的妖魔是什么伤极? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮姨伤,結(jié)果婚禮上哨坪,老公的妹妹穿的比我還像新娘。我一直安慰自己乍楚,他們只是感情好当编,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著徒溪,像睡著了一般忿偷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臊泌,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天鲤桥,我揣著相機與錄音,去河邊找鬼渠概。 笑死茶凳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的播揪。 我是一名探鬼主播贮喧,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剪芍!你這毒婦竟也來了塞淹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤罪裹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后运挫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體状共,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年谁帕,在試婚紗的時候發(fā)現(xiàn)自己被綠了峡继。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡匈挖,死狀恐怖碾牌,靈堂內(nèi)的尸體忽然破棺而出康愤,到底是詐尸還是另有隱情,我是刑警寧澤舶吗,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布征冷,位于F島的核電站,受9級特大地震影響誓琼,放射性物質(zhì)發(fā)生泄漏检激。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一腹侣、第九天 我趴在偏房一處隱蔽的房頂上張望叔收。 院中可真熱鬧,春花似錦傲隶、人聲如沸饺律。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽复濒。三九已至,卻和暖如春帖鸦,著一層夾襖步出監(jiān)牢的瞬間芝薇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工作儿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洛二,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓攻锰,卻偏偏與公主長得像晾嘶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子娶吞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在分析Vue的源碼之前我們需要了解一些前置知識垒迂,如Flow、源碼目錄妒蛇、構(gòu)建方式机断、編譯入口等。 認識 Flow Fl...
    oWSQo閱讀 1,086評論 1 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,103評論 1 32
  • 還有幾個月距離vue2的首次發(fā)布就滿3年了,而vue的作者尤雨溪也在去年年末發(fā)布了關(guān)于vue3.0的計劃陶耍,如果不出...
    周小肆閱讀 684評論 0 4
  • 明明是過去做夢才有的事奋蔚,明明是之前觸摸不到的人。 痛擊。
    小七快點飛閱讀 156評論 0 0
  • 我在盛夏與你相遇 我也在盛夏與你分離 我在茫茫人海中尋找你的身影 空洞無神的眼睛陷入了無限的夢境 緣分宛如曇花一現(xiàn)...
    Nathyiuu閱讀 78評論 0 0