React Loadable

欢迎访问我的最佳实践网站

React Loadable

一个动态导入加载组件的高阶组件.

示例

import Loadable from 'react-loadable';
import Loading from './my-loading-component';

const LoadableComponent = Loadable({
  loader: () => import('./my-component'),
  loading: Loading,
});

export default class App extends React.Component {
  render() {
    return <LoadableComponent/>;
  }
}

用户反馈:

用户

_ 如果你的公司或者项目也在使用 React Loadable,请加入这个列表(按照字母顺序)

Also See:

  • react-loadable-visibilityreact-loadable 使用相同的API构建, 这个组件能让你动态加载只显示在屏幕上的组件.

<h2>





<img src="http://thejameskyle.com/img/react-loadable-guide.png" alt="GUIDE">




<small>Guide</small>
</h2>

所以你现在有了一个React App, 并且你用Webpack去打包你的应用, 所有的事情看起来是那么的顺畅. 但是有一天你突然发现你的文件变得越来越大,打包变得越来越慢。

这时候我们是时候该引入 code-splitting(代码分割)到我们的项目中了!

A single giant bundle vs multiple smaller bundles

Code-splitting(代码分割)是把项目中一个大的入口文件分割成多个小的、单独的文件的进程。

这看起来很难,但是一些类似于Webpack的工具已经做到这些了,并且React Loadable是为了让这件事儿变得更加简单。

Route-based splitting(基于路由的代码风格) vs. Component-based splitting(基于组件的代码分割)

一般我们建议通过不同路由切断你的程序,并且异步加载它们,这对于大多数应用来讲很不错-在浏览器里一个用户点击一个链接并且等待页面加载完成是一个在正常不过的行为。

但是我们可以做的更好。

在React的大多数路由工具中,一个路由就是一个简单的组件,这真没有什么特殊的(Sorry Ryan和Michael-你们是特殊的)。所以我们如果通过组件而不是路由优化分割代码,我们会得到什么呢?

Route vs. component centric code splitting

事实证明:相当多。不仅仅是通过路由,还有更多的地方你可以将你的应用程序拆分出来,Modals、tabs还有许多隐藏的UI组件,当用户执行某些操作的时候,你再去加载他们。

Example: 假设你的主应用程序是在一个选项卡里,用户可能永远也不会进入这个选项卡下的应用程序,所以父路由组件为何要加载这个选项卡所对应的组件呢?

还有一个地方,你可以优先加载优先级高的组件。那些页面底部的组件为何要和页面顶部的组件同时加载呢?

由于路由即组件,所以我们仍然可以很轻松的在组件层面做code-split(代码分割)

在一个新项目中使用code-spliting非常简单,以至于你都不用想两遍,你只需要改动少量代码就可以完成自动的代码分割。

React Loadable 简介

React Loadable 是一个轻量级的代码分割组件,它简单到令人难以置信。

Loadable 是一个告诫组件 (一个创建并返回组件的函数),它能让你的应用程序在渲染之前动态的加载任何模块

想象有两个组件,一个组件是import的组件,另一个是渲染组件

import Bar from './components/Bar';

class Foo extends React.Component {
  render() {
    return <Bar/>;
  }
}

现在我们通过import引入Bar这个组件,这是一个同步的引入,但是我们在渲染之前是并不需要这个组件的,所以我们为何不推迟引入这个组件呢?

使用 dynamic import(动态引入) (a tc39 proposal currently at Stage 3)
我们可以修改组件Bar使之成为一个异步的。

class MyComponent extends React.Component {
  state = {
    Bar: null
  };

  componentWillMount() {
    import('./components/Bar').then(Bar => {
      this.setState({ Bar });
    });
  }

  render() {
    let {Bar} = this.state;
    if (!Bar) {
      return <div>Loading...</div>;
    } else {
      return <Bar/>;
    };
  }
}

但是这是一整个工作流程,并不是单纯的代码分割这一件事儿这么简单,比如当import()失败我们该怎么办?怎么作server-side rendering(服务端渲染)?这时候你可以抽象出Loadable来解决这些问题。

import Loadable from 'react-loadable';

const LoadableBar = Loadable({
  loader: () => import('./components/Bar'),
  loading() {
    return <div>Loading...</div>
  }
});

class MyComponent extends React.Component {
  render() {
    return <LoadableBar/>;
  }
}

通过 import()自动 code-splitting(代码分割)

在webpack2+中,当你使用import()的时候,它会为你自动代码分割,而不用做外的设置。

这意味着通过使用React Loadable和import()可以很快的实验出新的代码分割点来,这是程序中的最佳实践.

创建一个更好的"Loading..." 组件

渲染一个静态的"Loading..."已经不能传达出足够的信息给用户了。有时有我们还要想要表现出更多的状态来,比如错误和超时等,这是一个非常好的经历。

function Loading() {
  return <div>Loading...</div>;
}

Loadable({
  loader: () => import('./WillFailToLoad'), // oh no!
  loading: Loading,
});

这样做非常好,你的loading component会接收多个不同的props。

Loading 的错误状态

loader加载失败,loading component组件会接收一个errortrue的prop(否则为false).

function Loading(props) {
  if (props.error) {
    return <div>Error!</div>;
  } else {
    return <div>Loading...</div>;
  }
}

避免 Loading 组件闪烁的问题

有时候你的组件加载速度非常快(<200ms),Loading组件的loading效果在屏幕上一闪而过.
许多用户反馈,这样的效果会导致用户认为等待的时间要比实际时间还要长,但是如果你什么都不显示,用户反而认为加载很快。

所以你的loading compoent将会得到一个布尔类型为truepastDelay prop的返回值,当组件的加载时间比设置的delay时间长的时候。

function Loading(props) {
  if (props.error) {
    return <div>Error!</div>;
  } else if (props.pastDelay) {
    return <div>Loading...</div>;
  } else {
    return null;
  }
}

默认的delay参数为200ms,但是你可以根据需要自定义delay这个参数值

Loadable({
  loader: () => import('./components/Bar'),
  loading: Loading,
  delay: 300, // 0.3 seconds
});

loader超时

有时候网络连接断开或者失败,或者永久性挂起,这时候网页无反应,用户不知道是继续等待还是重新刷新页面。这时候当loader超时后loading component 将会接收一个timedOut prop 并且这个只值为true

function Loading(props) {
  if (props.error) {
    return <div>Error!</div>;
  } else if (props.timedOut) {
    return <div>Taking a long time...</div>;
  } else if (props.pastDelay) {
    return <div>Loading...</div>;
  } else {
    return null;
  }
}

然而这个特性默认是被禁止的,如果想打开特性,你可以通过timeout option参数传递给 Loadable.

Loadable({
  loader: () => import('./components/Bar'),
  loading: Loading,
  timeout: 10000, // 10 seconds
});

自定义渲染

Loadable会渲染default导出的组件,如果你想渲染自定义导出的组件,请使用render option参数

Loadable({
  loader: () => import('./my-component'),
  render(loaded, props) {
    let Component = loaded.namedExport;
    return <Component {...props}/>;
  }
});

加载多个资源

从技术上讲,你可以用loader()加载任何只要是一个promise的资源you're able to render something,但是这样用起来确实有点让人恼火。

你可以使用Loadable.Map让加载多个资源变得更加容易一些。

Loadable.Map({
  loader: {
    Bar: () => import('./Bar'),
    i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
  },
  render(loaded, props) {
    let Bar = loaded.Bar.default;
    let i18n = loaded.i18n;
    return <Bar {...props} i18n={i18n}/>;
  },
});

当使用Loadable.Map的时候,render() method是一个必要参数,他会传递一个匹配后的对象参数到loader中去。

预加载

还有一个优化项,你可以决定哪些组件在渲染之前进行预先加载。
比如:当按钮点击之前你需要加载一个新的组件,这个组件是被Loadable中的static preload method创建的。

const LoadableBar = Loadable({
  loader: () => import('./Bar'),
  loading: Loading,
});

class MyComponent extends React.Component {
  state = { showBar: false };

  onClick = () => {
    this.setState({ showBar: true });
  };

  onMouseOver = () => {
    LoadableBar.preload();
  };

  render() {
    return (
      <div>
        <button
          onClick={this.onClick}
          onMouseOver={this.onMouseOver}>
          Show Bar
        </button>
        {this.state.showBar && <LoadableBar/>}
      </div>
    )
  }
}

<h2>





<img src="http://thejameskyle.com/img/react-loadable-ssr.png" alt="SERVER SIDE RENDERING">




<small>Server-Side Rendering(服务端渲染)</small>
</h2>

当你渲染所有所有已经动态加载完成的组件的时候,你将会得到一堆loading的效果,这看起来确实很糟糕,但是好消息是React Loadable在设计之初就支持服务端渲染的,这样就不会出现首屏加载效果了。

我们通过Express开启一个服务。

import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './components/App';

const app = express();

app.get('/', (req, res) => {
  res.send(`
    <!doctype html>
    <html lang="en">
      <head>...</head>
      <body>
        <div id="app">${ReactDOMServer.renderToString(<App/>)}</div>
        <script src="/dist/main.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000, () => {
  console.log('Running on http://localhost:3000/');
});

服务端预加载所有的组件

第一件事儿就是在渲染正确的内容之前,确保你的服务端已经加载了所有的组件了。

我们可以使用 Loadable.preloadAll
这个方法.他会返回一个所有组件加载完成的一个代理。

Loadable.preloadAll().then(() => {
  app.listen(3000, () => {
    console.log('Running on http://localhost:3000/');
  });
});

服务端拾起客户端的状态

这可能稍微有一些复杂,这可能会话费我们多一些的精力。
为了能让客户端接管服务端的状态,我们需要在服务端使用相同的代码,
为了实现这一点,我们首先就要通过loadable组件告诉我们到底哪个组件正在渲染。

声明哪个模块被加载

这里有两个参数LoadableLoadable.Map能告诉我们哪个组件正在加载: opts.modules
opts.webpack

Loadable({
  loader: () => import('./Bar'),
  modules: ['./Bar'],
  webpack: () => [require.resolveWeak('./Bar')],
});

但是我们不必太担心这些参数,React Loadable有一个Babel plugin插件可以完成这些设置。

react-loadable/babel 加入到你的Babel config中:

{
  "plugins": [
    "react-loadable/babel"
  ]
}

现在这些参数将会被自动被创建。

找出哪些动态模块正在被加载

下一步我们将找到当请求进来的时候,哪些模块是真正需要被加载的。
为此,我们有一个Loadable.Capture组件可以使用,它能收集所有的被加载的模块。

import Loadable from 'react-loadable';

app.get('/', (req, res) => {
  let modules = [];

  let html = ReactDOMServer.renderToString(
    <Loadable.Capture report={moduleName => modules.push(moduleName)}>
      <App/>
    </Loadable.Capture>
  );

  console.log(modules);

  res.send(`...${html}...`);
});

将加载的模块映射到打文件上

为了确保客户端加载了所有服务端渲染的模块,我们需要将服务端的模块和webpack打包出来的打包文件做一个映射。

这包含两部分,第一部分我们需要让Webpack告诉我们每个模块需要哪个打包文件,为此我们可以使用React Loadable Webpack plugin插件,在webpack config中从react-loadable/webpack 引入ReactLoadablePlugin插件,传递一个filename参数,webpack会将打包文件作为一个JSON数据输出到这个文件中去。

// webpack.config.js
import { ReactLoadablePlugin } from 'react-loadable/webpack';

export default {
  plugins: [
    new ReactLoadablePlugin({
      filename: './dist/react-loadable.json',
    }),
  ],
};

然后我们回到我们的服务端,用刚才文件中的数据将模块转换成打包文件的数据
将模块转换成打包文件,需要从react-loadable/webpack引入getBundles方法。

import Loadable from 'react-loadable';
import { getBundles } from 'react-loadable/webpack'
import stats from './dist/react-loadable.json';

app.get('/', (req, res) => {
  let modules = [];

  let html = ReactDOMServer.renderToString(
    <Loadable.Capture report={moduleName => modules.push(moduleName)}>
      <App/>
    </Loadable.Capture>
  );

  let bundles = getBundles(stats, modules);

  // ...
});

这时候我们可以通过<script>标签渲染这些打包后的文件输出到HTML中。

let bundles = getBundles(stats, modules);

res.send(`
  <!doctype html>
  <html lang="en">
    <head>...</head>
    <body>
      <div id="app">${html}</div>
      <script src="/dist/main.js"></script>
      ${bundles.map(bundle => {
        return `<script src="/dist/${bundle.file}"></script>`
      }).join('\n')}
    </body>
  </html>
`);

客户端会等待所有的打包文件加载完成

因为Webpack的工作方式是,我们的主打包文件会比其他的scripts预先加载,所以我们需要等待所有的文件加载完成后才开始渲染。
为此我们需要一个全局的函数供我们调用当所有的打包文件被加载后,我们将在客户端使用Loadable.preloadReady()这个方法,就像在服务器使用Loadable.preloadAll()这个方法一样。

// src/entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';
import App from './components/App';

window.main = () => {
  Loadable.preloadReady().then(() => {
    ReactDOM.hydrate(<App/>, document.getElementById('app'));
  });
};

这时候在我们服务端返回的HTML的末尾处的 <script>标签中调用那个全局函数。

let bundles = getBundles(stats, modules);

res.send(`
      ...
      <script src="/dist/main.js"></script>
      ${bundles.map(...).join('\n')}
      <script>window.main();</script>
    </body>
  </html>
`);

<h4 align="center">
Now server-side rendering should work perfectly!
</h4>

<h2>





<img src="http://thejameskyle.com/img/react-loadable-api-docs.png" alt="API DOCS">




<small>API 文档</small>
</h2>

Loadable

rendering前动态loading模块的一个高阶组件,当模块无法被加载的时候,loading 组件会被渲染.

const LoadableComponent = Loadable({
  loader: () => import('./Bar'),
  loading: Loading,
  delay: 200,
  timeout: 10000,
});

它返回一个 LoadableComponent组件.

Loadable.Map

一个允许你并行加载多个资源点的高阶组件。
Loadable.Map's opts.loader接收一个对象,并且需要opts.render方法

Loadable.Map({
  loader: {
    Bar: () => import('./Bar'),
    i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
  },
  render(loaded, props) {
    let Bar = loaded.Bar.default;
    let i18n = loaded.i18n;
    return <Bar {...props} i18n={i18n}/>;
  }
});

当调用Loadable.Map中的render()方法, 这个方法中的loaded参数将会和loader方法起到一样的作用。

Loadable and Loadable.Map Options

opts.loader

一个加载模块的promose函数

Loadable({
  loader: () => import('./Bar'),
});

当调用Loadable.Map的时候,它接收对象行函数。

Loadable.Map({
  loader: {
    Bar: () => import('./Bar'),
    i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
  },
});

当调用Loadable.Map的时候,你也需要传递opts.render函数

opts.loading

当模块加载或者加载失败的时候,这个组件会被渲染。

Loadable({
  loading: LoadingComponent,
});

这个参数是必选参数,如果你不想渲染任何,让它返回null就好了。

Loadable({
  loading: () => null,
});

opts.delay

延时毫秒数props.pastDelay后加载渲染loading组件,默认值是200

Loadable({
  delay: 200
});

查看关于更多delay.

opts.timeout

props.timedOut超时的毫秒数后显示loading组件,默认是关闭的。

Loadable({
  timeout: 10000
});

更多关于 timeouts.

opts.render

自定义渲染加载模块的函数
它接收 opts.loader代理返回的loaded参数和LoadableComponent传递的props两个参数。

Loadable({
  render(loaded, props) {
    let Component = loaded.default;
    return <Component {...props}/>;
  }
});

opts.webpack

可选参数,可通过require.resolveWeak获取返回的一个Webpack模块id的集合。

Loadable({
  loader: () => import('./Foo'),
  webpack: () => [require.resolveWeak('./Foo')],
});

这个参数可通过Babel Plugin自动生成.

opts.modules

可选参数,imports模块路径的数组集合

Loadable({
  loader: () => import('./my-component'),
  modules: ['./my-component'],
});

可选参数,可通过Babel Plugin插件自动生成。

LoadableComponent

LoadableLoadable.Map返回的组件.

const LoadableComponent = Loadable({
  // ...
});

当组件加载的时候调用opts.render方法,它会直接接收props参数。

LoadableComponent.preload()

LoadableComponent调用的一个静态方法,可以让组件预加载。

const LoadableComponent = Loadable({...});

LoadableComponent.preload();

返回一个代理,但是尽量避免此代理阻塞你的UI更新,否则会带来非常不好的用户体验。

关于更多 preloading.

LoadingComponent

传给opts.loading方法的一个组件.

function LoadingComponent(props) {
  if (props.error) {
    // When the loader has errored
    return <div>Error!</div>;
  } else if (props.timedOut) {
    // When the loader has taken longer than the timeout
    return <div>Taking a long time...</div>;
  } else if (props.pastDelay) {
    // When the loader has taken longer than the delay
    return <div>Loading...</div>;
  } else {
    // When the loader has just started
    return null;
  }
}

Loading({
  loading: LoadingComponent,
});

关于更多 loading components

props.error

LoadingComponent的一个布尔类型的参数,当loader模块加载失败的时候,为true.

function LoadingComponent(props) {
  if (props.error) {
    return <div>Error!</div>;
  } else {
    return <div>Loading...</div>;
  }
}

更多关于 errors.

props.timedOut

LoadingComponent组件设置timeout参数后,props.timedOut将接受一个布尔类型的返回值。

function LoadingComponent(props) {
  if (props.timedOut) {
    return <div>Taking a long time...</div>;
  } else {
    return <div>Loading...</div>;
  }
}

更多关于 timeouts.

props.pastDelay

LoadingComponent设置delay参数后,props.pastDelay将接受一个布尔类型的返回值.

function LoadingComponent(props) {
  if (props.pastDelay) {
    return <div>Loading...</div>;
  } else {
    return null;
  }
}

更多关于delays.

Loadable.preloadAll()

等待所有被预加载的组件LoadableComponent.preload完成加载,允许你在各种环境里预加载你的组件,比如在服务端。

Loadable.preloadAll().then(() => {
  app.listen(3000, () => {
    console.log('Running on http://localhost:3000/');
  });
});

注意非常重要的一点,预先加载你所有声明过的loadable组件在启动你的服务.

Good:

// During module initialization...
const LoadableComponent = Loadable({...});

class MyComponent extends React.Component {
  componentDidMount() {
    // ...
  }
}

Bad:

// ...

class MyComponent extends React.Component {
  componentDidMount() {
    // During app render...
    const LoadableComponent = Loadable({...});
  }
}

注意: Loadable.preloadAll() 如果你的项目里有多个Loadable.preloadAll(),react-loadable将会失效。

更多关于 preloading on the server.

Loadable.preloadReady()

检查浏览器里已经加载所有模块并且调用matchingLoadableComponent.preload方法

window.main = () => {
  Loadable.preloadReady().then(() => {
    ReactDOM.hydrate(<App/>, document.getElementById('app'));
  });
};

更多关于 preloading on the client.

Loadable.Capture

记录哪个模块被渲染的一个组件.

每个被React Loadable组件在被渲染的时候,接收一个被每个moduleName调用的report prop。

let modules = [];

let html = ReactDOMServer.renderToString(
  <Loadable.Capture report={moduleName => modules.push(moduleName)}>
    <App/>
  </Loadable.Capture>
);

console.log(modules);

更多关于 capturing rendered modules.

Babel Plugin

为每个loadable 组件适配opts.webpackopts.modules参数是一件很耗费体力的一件事儿,并且你还要始终记着去做。

你可以用Babel plugin写到你的配置文件里去让Webpack自动完成这件事儿,从而代替手动去做。

{
  "plugins": ["react-loadable/babel"]
}

Input

import Loadable from 'react-loadable';

const LoadableMyComponent = Loadable({
  loader: () => import('./MyComponent'),
});

const LoadableComponents = Loadable.Map({
  loader: {
    One: () => import('./One'),
    Two: () => import('./Two'),
  },
});

Output

import Loadable from 'react-loadable';
import path from 'path';

const LoadableMyComponent = Loadable({
  loader: () => import('./MyComponent'),
  webpack: () => [require.resolveWeak('./MyComponent')],
  modules: [path.join(__dirname, './MyComponent')],
});

const LoadableComponents = Loadable.Map({
  loader: {
    One: () => import('./One'),
    Two: () => import('./Two'),
  },
  webpack: () => [require.resolveWeak('./One'), require.resolveWeak('./Two')],
  modules: [path.join(__dirname, './One'), path.join(__dirname, './Two')],
});

更多关于declaring modules.

Webpack Plugin

当服务端渲染的时候,为了send the right bundles down,你需要React Loadable Webpack plugin插件生成一个模块和打包文件有对应关系的JSON文件.

// webpack.config.js
import { ReactLoadablePlugin } from 'react-loadable/webpack';

export default {
  plugins: [
    new ReactLoadablePlugin({
      filename: './dist/react-loadable.json',
    }),
  ],
};

它将产生一个JSON文件(opts.filename),你可以引入这个模块和打包文件相对应的JSON文件

更多关于 mapping modules to bundles.

getBundles

通过react-loadable/webpack方法可以导出一个模块和打包文件的映射关系.

import { getBundles } from 'react-loadable/webpack';

let bundles = getBundles(stats, modules);

更多关于 mapping modules to bundles.

<h2>





<img src="http://thejameskyle.com/img/react-loadable-faq.png" alt="FAQ">




<small>FAQ</small>
</h2>

如果避免重复调用?

假定你的Loadable()会重复设置loading组件和delay参数,你可以用高阶组件 (HOC)去封装一层Loadable,并为它设置一些默认参数.

import Loadable from 'react-loadable';
import Loading from './my-loading-component';

export default function MyLoadable(opts) {
  return Loadable(Object.assign({
    loading: Loading,
    delay: 200,
    timeout: 10,
  }, opts));
};

这时候你只需要设置loader就可以使用它。

import MyLoadable from './MyLoadable';

const LoadableMyComponent = MyLoadable({
  loader: () => import('./MyComponent'),
});

export default class App extends React.Component {
  render() {
    return <LoadableMyComponent/>;
  }
}

不幸的是,如果你用HOC对Loadable封装一层会使react-loadable/babel失效,所以这时候你需要手动的添加必要参数(modules, webpack).

import MyLoadable from './MyLoadable';

const LoadableMyComponent = MyLoadable({
  loader: () => import('./MyComponent'),
  modules: ['./MyComponent'],
  webpack: () => [require.resolveWeak('./MyComponent')],
});

export default class App extends React.Component {
  render() {
    return <LoadableMyComponent/>;
  }
}

如果在服务端渲染中使用其他的比如.css或者.map资源

当你调用getBundles方法的时候,它会返回Javascript依赖的文件类型在你的Webpack配置中.

为得到这些,你需要手动的过滤一下文件类型,像这样:

let bundles = getBundles(stats, modules);

let styles = bundles.filter(bundle => bundle.file.endsWith('.css'));
let scripts = bundles.filter(bundle => bundle.file.endsWith('.js'));

res.send(`
  <!doctype html>
  <html lang="en">
    <head>
      ...
      ${styles.map(style => {
        return `<link href="/dist/${style.file}" rel="stylesheet"/>`
      }).join('\n')}
    </head>
    <body>
      <div id="app">${html}</div>
      <script src="/dist/main.js"></script>
      ${scripts.map(script => {
        return `<script src="/dist/${script.file}"></script>`
      }).join('\n')}
    </body>
  </html>
`);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车! React 组件代码...
    王饱饱阅读 2,419评论 1 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,429评论 25 707
  • 无意中看到zhangwnag大佬分享的webpack教程感觉受益匪浅,特此分享以备自己日后查看,也希望更多的人看到...
    小小字符阅读 8,140评论 7 35
  • 首先感恩本元跟本然两位大爱的老师,也感恩我们班主任丁丁老师,也感恩我的父母以及詹静老师,贝贝老师和所有关心我支持我...
    当下绽放阅读 785评论 0 0
  • //数据挖掘(六):预测 - kingzone的专栏 - 博客频道 - CSDN.NEThttp://blog.c...
    葡萄喃喃呓语阅读 342评论 0 0