手寫Vue2核心(七):vue-router實現(xiàn)

源碼相關的文章確實不好寫杂数,一個是每個人基礎功不一樣拍柒,我覺得說的清楚的東西可能對到別人依舊含糊惊来,一個是對一些邏輯的理解也未必就敢說百分百正確地技,最后是真想拆分一步步的關鍵代碼都不好拆炫狱。如果有這種文章經(jīng)驗的作者歡迎交流态鳖。

本文實現(xiàn)的是vue-router v3.4.8版主要核心功能

準備工作

自行實現(xiàn)的vue畢竟是閹割版的篙议,所以這里下載vue/cli來進行router與后面的vuex開發(fā)。執(zhí)行命令npm i @vue/cli -D進行安裝(這里怎么安裝都可以擎宝,具體看個人)
由于不是全局安裝郁妈,所以使用vue命令會報錯,需要自行配置環(huán)境變量绍申,參考傳送門:非全局vue-cli

安裝完成噩咪,執(zhí)行vue create 你的項目工程名稱,一路想怎么選就看個人了极阅,我選的自定義然后按照自己習慣配置胃碾。
去到工程目錄,執(zhí)行npm run serve啟動服務

vue-router兩種模式簡介

單頁應用也叫spa應用筋搏,路徑切換不刷新頁面仆百,但可以重新渲染組件
vue-router是一個構造函數(shù),前端路由實現(xiàn)奔脐,有兩種模式:hash模式與history模式
hash鏈接上會帶有#號俄周,但是兼容性好,不同路徑可展示不同頁面組件髓迎,基于location.hash
history與一般的鏈接無異峦朗,但鏈接是模擬出來的,并非真實鏈接排龄,因此直接進入會404波势,需要后臺配置(本地開發(fā)不需要考慮,因為使用了history-fallback插件)橄维,基于window.history.pushState

初始化結構目錄

src目錄下新建vue-router文件夾尺铣,創(chuàng)建index.jsinstall.js來替換node_modules中的vue-router
src/router/index.js中引用的vue-router替換為自行創(chuàng)建的vue-routerimport VueRouter from '@/vue-router'

vue.use

vue使用插件的方式是使用vue.usevue.use會自動執(zhí)行插件的install方法争舞,這樣做的好處是插件需要依賴于vue凛忿,但如果插件中指定了某個vue版本,而用戶下載使用的版本與插件的版本不一致時竞川,就會導致沖突店溢。所以通過vue,將用戶使用的vue傳入組件中流译,就能保證用戶使用的vue與插件使用的vue是完全一致的(意思就是你插件中直接使用import Vue from 'vue'的話逞怨,那么這個vue是不是就有可能跟用戶使用的不一致了)

代碼示例:

Vue.use = function (plugin, options) {
    plugin.install(this, options)
}

Vue.use(VueRouter)

VueRouter與install

// vue-router/index.js
// 拿到的是變量_Vue者疤,所以Vue.use時就可以拿到Vue
import { install, _Vue } from './install'

export default class VueRouter {
    constructor (options) {}
}

VueRouter.install = install

我們在任何組件中都可以通過vue.router來獲取到router實例福澡,其實現(xiàn)主要是靠mixin,向beforeCreate生命周期注入

我們知道初始化vue會產(chǎn)生兩個實例驹马,一個是new Vue革砸,一個是實例化app的vue組件

初始化vue

而我們路由只會在實例化Vue時注入除秀,子組件中(上圖為app)中是不會有該router實例的

new Vue({
    name: 'Root',
    router,
    render: h => h(App)
}).$mount('#app')

因此可以通過判斷$options中是否有router來鑒別是否為vue實例,否則證明是子組件算利,子組件通過$parent來獲取router實例

// vue-router/install.js
// 需要將install方法單獨的進行拆分
export let _Vue

export function install (Vue, options) {
    _Vue = Vue

    // 將當前的根實例提供的router屬性共享給所有子組件
    Vue.mixin({
        beforeCreate () {
            // 獲取到每個子組件的實例册踩,給實例添加屬性
            if (this.$options.router) {
                this._routerRoot = this
            } else {
                this._routerRoot = this.$parent && this.$parent._routerRoot
            }
        }
    })
}

createRouteMap

vue-router需要生成一份配置表,用于匹配路徑來決定使用什么組件效拭,還可以支持動態(tài)加載路由addRoute

// vue-router/index.js
+ import createMather from './createMather'

export default class VueRouter {
    constructor (options) {
        // 根據(jù)用戶的配置生成一個映射表暂吉,跳轉時,根據(jù)路徑找到對應的組件來進行渲染
        // 創(chuàng)建匹配器后缎患,核心的方法就是匹配
        // 但用戶可能還會動態(tài)的添加路由(match/addRoutes)
        this.mather = createMather(options.routes || [])
    }
    // 路由初始化
    init (app) { // app就是根實例 new Vue

    }
}
// vue-router/createMather.js
import createRouteMap from './create-route-map'

export default function createMather (routes) {
    const { pathMap } = createRouteMap(routes) // 根據(jù)用戶的路由配置創(chuàng)建一個映射表

    // 動態(tài)添加路由權限
    function addRoutes (routes) {
        createRouteMap(routes, pathMap) // 實現(xiàn)動態(tài)路由
    }

    // 根據(jù)提供的路徑匹配路由
    function match (path) {
        // 先占個坑
    }

    return {
        addRoutes,
        match
    }
}

生成路由映射表慕的,根據(jù)用戶傳入的routes生成一份路由相對應的映射表,后續(xù)通過該映射表就可以快速知道使用的參數(shù)插件等等

// vue-router/create-route-map.js
// 生成路由映射表挤渔,支持動態(tài)加載路由
export default function createRouteMap (routes, oldPathMap) {
    // 一個參數(shù)是初始化肮街,兩個參數(shù)是動態(tài)添加路由
    const pathMap = oldPathMap || {}

    routes.forEach(route => {
        addRouteRecord(route, pathMap, null)
    })

    return pathMap
}

// 填充路由,生成路由對象
function addRouteRecord (route, pathMap, parent) { // pathMap = {路徑: 記錄}
    // 要判斷兒子的路徑不是以 / 開頭的判导,否則不拼接父路徑
    const path = route.path.startsWith('/') ? route.path : parent ? parent.path + '/' + route.path : route.path
    const record = {
        path,
        parent, // 父記錄
        component: route.component,
        name: route.name,
        props: route.props,
        params: route.params || {},
        meta: route.meta
    }

    // 判斷是否存在路由記錄嫉父,沒有則添加
    if (!pathMap[path]) {
        pathMap[path] = record
    }

    if (route.children) {
        // 遞歸,沒有孩子就停止遍歷
        route.children.forEach(childRoute => {
            addRouteRecord(childRoute, pathMap, record)
        })
    }
}

不同模式處理眼刃,hash模式實現(xiàn)

前面說了router有兩種模式绕辖,一種時hash,另一種時history鸟整,hash與history路徑變化是不一致的引镊。所以需要分開處理,而兩者又都有一樣的部分操作篮条,所以可以通過三個類來進行不同處理
History主要負責跳轉弟头,渲染等,因為這些事情不管使用哪一種模式都是一致的涉茧,HashHistoryH5History都繼承于該類

// history/base.js
export default class History {
    constructor (router) {
        this.router = router
    }
    // 根據(jù)路徑進行組件渲染赴恨,數(shù)據(jù)變化更新視圖
    transitionTo (location, onComplete) { // 默認會先執(zhí)行一次
        onComplete && onComplete() // onComplete調用hash值變化會再次調用transitionTo
    }
}
// history/hash.js
import History from './base'

// 判斷鏈接是否帶有hash,沒有則添加#/伴栓,否則不添加
function ensureSlash () {
    if (window.location.hash) { return }
    window.location.hash = '/' // url如果不帶hash伦连,自動添加 #/
}

function getHash () {
    return window.location.hash.slice(1)
}

export default class HashHistory extends History {
    constructor (router) {
        super(router)

        // 默認hash模式需要加 #/
        ensureSlash()
    }
    setupListener () {
        // 好陌生,查了一下事件居然有這么多:https://www.runoob.com/jsref/dom-obj-event.html
        // hashchange性能不如popstate钳垮,popstate用于監(jiān)聽瀏覽器歷史記錄變化惑淳,hash變化也會觸發(fā)popstate
        window.addEventListener('popstate', () => {
            // 根據(jù)當前hash值,去匹配對應的組件
            this.transitionTo(getHash())
        })
    }
    getCurrentLocation () {
        return getHash()
    }
}
// history/history.js
import History from './base'

// 沒按照源碼 HTML5History饺窿,指的是瀏覽器跳轉
export default class BrowserHistory extends History {
    constructor (router) {
        console.log('history mode')
        super(router)
    }
    getCurrentLocation () {
        return window.location.pathname
    }
}

有了上面不同的實例后歧焦,就能在初始化時實例化不同歷史實例

import { install, _Vue } from './install'
import createMather from './createMather'
import HashHistory from './history/hash'
import BrowserHistory from './history/history'

export default class VueRouter {
    constructor (options) {
+       // 根據(jù)當前的mode,創(chuàng)建不同的history管理策略
+       switch (options.mode) {
+           case 'hash':
+               this.history = new HashHistory(this)
+               break
+           case 'history':
+               this.history = new BrowserHistory(this)
+               break
+       }
    }
    // 路由初始化
    init (app) { // app就是根實例 new Vue
+       // 初始化后肚医,需要先根據(jù)路徑做一次匹配绢馍,后續(xù)根據(jù)hash值變化再次匹配
+       const history = this.history // history的實例
+       const setupListener = () => {
+           history.setupListener() // 掛載監(jiān)聽向瓷,監(jiān)聽hash值變化
+       }
+       // 跳轉到哪里,getCurrentLocation為私有舰涌,因為 hash 與 history 處理不一致
+       history.transitionTo(history.getCurrentLocation(), setupListener)
    }
}

VueRouter.install = install

根據(jù)跳轉路徑猖任,匹配及產(chǎn)生對應路由記錄

目前跳轉時,history并不知道發(fā)生了什么事瓷耙,也不知道應該使用什么記錄朱躺。因此需要根據(jù)跳轉路徑獲取對應的路由記錄。路由記錄需要從子頁面到父頁面都產(chǎn)生出來搁痛,需要使用matcher進行匹配室琢,產(chǎn)生對應的所有路由記錄

// history/base.js
// 根據(jù)路徑,返回該路徑所需的所有記錄
+ export function createRoute (record, location) {
+     const res = []
+ 
+     if (record) {
+         while (record) { // 二級菜單及N級菜單落追,將對應的菜單一個個往棧中加
+             res.unshift(record)
+             record = record.parent
+         }
+     }
+ 
+     return {
+         ...location,
+         matched: res
+     }
+ }

export default class History {
    constructor (router) {
        this.router = router
+       // 最終核心需要將current屬性變化成響應式的盈滴,后續(xù)current變化會更新視圖
+       this.current = createRoute(null, {
+           path: '/'
+       })
    }
    // 根據(jù)路徑進行組件渲染,數(shù)據(jù)變化更新視圖
    transitionTo (location, onComplete) { // 默認會先執(zhí)行一次
        // 根據(jù)跳轉的路徑轿钠,獲取匹配的記錄
        const route = this.router.match(location)
+       this.current = route
        // 由于由響應式變換的是_route(install中進行的響應式定義)巢钓,而更改的是this.current,無法觸發(fā)響應式
        
+       /** 
+        * vueRoute用于提供給用戶直接使用疗垛,vueRoute中又需要對歷史記錄進行操作
+        * 跳轉的時候又是由歷史記錄所觸發(fā)症汹,需要通知變更vue._route,而現(xiàn)在變更的是歷史記錄中的current
+        * 需要將自身變更后匹配到的路由返回給vueRouter贷腕,這里也不能直接使用 install 導出的 _vue
+        * 是因為考慮到有可能實例化了多個Vue背镇,這個時候的_Vue是最后實例化的Vue,并非對應vueRouter所使用的Vue實例
+        * 通過listen去執(zhí)行vueRouter綁定的函數(shù)泽裳,vueRouter中有當前Vue實例瞒斩,就能將當前匹配到的路由賦值給Vue._route,這樣就能觸發(fā)響應式變化
+        */
+       this.cb && this.cb(route) // 第一次cb不存在涮总,還未進行綁定回調
        onComplete && onComplete() // cb調用hash值變化會再次調用transitionTo
    }
+   listen (cb) {
+       this.cb = cb
+   }
}

match填坑

+ import { createRoute } from './history/base.js'

// 匹配器
export default function createMather (routes) {
    // 根據(jù)提供的路徑匹配路由
+   function match (path) {
+       const record = pathMap[path]
+
+       return createRoute(record, {
+           path
+       })
    }
}

定義響應式及掛載屬性胸囱,注冊組件

history中已經(jīng)可以根據(jù)路由變化產(chǎn)生對應的路由記錄(createRoute),但是用戶操作的是vue.route并不是響應式的瀑梗∨氡剩總不能用戶路由跳轉之后還得調一個方法才能產(chǎn)生頁面渲染,數(shù)據(jù)變了則視圖更新抛丽,需要vue的響應式谤职,但插件中vue還未被實例化,因此不能直接使用`set來進行亿鲜。前面實現(xiàn)的vue核心中允蜈,有一個defineReactive方法用于定義響應式,因此插件中是直接通過使用Vue.util.defineReactive`來定義成響應式的

export function install (Vue, options) {
+   // 如果已經(jīng)注冊過router并且是同一個Vue實例,直接返回
+   if (install.installed && _Vue === Vue) { return }
+   install.installed = true
+   _Vue = Vue

    // 將當前的根實例提供的router屬性共享給所有子組件
    Vue.mixin({
        beforeCreate () {
            // 獲取到每個子組件的實例陷寝,給實例添加屬性
            if (this.$options.router) {
                // code...

+               // 使用 Vue 的工具類方法定義成響應式的,真實項目需要使用 $set其馏,這里沒法用是因為Vue還未實例化
+               Vue.util.defineReactive(this, '_route', this._router.history.current)
            } else {
                // code...
            }
        }
    })

我們需要使用vue-router時凤跑,是通過vue.$routevue.$router來訪問路由對象及獲取當前路由對應屬性的,插件中是將這兩個屬性掛載原型上并進行劫持
vue-router中還提供兩個組件叛复,用于跳轉與渲染視圖:RouteLinkRouteView

+ import RouteLink from './components/link'
+ import RouteView from './components/view'

export function install (Vue, options) {
    // code...

+   // 讓用戶可以直接使用 vue.$route 和 $router
+   Object.defineProperty(Vue.prototype, '$route', {
+       get () {
+           return this._routerRoot._route // current對象里面的所有屬性
+       }
+   })
+
+   Object.defineProperty(Vue.prototype, '$router', {
+       get () {
+           return this._routerRoot._router // addRoute match 方法等
+       }
+   })
+
+   // 注冊所需組件
+   Vue.component('router-link', RouteLink)
+   Vue.component('router-view', RouteView)
}

創(chuàng)建這兩個組件

// components/link.js
export default {
    name: 'router-link',
    props: {
        to: {
            type: String,
            required: true
        },
        tag: {
            type: String,
            default: 'a'
        }
    },
    render (h) {
        // jsx仔引,但不同于react的jsx需要寫死標簽,vue中可以寫變量標簽
        const tag = this.tag
        return <tag onClick={() => {
            this.$router.push(this.to)
        }}>{this.$slots.default}</tag>

        // 等價的render函數(shù)褐奥,寫起來太痛苦
        // return h(this.tag, {}, this.$slots.default)
    }
}

// components/view.js
export default {
    name: 'router-view',
    render (h) {
        return h()
    }
}

RouteView實現(xiàn)

routerView負責的工作咖耘,就是通過當前路徑,渲染對應的組件
routerView的渲染方式為functional撬码,無狀態(tài) (沒有響應式數(shù)據(jù))儿倒,也沒有實例 (沒有 this 上下文),傳送門:(函數(shù)式組件)[https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6]

vnode 與 _vnode 的區(qū)別vnode 表示的是組件本身是長啥樣的
_vnode 表示的是組件真實渲染出來的結果是啥樣的

<my></my> // $vnode => {type: {name: 'vue-component-id-my'}, data: {...}, children: undefind}
          // _vnode => {type: 'div', dataL {...} children: undefined, el: div}

假設頁面中有兩個router-view呜笑,一個為App.vue中寫的router-view夫否,一個為about頁面中的router-view,當前路徑為/about/aa叫胁,簡單的描述這一整個過程:
此時匹配出的matched:[/about, /about/aa]
此時的Vue文件中調用router-view的順序:[App.vue/router-view凰慈,About.vue/router-view]

app.vue => routerView => routerViewComponent.data.routerView = true => parent.$vnode.data.routerView為undefined,不進入depth++ => 取出record為 /about => 執(zhí)行渲染函數(shù)驼鹅,出入的data為標記過routerView(其實就是原本的data加上一個routerView標識)=> 來到about.vue頁面微谓,發(fā)現(xiàn)里面寫了一個routerView => routerViewComponent.data.routerView = true => parent.$vnode.data.routerView(就是app.vue頁面的router-view組件,上一個步驟已經(jīng)掛上一個routerView標識) => 進入depth++ => 取出匹配結果為(/about/aa)=> 執(zhí)行渲染 => 然后就是各種實例化結束的生命周期等

// component/view.js
export default {
    functional: true, // 函數(shù)式組件输钩,可以節(jié)省性能豺型,但沒有實例與沒有響應式變化
    name: 'RouterView',
    render (h, { data, parent }) {
        const route = parent.$route // 會做依賴收集了
        let depth = 0
        const records = route.matched
        data.routerView = true // 渲染router-view時標記它是一個router-view,這樣如果子組件中繼續(xù)調用router-view买乃,不至于會死循環(huán)

        // 二級節(jié)點触创,看之前渲染過幾個router-view
        while (parent) {
            // 由于 $vnode 與 _vnode 命名太相像,vue3中將 _vnode 命名未 subtree
            if (parent.$vnode && parent.$vnode.data.routerView) {
                depth++
            }

            parent = parent.$parent
        }

        const record = records[depth]

        if (!record) { return h() } // 匹配不到为牍,返回一個空白節(jié)點
        return h(record.component, data) // 渲染一個組件哼绑,函數(shù)式寫法為:h(component),這里就是去渲染組件
    }
}

history實現(xiàn)

history觀測的是瀏覽器的前進后退碉咆,不同于hash抖韩,跳轉的時候window.history.pushState并不會觸發(fā)popstate(因為該api是歷史管理,并不會觀測路徑變化)疫铜,所以需要手動執(zhí)行跳轉茂浮,再去調用pushState

import History from './base'

export default class BrowserHistory extends History {
    constructor (router) {
        console.log('history mode')
        super(router)
    }

    getCurrentLocation () {
        return window.location.pathname
    }

    setupListener () {
        window.addEventListener('popstate', () => {
            // 監(jiān)聽路徑變化(瀏覽器的前進后退)進行跳轉
            this.transitionTo(this.getCurrentLocation())
        })
    }

    push (location) {
        this.transitionTo(location, () => {
            // 采用 H5 的 API 跳轉,這里的切換不會觸發(fā) popstate次和,所以不能像hash一樣微王,需要放到回調中來處理
            window.history.pushState({}, null, location)
        })
    }
}

hook實現(xiàn)

導航具體的觸發(fā)流程,建議閱讀官方文檔财剖,傳送門: 完整的導航解析流程幌羞,根據(jù)面試造火箭特性寸谜,父子組件生命周期渲染流程經(jīng)常提問,或許以后會出現(xiàn)導航解析流程
vueRouter有一個方法属桦,beforeEach(全局前置守衛(wèi))熊痴,實際項目中被用來做一些權限判斷(攔截器),簡單的理解聂宾,就是類似于Koa的中間件(比如本人前面Koa的文章使用koa-jwt對用戶登錄權限判斷)
多次使用依次執(zhí)行果善,實質就是個迭代器

使用示例代碼:

router.beforeEach((to, from, next) => {
    setTimeout(() => {
        console.log(1)
        next()
    }, 1000)
})

router.beforeEach((to, from, next) => {
    setTimeout(() => {
        next()
    }, 1000)
})

具體實現(xiàn)代碼

+ function runQueue (queue, interator, cb) {
+     function next (index) {
+         if (index >= queue.length) {
+             return cb() // 一個鉤子都沒有,或者鉤子全部執(zhí)行完畢系谐,直接調用cb完成渲染即可
+         } else {
+             const hook = queue[index]
+             interator(hook, () => next(index + 1))
+         }
+     }
+ 
+     next(0)
+ }

export default class History {
    // 根據(jù)路徑進行組件渲染巾陕,數(shù)據(jù)變化更新視圖
    transitionTo (location, onComplete) { // 默認會先執(zhí)行一次
        // 根據(jù)跳轉的路徑,獲取匹配的記錄
        const route = this.router.match(location)

+       const queue = [].concat(this.router.beforeEachHooks)

+       // 迭代器
+       const interator = (hook, cb) => { // 這里如果用function來聲明纪他,this則為undefined惜论,因為構建后是嚴格模式
+           hook(route, this.current, cb) // to, from, next
+       }

+       runQueue(queue, interator, () => {
            this.current = route
            // 由于由響應式變換的是_route(install中進行的響應式定義),而更改的是this.current止喷,無法觸發(fā)響應式
            // vueRoute用于提供給用戶直接使用馆类,vueRoute中又需要對歷史記錄進行操作
            // 跳轉的時候又是由歷史記錄所觸發(fā),需要通知變更vue._route弹谁,而現(xiàn)在變更的是歷史記錄中的current
            // 需要將自身變更后匹配到的路由返回給vueRouter乾巧,這里不能直接使用 install導出的_vue
            // 是因為考慮到有可能實例化了多個Vue,這個時候的_Vue是最后實例化的Vue预愤,并非對應vueRouter所使用的Vue實例
            // 通過listen去執(zhí)行vueRouter綁定的函數(shù)沟于,vueRouter中有當前Vue實例,就能將當前匹配到的路由賦值給Vue._route植康,這樣就能觸發(fā)響應式變化

            this.cb && this.cb(route) // 第一次cb不存在旷太,還未進行綁定回調,cb調用觸發(fā)視圖更新
            onComplete && onComplete() // cb調用hash值變化會再次調用transitionTo
+       })
    }
    listen (cb) {
        this.cb = cb
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末销睁,一起剝皮案震驚了整個濱河市供璧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冻记,老刑警劉巖睡毒,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異冗栗,居然都是意外死亡演顾,警方通過查閱死者的電腦和手機供搀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钠至,“玉大人葛虐,你說我怎么就攤上這事∶蘧” “怎么了屿脐?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掰盘。 經(jīng)常有香客問我,道長赞季,這世上最難降的妖魔是什么愧捕? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮申钩,結果婚禮上次绘,老公的妹妹穿的比我還像新娘。我一直安慰自己撒遣,他們只是感情好邮偎,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著义黎,像睡著了一般禾进。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上廉涕,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天泻云,我揣著相機與錄音,去河邊找鬼狐蜕。 笑死宠纯,一個胖子當著我的面吹牛,可吹牛的內容都是我干的层释。 我是一名探鬼主播婆瓜,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贡羔!你這毒婦竟也來了廉白?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤乖寒,失蹤者是張志新(化名)和其女友劉穎蒙秒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宵统,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡晕讲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年覆获,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓢省。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弄息,死狀恐怖,靈堂內的尸體忽然破棺而出勤婚,到底是詐尸還是另有隱情摹量,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布馒胆,位于F島的核電站缨称,受9級特大地震影響,放射性物質發(fā)生泄漏祝迂。R本人自食惡果不足惜睦尽,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望型雳。 院中可真熱鬧当凡,春花似錦、人聲如沸纠俭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冤荆。三九已至朴则,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钓简,已是汗流浹背佛掖。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涌庭,地道東北人芥被。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像坐榆,于是被迫代替她去往敵國和親拴魄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容