Vue3 源碼瞎看 2——reactivity 淺析

前言

感覺 reactivity 代碼比較少兵琳,看看能不能一篇寫個(gè)大概狂秘。
一般這種響應(yīng)式的套路,都是 reactive躯肌、observable者春、observeobserver 之類的名字清女,vue2x 中相應(yīng)邏輯的關(guān)鍵字也差不多:defineReactive + observe + new Observer

先說(shuō)結(jié)論

如果用盡可能少的文字來(lái)描述 reactive 的工作流程钱烟,就是:

  1. 定義 Proxy
  2. mount 過(guò)程中觸發(fā) get,進(jìn)而觸發(fā) track
  3. 通過(guò) effect 方法設(shè)置 activeEffect
  4. 綁定當(dāng)前 dep(依賴) 和 activeEffect
  5. 數(shù)據(jù)更新的時(shí)候觸發(fā) set,找到 dep拴袭,并觸發(fā)與其綁定的 effect

對(duì)比 2x 版本的詳細(xì)流程

  1. init 過(guò)程中定義 dep
  2. init 過(guò)程中調(diào)用 defineProperty
  3. beforeMount 到 mounted 之間創(chuàng)建 watcher
  4. 第一次執(zhí)行 watcher 的 getter 渲染視圖
  5. 觸發(fā) getter传惠,進(jìn)行 dep 與 Watcher 的綁定
  6. 數(shù)據(jù)更新觸發(fā) set,調(diào)用收到影響 dep 上的 watcher(例如組件的 render稻扬、watch 函數(shù)之類的操作)

之所以把結(jié)論搬到了前面。羊瘩。泰佳。是因?yàn)楹?jiǎn)書的 md 可讀性略差,接下來(lái)去看源碼尘吗。逝她。。

Proxy

掃了一眼vue-next-master/packages/reactivity/src/reactive.ts睬捶,感覺出現(xiàn)頻率最高的就是函數(shù) createReactiveObject黔宛,但是函數(shù)本身并沒(méi)有被 export ,對(duì)外暴露的是(主要就看了 reactive):

  1. reactive
    a. vue 內(nèi)部:在 collectionHandlers 的 toReactive 和 baseHandlers 的 createGetter 中被調(diào)用
    b. 兼容的 data 函數(shù):在 data 函數(shù)里面的內(nèi)容是由該方法進(jìn)行 Proxy 加工的
    c. 兼容的類+裝飾器寫法:使用 vue-class-component 直接掛在類里面的變量會(huì)通過(guò) proxyRefs (mountComponent => setupComponent => setupStatefulComponent => handleSetupResult => proxyRefs) 進(jìn)行 Proxy 的加工
    d. 新版 setup 中:可以直接創(chuàng)建 Proxy擒贸,然后會(huì)走上述 c 的流程臀晃,區(qū)別就是最后一步不需要再用 Proxy 進(jìn)行加工,直接把當(dāng)前的 Proxy return 了出去直接掛在 instance 的 setupState 上
  2. shallowReactive
    a. vue 內(nèi)部:調(diào)用時(shí)機(jī)在 setupComponent 的 initProps 時(shí)介劫,會(huì)把 vNode 上的 rawProps 變成 Proxy 掛在 instance.props 上
  3. readonly
    a. vue 內(nèi)部:在 collectionHandlers 的 toReadonly 被調(diào)用
    b. 對(duì)外作為只讀加工使用
  4. shallowReadonly
    a. vue 內(nèi)部:調(diào)用時(shí)機(jī)在 setupComponent 的 initProps 之后的 setupStatefulComponent徽惋,用于進(jìn)一步處理 instance.props 和 組件內(nèi)的 slots

這四個(gè)函數(shù),對(duì)應(yīng)封裝好的 readonly 狀態(tài)座韵、handlers 和 collectionHandlers险绘,外部只需要傳個(gè) target 進(jìn)來(lái)就可以調(diào)用 createReactiveObject 函數(shù),舉個(gè)例子:

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
        return target
    }
    return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers
    )
}

然后看下 createReactiveObject 的源碼:

function createReactiveObject(
    target: Target,
    isReadonly: boolean,
    baseHandlers: ProxyHandler<any>,
    collectionHandlers: ProxyHandler<any>
) {
    if (!isObject(target)) {
        if (__DEV__) {
            console.warn(`value cannot be made reactive: ${String(target)}`)
        }
        return target
    }
    // target is already a Proxy, return it.
    // exception: calling readonly() on a reactive object
    if (
        target[ReactiveFlags.RAW] &&
        !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
    ) {
        return target
    }
    // target already has corresponding Proxy
    const proxyMap = isReadonly ? readonlyMap : reactiveMap
    const existingProxy = proxyMap.get(target)
    if (existingProxy) {
        return existingProxy
    }
    // only a whitelist of value types can be observed.
    const targetType = getTargetType(target)
    if (targetType === TargetType.INVALID) {
        return target
    }
    const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
    )
    proxyMap.set(target, proxy)
    return proxy
}

官方的注釋已經(jīng)很清晰了誉碴,再?gòu)?qiáng)行描述一下流程就是:

  1. 判斷是不是對(duì)象宦棺,非對(duì)象無(wú)法處理成響應(yīng)式
  2. 判斷是不是已經(jīng)是一個(gè) Proxy
  3. 判斷 readOnly 狀態(tài),去相應(yīng)的 Map 找是否已存在對(duì)應(yīng)的 Proxy
  4. 判斷是不是非法類型
  5. 在 Map 中存儲(chǔ) Proxy黔帕,并 return 出去

看完了取代 defineProperty 的 Proxy代咸,還需要找到 2x 版本的依賴收集和派發(fā)更新對(duì)應(yīng)的邏輯

依賴收集

依賴收集就是 vue 去收集在改變的時(shí)候需要觸發(fā)視圖更新的變量

在 2 版本,是在 defineProperty 之前創(chuàng)建了對(duì)應(yīng)的 Dep 實(shí)例成黄,然后在變量 get 的時(shí)候把 Dep 和 Watcher 建立聯(lián)系

先只看看最常用的 reactive 的實(shí)現(xiàn)邏輯吧:
createReactiveObject 最后兩個(gè)入?yún)?baseHandlerscollectionHandlers侣背,直接讀起來(lái) collectionHandlers 就是我們想找的邏輯,而 reactive 函數(shù)傳入的則是 mutableCollectionHandlers慨默。

但是在 createReactiveObject 里兩個(gè) handlers 的使用邏輯贩耐,是基于 target 的 targetType 來(lái)判斷的,代碼如下:

function getTargetType(value: Target) {
    return value[ReactiveFlags.SKIP] || !Object.isExtensible(value) // ReactiveFlags.SKIP 就是 "__v_skip"
        ? TargetType.INVALID
        : targetTypeMap(toRawType(value))
}

function targetTypeMap(rawType: string) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return TargetType.COMMON // 1
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return TargetType.COLLECTION // 2
        default:
            return TargetType.INVALID // 0
    }
}
// ...
const targetType = getTargetType(target)
// ...
const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)

然后你就發(fā)現(xiàn)我們應(yīng)該需要去看的是 baseHandlers 對(duì)應(yīng)的 mutableHandlers

export const mutableHandlers: ProxyHandler<object> = {
    get,
    set,
    deleteProperty,
    has,
    ownKeys
}

const get = /*#__PURE__*/ createGetter()

function createGetter(isReadonly = false, shallow = false) {
    return function get(target: Target, key: string | symbol, receiver: object) {
        if (key === ReactiveFlags.IS_REACTIVE) {
            return !isReadonly
        } else if (key === ReactiveFlags.IS_READONLY) {
            return isReadonly
        } else if (
            key === ReactiveFlags.RAW &&
            receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
        ) {
            return target
        }

        const targetIsArray = isArray(target)
        if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
            return Reflect.get(arrayInstrumentations, key, receiver)
        }

        const res = Reflect.get(target, key, receiver)

        const keyIsSymbol = isSymbol(key)
        if (
            keyIsSymbol
                ? builtInSymbols.has(key as symbol)
                : key === `__proto__` || key === `__v_isRef`
        ) {
            return res
        }

        if (!isReadonly) {
            track(target, TrackOpTypes.GET, key) // <= 重點(diǎn)在這里
        }

        if (shallow) {
            return res
        }

        if (isRef(res)) {
            // ref unwrapping - does not apply for Array + integer key.
            const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
            return shouldUnwrap ? res.value : res
        }

        if (isObject(res)) { // 遞歸 reactive/readonly
            // Convert returned value into a proxy as well. we do the isObject check
            // here to avoid invalid value warning. Also need to lazy access readonly
            // and reactive here to avoid circular dependency.
            return isReadonly ? readonly(res) : reactive(res)
        }

        return res
    }
}

track 就是依賴收集的操作厦取,看下 track 的代碼(/packages/reactivity/src/effect.ts)

export function track(target: object, type: TrackOpTypes, key: unknown) {
    if (!shouldTrack || activeEffect === undefined) {
        return
    }
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
        activeEffect.deps.push(dep)
        if (__DEV__ && activeEffect.options.onTrack) {
            activeEffect.options.onTrack({
                effect: activeEffect,
                target,
                type,
                key
            })
        }
    }
}

感覺和 2 版本有些相同的地方潮太,就是依然有 dep 關(guān)聯(lián) watcher,watcher 的 deps 下面掛著收集起來(lái)的 dep。

簡(jiǎn)單的描述一下全局變量 activeEffect铡买,它是靠 createReactiveEffecteffect 方法(代碼在 packages/reactivity/src/effect.ts里)來(lái)進(jìn)行賦值操作的更鲁,比如在 mountComponent 的 setupRenderEffect,就是在 instance 的 update 方法上奇钞,掛了一個(gè) effect(function componentEffect() {...})澡为,這里面就會(huì)有組件的渲染邏輯。

而我們?cè)谟|發(fā) getter 的 track 過(guò)程中景埃,等于把變量和具有重新渲染視圖能力的 activeEffect 綁定在了一起媒至,從而完成了依賴的收集,具體代碼如下

export function effect<T = any>(
    fn: () => T,
    options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
    if (isEffect(fn)) {
        fn = fn.raw
    }
    const effect = createReactiveEffect(fn, options)
    if (!options.lazy) {
        effect()
    }
    return effect
}

function createReactiveEffect<T = any>(
    fn: () => T,
    options: ReactiveEffectOptions
): ReactiveEffect<T> {
    const effect = function reactiveEffect(): unknown {
        if (!effect.active) {
            return options.scheduler ? undefined : fn()
        }
        if (!effectStack.includes(effect)) {
            cleanup(effect)
            try {
                enableTracking()
                effectStack.push(effect)
                activeEffect = effect
                return fn()
            } finally {
                effectStack.pop()
                resetTracking()
                activeEffect = effectStack[effectStack.length - 1]
            }
        }
    } as ReactiveEffect
    effect.id = uid++
    effect._isEffect = true
    effect.active = true
    effect.raw = fn
    effect.deps = []
    effect.options = options
    return effect
}

派發(fā)更新

派發(fā)更新就是 vue 收集到的那些變量在變動(dòng)時(shí)谷徙,如何促使視圖跟著發(fā)生變化

在 2 版本拒啰,是靠在變量 set 的時(shí)候,dep 的 notify 觸發(fā) Watcher 的一系列響應(yīng)

而上文已經(jīng)看完了最簡(jiǎn)單的 get 相關(guān)邏輯( get 流程內(nèi)的分支代碼以及 watch 等的實(shí)現(xiàn)方式都還沒(méi)去看)完慧,也就是在 reactive 執(zhí)行時(shí)谋旦,會(huì)把對(duì)應(yīng)的 activeEffect(內(nèi)置 instance.update) 藏在 get 中等待收集。剩下就是大致看一下 set 觸發(fā)后屈尼,如何調(diào)用 到 instance.update 從而更新視圖的册着。

const set = /*#__PURE__*/ createSetter();

function createSetter(shallow = false) {
    return function set(
        target: object,
        key: string | symbol,
        value: unknown,
        receiver: object
    ): boolean {
        const oldValue = (target as any)[key]
        if (!shallow) {
            value = toRaw(value)
            if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
                oldValue.value = value
                return true
            }
        } else {
            // in shallow mode, objects are set as-is regardless of reactive or not
        }

        const hadKey =
            isArray(target) && isIntegerKey(key)
                ? Number(key) < target.length
                : hasOwn(target, key)
        const result = Reflect.set(target, key, value, receiver)
        // don't trigger if target is something up in the prototype chain of original
        if (target === toRaw(receiver)) {
            if (!hadKey) {
                trigger(target, TriggerOpTypes.ADD, key, value)
            } else if (hasChanged(value, oldValue)) {
                trigger(target, TriggerOpTypes.SET, key, value, oldValue)
            }
        }
        return result
    }
}

一眼就看到了 trigger

根據(jù)依賴收集的結(jié)果,現(xiàn)在需要去尋找的核心思路是脾歧,怎么在 targetMap 里找到當(dāng)前 target 對(duì)應(yīng)的 depsMap 指蚜,然后找到當(dāng)前 key 對(duì)應(yīng)的 dep,然后再在 dep(Set 結(jié)構(gòu))中讀取需要執(zhí)行的函數(shù)

export function trigger(
    target: object,
    type: TriggerOpTypes,
    key?: unknown,
    newValue?: unknown,
    oldValue?: unknown,
    oldTarget?: Map<unknown, unknown> | Set<unknown>
) {

    const depsMap = targetMap.get(target)
    if (!depsMap) {
        // never been tracked
        return
    }

    const effects = new Set<ReactiveEffect>()
    const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => effects.add(effect))
        }
    }

    if (type === TriggerOpTypes.CLEAR) {
        // collection being cleared
        // trigger all effects for target
        depsMap.forEach(add)
    } else if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep, key) => {
            if (key === 'length' || key >= (newValue as number)) {
                add(dep)
            }
        })
    } else {  // <= 在這里
        // schedule runs for SET | ADD | DELETE
        if (key !== void 0) {
            add(depsMap.get(key))
        }
        // also run for iteration key on ADD | DELETE | Map.SET
        const shouldTriggerIteration =
            (type === TriggerOpTypes.ADD &&
                (!isArray(target) || isIntegerKey(key))) ||
            (type === TriggerOpTypes.DELETE && !isArray(target))
        if (
            shouldTriggerIteration ||
            (type === TriggerOpTypes.SET && target instanceof Map)
        ) {
            add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))
        }
        if (shouldTriggerIteration && target instanceof Map) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
        }
    }

    const run = (effect: ReactiveEffect) => {
        if (__DEV__ && effect.options.onTrigger) {
            effect.options.onTrigger({
                effect,
                target,
                key,
                type,
                newValue,
                oldValue,
                oldTarget
            })
        }
        if (effect.options.scheduler) {
            effect.options.scheduler(effect)
        } else {
            effect()
        }
    }

    effects.forEach(run)
}

這里主要就是識(shí)別一下 trigger 的 type涨椒,然后靠 add 收集 effect摊鸡,最后靠 run 來(lái)執(zhí)行收集起來(lái)的 effect,如果中間那一坨看著不太直觀蚕冬,下面是打包出來(lái)的 esm 代碼

// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
    case "add" /* ADD */:
        if (!isArray(target)) {
            add(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
                add(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
        }
        else if (isIntegerKey(key)) {
            // new index added to array -> length changes
            add(depsMap.get('length'));
        }
        break;
    case "delete" /* DELETE */:
        if (!isArray(target)) {
            add(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
                add(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
        }
        break;
    case "set" /* SET */:
        if (isMap(target)) {
            add(depsMap.get(ITERATE_KEY));
        }
        break;
}

擴(kuò)展閱讀(沒(méi)啥意義)

下面是擴(kuò)展閱讀免猾,沒(méi)啥太大意義,只是檢查除此以為沒(méi)有其他的依賴收集入口

前文還提到了囤热,使用 reactive 之后猎提,在組件初始化的時(shí)候,會(huì)走一遍(mountComponent => setupComponent => setupStatefulComponent => handleSetupResult => proxyRefs)這個(gè)流程旁蔼。更直白的講锨苏,我們這里只是在 setup,組件初始化的時(shí)候會(huì)執(zhí)行 handleSetupResult 來(lái)處理我們 setup 的結(jié)果

setupStatefulComponent 中創(chuàng)建了一個(gè)新的 Proxy棺聊,看注釋是說(shuō)一個(gè)公用的實(shí)例/渲染的 proxy

但是你追進(jìn)去就會(huì)發(fā)現(xiàn)伞租,這個(gè)只針對(duì) instance.ctx 的 proxy 只有在 dev 模式下,才會(huì)把 setup 內(nèi) reactive 加工的變量加到 ctx 上 (邏輯在 handleSetupResult 函數(shù)內(nèi)部限佩,exposePropsOnRenderContext 的調(diào)用)

function setupStatefulComponent(
    instance: ComponentInternalInstance,
    isSSR: boolean
) {
    const Component = instance.type as ComponentOptions

    //...

    // 0. create render proxy property access cache
    instance.accessCache = {}
    // 1. create public instance / render proxy  <= 這里看上去很重要
    // also mark it raw so it's never observed
    instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
    if (__DEV__) {
        exposePropsOnRenderContext(instance)
    }
    // 2. call setup()  <= 處理我們的 setup 函數(shù)
    const { setup } = Component
    if (setup) {
        const setupContext = (instance.setupContext =
            setup.length > 1 ? createSetupContext(instance) : null)

        currentInstance = instance
        pauseTracking()
        const setupResult = callWithErrorHandling(
            setup,
            instance,
            ErrorCodes.SETUP_FUNCTION,
            [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
        )
        resetTracking()
        currentInstance = null

        if (isPromise(setupResult)) {
            // ... 省略了葵诈,你可以全當(dāng)這里也是在 handleSetupResult
        } else {
            handleSetupResult(instance, setupResult, isSSR)
        }
    } else {
        finishComponentSetup(instance, isSSR)
    }
}

handleSetupResult的代碼:

export function handleSetupResult(
    instance: ComponentInternalInstance,
    setupResult: unknown,
    isSSR: boolean
) {
    if (isFunction(setupResult)) {
        // setup returned an inline render function
        instance.render = setupResult as InternalRenderFunction
    } else if (isObject(setupResult)) {
        if (__DEV__ && isVNode(setupResult)) {
            warn(
                `setup() should not return VNodes directly - ` +
                `return a render function instead.`
            )
        }
        // setup returned bindings.
        // assuming a render function compiled from template is present.
        if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            instance.devtoolsRawSetupState = setupResult
        }
        instance.setupState = proxyRefs(setupResult)
        if (__DEV__) {
            exposeSetupStateOnRenderContext(instance)
        }
    } else if (__DEV__ && setupResult !== undefined) {
        warn(
            `setup() should return an object. Received: ${setupResult === null ? 'null' : typeof setupResult
            }`
        )
    }
    finishComponentSetup(instance, isSSR)
}

// dev only
export function exposeSetupStateOnRenderContext(
    instance: ComponentInternalInstance
) {
    const { ctx, setupState } = instance
    Object.keys(toRaw(setupState)).forEach(key => {
        if (key[0] === '$' || key[0] === '_') {
            warn(
                `setup() return property ${JSON.stringify(
                    key
                )} should not start with "$" or "_" ` +
                `which are reserved prefixes for Vue internals.`
            )
            return
        }
        Object.defineProperty(ctx, key, {
            enumerable: true,
            configurable: true,
            get: () => setupState[key],
            set: NOOP
        })
    })
}

看來(lái)并沒(méi)有遺漏什么重要的邏輯裸弦,所以我們就不繼續(xù)追這個(gè) proxy 了,有興趣的同學(xué)可以繼續(xù)追一下 PublicInstanceProxyHandlers 的代碼作喘,看看有沒(méi)有什么神奇的地方

擴(kuò)展閱讀結(jié)束

下次準(zhǔn)備看看什么是 Composition API 的理疙,了解下熱點(diǎn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泞坦,一起剝皮案震驚了整個(gè)濱河市窖贤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贰锁,老刑警劉巖赃梧,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異李根,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)几睛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門房轿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人所森,你說(shuō)我怎么就攤上這事囱持。” “怎么了焕济?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵纷妆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我晴弃,道長(zhǎng)掩幢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任上鞠,我火速辦了婚禮际邻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芍阎。我一直安慰自己世曾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布谴咸。 她就那樣靜靜地躺著轮听,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岭佳。 梳的紋絲不亂的頭發(fā)上血巍,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音珊随,去河邊找鬼藻茂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辨赐。 我是一名探鬼主播优俘,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼掀序!你這毒婦竟也來(lái)了帆焕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤不恭,失蹤者是張志新(化名)和其女友劉穎叶雹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體换吧,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡折晦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沾瓦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片满着。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贯莺,靈堂內(nèi)的尸體忽然破棺而出风喇,到底是詐尸還是另有隱情,我是刑警寧澤缕探,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布魂莫,位于F島的核電站,受9級(jí)特大地震影響爹耗,放射性物質(zhì)發(fā)生泄漏耙考。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一潭兽、第九天 我趴在偏房一處隱蔽的房頂上張望琳骡。 院中可真熱鬧,春花似錦讼溺、人聲如沸楣号。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)炫狱。三九已至,卻和暖如春剔猿,著一層夾襖步出監(jiān)牢的瞬間视译,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工归敬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酷含,地道東北人鄙早。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像椅亚,于是被迫代替她去往敵國(guó)和親限番。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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