性能飆升50%,react-virtualized-list如何優(yōu)化大數(shù)據(jù)集滾動渲染

引言:在處理大規(guī)模數(shù)據(jù)集渲染時(shí),前端性能常常面臨巨大的挑戰(zhàn)。本文將探討 react-virtualized-list 庫如何通過虛擬化技術(shù)和 Intersection Observer,實(shí)現(xiàn)前端渲染性能飆升 50% 的突破,頁面渲染速度提升 95% !????

image.png

背景

最近,公司監(jiān)控系統(tǒng)出現(xiàn)了加載卡頓和白屏問題,需要一個(gè)能夠處理大規(guī)模數(shù)據(jù)渲染的方案。由于核心需求是列表項(xiàng)數(shù)據(jù)需要動態(tài)更新和自動刷新,所以用到了 react-virtualized-list 庫。這個(gè)過程相當(dāng)曲折,具體業(yè)務(wù)需求細(xì)節(jié)后面我會詳細(xì)寫一篇文章,這里先介紹一下react-virtualized-list庫的特性、適用場景、API和實(shí)現(xiàn)原理。

希望對你有所幫助、有所借鑒。大家有什么疑問或者建議,歡迎在評論區(qū)一起討論!

什么是虛擬化?

虛擬化技術(shù),顧名思義,是一種通過僅渲染當(dāng)前用戶可見的數(shù)據(jù)項(xiàng),而不是整個(gè)數(shù)據(jù)集,來優(yōu)化性能的技術(shù)。這種技術(shù)在處理大量數(shù)據(jù)時(shí)尤為重要,因?yàn)樗@著減少了 DOM 節(jié)點(diǎn)的數(shù)量,從而提高了性能。通過虛擬化,可以在用戶滾動列表時(shí)動態(tài)加載和卸載元素,保持界面流暢。

下面是react-virtualized-list在虛擬化方面做的處理:

image.png

我們來看看真實(shí)的 DOM 情況!

image.png

react-virtualized-list 簡介

react-virtualized-list 是一個(gè)專門用于顯示大型數(shù)據(jù)集的高性能 React 組件庫。它同時(shí)適用于 PC 端移動端,通過虛擬化技術(shù)實(shí)現(xiàn)了延遲加載和無限滾動功能,尤其是非常適合需要高效渲染和加載大量數(shù)據(jù)的應(yīng)用場景,如聊天記錄、商品列表等。

此外,react-virtualized-list 庫還提供了場景適用的效果展示和示例代碼。

核心特性 ????

  1. 高性能:僅渲染當(dāng)前視口內(nèi)的元素,顯著減少 DOM 節(jié)點(diǎn)數(shù)量。
  2. 延遲加載:動態(tài)加載數(shù)據(jù),避免一次性加載大量數(shù)據(jù)帶來的性能問題。
  3. 無限滾動:支持無限滾動,用戶可以持續(xù)滾動查看更多內(nèi)容。
  4. 自定義渲染:提供靈活的 API,允許開發(fā)者自定義列表項(xiàng)的渲染方式。
  5. 視口內(nèi)刷新:支持自動刷新視口內(nèi)的內(nèi)容,確保數(shù)據(jù)的實(shí)時(shí)性。
  6. 支持 TS 和 JS:適用于 TypeScript 和 JavaScript 項(xiàng)目。

安裝

可以通過 npm 或 yarn 輕松安裝 react-virtualized-list

npm install react-virtualized-list
# 或者
yarn add react-virtualized-list

基本用法

下面是一個(gè)簡單的示例,展示了如何使用 react-virtualized-list 創(chuàng)建一個(gè)無限滾動的虛擬化列表:

import React, { useState, useEffect } from 'react';
import VirtualizedList from 'react-virtualized-list';
import './style/common.css';

const InfiniteScrollList = () => {
  const [items, setItems] = useState([]);
  const [hasMore, setHasMore] = useState(true);

  const loadMoreItems = () => {
    // 模擬 API 調(diào)用,延遲 1 秒加載新數(shù)據(jù)
    setTimeout(() => {
      const newItems = Array.from({ length: 20 }, (_, index) => ({
        id: items.length + index,
        text: `Item ${items.length + index}`
      }));
      setItems(prevItems => [...prevItems, ...newItems]);
      setHasMore(newItems.length > 0);
    }, 1000);
  };

  useEffect(() => {
    loadMoreItems();
  }, []);

  const renderItem = (item) => <div>{item.text}</div>;

  return (
      <div className='content'>
        <VirtualizedList
          listData={items}
          renderItem={renderItem}
          containerHeight='450px'
          itemClassName='item-class'
          onLoadMore={loadMoreItems}
          hasMore={hasMore}
          loader={<div>Loading...</div>}
          endMessage={<div>No more items</div>}
        />
      </div>
  );
};

export default InfiniteScrollList;
/* ./style/common.css  */
.content {
    width: 350px;
    padding: 16px;
    border: 1px solid red;
    margin-top: 10vh;
}
.item-class {
    height: 50px;
    border: 1px solid blue;
    margin: 0px 0 10px;
    padding: 10px;
    background-color: #f0f0f0;
}

通過 onLoadMorehasMore 屬性實(shí)現(xiàn)無限滾動,在用戶滾動到列表底部時(shí)自動加載更多數(shù)據(jù)。這種功能常見于滾動加載下頁數(shù)據(jù)。

image.png

進(jìn)階用法

動態(tài)加載數(shù)據(jù)

為了進(jìn)一步提高性能,可以使用動態(tài)加載技術(shù),只在需要時(shí)加載數(shù)據(jù)。以下是一個(gè)示例,展示了如何結(jié)合 react-virtualized-list 和動態(tài)數(shù)據(jù)加載:

import React, { useState, useEffect } from 'react';
import VirtualizedList from 'react-virtualized-list';
import './style/common.css';

const fetchProductData = async (product) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ description: `Description for ${product.name}`, imageUrl: `https://via.placeholder.com/150?text=Product+${product.id}` });
    }, 500);
  });
};

const fetchProducts = async (page) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const products = Array.from({ length: 10 }, (_, i) => ({
        id: page * 10 + i,
        name: `Product ${page * 10 + i}`
      }));
      resolve(products);
    }, 500);
  });
};

const DynamicInfiniteList = () => {
  const [products, setProducts] = useState([]);
  const [hasMore, setHasMore] = useState(true);
  const [page, setPage] = useState(0);

  const loadMoreProducts = async () => {
    const newProducts = await fetchProducts(page);
    setProducts(prevProducts => [...prevProducts, ...newProducts]);
    setPage(prevPage => prevPage + 1);
    if (newProducts.length < 10) setHasMore(false);
  };

  useEffect(() => {
    loadMoreProducts();
  }, []);
   
  return (
      <div className='content'>
        <VirtualizedList
          listData={products}
          renderItem={(product, data) => (
            <div>
              <h2>{product.name}</h2>
              <p>{data ? data.description : 'Loading...'}</p>
              {data && <img src={data.imageUrl} alt={product.name} />}
            </div>
          )}
          itemClassName='item-class-dynamic'
          fetchItemData={fetchProductData}
          onLoadMore={loadMoreProducts}
          hasMore={hasMore}
          containerHeight='500px'
          loader='Loading more products...'
          endMessage='No more products'
        />
      </div>
  );
};

export default DynamicInfiniteList;
/* ./style/common.css  */
.content {
    width: 350px;
    padding: 16px;
    border: 1px solid red;
    margin-top: 10vh;
}
.item-class-dynamic {
    height: 300px;
    padding: 20px;
    border-bottom: 1px solid #eee;
}

注意:在上面代碼中,我們使用 onLoadMore 模擬商品列表的滾動加載,并在 VirtualizedList 組件的 fetchItemData 實(shí)現(xiàn)了商品詳情的動態(tài)加載。這對于大數(shù)據(jù)集下,后端無法一次性返回?cái)?shù)據(jù)非常有利!

自定義渲染

react-virtualized-list 還提供了自定義渲染功能,開發(fā)者可以根據(jù)具體需求定制列表項(xiàng)的渲染方式。以下是一個(gè)示例,展示了如何自定義列表項(xiàng)的樣式和內(nèi)容:

import React from 'react';
import VirtualizedList from 'react-virtualized-list';

const data = Array.from({ length: 1000 }).map((_, index) => ({
  title: `Item ${index}`,
  index: index,
  description: `This is the description for item ${index}.`
}));

const ListItem = ({ item, style }) => (
  <div style={{ ...style, padding: '10px', borderBottom: '1px solid #ccc' }}>
    <h3>{item.title}</h3>
    <p>{item.description}</p>
  </div>
);

const itemStyle = {
    height: '100px',
    border: '1px solid blue',
    margin: '0px 0 10px',
    padding: '10px',
    backgroundColor: '#f0f0f0'
};

const MyVirtualizedList = () => (
  <div style={{width: '350px', padding: '16px', border: '1px solid red'}}>
    <VirtualizedList
        listData={data}
        itemStyle={itemStyle}
        renderItem={({ index, style }) => <ListItem item={data[index]} style={style} />}
        containerHeight='80vh'
    />
  </div>
);

export default MyVirtualizedList;

此外,react-virtualized-list 還提供了其他的用法場景和相關(guān) API,詳情請見使用文檔。

image.png

實(shí)現(xiàn)原理(??核心重點(diǎn),一定要了解)

在構(gòu)建大型 Web 應(yīng)用時(shí),經(jīng)常會遇到需要展示大量數(shù)據(jù)的情況,比如電子商務(wù)平臺的產(chǎn)品列表等。傳統(tǒng)的渲染方式可能會面臨性能問題,因?yàn)樗鼈冃枰陧撁嫔贤瑫r(shí)呈現(xiàn)大量 DOM 元素,導(dǎo)致頁面加載緩慢、滾動卡頓等問題。

為了解決這個(gè)問題,我們可以使用虛擬化列表來優(yōu)化渲染過程。而 react-virtualized-list 庫的核心在于通過虛擬化技術(shù)優(yōu)化渲染過程。其主要原理包括以下幾點(diǎn):

image.png

1. 可視區(qū)域監(jiān)測:利用Intersection Observer API

在虛擬化列表的實(shí)現(xiàn)中,一個(gè)關(guān)鍵步驟是監(jiān)測可視區(qū)域內(nèi)的元素。傳統(tǒng)的方法是通過監(jiān)聽滾動事件并計(jì)算每個(gè)元素的位置來實(shí)現(xiàn),然而這種方式效率較低。

// 獲取需要監(jiān)測可視性的元素
const elements = document.querySelectorAll('.target-element');

// 監(jiān)聽滾動事件
window.addEventListener('scroll', () => {
    // 計(jì)算每個(gè)元素的位置
    elements.forEach(element => {
        const rect = element.getBoundingClientRect();
        if (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
        ) {
            // 元素在可視區(qū)域內(nèi)
            // 執(zhí)行相應(yīng)操作
            console.log(`${element} is visible.`);
        }
    });
});

相比之下,我們可以利用現(xiàn)代瀏覽器提供的 Intersection Observer API 來更高效地監(jiān)測元素的可見性變化。

// 定義一個(gè) Intersection Observer
const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        // 如果元素可見
        if (entry.isIntersecting) {
            // 執(zhí)行相應(yīng)操作
            console.log(`${entry.target} is visible.`);
        }
    });
});

// 獲取需要監(jiān)測可視性的元素
const elements = document.querySelectorAll('.target-element');

// 監(jiān)測每個(gè)元素
elements.forEach(element => {
    observer.observe(element);
});

這里我封裝了一個(gè) React Hooks useIntersectionObserver,提供了Intersection Observer API 的能力。

image.png

2. 僅渲染可見區(qū)域:優(yōu)化性能

虛擬化列表的另一個(gè)關(guān)鍵優(yōu)化是僅渲染可見區(qū)域內(nèi)的元素,而不是渲染整個(gè)列表。這樣做可以大大減少渲染所需的時(shí)間和資源,提高頁面的性能表現(xiàn)。

import useIntersectionObserver from './useIntersectionObserver';

const [visibleItems, setVisibleItems] = useState<Set<number>>(new Set());

const handleVisibilityChange = useCallback((isVisible: boolean, entry: IntersectionObserverEntry) => {
    const index = parseInt(entry.target.getAttribute('data-index')!, 10);
    setVisibleItems(prev => {
      const newVisibleItems = new Set(prev);
      if (isVisible) {
        newVisibleItems.add(index);
      } else {
        newVisibleItems.delete(index);
      }
      return newVisibleItems;
    });
  }, []);
  
const { observe, unobserve } = useIntersectionObserver(containerRef.current, handleVisibilityChange, null, observerOptions);

3. 動態(tài)加載和卸載:保持內(nèi)存使用最小化

最后,虛擬化列表還可以通過動態(tài)加載和卸載元素來保持內(nèi)存使用最小化。當(dāng)用戶滾動到可視區(qū)域時(shí),新的元素被動態(tài)加載,而離開可視區(qū)域的元素則被卸載,從而減少頁面的內(nèi)存占用。

image.png
const visibleRange = useMemo(() => {
    const sortedVisibleItems = [...visibleItems].sort((a, b) => a - b);
    const firstVisible = sortedVisibleItems[0] || 0;
    const lastVisible = sortedVisibleItems[sortedVisibleItems.length - 1] || 0;
    // 設(shè)置緩存區(qū)
    return [Math.max(0, firstVisible - BUFFER_SIZE), Math.min(listData.length - 1, lastVisible + BUFFER_SIZE)];
  }, [visibleItems, listData.length]);
  
const renderItems = () => {
    return listData.length ? listData.map((item, index) => {
      if (index >= visibleRange[0] && index <= visibleRange[1]) {
        return (
          <div
            className={itemClassName || undefined}
            style={itemContainerStyle}
            ref={node => handleRef(node, index)}
            key={index}
            data-index={index}
          >
            <VirtualizedListItem
              item={listData[index]}
              isVisible={visibleItems.has(index)}
              refreshOnVisible={refreshOnVisible}
              fetchItemData={fetchItemData}
              itemLoader={itemLoader}
            >
              {renderItem}
            </VirtualizedListItem>
          </div>
        );
      }
      return null;
    }) : (
      emptyListMessage ? emptyListMessage : null
    );
  };

當(dāng)元素進(jìn)入視口時(shí),我們加載它;當(dāng)元素離開視口時(shí),我們卸載它。這樣就可以保持頁面上始終只有視口內(nèi)的內(nèi)容被渲染,從而提高頁面的性能和響應(yīng)速度。

除此之外,通過使用 useMemo 計(jì)算當(dāng)前可見的列表項(xiàng)范圍 (visibleRange),以及設(shè)置一個(gè)緩沖區(qū) (BUFFER_SIZE);使用useMemouseCallback 用于性能優(yōu)化的 Hook。它們幫助避免不必要的計(jì)算和重新渲染。

image.png

性能對比(??性能飆升 50%)

下面我們就來看下,傳統(tǒng)滾動 Scroll 監(jiān)聽和 Intersection Observer API 的性能對比數(shù)據(jù)(假設(shè)在相同環(huán)境和數(shù)據(jù)集下測試):

方法 初始渲染時(shí)間 滾動性能 內(nèi)存使用
傳統(tǒng)滾動監(jiān)聽 300ms
Intersection Observer API 150ms
  • 初始渲染時(shí)間:使用 Intersection Observer API 的初始渲染時(shí)間較短,因?yàn)橹讳秩究梢妳^(qū)域。
  • 滾動性能:傳統(tǒng)滾動監(jiān)聽由于頻繁的滾動事件觸發(fā)和位置計(jì)算,滾動性能較低;Intersection Observer API 的滾動性能較高,因?yàn)樗昧藶g覽器的優(yōu)化機(jī)制。
  • 內(nèi)存使用:Intersection Observer API 由于僅加載和渲染可見元素,內(nèi)存使用更低。

性能測試代碼分析

以下是一個(gè)示例,展示了如何使用 console.time 和 console.timeEnd 來測量性能:

// 測量傳統(tǒng)滾動監(jiān)聽的性能
console.time('Scroll');
window.addEventListener('scroll', () => {
    // 模擬計(jì)算每個(gè)元素的位置
    const elements = document.querySelectorAll('.target-element');
    elements.forEach(element => {
        const rect = element.getBoundingClientRect();
        if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
            // 模擬渲染邏輯
        }
    });
});
console.timeEnd('Scroll');

// 測量 Intersection Observer API 的性能
console.time('IntersectionObserver');
const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            // 模擬渲染邏輯
        }
    });
});
const elements = document.querySelectorAll('.target-element');
elements.forEach(element => observer.observe(element));
console.timeEnd('IntersectionObserver');

注意:傳統(tǒng)滾動監(jiān)聽方法還會涉及大量計(jì)算,這里僅簡單測量了監(jiān)聽性能的統(tǒng)計(jì)部分。

傳統(tǒng)的滾動監(jiān)聽方式通過監(jiān)聽 scroll 事件,在每次滾動時(shí)計(jì)算每個(gè)目標(biāo)元素的位置,并判斷其是否在視窗內(nèi)。這部分代碼的執(zhí)行會阻塞主線程,尤其在滾動頻繁的情況下可能導(dǎo)致性能問題,因?yàn)樾枰粩嘀匦掠?jì)算元素位置。

相比之下,Intersection Observer API 更高效。它可以檢測元素是否可見,并在元素進(jìn)入或退出視窗時(shí)觸發(fā)回調(diào)函數(shù),從而實(shí)現(xiàn)需要的功能。

0.gif

性能總結(jié)

在性能方面,傳統(tǒng)實(shí)現(xiàn)方法通常需要通過監(jiān)聽滾動(scroll)事件來計(jì)算元素位置。這種方法存在以下問題:

  • 性能消耗大:頻繁監(jiān)聽滾動事件會導(dǎo)致性能消耗增加,尤其是在大型數(shù)據(jù)集的情況下。
  • 計(jì)算復(fù)雜度高:需要手動計(jì)算每個(gè)列表項(xiàng)與視口的交叉情況,邏輯復(fù)雜且容易出錯。需要花費(fèi)大量時(shí)間和精力來優(yōu)化和調(diào)試這些計(jì)算邏輯。

相比之下,Intersection Observer API 的性能更優(yōu),具有以下優(yōu)點(diǎn):

  1. 性能開銷低Intersection Observer API 利用瀏覽器的內(nèi)部優(yōu)化機(jī)制,減少了不必要的計(jì)算和事件觸發(fā),從而提高了性能。相比之下,傳統(tǒng)的 scroll 事件監(jiān)聽方式由于密集觸發(fā),可能會導(dǎo)致較大的性能問題。
  2. 多元素監(jiān)測Intersection Observer API 允許同時(shí)監(jiān)測多個(gè)元素的交叉狀態(tài),而不需要為每個(gè)元素都綁定事件監(jiān)聽器。這使得在處理復(fù)雜布局和交互時(shí)更加高效。
  3. 異步執(zhí)行:當(dāng)元素進(jìn)入或離開交叉狀態(tài)時(shí),Intersection Observer 會異步執(zhí)行回調(diào)函數(shù),不會阻塞主線程。這有助于保持頁面的響應(yīng)性和流暢性。
  4. 應(yīng)用場景廣泛Intersection Observer API 可以應(yīng)用于多種場景,如懶加載、無限滾動、廣告展示與統(tǒng)計(jì)、頁面元素動畫等。這些應(yīng)用場景通常需要高效地處理元素與視口之間的交互。

綜上所述,Intersection Observer API 在處理大型數(shù)據(jù)集和復(fù)雜交互時(shí),相比傳統(tǒng)的 scroll 事件監(jiān)聽方式,提供了更高的性能和更靈活的解決方案。

項(xiàng)目成果展示(??渲染速度提升95%)

下面我們看下優(yōu)化后的性能,展示實(shí)際改進(jìn)的用戶體驗(yàn)和加載時(shí)間。

首先從視覺感官上看,幾乎是一瞬間圖表就加載了出來。我們接著再來看看接口Network與數(shù)據(jù)對比!

image.png

為了清楚地展示優(yōu)化前后頁面加載速度的提升,我們可以將相關(guān)數(shù)據(jù)整理成一個(gè)表格形式,如下所示:

優(yōu)化指標(biāo) 優(yōu)化前 優(yōu)化后 加載速度提升
總耗時(shí) 15000 毫秒(15秒) 750 毫秒 提速了95%

這個(gè)表格展示了優(yōu)化措施的顯著效果,從中可以看出,經(jīng)過優(yōu)化后,整體加載時(shí)間也從15000毫秒大幅減少至750毫秒,加載速度提高了95%。

總結(jié)

通過使用 react-virtualized-list 庫,監(jiān)控系統(tǒng)項(xiàng)目前端渲染性能得到了顯著提升。統(tǒng)計(jì)結(jié)果顯示:頁面加載速度提高了 95%,用戶體驗(yàn)得到了明顯改善。如果你也在處理大數(shù)據(jù)集的渲染問題,不妨試試這個(gè)庫。

希望本文能對你有所幫助,有所借鑒!大家有什么疑問或者建議,歡迎在評論區(qū)一起討論。

參考資料

  1. Intersection Observer API
  2. react-virtualized-list
  3. 詳解 Intersection Observer API ( 交叉觀察器 )
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評論 2 374

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