2019-12-19

Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。

前言:hooks出了已有大半年了,關注的公眾號也大都推了關于hooks的文章,可是因為工作中一直用的是class,所以一直沒有用,也沒有學,趁著這段時間項目不那么干,將hooks系統性的學習一下,并做筆記記錄一下。

<span id="目錄">目錄</span>
  1. useState
  2. userEffect
  3. userEffect實現componentWillUnmont
  4. 父子組件傳值
  5. userContext
  6. userReducer
  7. useReducer替代Redux案例
  8. useMemo
  9. useRef
  10. useCallBack
  11. 自定義函數

一:<span id="useState">useState</span>

在組件中,我們難免使用state來進行數據的實時響應,這是react框架的一大特性,只需更改state,組件就會重新渲染,試圖也會響應更新。<br />
不同于reactclass可以直接定義state,或者是在constructor中使用this.state來直接定義state值,在hooks中使用state需要useState函數,如下:

import React, { useState, useEffect } from 'react';

function Hooks() {
  const [count, setCount] = useState(0);
  const [age] = useState(16);
  useEffect(() => {
    console.log(count);
  });
  return (
    <div>
      <p>小女子芳年{age}</p>
      <p>計數器目前值為{count}</p>
      <button type="button" onClick={() => { setCount(count + 1); }}>點擊+1</button>
      <button type="button" onClick={() => { setCount(count - 1); }}>點擊-1</button>
    </div>
  );
}

export default Hooks;

在上面的例子中,我們使用了useState定義了兩個state變量,countage,其中定義count的時候還定義了setCount,就是用來改變count值的函數。在class類中,改變state是使用setState函數,而在hooks中是定義變量的同時定義一個改變變量的函數。<br />
userState是一個方法,方法返回值為當前state以及更新state的函數,所以,在上面的例子中,我們用const [count, setCount] = useState(0);將count和setCount解構出來,而userState方法的參數就是state的初始值。當然count和與之對應的改變函數名稱并不一定非得是setCount,名稱可以隨便起,只要是一塊解構出來的即可。<br />
class組件中,我們可以用setState一次更改多個state值而只渲染一次,同樣的,在hooks中,我們調用多個改變state的方法,也只是渲染一次。

二:<span id="userEffect">userEffect</span> 回目錄

class組件中,有生命周期的概念,最常用的,我們通常會在componentDidMount這個生命周期中做數據請求,偶爾,我們也會用一些其它的生命周期,像是componentDidUpdatacomponentWillReceiveProps等。在hooks中,沒有生命周期的概念,但是,有副作用函數useEffect。<br />
使用useEffect,和使用useState相同,必須得先引入import React, { useState, useEffect } from 'react';,默認情況下,useEffect會在第一次和每次更新之后都會執行,useEffect函數接受兩個參數,第一個參數是一個函數,每次執行的就是函數中的內容,第二個函數是個數組,數組中可選擇性寫state中的數據,代表只有當數組中的state發生變化是才執行函數內的語句。如果是個空數組,代表只執行一次,類似于componentDidUpdata。所以,向后端請求可以寫成下面這種方式:

// 頁面進來只調用一次
useEffect(()=>{
    axios.get('/getYearMonth').then(res=> {
        console.log('getYearMonth',res);
        setValues(oldValues => ({
            ...oldValues,
            fileList:res.data.msg
        }));
    })
},[]);

effect函數會在瀏覽器完成畫面渲染之后延遲調用<br />
在一個hooks函數中,可以同時存在多個effect函數,所以,當有需求每次更新都執行useEffect中的代碼時,可以用一個useEffect請求數據,用其他的useEffect做另外的事情。只需根據第二個參數即可區別不同作用。

//官方示例性能優化
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新

三:<span id="userEffect實現componentWillUnmount">userEffect實現componentWillUnmount</span> 回目錄

部分情況下,需要在組件卸載是做一些事情,例如移除監聽事件等,在class組件中,我們可以在componentWillUNmount這個生命周期中做這些事情,而在hooks中,我們可以通過useEffect第一個函數參數中返回一個函數來實現相同效果。

// 官方示例
useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
});

個人示例:

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

function Index() {
  useEffect(() => {
    console.log('useEffect:come-index');
    return () => {
      console.log('useEffect:leave-index');
    };
  }, []);
  return <div>這是首頁</div>;
}

function List() {
  useEffect(() => {
    console.log('useEffect:come-list');
    return () => {
      console.log('useEffect:leave-list');
    };
  }, []);
  return <div>這是列表頁</div>;
}

function HooksEffect() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(count);
    return () => {
      console.log('-------------------');
    };
  }, [count]);

  return (
    <div>
      <p>你點擊了{count}次</p>
      <button 
        type="submit" 
        onClick={() => { setCount(count + 1); }}
      >
        點擊+1
      </button>
      <ul>
        <li><Link to="/index">首頁</Link></li>
        <li><Link to="/list">列表</Link></li>
      </ul>
      <Switch>
        <Route path="/index" exact component={Index} />
        <Route path="/list" component={List} />
      </Switch>
    </div>
  );
}

export default HooksEffect;

在上面的例子中,全部用了清除副作用的return 函數,其中,hooksEffect組件為父組件,listindex為子組件,如果在子組件的useEffect中不使用第二個參數空數組,則父組件的每次更新都會引發子組件的useEffect的調用,在父組件的useEffect函數中,第二個參數數組中為count,代表每次count的變化都會引起useEffect函數的觸發以及返回函數的調用。

四:<span id="父子組件傳值">父子組件傳值</span> 回目錄

父子組件傳值在實際開發中是必不可少的,在class組件中,我們可以直接給子組件添加屬性,然后在子組件通過props即可獲取到父組件的值。但是在hooks中,組件都是函數,沒有props,所以不能用相同的方式傳值<br />
hooks中,組件都是函數,所以我們可以通過參數的方式進行傳值,也可以通過content來進行傳值,這一小節主要是講通過參數方式進行傳值,案例如下:

import React, { useState } from 'react';

function Show({ count, age, clear }) {
  return (
    <div>
      數量:{count}  
      年齡:{age} 
      <button 
        type="button" 
        onClick={() => { clear(); }}
      >
        復原
      </button>
    </div>
  );
}

function HooksContext() {
  const [count, setCnt] = useState(0);
  const [age, setAge] = useState(16);
  function clear() {
    setCnt(0);
    setAge(16);
  }
  return (
    <div>
      <p>小女子芳年{age}</p>
      <p>你點擊了{count}次</p>
      <button 
        type="button" 
        onClick={() => { setCnt(count + 1); 
        setAge(age + 1); }}
      >
        點擊+1
      </button>
      <Show count={count} age={age} clear={clear} />
    </div>
  );
}

export default HooksContext;

在上面的案例中,通過給Show組件屬性賦值,然后在Show函數組件中以解構參數的方式獲取父組件的值。這種傳值方式和類組件本質上還是一樣的。

五:<span id="userContext">userContext</span> 回目錄

使用userContext,不僅可以實現父子組件傳值,還可以跨越多個層級進行傳值,例如父組件可以給孫子組件甚至重孫子組件進行直接傳值等,redux全局狀態管理本質上也是對content的一種應用。<br />
hooks中使用content,需要使用createContextuseContext,廢話不多說,直接示例展示用法

// context.js  新建一個context
import { createContext } from 'react';

const ShowContext = createContext('aaa');

export default ShowContext;

// HooksContext.jsx  父組件,提供context
import React, { useState } from 'react';
import Show from './Show.jsx';
import ShowContext from './context';

function HooksContext() {
  const [count, setCnt] = useState(0);
  const [age, setAge] = useState(16);
  function clear() {
    setCnt(0);
    setAge(16);
  }
  return (
    <div>
      <p>小女子芳年{age}</p>
      <p>你點擊了{count}次</p>
      <button
        type="button"
        onClick={() => { setCnt(count + 1); setAge(age + 1); }}
      >
        點擊+1
      </button>
      <ShowContext.Provider value={{ count, age, clear }}>
        <Show />
      </ShowContext.Provider>
    </div>
  );
}

export default HooksContext;

// Show.jsx  子組件,使用context
import React, { useContext } from 'react';
import ShowContext from './context';

function Show() {
  const { count, age, clear } = useContext(ShowContext);
  return (
    <div>
      數量:{count}
      年齡:{age}
      <button
        type="button"
        onClick={() => { clear(); }}
      >
        復原
      </button>
    </div>
  );
}

export default Show;

上面是一個完整的使用content實現父子組件傳值的過程,如果Show組件下還有子組件,無論多少層,都可以用useContext直接取到HooksContext父組件提供的值,而context.js文件是新建一個context,新建必須要單獨列出來,否則子組件無法使用useContext。<br />
content提供了一種樹狀結構,被Context.Provider所包裹的所有組件,都可以直接取數據。redux就是利用了context的這種特性實現全局狀態管理。在下面的幾小節中,我們會講hookscontext搭配useReducer來實現redux的功能。

六:<span id="userReducer">userReducer</span> 回目錄

userReduceruseState的替代方案,它接收一個形如(state,action) => newStatereducer,并返回當前的state以及其配套的dispatch方法。
總的來說呢,userReducer可以接受兩個參數,第一個參數就是和redux中的reducer一樣的純函數,第二個參數是state的初始值,并返回當前state以及dispatch。<br />
還是以官方示例的計數器為例

import React, { useReducer } from 'react';

function countReducer(state, action) {
  switch (action.type) {
    case 'add':
      return state + 1;
    case 'minus':
      return state - 1;
    default:
      return state;
  }
}

function HooksEffect() {
  const [count, dispatch] = useReducer(countReducer, 0);

  return (
    <div>
      <p>你點擊了{count}次</p>
      <button
        type="button"
        onClick={() => { dispatch({ type: 'add' }); }}
      >
        點擊+1
      </button>
      <button
        type="button"
        onClick={() => { dispatch({ type: 'minus' }); }}
      >
        點擊-1
      </button>
    </div>
  );
}

export default HooksEffect;

相比起redux還需要connect高階函數包裹一下才能將dispatch和state注入到props中,hooks中使用reducer更加簡潔。在下面一小節中,我們會用案例來實現redux。

七:<span id="useReducer替代Redux案例">useReducer替代Redux案例</span> 回目錄

在本小節中,我們會用contextuseReducer來實現redux的效果。依然是使用計數器這個功能,先貼代碼,后面會詳細講解:

// count.js  定義context和reducer,導出context和包含reducer的context包裹組件。
import React, { createContext, useReducer } from 'react';

function countReducer(state, action) {
  switch (action.type) {
    case 'add':
      return state + 1;
    case 'minus':
      return state - 1;
    default:
      return state;
  }
}

const ADDCOUNT = 'add';
const MINUSCOUNT = 'minus';

export const CountContext = createContext();
export const CountWrap = (props) => {
  const [count, dispatch] = useReducer(countReducer, 0);
  return (
    <CountContext.Provider
      value={{ count, dispatch, ADDCOUNT, MINUSCOUNT }}
    >
      {props.children}
    </CountContext.Provider>
  );
};

// ReducerToRedux.jsx,連接組件,
import React from 'react';
import Button from './Button';
import Show from './Show';
import { CountWrap } from './count';

function ReducerToRedux() {
  return (
    <div>
      <CountWrap>
        <Show />
        <Button />
      </CountWrap>
    </div>
  );
}

export default ReducerToRedux;

// Show.jsx  顯示當前數值的組件
import React, { useContext } from 'react';
import { CountContext } from './count';

function ReducerToRedux() {
  const { count } = useContext(CountContext);
  return (
    <div>現在的計數器值為:{count}</div>
  );
}

export default ReducerToRedux;

// Button.jsx  按鈕組件,可以實現計數器的增和減
import React, { useContext } from 'react';
import { CountContext } from './count';

function ReducerToRedux() {
  const { dispatch, ADDCOUNT, MINUSCOUNT } = useContext(CountContext);
  return (
    <div>
      <button
        type="button"
        onClick={() => { dispatch({ type: MINUSCOUNT }); }}
      >點我-1</button>
      <button
        type="button"
        onClick={() => { dispatch({ type: ADDCOUNT }); }}
      >點我+1</button>
    </div>
  );
}

export default ReducerToRedux;

通過reducer和context實現計數器的功能,我們共用了四個文件,當然count.js這個文件本應該拆分成三個文件,常量單獨定義一個文件,reducer純函數也應該單獨定一個文件,不過代碼不多,就暫時合一塊了。<br />
count.js中,我們導出CountContextCountWrap,其中,CoutWrap就是provider,也就是只要被CountWrap包裹過的組件,就可以使用userContent取到傳遞數據,而CountContext就是用createContext新建的一個content,使用useContext取傳遞數據的時候會用到。同時,在這個文件中,我們還將從useReducer解構出的countdispatch,以及常量增減通過provider傳遞給包裹組件,使被包裹的組件可以通過useContext取到這些數據。函數countReducer就是和redux中的reducer一樣的純函數,子組件dispatch action,reducer則是接受當前stateaction,通過判斷action,返回新的state。<br />
Button組件中,我們通過countContext取到dispatch及常量,改變count這個數值,在Show組件中,只是展示count,在ReducerToRedux文件中,是做一個連接器,用CountWrap包裹ButtonShow組件。<br />
可能說的有些啰嗦,看上去有些復雜,其實稍一整理,原理很簡單,自己寫一遍整理清楚邏輯使用上就很簡單了。<br />
講到這里,其實hooks已經能應對絕大部分場景了,下面兩小節,我們會講一下useMemouseRef,用于優化渲染以及處理特殊情況。

八:<span id="useMemo">useMemo</span> 回目錄

把“創建”函數和依賴項數組作為參數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助于避免在每次渲染時都進行高開銷的計算。<br />

useMemo是函數式組件官方提供的性能優化的一個方法,接受兩個參數,第一個參數是要執行的函數,第二個參數是state中的值或者父組件傳下來的值,代表只有當第二個參數的值發生變化時,才執行函數。其中,第二個參數是數組,可以同時優化多個state或者父組件傳下來的參數,首次渲染組件是,如果頁面用到要優化的值,函數會執行。<br />
我們還是以計數器以及年齡為例

import React, { useState, useMemo } from 'react';

function Show({ count, age, clear }) {
  function ageChange(value) {
    console.log(value);
    return value + 2;
  }
  const myAge = useMemo(() => ageChange(age), [age]);
  return (
    <div>
      數量:{count}  我的年齡:{myAge}
      <button
        type="button"
        onClick={() => { clear(); }}
      >復原</button>
    </div>
  );
}

function HooksUseMome() {
  const [count, setCnt] = useState(0);
  const [age, setAge] = useState(16);
  function clear() {
    setCnt(0);
    setAge(16);
  }
  return (
    <div>
      <p>小女子芳年{age}</p>
      <p>你點擊了{count}次</p>
      <button
        type="button"
        onClick={() => { setAge(age + 1); }}
      >點擊年齡+1</button>
      <button
        type="button"
        onClick={() => { setCnt(count + 1); }}
      >點擊計數器+1</button>
      <Show count={count} age={age} clear={clear} />
    </div>
  );
}

export default HooksUseMome;

在上面的例子中,父組件小女子初始年齡為16歲,而到子組件經過ageChange函數,返回我的年齡永遠比小女子年齡大兩歲。<br />
但是如果沒有useMemo,當父組件的計數器count值發生變化時,子組件的ageChange函數也會執行,這不是我們想要的結果,我們只想當小女子的年齡發生變化時,再執行ageChange函數。所以,用useMemo可以實現我們想要的效果。如上面代碼所示const myAge = useMemo(() => ageChange(age), [age]);,使用useMemo,第二個參數是age,這樣,只有當age發生變化時,才執行其中的函數。<br />

在類組件中,有shouldComponentDidUpdata生命周期,我們可以在其中做監測,當檢測到state值沒發生變化時,直接不渲染組件,而useMemo和這個生命周期還有些許不同。它是當檢測的state發生變化時而執行某些函數,避免額外的開銷,節省性能。

九: <span id="useRef">useRef</span> 回目錄

在項目開發中,我們比較少用到ref,一般我們不直接操作DOM,都是通過狀態來控制DOM,不過在某些情況下,可能還是會用到ref,這一節我們通過對input輸入框數據的雙向綁定來認識useRef

import React, { useState, useRef } from 'react';

function HooksUseRef() {
  const [inputValue, setInputValue] = useState();
  const inputRef = useRef(null);

  function inputChangeHandle(e) {
    setInputValue(e.target.value);
  }

  function inputRefChangeHandle() {
    console.log(inputRef.current.value);
  }
  return (
    <div>
      <div>
        <input
          value={inputValue}
          onChange={inputChangeHandle}
          type="text"
        />
        <span>使用state綁定inputValue值</span>
      </div>
      <div>
        <input
          ref={inputRef}
          onChange={inputRefChangeHandle}
          type="text"
        />
        <span>使用Ref綁定inputValue值</span>
      </div>
    </div>
  );
}

export default HooksUseRef;

在上面的案例中,我們如果要取input的值,如果是state雙向綁定,可以直接取inputValue,如果是用ref,則可以通過inputRef.current.value取到值
通過const inputRef = useRef(null);,我們獲取到的是一個對象,而current屬性就是其中的dom元素。

十: <span id="useCallBack">useCallBack</span> 回目錄

把內聯回調函數及依賴項數組作為參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時才會更新。

簡而言之,useCallBack是用來緩存函數的,在class類中,我們通常在constructor中使用this.fn = this.fn.bind(this)來綁定this,是每次調用的fn都是之前的fn,而不用開辟新的函數。而useCallback同樣有此功能,useCallBackuseMemo的不同點在于useMemo相當于緩存state,而useCallBack相當于緩存函數,官方給的解釋是這樣的useCallback(fn, deps) 相當于 useMemo(() => fn, deps)。.

我們下面還是用計數器和年齡做例子

import React, { useState, useEffect, useCallback } from 'react';

function Show({ countCallBack, ageCallBack }) {
  const [count, setCount] = useState(() => { countCallBack(); });
  const [age, setAge] = useState(() => { ageCallBack(); });

  useEffect(() => {
    setCount(countCallBack());
  }, [countCallBack]);

  useEffect(() => {
    setAge(ageCallBack());
  }, [ageCallBack]);

  return (
    <div>
      數量:{count}  年齡:{age}
    </div>
  );
}

function HooksCallBack() {
  const [count, setCnt] = useState(0);
  const [age, setAge] = useState(16);

  const countCallBack = useCallback(() => {
    return count;
  }, [count]);

  const ageCallBack = useCallback(() => {
    return age;
  }, []);

  return (
    <div>
      <p>小女子芳年{age}</p>
      <p>你點擊了{count}次</p>
      <button
        type="button"
        onClick={() => { setAge(age + 1); }}
      >點擊年齡+1</button>
      <button
        type="button"
        onClick={() => { setCnt(count + 1); }}
      >點擊計數器+1</button>
      <Show countCallBack={countCallBack} ageCallBack={ageCallBack} />
    </div>
  );
}

export default HooksCallBack;

在上面的例子中,只有點擊計數器按鈕,子組件才會跟著更新,點擊年齡按鈕子組件則不跟著更新。使用useCallback如果沒有依賴,則只會執行一次,只有依賴改變,才會返回新的函數,我們可以根據這個規則實現bind的效果。

十一: <span id="自定義函數">自定義函數</span> 回目錄

這一小節,我們做一個監聽瀏覽器窗口的自定義函數,廢話不多說,直接上例子:

import React, { useState, useEffect, useCallback } from 'react';

function useWinSize() {
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });

  const resizeHandle = useCallback(() => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    });
  }, []);

  useEffect(() => {
    window.addEventListener('resize', resizeHandle);
    return () => {
      window.removeEventListener('resize', resizeHandle);
    };
  }, []);
  return size;
}

function HooksFunction() {
  let size = useWinSize();
  return (
    <div>
      瀏覽器窗口尺寸{`${size.width}*${size.height}`}
    </div>
  );
}

export default HooksFunction;


上面的代碼就不多解釋了,所需要注意的是自定義函數需要以use開頭,且后面應該用大寫字母與use分隔開。到此呢,hooks先寫到這里,基本上也能面對絕大多數的業務場景,其它的hooksAPI等以后開發中如果有用到,再來補充。<br />

在下一節中,我們將會把TS從基礎到項目應用整個的梳理出來,分兩篇來完成,待續。。。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容