redux篇

# hello

传统的状态管理

在jquery时代,往往把全局变量定义在代码的最前面,使每一个页面都能使用他

<body>
    <div>
        <button id="add">+ 1</button>
        <span id="number"></span>
        <button id="minus">- 1</button>
    </div>

    <script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
    <script>
        $(function () {

            // 变量 注释1
            const myState = { number: 1 }

            // 增加
            $("#add").click(() => {
                myState.number++; // 注释2
                render();
            })

            // 减少
            $("#minus").click(() => {
                myState.number--; // 注释2
                render();
            })

            // 渲染DOM
            const render = () => $("#number").html(myState.number);

            render();
        });
    </script>
</body>

在 React 中,我们把变量(注释1)称作状态(state)

而通过 React 基础的学习我们发现, React 的状态都是在组件内部(局部),当组件嵌套复杂时,很难管理状态,也就是说,很难实现全局变量

这个时候,市场上出现了很多优秀的解决方案,比如Flux,Redux,React-Redux,useContext+useReduce,dva等

Redux 是基于 Flux 的改良版,不仅仅服务于React,任何前端框架都可以使用

Redux 思路

把状态(上述代码注释1),操作(上述代码注释2)融合成一个容器,通过视图分发指令去更新状态,并通过订阅更新视图

状态:State

操作:Reduce

容器:Store

分发:dispatch

指令:action

订阅:subscribe

hello Redux

Redux 通过 createStore 创建一个容器,createStore 需要参数 Reduce , Reduce 有两个参数,状态和指令

Reduce 是一个纯函数,只能回返新状态,不更新原状态

dispatch 分发的必须是一个对象

分发后,会调用 Reduce,而且会把 dispatch 的参数会作为 Reduce 的第二个参数

<body>
    <div>
        <button id="add">+ 1</button>
        <span id="number"></span>
        <button id="minus">- 1</button>
    </div>

    <script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
    <script src="./redux.js"></script>
    <script>
        $(function () {

            // 状态
            const myState = { number: 1 }

            // 操作
            const Reduce = (state = myState, action) => {
                if (action.type == "增加") return { ...state, number: state.number + 1 }
                else if (action.type == "减少") return { ...state, number: state.number - 1 }
                else return state;
            }

            // 容器
            const Store = Redux.createStore(Reduce);

            // 订阅
            Store.subscribe(() => render());

            // 增加
            $("#add").click(() => Store.dispatch({ type: "增加" }));

            // 减少
            $("#minus").click(() => Store.dispatch({ type: "减少" }));

            // 渲染DOM
            const render = () => $("#number").html(Store.getState().number);

            render();
        });
    </script>
</body>

指令重写

指令可以带一个参数,约定俗称payload,而且type我们把它改成英文名显得更专业些

// 操作
const Reduce = (state = myState, action) => {
    if (action.type == "add") return { ...state, number: action.payload }
    else if (action.type == "minus") return { ...state, number: action.payload }
    else return state;
}

// 增加
$("#add").click(() => Store.dispatch({ type: "add", payload: Store.getState().number + 1 }));

// 减少
$("#minus").click(() => Store.dispatch({ type: "minus", payload: Store.getState().number - 1 }));

可以所有的指令用一个变量存储(大写表示常量),那么再使用的过程中,不容易出错,还可以复用

// 所有指令
const Actions = {
    ADD: "add",
    MINUS: "minus"
}

// 操作
const Reduce = (state = myState, action) => {
    if (action.type == Actions.ADD) return { ...state, number: action.payload }
    else if (action.type == Actions.MINUS) return { ...state, number: action.payload }
    else return state;
}

// 增加
$("#add").click(() => Store.dispatch({ type: Actions.ADD, payload: Store.getState().number + 1 }));

// 减少
$("#minus").click(() => Store.dispatch({ type: Actions.MINUS, payload: Store.getState().number - 1 }));

为了更加贴合管网(深层装X),我们可以在 dispatch 一个由 createAction 生成的指令

// 所有指令
const Actions = {
    ADD: "add",
    MINUS: "minus"
}

// 生成指令
const createAction = {
    ADD(payload) {
        return { type: Actions.ADD, payload }
    },
    MINUS(payload) {
        return { type: Actions.MINUS, payload }
    }
}

// 操作
const Reduce = (state = myState, action) => {
    if (action.type == Actions.ADD) return { ...state, number: action.payload }
    else if (action.type == Actions.MINUS) return { ...state, number: action.payload }
    else return state;
}

// 增加
$("#add").click(() => Store.dispatch(createAction.ADD(Store.getState().number + 1)));

// 减少
$("#minus").click(() => Store.dispatch(createAction.ADD(Store.getState().number - 1)));

再把 Reduce 的if判断改为更为高大上的 switch

OK ,全部改完后的代码如下

<body>
    <div>
        <button id="add">+ 1</button>
        <span id="number"></span>
        <button id="minus">- 1</button>
    </div>

    <script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script>
    <script src="./redux.js"></script>
    <script>
        $(function () {

            // 状态
            const myState = { number: 1 }

            // 所有指令
            const Actions = {
                ADD: "add",
                MINUS: "minus"
            }

            // 生成指令
            const createAction = {
                ADD(payload) {
                    return { type: Actions.ADD, payload }
                },
                MINUS(payload) {
                    return { type: Actions.MINUS, payload }
                }
            }

            // 操作
            const Reduce = (state = myState, action) => {
                switch (action.type) {
                    case Actions.ADD:
                        return { ...state, number: action.payload };
                    case Actions.MINUS:
                        return { ...state, number: action.payload };
                    default:
                        return state;
                }
            }

            // 容器
            const Store = Redux.createStore(Reduce);

            // 订阅
            Store.subscribe(() => render());

            // 增加
            $("#add").click(() => Store.dispatch(createAction.ADD(Store.getState().number + 1)));

            // 减少
            $("#minus").click(() => Store.dispatch(createAction.ADD(Store.getState().number - 1)));

            // 渲染DOM
            const render = () => $("#number").html(Store.getState().number);

            render();
        });
    </script>
</body>

再读 Redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理

我们把这句话分解一下:

状态(State)

容器(Store)

可预测(Action)

状态管理(Reduce)

# React 配合 Redux

安装Redux

yarn add Redux

容器

import { createStore } from "redux"

// 状态
const myState = { number: 1 }

// 所有指令
const Actions = {
    ADD: "add",
    MINUS: "minus"
}

// 生成指令
const createAction = {
    ADD(payload) {
        return { type: Actions.ADD, payload }
    },
    MINUS(payload) {
        return { type: Actions.MINUS, payload }
    }
}

// 操作
const Reduce = (state = myState, action) => {
    switch (action.type) {
        case Actions.ADD:
            return { ...state, number: action.payload };
        case Actions.MINUS:
            return { ...state, number: action.payload };
        default:
            return state;
    }
}

// 容器
const Store = createStore(Reduce);

export {
    createAction, Store
}

App组件

import React, { useState } from 'react';
import { Store, createAction } from "./Store/index"

function App() {

  const [number, setNumber] = useState(Store.getState().number);

  // 订阅状态更新
  Store.subscribe(() => setNumber(Store.getState().number));

  const changeNumber = (props) => {
    // 分发指令
    if (props == `add`) Store.dispatch(createAction.ADD(number + 1));
    else if (props == `minus`) Store.dispatch(createAction.MINUS(number - 1));
  }

  return (
    <div>
      <button onClick={() => changeNumber(`add`)}>+ 1</button>
      <span>{number}</span>
      <button onClick={() => changeNumber(`minus`)}>- 1</button>
    </div>
  )
}

export default App;

# 图解 React + Redux

图一

download.png

React 某个组件分发(Dispatch)一个指令更新Store

容器订阅(Subscribe)状态(State)更新后,重新渲染组件

图二

download (1).png

视图(View)把指令(Actions)分发(Dispatch)给容器(Store)

容器根据分发的指令可预测的管理(Redux)状态(State)

容器侦听(Subscibe)了状态(State)的变化,更新了视图(View)

# 取消订阅

每次在组件销毁的时候,必须取消订阅避免内存泄露

其实在组件销毁时,不仅仅要取消订阅,还要清空定时器,DOM侦听等副作用

import React, { useState, useEffect } from 'react';
import { Store, createAction } from "./Store/index"

function App() {

  const [number, setNumber] = useState(Store.getState().number);

  // 订阅状态更新
  const sub = Store.subscribe(() => setNumber(Store.getState().number));

  useEffect(() => {
    return () => {
      sub();
    }
  });

  const changeNumber = (props) => {
    // 分发指令
    if (props == `add`) Store.dispatch(createAction.ADD(number + 1));
    else if (props == `minus`) Store.dispatch(createAction.MINUS(number - 1));
  }

  return (
    <div>
      <button onClick={() => changeNumber(`add`)}>+ 1</button>
      <span>{number}</span>
      <button onClick={() => changeNumber(`minus`)}>- 1</button>
    </div>
  )
}

export default App;

# reduce

介绍

当页面非常庞大的时候,如果状态管理都写在一起非常不好维护,redux 有一个 combineReducers 可以把状态管理拆成小块,从而使开发维护更加方便

快速 demo

Store.js

import { createStore, combineReducers } from "redux"

// 状态
const NumberState = { number: 1 }
const UserState = [{ name: "张三", age: 12 }]

// 所有指令
const NumberActions = {
    ADD: "add",
    MINUS: "minus"
}
const UserActions = {
    INIT: "init",
    ADD: "add",
    UPDATE: "update"
}

// 生成指令
const createNumberAction = {
    ADD(payload) {
        return { type: NumberActions.ADD, payload }
    },
    MINUS(payload) {
        return { type: NumberActions.MINUS, payload }
    }
}

const createUserAction = {
    INIT(payload) {
        return { type: UserActions.INIT, payload }
    },
    ADD(payload) {
        return { type: UserActions.ADD, payload }
    },
    UPDATE(payload) {
        return { type: UserActions.UPDATE, payload }
    }
}

// 操作
const NumberReduce = (state = NumberState, action) => {
    switch (action.type) {
        case NumberActions.ADD:
            return { ...state, number: action.payload };
        case NumberActions.MINUS:
            return { ...state, number: action.payload };
        default:
            return state;
    }
}

const UserReduce = (state = UserState, action) => {
    switch (action.type) {
        case NumberActions.ADD:
            return { ...state, number: action.payload };
        case NumberActions.MINUS:
            return { ...state, number: action.payload };
        default:
            return state;
    }
}

const reduce = combineReducers({
    Num: NumberReduce,
    User: UserReduce
});

// 容器
const Store = createStore(reduce);

export {
    createNumberAction, Store
}

action , createAction, Reduce 都要写两份,最后合成一份Store,并在 combineReducers 时,给不同的 redux 加上名称,方便 Store 调用

APP.js

import React, { useState, useEffect } from 'react';
import { Store, createNumberAction } from "./Store/index"

function App() {

  const [number, setNumber] = useState(Store.getState().Num.number);

  // 订阅状态更新
  const sub = Store.subscribe(() => setNumber(Store.getState().Num.number));

  useEffect(() => {
    return () => {
      sub();
    }
  });

  const changeNumber = (props) => {
    // 分发指令
    if (props == `add`) Store.dispatch(createNumberAction.ADD(number + 1));
    else if (props == `minus`) Store.dispatch(createNumberAction.MINUS(number - 1));
  }

  return (
    <div>
      <button onClick={() => changeNumber(`add`)}>+ 1</button>
      <span>{number}</span>
      <button onClick={() => changeNumber(`minus`)}>- 1</button>
    </div>
  )
}

export default App;

调用的时候,需要用 Store 调用 combineReducers 合并时取的别名

Redux 文件拆分

可以把不同的Redux拆分文件单独提出

├── Store                   # 状态管理容器
│   ├── numberReduce.js     # 数字reduce
│   ├── userReduce.js       # 用户reduce
│   └── index.js
├── App.js                  # 根组件

./Store/numberReduce.js

// 状态
const NumberState = { number: 1 }

// 所有指令
const NumberActions = {
    ADD: "add",
    MINUS: "minus"
}

// 生成指令
const createNumberAction = {
    ADD(payload) {
        return { type: NumberActions.ADD, payload }
    },
    MINUS(payload) {
        return { type: NumberActions.MINUS, payload }
    }
}

// 操作
const NumberReduce = (state = NumberState, action) => {
    switch (action.type) {
        case NumberActions.ADD:
            return { ...state, number: action.payload };
        case NumberActions.MINUS:
            return { ...state, number: action.payload };
        default:
            return state;
    }
}

export { createNumberAction, NumberReduce }
export default NumberReduce;

./Store/userReduce.js

// 状态
const UserState = [{ name: "张三", age: 12 }]

// 所有指令
const UserActions = {
    INIT: "init",
    ADD: "add",
    UPDATE: "update"
}

// 生成指令
const createUserAction = {
    INIT(payload) {
        return { type: UserActions.INIT, payload }
    },
    ADD(payload) {
        return { type: UserActions.ADD, payload }
    },
    UPDATE(payload) {
        return { type: UserActions.UPDATE, payload }
    }
}

// 操作
const UserReduce = (state = UserState, action) => {
    return UserState;
}
export { createUserAction, UserReduce }
export default UserReduce;

./Store/index.js

import { createStore, combineReducers } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"

// 合并
const reduce = combineReducers({
    Num: NumberReduce,
    User: UserReduce
});

// 容器
export default createStore(reduce);

./App.js

import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import { createNumberAction } from "./Store/numberReduce"

function App() {

  const [number, setNumber] = useState(Store.getState().Num.number);

  // 订阅状态更新
  const sub = Store.subscribe(() => setNumber(Store.getState().Num.number));

  useEffect(() => {
    return () => {
      sub();
    }
  });

  const changeNumber = (props) => {
    // 分发指令
    if (props == `add`) Store.dispatch(createNumberAction.ADD(number + 1));
    else if (props == `minus`) Store.dispatch(createNumberAction.MINUS(number - 1));
  }

  return (
    <div>
      <button onClick={() => changeNumber(`add`)}>+ 1</button>
      <span>{number}</span>
      <button onClick={() => changeNumber(`minus`)}>- 1</button>
    </div>
  )
}

export default App;

这是以功能模块拆分,还可以以 Store 拆分

├── Store                   # 状态管理容器
│   ├── action
│   │   ├── numberAction
│   │   ├── userAction
│   ├── reduce
│   │   ├── numberReduce
│   │   ├── userReduce
│   ├── state
│   │   ├── numberState
│   │   ├── userState
│   └── index.js
├── App.js                  # 根组件

# 配合请求更新状态

用 json-server 模拟数据

安装

$ npm install -g json-server

数据模拟

{
    "User": [
        {
            "id": "001",
            "name": "Sherry",
            "age": 24
        },
        {
            "id": "002",
            "name": "Addy",
            "age": 24
        },
        {
            "id": "003",
            "name": "Jack",
            "age": 24
        },
        {
            "id": "004",
            "name": "Rebeca",
            "age": 24
        }
    ]
}

启动服务

$ json-server --watch --port 3001 db.json

请求数据更新状态

为了方便测试,把 numberReducer 的功能去掉

App.js

import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import axios from "axios"
import { createUserAction } from "./Store/userReduce"

function App() {

  const [user, setUser] = useState(Store.getState().User);

  const sub = Store.subscribe(() => setUser(Store.getState().User));

  useEffect(() => {

    axios.get(`http://localhost:3001/User`).then(res => {
      Store.dispatch(createUserAction.INIT(res.data));
    })

    return () => {
      sub();
    }
  }, []);

  return (
    <>
      {user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
    </>
  )
}

export default App;

userReduce 简化成一个指令

// 所有指令
const UserActions = {
    INIT: "init"
}

// 生成指令
const createUserAction = {
    INIT(payload) {
        return { type: UserActions.INIT, payload }
    }
}

// 操作
const UserReduce = (state = [], action) => {
    if (action.type == UserActions.INIT) {
        return action.payload;
    }
    return state;
}
export { createUserAction, UserReduce }
export default UserReduce;

此时就能配合请求更新状态了。如果需要指令异步执行,可以使用redux中间件

中间件

redux 中间件是在 action 和 reduce环节中,添加的功能

使用例子

import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import thunk from "redux-thunk"

// 合并
const reduce = combineReducers({
    Num: NumberReduce,
    User: UserReduce
});

// 容器
export default createStore(reduce, applyMiddleware(thunk));

# redux-thunk

介绍

redux-thunk 属于 redux 的中间件,作用是可以让 dispatch 返回的是一个函数,那么就可以把异步的写法搬到 action 中

App.js

import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import axios from "axios"
import { createUserAction } from "./Store/userReduce"

function App() {

  const [user, setUser] = useState(Store.getState().User);

  const sub = Store.subscribe(() => setUser(Store.getState().User));

  useEffect(() => {

    Store.dispatch(createUserAction.ASYNCINIT());

    return () => {
      sub();
    }
  }, []);

  return (
    <>
      {user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
    </>
  )
}

export default App;

Reduce.js

import axios from "axios"

// 所有指令
const UserActions = {
    ASYNCINIT: "init"
}

// 生成指令
const createUserAction = {
    ASYNCINIT() {
        return (dispatch, getState) => {
            axios.get(`http://localhost:3001/User`).then(res => {
                dispatch({ type: UserActions.ASYNCINIT, payload: res.data });
            })
        }
    }
}

// 操作
const UserReduce = (state = [], action) => {
    if (action.type == UserActions.ASYNCINIT) {
        return action.payload;
    }
    return state;
}
export { createUserAction, UserReduce }
export default UserReduce;

index.js

import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import thunk from "redux-thunk"

// 合并
const reduce = combineReducers({
    Num: NumberReduce,
    User: UserReduce
});

// 容器
export default createStore(reduce, applyMiddleware(thunk));

# redux-saga

介绍

redux-saga 和 redux-thunk 一样,都是 redux 中,名气非常高的中间件

dva 还是基于 redux-saga 的一个数据流方案

思路

容器分发一个指令,用 redux-saga 去侦听(其实reduce也侦听到了,只不过不处理事件),侦听到后,做一系列的异步或同步操作,再用put发送一个指令,再由reduce侦听并更新状态

注意:redux-saga 发送的指令不能和侦听的指令相同,否则会一直执行

App.js

import React, { useState, useEffect } from 'react';
import Store from "./Store/index"
import { createUserAction } from "./Store/userReduce"

function App() {

  const [user, setUser] = useState(Store.getState().User);
  const sub = Store.subscribe(() => setUser(Store.getState().User));

  useEffect(() => {
    Store.dispatch(createUserAction.ASYNCINIT);

    return () => {
      sub();
    }
  }, []);

  return (
    <>
      {user.map(item => <p key={item.id}>{item.name} --- {item.age}</p>)}
    </>
  )
}

export default App;

userReduce

// 所有指令
const UserActions = {
    ASYNCINIT: "asyncinit",
    INIT: "init"
}

// 生成指令
const createUserAction = {
    ASYNCINIT: {
        type: UserActions.ASYNCINIT
    },
    INIT(payload) {
        return {
            type: UserActions.INIT,
            payload: payload
        }
    }
}

// 操作
const UserReduce = (state = [], action) => {
    console.log(action);

    if (action.type == UserActions.INIT) {
        return action.payload || state;
    }
    return state;
}

export { createUserAction, UserReduce, UserActions }
export default UserReduce;

Store

import { createStore, combineReducers, applyMiddleware } from "redux"
import NumberReduce from "./numberReduce"
import UserReduce from "./userReduce"
import reduxSaga from "redux-saga"
import sagas from "./sagas"

const sage = reduxSaga();

// 合并
const reduce = combineReducers({
    Num: NumberReduce,
    User: UserReduce
});

// 容器
export default createStore(reduce, applyMiddleware(sage));

sage.run(sagas);

sagas

import { takeEvery, put } from "redux-saga/effects"
import { UserActions, createUserAction } from "./userReduce"
import axios from "axios"

function* mySaga() {
    yield takeEvery(UserActions.ASYNCINIT, getList);
}

function* getList() {
    const res = yield axios.get("http://localhost:3001/User");
    yield put(createUserAction.INIT(res.data));
}

export default mySaga;

# redux-devtools-extension 调试工具

chrome 插件

地址: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd

配置

$ npm install --save-dev redux-devtools-extension

配置

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(reducer, composeWithDevTools(
  applyMiddleware(...middleware),
  // other store enhancers if any
));

上一篇:React Hook篇

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