關(guān)于當前Web前端技術(shù)的一些感悟和筆記

最近這些年宠能,隨著前端應(yīng)用技術(shù)突飛猛進此再,產(chǎn)生了很多新的前端框架牢贸,當然也引入了數(shù)不勝數(shù)的前端技術(shù)概念,前端不在是早期Web Form的拖拉處理方式拍嵌,也不再是Ajax+HTML那么簡單遭赂,隨著前端技術(shù)的發(fā)展,前端的JS越來越重要横辆,也越來越復(fù)雜撇他,而為了開發(fā)的方便,引入了很多可以對JS+CSS進行編譯的框架狈蚤,而在發(fā)布的時候按需編譯處理困肩,從而增強了整個前端的開發(fā)過程,這些前端的技術(shù)包括AngularJS、React、Vue等等叉跛,這些前端技術(shù)應(yīng)用框架又囊括了很多相關(guān)的技術(shù),包括了MVVM(Model-View-ViewModel)潭枣、ES6、Babel幻捏、dva盆犁、umi、less等技術(shù)或概念粘咖。前端技術(shù)越滾越大蚣抗,范圍也越來越廣,大有日新月異的感覺瓮下。

1翰铡、前端技術(shù)的自我回顧和展望

記得在上大學(xué)時候,開始玩asp的年代讽坏,前端和后端糅合一起的困境锭魔;也曾記得WebForm開發(fā)的樂趣和無奈,快捷但是很丑很笨重路呜;而現(xiàn)在還在繼續(xù)做著Ajax + HTML的這種前端的處理迷捧,痛并快樂著织咧。技術(shù)總是一步步的推進則,但是眼光一旦聚焦在某個技術(shù)范疇漠秋,日月如梭笙蒙,抬頭間很快就會發(fā)現(xiàn)世界又多了新的前端技術(shù),從開始的猶豫和不確信的停留這段時間后庆锦,發(fā)現(xiàn)整個前端的世界也已經(jīng)漸成格局捅位,包括Angular、React搂抒、Vue等技術(shù)應(yīng)用已經(jīng)日趨成熟艇搀,而且擁有著龐大的擁躉群體,也有著豐富的資源可供學(xué)習和了解求晶。

下面是Angular焰雕、React、Vue幾個技術(shù)框架的一些介紹芳杏。

AngularJS誕生于2009年矩屁,由Misko Hevery 等人創(chuàng)建,后為Google所收購爵赵。是一款優(yōu)秀的前端JS框架档插,已經(jīng)被用于Google的多款產(chǎn)品當中。AngularJS有著諸多特性亚再,最為核心的是:MVC(Model–view–controller)、模塊化晨抡、自動化雙向數(shù)據(jù)綁定氛悬、語義化標簽、依賴注入等等耘柱。Angular開發(fā)在全球開發(fā)人員中廣泛流行如捅,并被谷歌,福布斯调煎,WhatsApp镜遣,Instagram,healthcare.gov和許多財富500強公司等大型組織使用士袄。

React 起源于 Facebook 的內(nèi)部項目悲关,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意娄柳,就決定自己寫一套寓辱,用來架設(shè) Instagram 的網(wǎng)站。做出來以后赤拒,發(fā)現(xiàn)這套東西很好用秫筏,就在2013年5月開源了诱鞠。 由于 React 的設(shè)計思想極其獨特,屬于革命性創(chuàng)新这敬,性能出眾航夺,代碼邏輯卻非常簡單。所以崔涂,越來越多的人開始關(guān)注和使用阳掐,認為它可能是將來 Web 開發(fā)的主流工具。

Vue.js是討論最多且發(fā)展最快的JavaScript框架之一堪伍。它由前谷歌員工Evan You創(chuàng)建锚烦,他在擔任Google員工時曾在Angular工作過。您可以認為它是成功的帝雇,因為它能夠使用HTML涮俄,CSS和JavaScript構(gòu)建有吸引力的UI。

這些技術(shù)各有優(yōu)點尸闸,很難片面的說明誰優(yōu)誰劣彻亲,它們都各自有自己的生存土壤和大批的擁躉,而我開始選型做前端技術(shù)更新的時候吮廉,主要看中的是阿里巴巴的Ant-Design開發(fā)框架苞尝,這個它是使用了React的技術(shù)框架,因此也就自然而然的研究學(xué)習起React和Ant-Design來宦芦,雖然之前對前端的一些技術(shù)有所涉獵宙址,但是真正等你想要進入Ant-Design的開發(fā)大門的時候,還是感覺自己像進入了一個前端技術(shù)的大觀園调卑,一個個新概念接踵而來抡砂,一種種代碼的寫法迎面沖擊,教程看了幾遍還是一頭霧水恬涧,真的開始懷疑人生了注益,不過學(xué)習新技術(shù)還是需要很多平靜的心態(tài),調(diào)整好溯捆,一步一個腳印相信還是有所斬獲的丑搔,偶爾看到阮一峰的大牛介紹在學(xué)習研究React的時候,也曾花了幾個月的時候提揍,雖然他的高度難以看齊啤月,但是學(xué)習的韌勁和毅力,是值得我們學(xué)習的碳锈。學(xué)習新的東西顽冶,從技術(shù)角度,可以滿足好奇心售碳,提高技術(shù)水平强重;從職業(yè)角度绞呈,有利于求職和晉升,有利于參與潛力大的項目(摘自阮一峰筆記)间景。

2佃声、React的技術(shù)學(xué)習

接觸一些新的東西,就必然需要投入精力來學(xué)習掌握倘要。對于學(xué)習Ant-Desin圾亏,雖然這個框架本身提供了很多教程介紹,但是我們一些技術(shù)點封拧,還是需要更細節(jié)的學(xué)習志鹃,首推還是阮一峰的技術(shù)日志吧。

1泽西、ECMAScript 6 入門

2曹铃、React 入門實例教程

3、Redux 入門教程(一):基本用法

4捧杉、Redux 入門教程(二):中間件與異步操作

5陕见、Redux 入門教程(三):React-Redux 的用法

6、Redux 文檔基礎(chǔ)教程

7味抖、DvaJS快速上手

下面有些內(nèi)容在學(xué)習的時候评甜,掌握的不是很好,摘錄并作為一個回顧吧仔涩。

模塊的 Import 和 Export

import 用于引入模塊忍坷,export 用于導(dǎo)出模塊。

// 引入全部
import dva from 'dva';

// 引入部分
import { connect } from 'dva';
import { Link, Route } from 'dva/router';

// 引入全部并作為 github 對象
import * as github from './services/github';

// 導(dǎo)出默認
export default App;
// 部分導(dǎo)出熔脂,需 import { App } from './file'; 引入
export class App extend Component {};

析構(gòu)賦值

析構(gòu)賦值讓我們從 Object 或 Array 里取部分數(shù)據(jù)存為變量承匣。

// 對象
const user = { name: 'guanguan', age: 2 };
const { name, age } = user;
console.log(`${name} : ${age}`);  // guanguan : 2

// 數(shù)組
const arr = [1, 2];
const [foo, bar] = arr;
console.log(foo);  // 1

我們也可以析構(gòu)傳入的函數(shù)參數(shù)。

const add = (state, { payload }) => {
  return state.concat(payload);
};

//析構(gòu)時還可以配 alias锤悄,讓代碼更具有語義
const add = (state, { payload: todo }) => {
  return state.concat(todo);
};

對象展開運算符(Object Spread Operator)

//可用于組裝數(shù)組。
const todos = ['Learn dva'];
[...todos, 'Learn antd'];  // ['Learn dva', 'Learn antd']

//也可用于獲取數(shù)組的部分項嘉抒。
const arr = ['a', 'b', 'c'];
const [first, ...rest] = arr;
rest;  // ['b', 'c']

// With ignore
const [first, , ...rest] = arr;
rest;  // ['c']

//還可收集函數(shù)參數(shù)為數(shù)組零聚。
function directions(first, ...rest) {
  console.log(rest);
}
directions('a', 'b', 'c');  // ['b', 'c'];


//代替 apply。
function foo(x, y, z) {}
const args = [1,2,3];

// 下面兩句效果相同
foo.apply(null, args);
foo(...args);


//對于 Object 而言些侍,用于組合成新的 Object 
const foo = {
  a: 1,
  b: 2,
};
const bar = {
  b: 3,
  c: 2,
};
const d = 4;

const ret = { ...foo, ...bar, d };  // { a:1, b:3, c:2, d:4 }

propTypes
JavaScript 是弱類型語言隶症,所以請盡量聲明 propTypes 對 props 進行校驗,以減少不必要的問題岗宣。

function App(props) {
  return <div>{props.name}</div>;
}
App.propTypes = {
  name: React.PropTypes.string.isRequired,
};

內(nèi)置的 prop type 有:

PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string

DVA數(shù)據(jù)流向
數(shù)據(jù)的改變發(fā)生通常是通過用戶交互行為或者瀏覽器行為(如路由跳轉(zhuǎn)等)觸發(fā)的蚂会,當此類行為會改變數(shù)據(jù)的時候可以通過 dispatch 發(fā)起一個 action,如果是同步行為會直接通過 Reducers 改變 State 耗式,如果是異步行為(副作用)會先觸發(fā) Effects 然后流向 Reducers 最終改變 State胁住。

image

Reducer和effects
reducer 是一個函數(shù)趁猴,接受 state 和 action,返回老的或新的 state 彪见。即:(state, action) => state

app.model({
  namespace: 'todos',
  state: [],
  reducers: {
    add(state, { payload: todo }) {
      return state.concat(todo);
    },
    remove(state, { payload: id }) {
      return state.filter(todo => todo.id !== id);
    },
    update(state, { payload: updatedTodo }) {
      return state.map(todo => {
        if (todo.id === updatedTodo.id) {
          return { ...todo, ...updatedTodo };
        } else {
          return todo;
        }
      });
    },
  },
};

建議最多一層嵌套儡司,以保持 state 的扁平化,深層嵌套會讓 reducer 很難寫和難以維護余指。

app.model({
  namespace: 'app',
  state: {
    todos: [],
    loading: false,
  },
  reducers: {
    add(state, { payload: todo }) {
      const todos = state.todos.concat(todo);
      return { ...state, todos };
    },
  },
});

effects示例

app.model({
  namespace: 'todos',
  effects: {
    *addRemote({ payload: todo }, { put, call }) {
      yield call(addTodo, todo);
      yield put({ type: 'add', payload: todo });
    },
  },
});

Put用于觸發(fā) action捕犬,call用于調(diào)用異步邏輯,支持 promise酵镜。

異步請求

異步請求基于 whatwg-fetch碉碉,API 詳見:https://github.com/github/fetch

GET 和 POST

import request from '../util/request';

// GET
request('/api/todos');

// POST
request('/api/todos', {
  method: 'POST',
  body: JSON.stringify({ a: 1 }),
});

統(tǒng)一錯誤處理

假如約定后臺返回以下格式時,做統(tǒng)一的錯誤處理淮韭。

{
  status: 'error',
  message: '',
}

編輯 utils/request.js垢粮,加入以下中間件:

function parseErrorMessage({ data }) {
  const { status, message } = data;
  if (status === 'error') {
    throw new Error(message);
  }
  return { data };
}

然后,這類錯誤就會走到 onError hook 里缸濒。

Subscription
subscriptions 是訂閱足丢,用于訂閱一個數(shù)據(jù)源,然后根據(jù)需要 dispatch 相應(yīng)的 action庇配。數(shù)據(jù)源可以是當前的時間斩跌、服務(wù)器的 websocket 連接、keyboard 輸入捞慌、geolocation 變化耀鸦、history 路由變化等等。格式為 ({ dispatch, history }) => unsubscribe 啸澡。

異步數(shù)據(jù)初始化
比如:當用戶進入 /users 頁面時袖订,觸發(fā) action users/fetch 加載用戶數(shù)據(jù)。

app.model({
  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(({ pathname }) => {
        if (pathname === '/users') {
          dispatch({
            type: 'users/fetch',
          });
        }
      });
    },
  },
});

react dva 的 connect 與 @connect

connect的作用是將組件和models結(jié)合在一起嗅虏。將models中的state綁定到組件的props中洛姑。并提供一些額外的功能,譬如dispatch
connect 的使用

connect 方法返回的也是一個 React 組件皮服,通常稱為容器組件楞艾。因為它是原始 UI 組件的容器,即在外面包了一層 State龄广。

connect 方法傳入的第一個參數(shù)是 mapStateToProps 函數(shù)硫眯,該函數(shù)需要返回一個對象,用于建立 State 到 Props 的映射關(guān)系择同。

簡而言之两入,connect接收一個函數(shù),返回一個函數(shù)敲才。

第一個函數(shù)會注入全部的models裹纳,你需要返回一個新的對象择葡,挑選該組件所需要的models。

export default connect(({ user, login, global = {}, loading }) => ({
    currentUser: user.currentUser,
    collapsed: global.collapsed,
    fetchingNotices: loading.effects['global/fetchNotices'],
    notices: global.notices
}))(BasicLayout);

// 簡化版
export default connect( 
  ({ user, login, global = {}, loading }) => {
        return {
          currentUser: user.currentUser,
          collapsed: global.collapsed,
          fetchingNotices: loading.effects['global/fetchNotices'],
          notices: global.notices
        }
  }
)(BasicLayout);

@connect的使用

其實只是connect的裝飾器痊夭、語法糖罷了刁岸。

// 將 model 和 component 串聯(lián)起來
export default connect(({ user, login, global = {}, loading }) => ({
    currentUser: user.currentUser,
    collapsed: global.collapsed,
    fetchingNotices: loading.effects['global/fetchNotices'],
    notices: global.notices,
    menuData: login.menuData,         
    redirectData: login.redirectData, 
}))(BasicLayout);
// 改為這樣(export 的不再是connect,而是class組件本身她我。)虹曙,也是可以執(zhí)行的,但要注意@connect必須放在export default class前面:
// 將 model 和 component 串聯(lián)起來
@connect(({ user, login, global = {}, loading }) => ({
  currentUser: user.currentUser,
  collapsed: global.collapsed,
  fetchingNotices: loading.effects['global/fetchNotices'],
  notices: global.notices,
  menuData: login.menuData,        
  redirectData: login.redirectData, 
}))

export default class BasicLayout extends React.PureComponent { 
   // ...
}

export default connect(從 model 的 state 中獲取數(shù)據(jù))(要將數(shù)據(jù)綁定到哪個組件)

以上部分內(nèi)容摘自 https://blog.csdn.net/zhangrui_web/article/details/79651812

2番舆、Ant-Design的框架

這款基于React開發(fā)的UI框架酝碳,界面非常簡潔美觀,是阿里巴巴旗下螞蟻金融服務(wù)集團(旗下?lián)碛兄Ц秾毢薇贰⒂囝~寶等產(chǎn)品)所設(shè)計的一個前端UI組件庫疏哗。目前支持了React, 并且有一個對Vue支持的測試版本禾怠。

學(xué)習和使用Ant-Design返奉,我們可以使用VSCode來對項目代碼進行維護和編輯,這樣可以在Mac和Window環(huán)境同樣的開發(fā)體驗和操作模式吗氏,非常方便芽偏。

如果需要掌握Ant-Design框架,我們需要了解model弦讽,namespace污尉,connect,dispatch往产,action被碗,reducer ,effect這些概念仿村。

DVA 的 model 對象有幾個基本的屬性介紹锐朴。

  1. namespace:model 的命名空間,只能用字符串蔼囊。一個大型應(yīng)用可能包含多個 model包颁,通過namespace區(qū)分。

  2. state:當前 model 狀態(tài)的初始值压真,表示當前狀態(tài)。

  3. reducers:用于處理同步操作蘑险,可以修改 state滴肿,由 action 觸發(fā)。reducer 是一個純函數(shù)佃迄,它接受當前的 state 及一個 action 對象泼差。action 對象里面可以包含數(shù)據(jù)體(payload)作為入?yún)⒐笊伲枰祷匾粋€新的 state。

  4. effects:用于處理異步操作(例如:與服務(wù)端交互)和業(yè)務(wù)邏輯堆缘,也是由 action 觸發(fā)滔灶。但是,它不可以修改 state吼肥,要通過觸發(fā) action 調(diào)用 reducer 實現(xiàn)對 state 的間接操作录平。

  5. action:action 就是一個普通 JavaScript 對象,是 reducers 及 effects 的觸發(fā)器缀皱,形如{ type: 'add', payload: todo }斗这,通過 type 屬性可以匹配到具體某個 reducer 或者 effect,payload 屬性則是數(shù)據(jù)體啤斗,用于傳送給 reducer 或 effect表箭。

整體的數(shù)據(jù)流向見下圖:

image

在Reducer里面,不要修改傳入的 state钮莲。 使用 Object.assign() 新建了一個副本免钻。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter }),因為它會改變第一個參數(shù)的值崔拥。你必須把第一個參數(shù)設(shè)置為空對象极舔。

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

或者使用使用對象展開運算符(Object Spread Operator)來處理,從而使用 { ...state, ...newState } 達到相同的目的握童。

  reducers: {
    save(state, action) {
      return {
        ...state,
        ...action.payload,
      };
    },
  },

在 default 情況下返回舊的 state姆怪。遇到未知的 action 時,一定要返回舊的 state澡绩。

每個 reducer 只負責管理全局 state 中它負責的一部分稽揭。每個 reducer 的 state 參數(shù)都不同,分別對應(yīng)它管理的那部分 state 數(shù)據(jù)肥卡。

下面兩種合成 reducer 方法完全等價:

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})

function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}

dva封裝了redux溪掀,減少很多重復(fù)代碼比如action reducers 常量等,dva所有的redux操作是放在models目錄下步鉴,通過namespace作為key揪胃,標識不同的模塊state,可以給state設(shè)置初始數(shù)據(jù)氛琢。

reducers跟傳統(tǒng)的react-redux寫法一致喊递,所有的操作放在reducers對象內(nèi)

image

Effect 被稱為副作用,在我們的應(yīng)用中阳似,最常見的就是異步操作骚勘,Effects 的最終流向是通過 Reducers 改變 State

其中上面的effects里面,call, put其實是saga的寫法俏讹,dva集成了saga当宴,可以參考上圖中的saga內(nèi)容

image

DVA 首先是一個基于 redux 和 redux-saga 的數(shù)據(jù)流方案,然后為了簡化開發(fā)體驗泽疆,DVA 還額外內(nèi)置了 react-router 和 fetch户矢,所以也可以理解為一個輕量級的應(yīng)用框架。

DVA 是基于現(xiàn)有應(yīng)用架構(gòu) (redux + react-router + redux-saga 等)的一層輕量封裝殉疼,沒有引入任何新概念梯浪,全部代碼不到 100 行。

在Ant-Design的Pages/.umi目錄里面株依,有一個initDva.js文件驱证,就是用來統(tǒng)一批量處理 DVA 的引入的,如下所示恋腕。

image

在有 DVA 之前抹锄,我們通常會創(chuàng)建 sagas/products.js, reducers/products.jsactions/products.js,然后在這些文件之間來回切換荠藤。

有了 DVA 后伙单,它最核心的是提供了 app.model 方法,用于把 reducer, initialState, action, saga 封裝到一起哈肖,這樣我們在書寫代碼的時候吻育,把它主要內(nèi)容,和加載分離出來淤井。如果建立的Model比較多布疼,每次開始的時候需要加入這一句好像也是挺麻煩的,如果可以自動把這個model批量加入币狠,應(yīng)該會更好吧游两,不過不知道是基于什么考量。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漩绵,一起剝皮案震驚了整個濱河市贱案,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌止吐,老刑警劉巖宝踪,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碍扔,居然都是意外死亡瘩燥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門不同,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厉膀,“玉大人,你說我怎么就攤上這事≌掘穑” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵卓鹿,是天一觀的道長菱魔。 經(jīng)常有香客問我,道長吟孙,這世上最難降的妖魔是什么澜倦? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮杰妓,結(jié)果婚禮上藻治,老公的妹妹穿的比我還像新娘。我一直安慰自己巷挥,他們只是感情好桩卵,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著倍宾,像睡著了一般雏节。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上高职,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天钩乍,我揣著相機與錄音,去河邊找鬼怔锌。 笑死寥粹,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的埃元。 我是一名探鬼主播涝涤,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亚情!你這毒婦竟也來了妄痪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤楞件,失蹤者是張志新(化名)和其女友劉穎衫生,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體土浸,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡罪针,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了黄伊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泪酱。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墓阀,到底是詐尸還是另有隱情毡惜,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布斯撮,位于F島的核電站经伙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勿锅。R本人自食惡果不足惜帕膜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望溢十。 院中可真熱鬧垮刹,春花似錦、人聲如沸张弛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乌庶。三九已至种蝶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞒大,已是汗流浹背螃征。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留透敌,地道東北人盯滚。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像酗电,于是被迫代替她去往敵國和親魄藕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容