【Vue3.0】- 如何渲染組件

組件

  • 組件是一個(gè)抽象的概念,它是對一棵 DOM 樹的抽象
  • 可以描述組件信息的JavaScript對象
  • 從表現(xiàn)上來看
    • 組件的模板決定了組件生成的DOM標(biāo)簽
    • Vue.js內(nèi)部塞椎,一個(gè)組件想要真正的渲染生成DOM
      image.png

應(yīng)用程序初始化

  • 整個(gè)組件樹是由根組件開始渲染的
  • 為了找到根組件的渲染入口桨仿,從應(yīng)用程序的初始化過程開始分析
  • 對比vue2.0vue3.0入口
// 在 Vue.js 2.x 中案狠,初始化一個(gè)應(yīng)用的方式如下
import Vue from 'vue'
import App from './App'
const app = new Vue({
  render: h => h(App)
})
app.$mount('#app')

// 在 Vue.js 3.0 中服傍,初始化一個(gè)應(yīng)用的方式如下
import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')
  • Vue.js 3.0中導(dǎo)入了一個(gè)createApp钱雷,這是個(gè)入口函數(shù),它是 Vue.js對外暴露的一個(gè)函數(shù)

createApp內(nèi)部實(shí)現(xiàn)

const createApp = ((...args) => {
  // 創(chuàng)建 app 對象
  const app = ensureRenderer().createApp(...args)
  const { mount } = app
  // 重寫 mount 方法
  app.mount = (containerOrSelector) => {
    // ...
  }
  return app
})
  • createApp主要做了兩件事情
    • 1)創(chuàng)建app對象
    • 2)重寫app.mount方法

創(chuàng)建app對象

  • ensureRenderer().createApp() 來創(chuàng)建 app 對象
  • 實(shí)現(xiàn)了跨平臺(tái)渲染
const app = ensureRenderer().createApp(...args)
  • ensureRenderer()用來創(chuàng)建一個(gè)渲染器對象
ensureRenderer 內(nèi)部實(shí)現(xiàn)
// 渲染相關(guān)的一些配置,比如更新屬性的方法灿椅,操作 DOM 的方法
const rendererOptions = {
  patchProp,
  ...nodeOps
}
let renderer
// 延時(shí)創(chuàng)建渲染器套蒂,當(dāng)用戶只依賴響應(yīng)式包的時(shí)候,可以通過 tree-shaking 移除核心渲染邏輯相關(guān)的代碼
function ensureRenderer() {
  return renderer || (renderer = createRenderer(rendererOptions))
}
function createRenderer(options) {
  return baseCreateRenderer(options)
}
function baseCreateRenderer(options) {
  function render(vnode, container) {
    // 組件渲染的核心邏輯
  }
  return {
    render,
    createApp: createAppAPI(render)
  }
}
function createAppAPI(render) {
  // createApp createApp 方法接受的兩個(gè)參數(shù):根組件的對象和 prop
  return function createApp(rootComponent, rootProps = null) {
    const app = {
      _component: rootComponent,
      _props: rootProps,
      mount(rootContainer) {
        // 創(chuàng)建根組件的 vnode
        const vnode = createVNode(rootComponent, rootProps)
        // 利用渲染器渲染 vnode
        render(vnode, rootContainer)
        app._container = rootContainer
        return vnode.component.proxy
      }
    }
    return app
  }
}
  • 首先用ensureRenderer()來延時(shí)創(chuàng)建渲染器
    • 好處是當(dāng)用戶只依賴響應(yīng)式包的時(shí)候婴洼,就不會(huì)創(chuàng)建渲染器
    • 可以通過tree-shaking的方式移除核心渲染邏輯相關(guān)的代碼
  • 通過createRenderer創(chuàng)建一個(gè)渲染器
    • 這個(gè)渲染器內(nèi)部會(huì)有一個(gè)createApp方法
      • 它是執(zhí)行createAppAPI方法返回的函數(shù)
      • 接受了rootComponentrootProps兩個(gè)參數(shù)
    • 我們在應(yīng)用層面執(zhí)行createApp(App)方法時(shí):
      • 會(huì)把App組件對象作為根組件傳遞給rootComponent骨坑。
      • 這樣,createApp內(nèi)部就創(chuàng)建了一個(gè)app對象
      • 它會(huì)提供mount方法柬采,這個(gè)方法是用來掛載組件的卡啰。

值得注意的是

  • app對象創(chuàng)建過程中,Vue.js利用閉包和函數(shù)柯里化的技巧警没,很好地實(shí)現(xiàn)了參數(shù)保留

重寫app.mount方法

為什么重寫匈辱?
  • createApp返回的app對象已經(jīng)擁有了mount方法了,為什么還有在入口重寫杀迹?
    • 為了支持跨平臺(tái)渲染
    • createApp函數(shù)內(nèi)部的app.mount方法是一個(gè)標(biāo)準(zhǔn)的可跨平臺(tái)的組件渲染流程:
mount(rootContainer) {
  // 創(chuàng)建根組件的 vnode
  const vnode = createVNode(rootComponent, rootProps)
  // 利用渲染器渲染 vnode
  render(vnode, rootContainer)
  app._container = rootContainer
  return vnode.component.proxy
}
  • 主要流程是亡脸,先創(chuàng)建vnode,再渲染 vnode
  • 參數(shù)rootContainer根據(jù)平臺(tái)不同而不同树酪,
  • 這里面的代碼不應(yīng)該包含任何特定平臺(tái)相關(guān)的邏輯浅碾,因此我們需要在外部重寫這個(gè)方法
app.mount 重寫都做了哪些事情?
app.mount = (containerOrSelector) => {
  // 標(biāo)準(zhǔn)化容器
  const container = normalizeContainer(containerOrSelector)
  if (!container)
    return
  const component = app._component
   // 如組件對象沒有定義 render 函數(shù)和 template 模板续语,則取容器的 innerHTML 作為組件模板內(nèi)容
  if (!isFunction(component) && !component.render && !component.template) {
    component.template = container.innerHTML
  }
  // 掛載前清空容器內(nèi)容
  container.innerHTML = ''
  // 真正的掛載
  return mount(container)
}
  • 首先是通過normalizeContainer標(biāo)準(zhǔn)化容器(這里可以傳字符串選擇器或者DOM對象垂谢,但如果是字符串選擇器,就需要把它轉(zhuǎn)成 DOM對象疮茄,作為最終掛載的容器)
  • 然后做一個(gè)if判斷滥朱,如果組件對象沒有定義render函數(shù)和 template模板,則取容器的innerHTML作為組件模板內(nèi)容
  • 接著在掛載前清空容器內(nèi)容力试,最終再調(diào)用app.mount的方法走標(biāo)準(zhǔn)的組件渲染流程

優(yōu)勢

  • 跨平臺(tái)實(shí)現(xiàn)
  • 兼容vue2.0寫法
  • app.mount既可以傳dom徙邻,又可以傳字符串選擇器

核心渲染流程:創(chuàng)建 vnode 和渲染 vnode

創(chuàng)建 vnode

  • 1、 vnode本質(zhì)上是用來描述DOMJavaScript對象

它在Vue.js中可以描述不同類型的節(jié)點(diǎn)畸裳,比如普通元素節(jié)點(diǎn)缰犁、組件節(jié)點(diǎn)等

vnode如何描述
// vnode 這樣表示<button>標(biāo)簽
const vnode = {
  type: 'button',
  props: { 
    'class': 'btn',
    style: {
      width: '100px',
      height: '50px'
    }
  },
  children: 'click me'
}
  • type屬性表示DOM的標(biāo)簽類型

  • props屬性表示DOM的一些附加信息,比如styleclass

  • children屬性表示DOM的子節(jié)點(diǎn)帅容,它也可以是一個(gè)vnode數(shù)組颇象,只不過vnode可以用字符串表示簡單的文本

  • 2、 vnode除了用于描述一個(gè)真實(shí)的DOM并徘,也可以用來描述組件

vnode其實(shí)是對抽象事物的描述

// vnode 這樣表示 <custom-component>
const CustomComponent = {
  // 在這里定義組件對象
}
const vnode = {
  type: CustomComponent,
  props: { 
    msg: 'test'
  }
}
  • 3遣钳、其他的,還有純文本vnode饮亏,注釋vnode
  • 4、Vue.js 3.0中阅爽,vnodetype路幸,做了更詳盡的分類,包括 Suspense付翁、Teleport等简肴,且把vnode的類型信息做了編碼,以便在后面的patch階段百侧,可以根據(jù)不同的類型執(zhí)行相應(yīng)的處理邏輯
vode優(yōu)勢
  • 抽象
  • 跨平臺(tái)
  • 但是砰识,和手動(dòng)修改dom對比,并不一定有優(yōu)勢

如何創(chuàng)建vnode

  • app.mount函數(shù)的實(shí)現(xiàn)佣渴,內(nèi)部是通過createVNode函數(shù)創(chuàng)建了根組件的vnode
const vnode = createVNode(rootComponent, rootProps)
createVNode 函數(shù)的大致實(shí)現(xiàn)
function createVNode(type, props = null,children = null) {
  if (props) {
    // 處理 props 相關(guān)邏輯辫狼,標(biāo)準(zhǔn)化 class 和 style
  }
  // 對 vnode 類型信息編碼
  const shapeFlag = isString(type)
    ? 1 /* ELEMENT */
    : isSuspense(type)
      ? 128 /* SUSPENSE */
      : isTeleport(type)
        ? 64 /* TELEPORT */
        : isObject(type)
          ? 4 /* STATEFUL_COMPONENT */
          : isFunction(type)
            ? 2 /* FUNCTIONAL_COMPONENT */
            : 0
  const vnode = {
    type,
    props,
    shapeFlag,
    // 一些其他屬性
  }
  // 標(biāo)準(zhǔn)化子節(jié)點(diǎn),把不同數(shù)據(jù)類型的 children 轉(zhuǎn)成數(shù)組或者文本類型
  normalizeChildren(vnode, children)
  return vnode
}
  • props做標(biāo)準(zhǔn)化處理
  • vnode的類型信息編碼
  • 創(chuàng)建vnode對象
  • 標(biāo)準(zhǔn)化子節(jié)點(diǎn)children

渲染 vnode

render(vnode, rootContainer)
const render = (vnode, container) => {
  if (vnode == null) {
    // 銷毀組件
    if (container._vnode) {
      unmount(container._vnode, null, null, true)
    }
  } else {
    // 創(chuàng)建或者更新組件
    patch(container._vnode || null, vnode, container)
  }
  // 緩存 vnode 節(jié)點(diǎn)辛润,表示已經(jīng)渲染
  container._vnode = vnode
}
  • 如果它的第一個(gè)參數(shù)vnode為空膨处,則執(zhí)行銷毀組件的邏輯
  • 否則執(zhí)行創(chuàng)建或者更新組件的邏輯
patch函數(shù)
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
  // 如果存在新舊節(jié)點(diǎn), 且新舊節(jié)點(diǎn)類型不同,則銷毀舊節(jié)點(diǎn)
  if (n1 && !isSameVNodeType(n1, n2)) {
    anchor = getNextHostNode(n1)
    unmount(n1, parentComponent, parentSuspense, true)
    n1 = null
  }
  const { type, shapeFlag } = n2
  switch (type) {
    case Text:
      // 處理文本節(jié)點(diǎn)
      break
    case Comment:
      // 處理注釋節(jié)點(diǎn)
      break
    case Static:
      // 處理靜態(tài)節(jié)點(diǎn)
      break
    case Fragment:
      // 處理 Fragment 元素
      break
    default:
      if (shapeFlag & 1 /* ELEMENT */) {
        // 處理普通 DOM 元素
        processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
      }
      else if (shapeFlag & 6 /* COMPONENT */) {
        // 處理組件
        processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
      }
      else if (shapeFlag & 64 /* TELEPORT */) {
        // 處理 TELEPORT
      }
      else if (shapeFlag & 128 /* SUSPENSE */) {
        // 處理 SUSPENSE
      }
  }
}
  • 這個(gè)函數(shù)有兩個(gè)功能:
    • 一個(gè)是根據(jù)vnode掛載DOM
    • 一個(gè)是根據(jù)新舊vnode更新DOM砂竖。對于初次渲染
  • patch函數(shù)入?yún)?
    • 第一個(gè)參數(shù) n1 表示vnode真椿,當(dāng) n1null 的時(shí)候,表示是一次掛載的過程乎澄;
    • 第二個(gè)參數(shù) n2 表示vnode 節(jié)點(diǎn)突硝,后續(xù)會(huì)根據(jù)這個(gè) vnode 類型執(zhí)行不同的處理邏輯;
    • 第三個(gè)參數(shù)container表示 DOM 容器置济,也就是 vnode 渲染生成 DOM 后解恰,會(huì)掛載到 container 下面。

渲染節(jié)點(diǎn)

  • 對組件的處理
  • 對普通DOM元素的處理
對組件的處理
processComponent函數(shù)實(shí)現(xiàn)
  • 用來處理組件
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  if (n1 == null) {
   // 掛載組件
   mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
  }
  else {
    // 更新組件
    updateComponent(n1, n2, parentComponent, optimized)
  }
}
  • 如果n1null浙于,則執(zhí)行掛載組件的邏輯
  • 否則執(zhí)行更新組件的邏輯
mountComponent掛載組件的實(shí)現(xiàn)
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  // 創(chuàng)建組件實(shí)例
  const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense))
  // 設(shè)置組件實(shí)例
  setupComponent(instance)
  // 設(shè)置并運(yùn)行帶副作用的渲染函數(shù)
  setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized)
}

主要做三件事情

  • 1修噪、 創(chuàng)建組件實(shí)例
    • Vue.js 3.0雖然不像Vue.js 2.x那樣通過類的方式去實(shí)例化組件,但內(nèi)部也通過對象的方式去創(chuàng)建了當(dāng)前渲染的組件實(shí)例
  • 2路媚、 設(shè)置組件實(shí)例
    • instance保留了很多組件相關(guān)的數(shù)據(jù)黄琼,維護(hù)了組件的上下文,包括對props、插槽脏款,以及其他實(shí)例的屬性的初始化處理
  • 3围苫、 設(shè)置并運(yùn)行帶副作用的渲染函數(shù)(setupRenderEffect)
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
  // 創(chuàng)建響應(yīng)式的副作用渲染函數(shù)
  instance.update = effect(function componentEffect() {
    if (!instance.isMounted) {
      // 渲染組件生成子樹 vnode
      const subTree = (instance.subTree = renderComponentRoot(instance))
      // 把子樹 vnode 掛載到 container 中
      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
      // 保留渲染生成的子樹根 DOM 節(jié)點(diǎn)
      initialVNode.el = subTree.el
      instance.isMounted = true
    }
    else {
      // 更新組件
    }
  }, prodEffectOptions)
}
  • 該函數(shù)利用響應(yīng)式庫的effect函數(shù)創(chuàng)建了一個(gè)副作用渲染函數(shù) componentEffect

副作用
當(dāng)組件的數(shù)據(jù)發(fā)生變化時(shí),effect函數(shù)包裹的內(nèi)部渲染函數(shù) componentEffect會(huì)重新執(zhí)行一遍撤师,從而達(dá)到重新渲染組件的目的

  • 渲染函數(shù)內(nèi)部也會(huì)判斷這是一次初始渲染還是組件更新剂府,在初始渲染流程中

初始渲染主要做兩件事情

  • 1、 渲染組件生成subTree
    注意剃盾,不要弄混subTree(執(zhí)行renderComponentRoot生成的子樹vnode)和initialVNode(組件 vnode
    • 每個(gè)組件都有render函數(shù)腺占,template也會(huì)編譯成render函數(shù)
    • renderComponentRoot函數(shù)就是去執(zhí)行 render 函數(shù)創(chuàng)建整個(gè)組件樹內(nèi)部的 vnode
    • 把這個(gè) vnode 再經(jīng)過內(nèi)部一層標(biāo)準(zhǔn)化痒谴,就得到了該函數(shù)的返回結(jié)果:subTree(子樹vnode
  • 2衰伯、把subTree掛載到container
    • 繼續(xù)調(diào)用 patch 函數(shù)把子樹 vnode 掛載到 container
    • 繼續(xù)對這個(gè)子樹vnode類型進(jìn)行判斷,此時(shí)子樹vnode為普通元素vnode
對普通 DOM 元素的處理
processElement函數(shù)
  • 用來處理普通DOM 元素
const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  isSVG = isSVG || n2.type === 'svg'
  if (n1 == null) {
    //掛載元素節(jié)點(diǎn)
    mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
  }
  else {
    //更新元素節(jié)點(diǎn)
    patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
  }
}
  • 如果n1null积蔚,走掛載元素節(jié)點(diǎn)的邏輯
  • 否則走更新元素節(jié)點(diǎn)邏輯
mountElement 函數(shù)
const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  let el
  const { type, props, shapeFlag } = vnode
  // 創(chuàng)建 DOM 元素節(jié)點(diǎn)
  el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is)
  if (props) {
    // 處理 props意鲸,比如 class、style尽爆、event 等屬性
    for (const key in props) {
      if (!isReservedProp(key)) {
        hostPatchProp(el, key, null, props[key], isSVG)
      }
    }
  }
  if (shapeFlag & 8 /* TEXT_CHILDREN */) {
    // 處理子節(jié)點(diǎn)是純文本的情況
    hostSetElementText(el, vnode.children)
  }
  else if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
    // 處理子節(jié)點(diǎn)是數(shù)組的情況
    mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', optimized || !!vnode.dynamicChildren)
  }
  // 把創(chuàng)建的 DOM 元素節(jié)點(diǎn)掛載到 container 上
  hostInsert(el, container, anchor)
}
  • 主要做四件事
  • 1怎顾、 創(chuàng)建DOM元素節(jié)點(diǎn)
    通過hostCreateElement方法創(chuàng)建,這是一個(gè)平臺(tái)相關(guān)的方法漱贱,在web端實(shí)現(xiàn):
// 調(diào)用了底層的 DOM API document.createElement 創(chuàng)建元素
function createElement(tag, isSVG, is) {
  isSVG ? document.createElementNS(svgNS, tag)
    : document.createElement(tag, is ? { is } : undefined)
}
  • 2槐雾、 處理props
    給這個(gè)DOM節(jié)點(diǎn)添加相關(guān)的 classstyle幅狮、event 等屬性蚜退,并做相關(guān)的處理
  • 3、 處理children
    • 子節(jié)點(diǎn)是純文本彪笼,則執(zhí)行hostSetElementText方法钻注,它在 Web環(huán)境下通過設(shè)置DOM元素的textContent屬性設(shè)置文本:
function setElementText(el, text) {
  el.textContent = text
}
* 如果子節(jié)點(diǎn)是數(shù)組,則執(zhí)行`mountChildren`方法
const mountChildren = (children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, start = 0) => {
  for (let i = start; i < children.length; i++) {
    // 預(yù)處理 child
    const child = (children[i] = optimized
      ? cloneIfMounted(children[i])
      : normalizeVNode(children[i]))
    // 遞歸 patch 掛載 child
    patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
  }
}
  • 遍歷children獲取到每一個(gè)child vnode

  • mountChildren函數(shù)的第二個(gè)參數(shù)是container配猫,傳入的是mountElement時(shí)創(chuàng)建的DOM節(jié)點(diǎn)幅恋,很好的建立了父子關(guān)系

  • 通過遞歸patch這種深度優(yōu)先遍歷樹的方式,我們就可以構(gòu)造完整的DOM樹泵肄,完成組件的渲染捆交。

  • 4、 掛載DOM元素到container
    調(diào)用hostInsert方法

function insert(child, parent, anchor) {
  if (anchor) {
    parent.insertBefore(child, anchor)
  }
  else {
    parent.appendChild(child)
  }
}
嵌套組件的處理
  • mountChildren的時(shí)候遞歸執(zhí)行的是 patch 函數(shù)腐巢,而不是 mountElement函數(shù)品追,這是因?yàn)樽庸?jié)點(diǎn)可能有其他類型的vnode,比如組件vnode

組件渲染流程圖

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冯丙,一起剝皮案震驚了整個(gè)濱河市肉瓦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖泞莉,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哪雕,死亡現(xiàn)場離奇詭異,居然都是意外死亡鲫趁,警方通過查閱死者的電腦和手機(jī)斯嚎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挨厚,“玉大人堡僻,你說我怎么就攤上這事∫咛辏” “怎么了钉疫?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長慌申。 經(jīng)常有香客問我陌选,道長理郑,這世上最難降的妖魔是什么蹄溉? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮您炉,結(jié)果婚禮上柒爵,老公的妹妹穿的比我還像新娘。我一直安慰自己赚爵,他們只是感情好棉胀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冀膝,像睡著了一般唁奢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窝剖,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天麻掸,我揣著相機(jī)與錄音,去河邊找鬼赐纱。 笑死脊奋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疙描。 我是一名探鬼主播诚隙,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼起胰!你這毒婦竟也來了久又?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎籽孙,沒想到半個(gè)月后烈评,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犯建,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年讲冠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片适瓦。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竿开,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玻熙,到底是詐尸還是另有隱情否彩,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布嗦随,位于F島的核電站列荔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏枚尼。R本人自食惡果不足惜钓试,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一亡嫌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦材义、人聲如沸校仑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呼巷,卻和暖如春囱修,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背王悍。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工破镰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人配名。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓啤咽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親渠脉。 傳聞我的和親對象是個(gè)殘疾皇子宇整,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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