1-路由-react-router-dom

React.js - 路由

[TOC]

路由

当应用变得复杂的时候,就需要分块的进行处理和展示,传统模式下,我们是把整个应用分成了多个页面,然后通过 <u>URL</u> 进行连接。但是这种方式也有一些问题,每次切换页面都需要重新发送所有请求和渲染整个页面,不止性能上会有影响,同时也会导致整个 <u>JavaScript</u> 重新执行,丢失状态。

SPA

<u>Single Page Application</u> : 单页面应用,整个应用只加载一个页面(入口页面),后续在与用户的交互过程中,通过 <u>DOM</u> 操作在这个单页上动态生成结构和内容

优点:

  • 有更好的用户体验(减少请求和渲染和页面跳转产生的等待与空白),页面切换快
  • 重前端,数据和页面内容由异步请求(AJAX)+ DOM 操作来完成,前端处理更多的业务逻辑

缺点:

  • 首屏处理慢

  • 不利于 <u>SEO</u>

<u>SPA</u> 的页面切换机制

虽然 <u>SPA</u> 的内容都是在一个页面通过 <u>JavaScript</u> 动态处理的,但是还是需要根据需求在不同的情况下分内容展示,如果仅仅只是依靠 <u>JavaScript</u> 内部机制去判断,逻辑会变得过于复杂,通过把 <u>JavaScript</u> 与 <u>URL</u> 进行结合的方式: <u>JavaScript</u> 根据 <u>URL</u> 的变化,来处理不同的逻辑,交互过程中只需要改变 <u>URL</u> 即可。这样把不同 <u>URL</u> 与 <u>JavaScript</u> 对应的逻辑进行关联的方式就是路由,其本质上与后端路由的思想是一样的。

后端路由与前端路由

后端路由与前端路由在本质上是类似的,都是把不同的 <u>URL</u> 与某个操作进行关联绑定,得到不一样的结果

后端路由

通过 <u>HTTP</u> 把请求发送到后端服务器,后端服务器接收到请求以后根据不同的请求 <u>URL</u> 来执行不同的操作,返回处理好的数据(JSON、HTML、JS 代码、CSS、图像……)

  • 需要发送 <u>HTTP</u> 请求
  • 业务逻辑由后端处理,返回处理后的结果给前端(浏览器)

前端路由

前端路由只是改变了 <u>URL</u> 或 <u>URL</u> 中的某一部分,但一定不会直接发送请求,可以认为仅仅只是改变了浏览器地址栏上的 <u>URL</u> 而已,<u>JavaScript</u> 通过各种手段处理这种 <u>URL</u> 的变化,然后通过 <u>DOM</u> 操作动态的改变当前页面的结构

  • <u>URL</u> 的变化不会直接发送 <u>HTTP</u> 请求
  • 业务逻辑由前端 <u>JavaScript</u> 来完成

目前前端路由主要的模式:

  • 基于 <u>URL Hash</u> 的路由
  • 基于 <u>HTML5 History API</u> 的路由
URL Hash

通过通过修改 <u>URL</u> 的 <u>Hash</u> 值来改变 <u>URL</u>,<u>Hash</u> 的变化是不会发送请求的,同时 <u>JavaScript</u> 通过监听 <u>hashchange</u> 事件来动态处理逻辑和页面渲染

优点

兼容性好

缺点

<u>URL</u> 不美观,<u>SEO</u> 不友好

HTML5 History API

封装一个函数,该函数通过 <u>HTML5 History</u> 提供的 <u>API</u> 来动态改变 <u>URL</u> ,这种方式也不会发送请求,然后同时根据要改变的目标 <u>URL</u> 来处理逻辑和页面渲染

<u>URL Hash</u> 模式类似 <u>Vue</u> 中的数据拦截机制

<u>HTML5 History API</u> 模式类似 <u>React.js</u> 中的 <u>setState</u>

React.js 中的路由

<u>React.js</u> 路由的基本思想就是,把不同的 <u>URL</u> 与 指定的某些 <u>React.js</u> 组件进行关联,不同的 <u>URL</u> 渲染显示不同的组件,其它框架(如:vue、angular) 都是一样的思想

React Router

理解了路由基本机制以后,也不需要重复造轮子,我们可以直接使用 <u>React Router</u> 库

https://reacttraining.com/react-router/

<u>React Router</u> 提供了多种不同环境下的路由库

  • web
  • native

基于 <u>Web</u> 的 <u>React Router</u>

基于 <u>web</u> 的 <u>React Router</u> 为:<u>react-router-dom</u>

安装

npm i -S react-router-dom

概览

<u>react-router-dom</u> 的核心是组件,它提供了一系列的组件,如:

  • <u>Router</u> 组件
  • <u>BrowserRouter</u> 组件
  • <u>HashRouter</u> 组件
  • <u>Route</u> 组件
  • <u>Link</u> 组件
  • <u>NavLink</u> 组件
  • <u>Switch</u> 组件
  • <u>Redirect</u> 组件

以及其它一些 <u>API</u>,来完成路由的功能

基础

应用场景(一)

假设当前应用有两个页面,对应的 <u>URL</u> 与 页面关系

/               :   首页
/about  :   关于我们

<u>Router</u> 组件

如果我们希望页面中某个部分的内容需要根据 <u>URL</u> 来动态显示,需要用到 <u>Router</u> 组件 ,该组件是一个容器组件,只需要用它包裹 <u>URL</u> 对应的根组件即可

<u>react-router-dom</u> 为我们提供了几个基于不同模式的 <u>router</u> 子组件

  • <u>BrowserRouter</u> 组件
  • <u>HashRouter</u> 组件
  • <u>MemoryRouter</u> 组件
  • <u>NativeRouter</u> 组件
  • <u>StaticRouter</u> 组件
<u>BrowserRouter</u> 组件

基于 <u>HTML5 History API</u> 的路由组件

<u>HashRouter</u> 组件

基于 <u>URL Hash</u> 的路由组件

css

ul {
    margin: 0;
    padding: 0;
}

li {
    list-style: none;
}

.item-list li {
    padding: 10px;
    display: flex;
    justify-content: space-between;
    height: 30px;
    line-height: 30px;
    border-bottom: 1px dotted #333;
}
.item-list li.head {
    font-weight: bold;
}
.item-list li span {
    min-width: 200px;
}

.pagination {
    margin: 10px;
}
.pagination a {
    margin: 0 5px;
    display: inline-block;
    width: 30px;
    height: 30px;
    background-color: #f4f4f5;
    line-height: 30px;
    text-align: center;
    cursor: pointer;
    color: #606266;
    text-decoration: none;
}
.pagination a:hover {
    color: #409eff;
}
.pagination a.active {
    background-color: #409eff;
    color: #fff;
    cursor: text;
}
.pagination .goto {
    margin: 0 5px;
    box-sizing: border-box;
    width: 80px;
    height: 30px;
    border: 1px solid #dcdfe6;
    outline: none;
    text-align: center;
    vertical-align: top;
}

App.js

import React from 'react';

import {BrowserRouter as Router} from 'react-router-dom';
// import {HashRouter as Router} from 'react-router-dom';

import BaseApp from './BaseApp/Index';

function App() {
    return (
        <div>
            <h1>React-Router</h1>
            <hr/>

            <Router>
                <BaseApp />
            </Router>
        </div>
    );
}

export default App;
import {BrowserRouter as Router} from 'react-router-dom'

导入 BrowserRouter 组件,并命名 Router 别名,方便使用

<Router>
      <BaseApp />
</Router>

只对页面中的 BaseApp 组件使用路由

./BaseApp/Index.js

import React from 'react';
import {Route, Link} from 'react-router-dom';

import Home from './Home';
import About from './About';

export default class BaseApp extends React.Component {

    render() {
        return(
            <div>
                <h2>路由基础使用</h2>

                <nav>
                    <Link to="/">Home</Link>
                    <span> | </span>
                    <Link to="/about">About</Link>
                </nav>
                <br/>

                <Route exact path='/' component={Home} />
                <Route path='/about' component={About} />
            </div>
        );
    }

}
import {Route} from 'react-router-dom';

<u>Route</u> 组件

注意这个组件是没有字母 <u>r</u> 的,通过该组件来设置应用单个路由信息,<u>Route</u> 组件所在的区域就是就是当 <u>URL</u> 与当前 <u>Route</u> 设置的 <u>path</u> 属性匹配的时候,后面 <u>component</u> 将要显示的区域

<Route path='/' component={Home} />

当 <u>URL</u> 为:'/' 的时候,组件 <u>Home</u> 将显示在这里

exact

<u>exact</u> 属性表示路由使用 <u>精确匹配模式</u>,非 <u>exact</u> 模式下 '/' 匹配所有以 '/' 开头的路由

<u>Link</u> 组件

<u>Link</u> 组件用来处理 <u>a 链接</u> 类似的功能(它会在页面中生成一个 <u>a</u> 标签),但设置这里需要注意的,<u>react-router-dom</u> 拦截了实际 <u>a</u> 标签的默认动作,然后根据所有使用的路由模式(Hash 或者 HTML5)来进行处理,改变了 <u>URL</u>,但不会发生请求,同时根据 <u>Route</u> 中的设置把对应的组件显示在指定的位置

<u>to</u> 属性

<u>to</u> 属性类似 <u>a</u> 标签中的 <u>href</u>

应用场景(二)

最近生活拮据,想在应用中卖点东西补贴一下家用

状态提升

前面讲组件中提到过组件状态提升,如果多个组件需要共享一个状态,那么就讲状态提升到使用该状态的共同父级组件上

./BaseApp/Index.js

this.state = {
  items: [
    {
      id: 1,
      name: 'iPhone XR',
      price: 542500
    },
    {
      id: 2,
      name: 'Apple iPad Air 3',
      price: 377700
    },
    {
      id: 3,
      name: 'Macbook Pro 15.4',
      price: 1949900
    },
    {
      id: 4,
      name: 'Apple iMac',
      price: 1629900
    },
    {
      id: 5,
      name: 'Apple Magic Mouse',
      price: 72900
    },
    {
      id: 6,
      name: 'Apple Watch Series 4',
      price: 599900
    }
  ]
}

传递 props

<Route exact path='/' component={Home}

如果 <u>Route</u> 使用的是 <u>component</u> 来指定组件,那么不能使用 <u>props</u>

<u>Route</u> : <u>render</u>
<Route exact path='/' render={() => <Home items={this.state.items} />} />

通过 <u>render</u> 属性来指定渲染函数,<u>render</u> 属性值是一个函数,当路由匹配的时候指定该函数进行渲染

渲染列表

Home.js

import React from 'react';

import {Link} from 'react-router-dom';

export default class Home extends React.Component {

    render() {
        let {items} = this.props;

        return (
            <div>
                <h2>商品列表</h2>
                <ul className="item-list">
                    <li className="head">
                        <span>名称</span>
                        <span>价格</span>
                    </li>
                    {
                        items.map(item=>(
                            <li key={item.id}>
                                <span>
                                    <Link to={'/Item/' + item.id}>{item.name}</Link>
                                </span>
                                <span>¥ {(item.price / 100).toFixed(2)}</span>
                            </li>
                        ))
                    }
                </ul>
            </div>
        );
    }

}
<u>params</u>

在路由中,<u>params</u> 指的是 <u>path</u> 部分可变的部分,在这里我们需要定义一个新的路由用来展示商品的详情,我们希望通过 <u>/item/1</u> 、<u>/item/2</u>、<u>/item/……</u> 来访问对应 <u>id</u> 的商品,那么后面的数字部分就是可变的 - <u>params</u>,我们也称这样的路由为:动态路由

动态路由

为了能给处理上面的动态路由地址的访问,我们需要为 <u>Route</u> 组件配置特殊的 <u>path</u>

<Route path='/item/:id' component={Item} />
<u>path-to-regexp</u>

<u>path-to-regexp</u> 是一个专门用来处理 <u>URL</u> 的库,它用来了一种类似正则的字符串表示法

: 表示后面的部分为可变,: 后面的单词为匹配后的内容存储的名称,如:<u>/item/1</u> ,就是 id=1

默认情况下,它为我们提供了几个常用的字符表示:<u>*</u>、<u>?</u>、<u>+</u>

但是我们可以通过 () 来使用正则

<Route path='/item/:id(\d+)' component={Item} />

上面表示 <u>/item/</u> 后面只能是数字

<u>Route</u> : <u>props</u>

当动态路由匹配以后,我们可以通过对应组件的 <u>props</u> 属性来访问当前路由匹配信息

路由组件

如果一个组件是通过路由直接访问的,我们称为:<u>路由组件 - 视图组件</u>,其它的类型,根据具体功能可以称为:<u>业务组件</u>,<u>UI 组件</u>,<u>容器组件</u>,……

如果一个组件是路由组件,那么组件的 <u>props</u> 属性就会自动添加几个与路由有关的几个属性

  • history : 对象,提供了一些用于操作路由的方法,类似原生 <u>JavaScript</u> 中的 <u>history</u> 对象
  • location : 对象,通过它可以获取当前路由 <u>URL</u> 信息,类似原生 <u>JavaScript</u> 中的 <u>history.location</u> 对象
  • match : 对象,当前路由解析后的 <u>URL</u> 信息
<u>match</u> : <u>params</u>
this.props.match.params.id

<span style="color: red">注意:非路由组件是没有路由数据的</span>

如果一个组件既要接收手动传入的 <u>props</u>,又想接收路由信息

<Route path='/item/:id(\d+)' render={props => <Item {...props} items={this.state.items} />} />

商品详情

render() {
  let items = this.props.items;
  let id = Number(this.props.match.params.id) || 0;
  let item = items.find(item => item.id === id);

  return item ? (
    <div>
      <h2>商品详情 - {item.name}</h2>
      <dt>ID</dt>
      <dd>{item.id}</dd>
      <dt>名称</dt>
      <dd>{item.name}</dd>
      <dt>价格</dt>
      <dd>¥ {(item.price / 100).toFixed(2)}</dd>
    </div>
  ) : <div>不存在该商品!</div>;
}

应用场景(三)

我想增加一个功能,用户可以按照商品价格的高低进行选择展示

通过 <u>JavaScript</u> 实现排序切换

Home.js

...
this.state = {
    sort: 'desc'
  
    this.changeSort = this.changeSort.bind(this);
}
...
changeSort({target: {value: sort}}) {
    this.setState({sort});
}
...
render() {
    let {items} = this.props;
    let {sort} = this.state;
    items = items.sort((a, b) => sort === 'asc'  ? a.price - b.price : b.price - a.price);
    return(
        ...
        <select value={sort} onChange={this.changeSort}>
          <option value="desc">从高到低</option>
          <option value="asc">从低到高</option>
        </select>
        ...
    )
}

问题:刷新页面、分享 URL 都会丢失状态

通过路由实现排序切换

queryString

通常我们把 <u>URL</u> ? 后面的内容称为 <u>queryString</u>,在 <u>React.js</u> 中,我们可以通过 this.props.location.search 来获取,它的值是字符串,格式为:<u>?k1=v1&k2=v2</u>,为了方便操作,我们把它转成对象形式

URLSearchParams

在原生 <u>JJavaScript</u> 中内置了一个 <u>URLSearchParams</u> 的类,我们通过它可以很方便的操作 <u>queryString</u>

let {location: {search}} = this.props;
let qs = new URLSearchParams(search);
let sort = qs.get('sort');

扩展

qs 库

https://www.npmjs.com/package/qs

安装
npm i -S qs
使用
import queryString from 'qs';

let qsTest = queryString.parse(search, {ignoreQueryPrefix: true});
let sort = qsTest.sort;
通过 <u>JavaScript</u> 编程的方式切换路由

除了使用 <u><Link></u> 组件像 <u>a</u> 一样点击跳转路由,我们还可以通过编程的方式(<u>JavaScript</u>) 来切换路由

let {history} = this.props;
<select defaultValue={sort} onChange={({target:{value}})=>{
    history.push('/?sort=' + value);
  }}>
  <option value="desc">从高到低</option>
  <option value="asc">从低到高</option>
</select>

应用场景(四)

现在,我想给页面顶部的导航加上高亮效果,用来标识当前页面。这个时候,我们就可以使用 <u>react-router-dom</u> 提供的 <u>NavLink</u> 组件来实现

<u>NavLink</u> 组件

<u>NavLink</u> 与 <u>Link</u> 类似,但是它提供了两个特殊属性用来处理页面导航

activeStyle

当当前 <u>URL</u> 与 <u>NavLink</u> 中的 <u>to</u> 匹配的时候,激活 <u>activeStyle</u> 中的样式

activeClassName

与 <u>activeStyle</u> 类似,但是激活的是 <u>className</u>

isActive

默认情况下,匹配的是 <u>URL</u> 与 <u>to</u> 的设置,通过 <u>isActive</u> 可以自定义激活逻辑,<u>isActive</u> 是一个函数,返回布尔值

index.js

<NavLink to="/" activeStyle={{color:'red'}} isActive={(match, location) => {
    return match || location.pathname.startsWith('/item')
}} exact>Home</NavLink>
<span> | </span>
<NavLink to="/about" activeStyle={{color:'red'}} exact>About</NavLink>
exact

精确匹配

应用场景(五)

当用户访问不存在的路由,我们需要提供一个反馈页面,也就是 404

index.js

<Route exact path='/' render={props => <Home {...props} items={this.state.items} />} />
<Route path='/about' component={About} />
<Route path='/item/:id(\d+)' render={props => <Item {...props} items={this.state.items} />} />
{/*NotFound*/}
<Route component={NotFound} />

NotFound.js

import React from 'react';

export default function NotFound() {
    return (
        <div>
            Not Found
        </div>
    );
}

默认情况下,<u>React.js</u> 会渲染所有与之匹配的路由组件,上面的案例中,当我们访问任意路由(如:/about),也会匹配 <u>NotFound</u> 路由组件渲染

<u>Switch</u> 组件

该组件只会渲染首个被匹配的组件

<Switch>
    <Route exact path='/' render={props => <Home {...props} items={this.state.items} />} />
    <Route path='/about' component={About} />
    <Route path='/item/:id(\d+)' render={props => <Item {...props} items={this.state.items} />} />
    {/*NotFound*/}
    <Route component={NotFound} />
</Switch>

应用场景(六)

现在,我们要给用户增加一个购物车的功能

index.js

// 添加一个连接
<NavLink to="/cart" activeStyle={{color:'red'}} exact>Cart</NavLink>

Cart.js

// 购物车组件
import React from 'react';

export default class Cart extends React.Component {

    render() {
        return(
            <div>
                购物车
            </div>
        );
    }

}

我们还要配一个用户系统,比如:注册登录啥的

index.js

// 模拟一个添加登录用户状态的 state
this.state = {
    users: [
      {
        id: 1,
        username: 'baoge',
        password: '123'
      },
      {
        id: 2,
        username: 'MT',
        password: '123'
      },
      {
        id: 3,
        username: 'dahai',
        password: '123'
      },
      {
        id: 4,
        username: 'zMouse',
        password: '123'
      }
    ],
    userInfo: {
        id: 0,
        username: ''
    },
    ...
}
  
// 添加一个登录链接
{
    this.state.userInfo.id > 0 ? (
            <span>{this.state.userInfo.username}</span>
    ) : (
            <NavLink to='/login' activeStyle={{color:'red'}} exact>Login</NavLink>
    )
}

<u>Redirect</u> 组件

未登录用户是不能访问购物车的,所以,我们需要在访问购物车的时候加入用户权限验证(鉴权),如果没有登录则跳转到登录

index.js

<Route path='/cart' render={props => {
    if (this.state.userInfo.uid > 0) {
            return <Cart />;
    } else {
            // return <Login />;
            return <Redirect to='/login' />;
    }
}}  />
<u>to</u>

设置跳转的 <u>URL</u>

登录代码

// index.js
login({username, password}) {
  return new Promise( (resolve, reject) => {
    if (!username || !password) {
      reject('请输入用户名和密码');
    }

    let user = this.state.users.find(user => user.username === username && user.password === password);
    if ( !user ) {
      reject('用户不存在或者密码错误');
    }

    this.setState({
      userInfo: {
        id: user.id,
        username: user.username
      }
    });

    resolve('登录成功');
  } );
}
...
<Route path='/login' component={props => {
    return <Login {...props} onLogin={this.login.bind(this)} />;
}} />

// login.js
import React from 'react';

export default class Login extends React.Component {

    constructor(...props) {
        super(...props);

        this.login = this.login.bind(this);

        this.usernameRef = React.createRef();
        this.passwordRef = React.createRef();
    }

    login() {
        let {onLogin, history: {push}} = this.props;

        if (typeof onLogin === 'function') {
            onLogin({
                username: this.usernameRef.current.value,
                password: this.passwordRef.current.value
            }).then(msg=>{
                alert(msg);
                push('/');
            }).catch(e=>alert(e));
        } 
    }

    render() {
        return(
            <div>
                <p>
                    用户名:<input type="text" ref={this.usernameRef} />
                </p>
                <p>
                    密码:<input type="password" ref={this.passwordRef} />
                </p>
                <p>
                    <button onClick={this.login}>登录</button>
                </p>
            </div>
        );
    }

}

应用场景(七)

我突然发现卖东西比做码农要赚钱(主要是能生发),所以加大产品投入量,期待有一天,我的头发要宝哥多,然后推荐给莫莫

随着产品的增多,那么多的数据就不可能一次性全部展示出来,所以我们为商品的展示添加一个分页功能

// Pagination.js
import React from 'react';
import PropTypes from 'prop-types';

import {withRouter, Link} from 'react-router-dom';

class Pagination extends React.Component {

    static defaultProps = {
        pages: 1,
        page: 1
    }

    static propTypes = {
        pages: PropTypes.number,
        page: PropTypes.number
    }

    render() {

        let {pages, page, history: {push}} = this.props;
        // console.log(this.props);

        return (
            <div className="pagination">
                {
                    (new Array(pages)).fill('').map((v, i) => {`
                        return (
                            <Link 
                                key={++i}
                                className={i === page ? 'active' : ''}
                                to={'/'+i}
                            >
                                {i}
                            </Link>
                        );
                    })
                }
                前往
                <input type="text" className="goto" onKeyDown={({target:{value}})=>{
                    if (value !== '') {
                        push('/' + value);
                    }
                }} />
                页
            </div>
        );
    }

}

export default withRouter(Pagination);

<u>withRouter</u> 组件

如果一个组件不是路由绑定组件,那么该组件的 <u>props</u> 中是没有路由相关对象的,虽然我们可以通过传参的方式传入,但是如果结构复杂,这样做会特别的繁琐。幸好,我们可以通过 <u>withRouter</u> 方法来注入路由对象

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

推荐阅读更多精彩内容