基本语法

项目demo: https://github.com/Aluka-w/ReactBase

React学习

项目开始

  1. React Fiber: 16之后的版本, 对核心算法的一次重新实现

  2. React(响应) 和 Vue 的选择:

     (1) React 更加灵活, 适合逻辑复杂的场景
     (2) Vue Api 更加的丰富
    
  3. 脚手架 create-react-app '项目名'

     (1) 默认使用 yarn 
     (2) yarn start 启动项目
    
  4. index.js 里面的 registerServiceWorker() serviceWorker(涉及到web app)

    1. PWA 概念
      参考: https://blog.csdn.net/qq_19238139/article/details/77531191

      (1) 概念: PWA progressive web application (写网页的形式写APP的应用),
      (2) 写好的网页上线到支持https 的服务器上, 那么有网情况下加载完的页面, 突然没网的时候也会自动缓存上次的内容
      (3) PWA 可以添加在手机的主屏幕上, 通过网络应用程序 Manifest file 提供类似于 APP 的使用体验

    2. PWA 关键技术

       (1) Service Worker(服务工程): 离线缓存文件, 相当于代理服务器
       (2) Manifest    (应用清单)  Manifest.json 文件            (3)  Push Notification (推送通知)
      
  5. App.test.js 自动化测试文件

  6. manifest.json 文件

         (1)  能够将你浏览的网页添加到你的手机屏幕上
        (2) 在 Android 上能够全屏启动,不显示地址栏 ( 由于 Iphone 手机的浏览器是 Safari ,所以不支持哦)
        (3) 控制屏幕 横屏 / 竖屏 展示
        (4) 定义启动画面
        (5) 可以设置你的应用启动是从主屏幕启动还是从 URL 启动
        (6) 可以设置你添加屏幕上的应用程序图标、名字、图标大小
    

React的基础

  1. JSX 语法 ('react' 模块编译, 使用jsx必须引入react)

     (1) js 文件中使用 html标签
     (2) 组件声明必须大写字母开头, 原始html标签是小写
     (3) 必须包含根元素
     
     (4) JSX细节
     
          1. 注释: {/*注释*/}
    
          2. 样式: 外联, 内嵌
                     类名: className
                     
     (5) dangerouslySetInnerHtml = {{__html: item}} : 类比Vue的 v-html  可能存在 xss攻击
     
     (6) label标签: 扩大input标签的可点击范围
     
             用法:     <label htmlFor="insertArea">输入内容</label>  ==> 原生的for 改成 htmlFor
                         <input id="insertArea" /> 
                         点击label, input聚焦
     
     (7) setState, 推荐下面的, 性能上的问题
     
         注意: setState 是异步函数, 第二个参数是, 异步之后的回调
     
         const value = e.target.value    // 下面直接取 e.target.value 可能取不到, 所以先存起来
         
         this.setState((pre) => ({
             inputVal: value
             list: [...pre.list, pre.inputVal]
         }), () => {
             异步回调
         })
         
     (8) 数据驱动型: '{}' 代表变量
     
     (9) 展开运算符: [...this.state.lis] 代表 拿到原来的数组所有项生成数组, 但是原来的数组不会改变
     
     (10) Fragment jsx标签, 只是当做包裹, 不会真是的反应在dom中, 也就不会影响布局, react 16 之后的
    
  2. ToDoList 组件

     (1) Fragment 
             import { Component, Fragment } from 'react' 中,  Fragment 只做符合jsx的根元素, 但不影响布局
     (2) constructor 构造器类中的内容最先被执行
     
     (3) 实现新增删除功能:
             新增: list: [... this.statte.list, '新增的数据']    ==> 展开运算符, 会先继承原来的, 再加上新增的组成
                     新数组, 给到list
             删除: 
    

React设计思想

  1. 编程方式:

     (1) 命令式编程: 确切的操作DOM, 以前JQ
     (2) 声明式编程: 数据驱动型, 面向数据
    
  2. 可以与其他框架共存: 所有关于React的部分, 都是在rootDOM 里面, 其他框架可以不在root里面就行

  3. 组件化: 树状

  4. 单向数据流: 数据只能单向流动, 子改变父也不是直接改变数据传回去, 而是通过子组件调父组件的方法, 父组件自己去改的值

  5. 视图层框架: react 本身只能做到父子组件传值(只能搭好视图), 大型框架里面的跨组件传值, 则需要依赖其他的框架(flux, redux)

  6. 函数式编程: 便于前端自动化测试

  7. immutable概念: state 不允许我们直接去改变, 考虑到性能优化

React 调试工具

  1. React developer tools , 安装完记得重启浏览器即可

概念

1. PropTypes

  1. 在接受 props 的组件中: import { PropTypes } from 'prop-types'

  2. export default 前定义好父组件props传过来的值类型 本组件名.propTypes = { test: PropTypes.func.isRequired }

  3. 注意上一步, 第一个 propTypes 小写, 第二个 PropTypes 大写

  4. 传值类型错误, 会提警告

2. deaultProps

  1. 本组件名.deaultProps = {test: 'hello'}

  2. 记得是与PropTypes同级

3. props state render函数

  1. 当state, props发生改变, 自己的render函数(类里面的render函数)会被调用

  2. 当父组件的render函数被运行时, 它的子组件的render函数都将被重新运行

4. 虚拟DOM

  1. 自己实现

     (1) state 数据
     
     (2) JSX 模板
     
     (3) 数据 + 模板 结合, 生成真实的DOM, 显示
     
     (4) state 发生改变
     
     (5) 数据 + 模板 结合, 生成真实的DOM, 替换原始的DOM
     
     缺陷: DOM片段整个整个替换会很耗性能
    
  2. 改进

     (1) state 数据
     
     (2) JSX模板
     
     (3) 数据 + 模板 结合, 生成真实的DOM, 显示
     
     (4) state变化
     
     (5) 数据 + 模板结合, 生成新的真实的DOM, 并不直接替换原始的DOM
     
     (6) 新的DOM (DoucumentFragment文档碎片, 没那么耗性能) 和原始的DOM做对比, 找差异(DOM之间的比较)
     
     (7) 找到input 框的变化
     
     (8) 只用新的DOM中的input 元素进行替换老元素
     
     缺陷: 全部替换 --> 生成新的DoucumentFragment, DOM之间比较(DOM比对也耗性能) --> 只替换DOM
    
  3. React 的实现

     (1) state 数据
     
     (2) JSX模板
     
     (3) 数据 + 模板 生成虚拟DOM( 虚拟DOM 本质就是 JS对象 , 用来描述真实的DOM) (减少损耗)
         ['div', {id: 'abc'}, ['span', {}, 'Hello']]  --> js对象['标签名', {属性对象}, 子节点]的嵌套成虚拟DOM树
         
     (4) 用虚拟DOM, 生成真实的DOM, 显示
         <div id="abc"><span> Hello </span></div>  真实的DOM
     
     (5) state发生变化
     
     (6) 数据 + 模板 生成新的虚拟DOM, 只是比较 js对象 的差异, 进行改变(极大的提示性能)
     ['div', {id: 'abc'}, ['span', {}, 'Bye']]
     
     (7) 比较虚拟DOM(也即JS对象) 的差别
     
     (8) 直接操作DOM, 改变span 中的内容
     
     改进: 整个DOM的替换 -> 生成新的DOM(DocumentFragement), 比较DOM, 替换DOM -> 生成虚拟DOM(JS对象), 比较虚拟DOM(比较js对象), 替换DOM
     
     优点: 
             (1) 性能提升
             (2) 虚拟DOM 使得跨端应用得以实现, React Native, 虚拟DOM本身是js对象(web, 原生应用都可以识别), 网页里面渲染成DOM, 原生里面渲染成原生组件
    
  4. 深入了解虚拟DOM

     (1) render函数里面的  JSX -> React.createElement  -> 虚拟DOM(js对象) -> 真实的DOM
     
     (2) jsx: return ( <div> <span> hello </span> </div>  )  jsx就是React.createElement的语法糖
     
     (3) return ( React.createElement('div', {}, React.createElement('span', {}, 'hello')) )
    
  5. 虚拟DOM的 diff算法

     (1) 概念: 旧虚拟DOM 和 新虚拟DOM 的比较(diff 算法)
     
     (2) 调用 setState 时, 才会触发 diff算法, 
     
     (3) setState react在底层设计考虑时, 使用的是异步函数的, 连续的调用setState时, 只会调用一次
         
     (4) 虚拟DOM同层比对: 新 和 旧 的同一层比对, 发现差异时, 直接不再比较后面的dom节点, 而是直接全部替换, 这样, 减少计算难度
     
     (5) key值, 虚拟DOM比对的性能, 有key值(所以尽量不要index, index在list项被删除时会变), 能迅速找到同一层
    

5.ref 的使用

  1. 例子: JSX == ref = {(input) => {this.test = input }} 绑定 this.test 就是这个 input 标签. 形参就是标签名

  2. JS === this.test.value 调用

  3. 缺点: React 推荐数据驱动, 而不是操作Dom, 而有时setState异步的原因, 导致 ref取到的DOM 有滞后性

  4. 解决: setState的第二个参数, 回调函数里面

6.React 实现css过渡动画

  1. Vue: :class="isShow?'show':'hide'",

  2. react: className={this.state.isShoe? "show": "hide"} 变换类名绑定 css 动画

  3. 把简单的css 变成复杂的css , @keyframes 搭配 animation , 实现相对复杂的动画

7.react-transition-group 实现动画效果

(类比Vue) 进入前,进入中, 进入完成, 出场前, 出场中,出场后的 css样式和钩子

  1. yarn add react-transition-group -S

  2. 包括三个模块: transition, CSSTransition, TransitionGroup (transition最底层)

  3. CSSTransition使用步骤: 单独组件使用

  4. 使用了动画效果的, 它的子组件, 使用shouldComponentUpdate优化是没有用的

            JSX 部分: 可以css部分控制动画, 也可以js钩子控制动画
            
    import { CSSTransition } from "react-transition-group" // 引用 CSSTransition 模块
    
    <CSSTransition
      in={this.state.isShow}        // 启动动画的根据
      timeout={1000}                // timeout 触发动画
      classNames='fade'             // css来控制, css文件类名的命名是以 'fade'开头, 如: .fade=enter
      unmountOnExit                 // 动画结束是否销户DOM
      onEntered = {(el) => {el.style.color='tomato'}}   // JS钩子来控制动画, js部分, el代表dom元素
      appear={true}                 // 刚启动页面时是否启动动画
    >
    
      <div>hello</div>      // 需要动画的模块放在 CSSTransition 标签中
      
    </CSSTransition>

                CSS部分
        
    /* 搭配 react-transition-group 实现动画 */
    /* 动画进入前, 中, 后, 动画出场前, 中, 后 */
        .fade-enter {
          opacity: 0;
        }
        .fade-enter-active {
          opacity: 1;
          transition: opacity 1s ease-in;
        }
        .fade-enter-done {
          opacity: 1;
        }
        .fade-exit {
          opacity: 1;
        }
        .fade-exit-active {
          opacity: 0;
          transition: opacity 1s ease-in;
        }
        .fade-exit-done {
          opacity: 0;
        }
        /* 页面刚开始前, 页面启动完成 */
        .fade-appear {
          opacity: 0;
        }
        .fade-appear-active {
          opacity: 1;
          transition: opacity 1s ease-in;
        }
  1. TransitionGroup 使用步骤
        当动态增加的组件时的动画, 使用 TransitionGroup + CSSTransition
        
        import { CSSTransition, TransitionGroup } from "react-transition-group";
        
        <TransitionGroup>   // TransitionGroup 标签包裹在最外层
          {
            this.state.list.map((item, index) => {
              return (
                <CSSTransition      // 生成的每一项都包裹在 CSSTransition 标签里面, 跟单独使用一样
                  timeout={1000}
                  classNames='fade'
                  unmountOnExit
                  onEntered = {(el) => {el.style.color='tomato'}}
                >
                  <div>{item}</div> // 这个是真实的动态元素
                </CSSTransition>
              )
            })
          }
        </TransitionGroup>
        

生命周期函数

  1. 概念: 某一时刻自动调用的钩子

  2. Initialization: 初始化

        (1)相当于 construct, 一开始就初始化执行
    
  3. Mounting: 组件挂载

         (1)componentWillMount(组件挂载之前) -> render(JSX) -> componentDidMount(组件挂载之后)
    
  4. Updation: 组件更新, props 或者 state发生变化时

         (1)  props:  componentWillReceiveProps
                     (当组件从父组件接受props -> 当父组件的render(state, props更新)被重新执行, 且子组件第一次存在时,不会执行 -> 父render更新, 这里就会执行)
         
                         -> shouldComponentUpdate(要求必须return boolean值, 以判断是否需要更新)
                         
                         -> componentWillUpdate (更新之前, 上步返回false, 则不执行) 
                         
                         -> render -> componentDidUpdate (更新完成后)
         
         (2) state: shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
    
  5. Unmounting: 卸载页面

     (1) componentWillUnmount 
    
  6. 用法总结:

     (1) shouldComponentUpdate: 判断是否是props变化, 还是父的render变化, 以确定是否更新, 减少性能消耗, 否则父组件render更新一下, 子的render函数更新一次
     
     (2) componentDidMount: 发送axios的请求
    

组件

React 的性能优化

  1. 事件绑定this放在 construct做, this.handleClick = this.handleClick.bind(this)

         (1)这样保证只会执行一次, 并且最早执行, 而放在render函数内部, 每次更新都会重新绑定
    
  2. shouldComponentUpdate钩子函数:

        shouldComponentUpdate (nextProps, nextState) {
            // 假如props有更新才调用, 而不是父render函数调用就调用子的render函数
            if(nextProps.content !== this.props.content) {
                  return true
            } else {
                  return false
            }
          }
  1. React 的setState 是异步函数, 能把多次执行为一次, 还提供了回调

  2. 虚拟DOM, 引用key值 和 同层比对, 来减少diff 算法的计算

父子组件传值

  1. 父 --> 子: 父: key={value} 子: this.props.key

  2. 子 --> 父: 类比Vue, @handle = handleChange, 调用父组件传递过来的方法

         (1) 在父定义 handleChange 方法
         
         (2) 通过 父 --> 子, 即 key={this.handleChange.bind(this)}, 记得绑定this, 才能拿到父的方法
         
         (3) 通过this.props.handle() 调用, 传参达到传值
    

Charles 模拟数据

  1. 类比 mock.js, json-server

  2. 中间代理服务器: 会抓取浏览器发送的http 请求, 当发现ip地址为你配置的地址的时候, 会自动返回 你自己配置的本地文件给你

Redux

  1. 概念: Reducer + Flux, 它并不是为React设计的, 只是React可以使用

  2. redux-devtools: redux调试工具的使用

     (1) 安装 redux-devtools
     
     (2) store 的 index.js中添加, 然后浏览器中就有了redux调试工具
     const store = new createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
    
  3. Redux 的工作流程:

     (1) React Component(借书的人) 
             
             (1) store.dispatch(action)          // dispatch 组件触发 store的改变
             
             (2) store.subscribe('本地的方法')        // subscribe 组件监听 store 的变化
             
             (3) store.getState()            // 组件去获取store, 并不是实时的
             
             (4) action = {}                 // 必须定义第一步的action, 同时需要 type
     
     (2) -> Action Creators(借书的动作) 
     
     (3) -> store(图书馆管理员) 
     
             (1) window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), 代表有redux-devtools时, 就配置好
             
             (2) 一直在负责分发: Component的(state, action) -> reducer, 
     
     (4) -> Reducers (查询书籍目录)
     
             (1) state: 代表上一次的所有store数据
             
             (2) action: 接收到的更新, 包含更新的类型(执行哪个方法), 和更新的值
             
             (3) reducer: 可以接受state, 但是绝对不能直接修改state, 所以需要深拷贝一次state:  JSON.parse(JSON.stringify(state))
     
     (5) -> Store  -> Reat Component
    
  4. 注意点

     (1) actionTypes.js 文件: action.type 用在 actionCreators 和 reducer, 错了一个单词不会报错, 找错很难, 引入文件里面变量的方式就容易多了
     
     (2) actionCreators.js: 集中管理action的所有方法, export const test = (val) => ({type: xxx, val}
     
     (3) store: 必须唯一
     
     (4) 只有store 可以改变 store 里面的内容(自己更新, 所以reducer是深克隆)
     
     (5) reducer: 必须是一个纯函数, 给定固定输入, 就一定会有固定输出(不可以输出异步操作, 异步函数, 异步请求等), 且不会有任何副作用(不可以改变state)
    

Redux 中间件

  1. 概念: action 和 store 的中间, 对原始的dispatch进行封装(store才有, 所以一定是redux的中间件)

  2. redux-devtools-extension 中间件 (调试工具也是中间件)

     (1) 参考: https://github.com/zalmoxisus/redux-devtools-extension
     
     (2) github中使用方式
    
  3. redux-thunk 中间件

     (0) 实现: 对dispatch 的封装, 发现action是对象就直接派发给 store , 发现是函数就调用, 当函数里面还有dispatch时亦然
    
     (1) 进行ajax请求: 参考: https://github.com/reduxjs/redux-thunk
     
     (2) 存在的意义: 
                 
         (1) 复杂异步操作的代码拆分                 
         (2) 前端代码自动化测试
         
         (3) 最终还是为了减少组件的大小
         
     (3) 涉及的文件: 
     
         (1) React Component: 原来axios请求  ->  store.dispatch(action)
         
         (2) store的 index.js: 兼容两个中间件
         
         (3) actionCreator.js: 原先返回对象  ->  返回函数, 同时做axios请求, 最后dispatch(action) 去更新store
                                 文件内部处理action 和 异步请求 的两个逻辑 
    
  4. redux-saga 中间件

     (1) 存在的意义: 与redux-thunk意义, 两者可以互相替换
    
  5. redux-logger 中间件

     (0) 实现: action ->  dispatch -> store 过程中, 每次都在dispatch的时候console.log() 一下派发, 依然是对dispatch的封装
    
  6. 在store的index.js中 多个中间件共存, 参考各自的github

                
    import { createStore, applyMiddleware, compose } from 'redux';
    import thunk from 'redux-thunk';
    import reducer from './reducer';
    
    const composeEnhancers =
      typeof window === 'object' &&
      window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
        }) : compose;
    
    const enhancer = composeEnhancers(
      // applyMiddleware(...middleware),
      applyMiddleware(thunk),
    );
    const store = new createStore(
      reducer, 
      enhancer
    );
    export default store

React-redux

  1. 区别于中间件, 它是一个第三方模块, 对React更加的契合

  2. store.getState(), store.dispatch(action), store.subscribe() 都被 React-redux 更方便的实现了

  3. 核心API:

    (1) Provider: 入口文件, index.js
    (1) 使用: <Provider store={store}> <App/> </Provider>

         (2) 意义: 把store 直接注入到它的所有子组件中(就不用每个文件都引入store了), 包裹下的组件可以直接使用store
    

    (2) connect: React Component

          (1) 使用: React Component
              export default connect(mapStateToProps, mapDispatchToProps)('组件名')
          
          (2) 意义: 把store 和组件做映射, 映射规则是两个函数 mapStateToProps 和 mapStateToProps
          
          (3) 容器组件和ui组件: connect, 可以把业务逻辑 和 ui组件 相互映射, 然后导出一个容器组件
    

    (3) mapStateToProps: React Component

         (1) 使用: const mapStateToProps = (state) => {   // 必须接受store 里面的所有state
                     return {
                         list: state.list     // 把store 里面的list, 映射到组件的props 中, 名字也叫list
                     }
                 }
                 
         (2) redux 比较: 类似于 store.getState(), 同时由于是props的映射, 所以实现了store.subscribe()
    

    (4) mapDispatchToProps: React Component

         (1) 使用:  const mapDispatchToProps = (dispatch) => {
                         return {           // 在return 里面定义一个方法, 可以直接改变store 里面的值
                             handelChange (dispatch) {   // dispatch作为参数, 相当于 store.dispatch()
                                 const action = {
                                     type: '',
                                     value: ''
                                 }
                                 dispatch(action)    // 调用也是直接 this.props.handleChange
                             }
                         }
                     }
         (2) redux 比较: 类似于 stroe.dispatch(action)
    

UI组件和容器组件

  1. 一个React组件组成:

     (1) ui组价负责渲染(傻瓜组件), 主要html部分, 一般是无状态组件
     
     (2) 容器组件负责逻辑(聪明组件), js部分, 一般是有状态组件
    

无状态组件(函数式组件)

  1. 适用于ui组件

  2. 有状态组件 和 无状态组件

     (1) 类组件是: this.props, 
     
     (2) 函数式组件: props, 性能更优
    
    const ToDoList = (props) => {
        return (
            <div> { props.inputVal } </div>
        )
    }

Vue 和 React

  1. 动画

     (1) Vue: 官方Vue.js 里面直接有
     (2) React: react-transition-group 需要另行安装
    
  2. 路由

总结: 我自己学习React时候总结出来的, 有问题还望指教

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

推荐阅读更多精彩内容