你不知道的组件化开发:组件化的前世今生 - by winter

正文

大家好,我是winter,今天我分享的主题是“你不知道的组件化开发:组件化的前世今生”。

今天前端生态里面,React、Angular和Vue三分天下。虽然这三个框架的定位各有不同,但是它们有一个核心的共同点,那就是提供了组件化的能力。W3C也有Web Component的相关草案,也是为了提供组件化能力。今天我们就来聊聊组件化是什么,以及它为什么这么重要。

其实组件化思想是一种前端技术非常自然的延伸,如果你使用过HTML,相信你一定有过“我要是能定义一个标签就好了”这样的想法。HTML虽然提供了一百多个标签,但是它们都只能实现一些非常初级的功能。

但是,HTML本身的目标,是标准化的语义,既然是标准化,跟自定义标签名就有一定的冲突。所以从前端最早出现的2005年,到现在2019年,我们一直没有等到自定义标签这个功能,至今仍然是Draft状态。

不过有同学会问:自定义标签也不等于组件化本身啊。没错,不过可以进一步思考,其实我们需要的并不一定是自定义标签这样的一个形式,可以从软件工程的通用思想来解释,组件化可以拆解为四个更基本的概念:

  • 复用:组件将会作为一种复用单元,被用在多处。
  • 解耦:组件本身隔离了变化,组件开发者和业务开发者可以根据组件的约定各自独立开发和测试。
  • 封装:组件屏蔽了内部的细节,组件的使用者可以只关心组件的属性、事件和方法。
  • 抽象:组件通过属性和事件、方法等基础设施,提供了一种描述UI的统一模式,降低了使用者学习的心智成本。

我们可以进一步分析,其实组件化并非前端独有的一种需求,任何软件开发过程,或多或少都有那么一些组件化的需求。而你可以思考为什么较好的组件化解决方案(三大框架)会出现在2014年左右这个时间点,这是一个耐人寻味的问题。而我认为这个问题的答案,就藏在前端发展的历史当中。

我们可以看下复用、解耦、封装、抽象这四个概念,很显然,它们都是为了大型的、多人协作的软件开发所准备的。所以我认为,2014年左右这个时间点,正是前端发展到了一个需要规模化的时间点。

我们纵观前端的发展历程,开始于2005年左右,是特效为王的时代,前端领域追逐的是炫酷的效果;到了2009年左右,jQuery开始统治前端,这时API易用性和浏览器兼容性是最重要的;等到了2012年移动互联网出现之后,提供组件化方案的“三大框架”逐步占据了前端重要的生态地位。

接下来我们深入具体的技术细节,看看组件化是如何一步步发展的。

首先,标准的DOM元素是这样创建和挂载的:

01.jpg

这里的element是一个对象,但是其实JavaScript(早期)里面,根本没法创建这样的对象。一方面也是我们没有办法改变createElement这个函数的行为。

既然API风格没法靠拢DOM原生,那么就靠拢JS原生吧,所以一些前端开发同学就萌生了创建一个带容器的对象的想法:

02.jpg

不过,要想挂载又成了难题,普通的JS对象没法被用于appendChild,所以前端工程师就有了两种思路,第一种是反过来,设计一个appendTo方法,让组件把自己挂到DOM树上去。

03.jpg

第二种比较有意思,是让组件直接返回一个DOM元素,把方法和自定义属性挂到这个元素上:

04.jpg

虽然从前端全领域来看,组件化到后期(2014年)才有比较普及的应用,但是早年用这样的思路实现组件体系的方案并不少,说明组件化在一些公司和领域始终有需求。虽然当时有一些组件化方案没能够影响行业,但是不可否认它们也还算是不错的解决方案,比如著名的ExtJS(现已更名为Sencha),我们来看看它的组件定义:

05.jpg

你可以看到,这是一个完全使用JS来实现组件的体系,它定义了严格的继承关系,这样的方案很好地支撑了ExtJS的前端架构。

不过,创建和挂载对象的方式可不止DOM API,还有HTML语言,如何让前端组件融合进HTML语言呢?

06.jpg

关于这方面,依赖早期标准的前端技术可以说几乎没有办法。但是,历史中总有些遗珠,微软的IE浏览器已经提供了组件化的解决方案,名为HTML Component,我找了一段非常古老的示例。

07.jpg

这项技术提供了事件绑定和属性、方法定义,以及一些生命周期相关的事件,应该说已经是一个比较完整的组件化方案了。但是我们可以看到后来的结果,它没有能够进入标准,默默地消失了。用我们今天的角度来看,它可以说是生不逢时。

到了“三大框架”出现的时代,因为面向的客户群体从少数公司、少数领域变成了广大前端开发群众,也因为一些新技术的出现,让旧时代组件化没法解决的问题有了新的可能性,这些新的组件化方案都保持了HTML甚至CSS的书写习惯。

Vue.js采用了JSON的方法描述一个组件:

08.jpg

还提供了SFC(Single File Component,单文件组件)“.vue”文件格式:

09.jpg

React.js发明了JSX,把CSS和HTML都塞进JS文件里:

10.jpg

Angular.js选择在原本的HTML上扩展,定义组件的方式也是JS class:

11.jpg

我们可以看到,现代的组件化方案跟旧时代组件化方案的一个明显区别就是,现在的组件化方案保留了原有的标记语言部分,并且努力保留了样式表部分。

虽然技术从旧到新经历了各种变迁,但是组件化的核心并没有变化,我们的目标仍然是在API设计尽可能接近原生的情况下完成复用、解耦、封装、抽象的目标,最终服务于开发,提高效率,降低错误发生比率。

如果你的公司和前端团队规模正好面临需要建立组件化体系,希望你能从今天所分享的历史中获得一点灵感。

Q & A

Q1:我想请问老师,组件的颗粒度什么样比较合适?它的包含度又应该是什么样的范围
A:其实组件的粒度是无所谓的,它是一个级联的结构,所以说可以有粗粒度的组件,也可以有细粒度的组件。我个人觉得用体系这个词去替代粒度的这个概念会更好。你怎样设计细粒度组件,又怎样设计这个粗粒度组件,最后是要形成一个很好用的体系,这个组件体系可以满足你,简单地用,复杂地用,现成地用,大块儿地用...

Q2:组件化了还能做SEO么?
A:如果你对现代的这个seo有一定的了解的话,你会发现seo其实已经走到了一条完全不一样的道路上了。我们现在很多的seo方案是服务端专门渲染一套给搜索引擎去看的一个页面。如果我们正常的使用各种组件化的技术,这肯定会对seo产生负面影响。毕竟你用了一个自定义的标签,而浏览器并不认识。不过其实浏览器也有专门专门写给搜索引擎看的一些标签。

Q3:如果避免组件过度封装?
A:其实我觉得现在大家还没有到担心过度封装的时候,但是大家往往会发生一种没有封装的情况。当然,这个状态也会比较危险,当你从没有封装过,然后突然发现封装这玩意儿很好用,就可能去过度封装了。实际上针对封装,我们应该去了解一些基本的知识。比如什么叫对象?为什么要把这些方法和属性,聚合成一个对象?这里有个概念就是局部性,当你把有局部性东西聚合起来,就是好的;你把没有局部性的聚合起来,就会适得其反。当然这只是个例子,主要是讲一下面向对象的基本原则,当你学习了这些,就不会过度封装。

Q4:如何进行组件化的体系设计?
A:UI组件可以参考w3c的AccessibilityInitiativeWAI标准,它把人类常用的组件规范都总结完了,它本身不是组件,但是你去设计组件的去参考它是非常好的。

Q5:老师能讲讲微服务吗?
A:微服务这个概念本身就是从后端来的一个纯粹的概念,我还没见过谁在前端搞微服务。服务这个词对前端页面的维度来说有点大,前端是没有服务这个概念的,更何况再衍生出来微服务的这个概念。微服务主张一个服务,只做一件事情,专注做好自己的这一块儿;而不是去混合成业务场景,去设计API。从某种意义上讲,我认为,一个前端页面,甚至有可能都不一定有一个后端的微服务大。所以我不建议把微服务引进前端里面来。

Q6:关于WebComponent、TypeScript、WebAssmebly
A:关于WebComponent、TypeScript、WebAssmebly未来的问题,一句话讲就是,我也不知道啊。未来的东西真的不太好预测,但是目前来看,我认为这三个东西都是偏向比较积极的,都是高速发展的,都是快速落地的。但是,你说它会不会真的超过现有的一些东西,它能发展到什么程度,就很难说了。我建议这三个东西是应该被积极对待的,都去学一下,毕竟可能哪个未来就会成为一块儿很重要的东西,而且它们也确实能够解决我们很多前端开发的问题。

Q7:组件设计好了之后,怎么样去给它添加功能?怎么样去迭代?
A:坦白地讲,这是个难题。虽然我可以讲出很多听起来特别有道理的理论。但事实上,曾经在淘宝发生的一件事,就是一个轮播,最后被加了30多个参数。当然,这个背后,当然我还是要讲一下关于这个的理论知识。我们设计组件也是遵循面对象的基本原则,对修改封闭,对扩展开放。所以,如果我们能做好这件事,比如说有个新功能,你就可以选择继承,然后包装这个组件,形成一个新的组件来完成这个新功能。这样,组件体系就会变丰富,不会有很多的麻烦。

Q8:关于GraphQL
A:我在以前在淘宝工作的时候,我们的GraphQL也没能真正落得特别好。而且,据我了解,GraphQL在Facebook自己那边,其实落地也不是特别好。我觉得这个理念特别先进,叫backend for frontend。我认为它是BFF的一个延伸,但是其实很少公司是这么落地的。比如说在淘宝,其实我们落了一个半像不像的一个东西,就是有些API,我们可以通过后端去写GraphQL来生成出来。最后你前端看到的还是一堆数据,但是其实这个和GraphQL原本的设计不太一样。

Q9:在开发公司内部组件库时,有必要去设计组件间的消息机制吗?如果有必要,能带来哪些明显的优势?
A:其实我不太建议有一个postmessage这样的机制。我记得在2015年左右,那时候听阿里的晋升面试,每个人都要讲一下他设计的组件体系和他组件间的消息通讯机制,有用消息池的,有用各种分发的策略的。但其实这个东西到最后,你这个消息进了这样的消息机制,你debug的链路就断了,消息过去了,你不知道后面执行了什么代码。所以,我不太建议做这种直接的消息机制。但你看现在,Flux、Redux这样的东西,其实它都是一种变形的消息机制。其实我比较推崇的是vue和angular的双向绑定。它其实不完全是一种消息机制,当你去操纵这个数据,数据变化以后,所有跟这个数据相关联的组件都会产生变化。实际上,这个里面是有消息流转的,你去看vue的源代码里面,它一定是有消息通知过来,这个代码变了,那边去observe这个属性。这样其实替代了某种程度上的消息,但它的语义化更好,它不是一个未经定义的所谓的消息,告诉你,你自己去拿一个字符串去定义,而是一种给你规定好了的数据变化形的消息,甚至规定了你应该如何响应它,我认为这种模式能够减少开发出错。

Q10:有vue 页面首次加载白屏好的方案吗?
A:vue页面首次加载白屏,你需要看一看是不是它的模板没有预编译。一般来说,vue首次加载白屏,都是因为在运行时,引用了vue的调试版本,所以导致它加载白屏,因为它要编译你的代码。我们一般来说用vue-cli创建的都是没有这个问题的。如果你的情况比较特殊,我建议你直接去vue社区留言,他们很喜欢这样的例子。如果你有这种真正意义上的会导致它白屏的案例,他们会很积极的帮你去设计方案去解决,看到底哪儿出了问题。

Q11:能聊聊关于页面性能吗?
A:从工程的角度来看,性能跟组件化其实是两个方向,所以说性能跟这次组件化分享没有关系。但是可以稍微聊一下,凡是工程体系,都需要有一些监控手段,评估手段。所以,你做性能的时候,肯定先建立这个标准,能够知道这个页面的性能怎么样,然后再去看怎么去优化它。网上有很多关于性能优化的这样的方法论,还能找到各种各样很好的性能优化的小细节,在此就不赘述了。

Q12:传统的架构师大多数是后端过来的,相对于后端架构师,我们前端在架构方面怎么能最大的体现价值?或者什么才是一个前端“架构师”?
A:这个问题挺不错的。前端架构师面临的问题,实际上跟别的架构师不太一样。前端是零碎的页面,大量的重复性劳动,而不是传统软件意义上来讲的,模块间的耦合和复杂性。所以,前端架构师重点关注的应该是复用,这就跟咱们今天聊的组件化非常相关了。我原来带手淘架构组的时候,基本上都是在做组件体系的事,先把复用做上来。
另外,前端架构跟服务端架构、客户端软件架构不太一样的地方就是,前端架构还是可以用一些工具去解决的。比如淘宝特别重视的dajian系统,是我们的工程体系里面的很重要的一环,可以大量地产出简单的页面,不需要经过前端,通过模板化或者是模块化的方法。

Q13:可是angularJS这种脏检查模式,在一些情况下,影响性能,有要注意改进的地方嘛?
A:angularJS的脏检查模式出来的第一天,我就在喷这个事情,真的影响性能。不过你会看到这个问题,其实是一个技术问题,是因为angularJS最开始的这个设计者的技术不过关造成的。技术问题在其影响力足够大了以后,一定会有人出来解决。比如后面JS标准也出了proxy。所以在angular2,这个问题已经得到了很好的处理,只要你去angular社区逛逛,是可以找到这个问题的答案的。

Q14:有必要向全栈发展吗?
A:说实话,目前来看,在中国国内,全栈的市场不是特别的看好。另外,我特别不建议你去做node全栈,不是我说我看不起node,反正是我觉得node难。node的局面,可以说是百废待兴。你拿node做server,想拿这个server真正能够稳定地跑到线上,能够自己重启,其实要写的代码是非常多的。所以只有阿里的那几位大神,宿迁、朴灵啊...他们在阿里才能搞起一块儿node这样的场景。其实他们也很艰难,所以我不建议转node全栈。
如果你拿一个已经很火的这种后端语言,比如python、ruby或Java跟JS搭一下去做这个全栈,我觉得肯定是可以。多学一门手艺肯定是好的,但就看你自己怎么分配精力了。另外还要看全栈是不是在市场上有足够的岗位。现在来看全栈的岗位,更偏向小公司一些;或者说你自己要创业,那你搞个全站是很好的。所以我觉得职业发展是个人的偏好问题,没有一个放之四海皆准的规则。我也不可能给你一个建议说,你就做全站吧,别做全栈了。其实我自己也是会一点儿服务端的,但是我肯定不会往全栈去做,因为我觉得精力跟不上。

Q15:电商网站,金额计算有必要前后端都进行计算嘛?
A:这个问题问得比较奇怪。一般来说金额计算是要后端进行计算的。至少在阿里,我们很少拿前端去做计算一些折扣这样的事情。这里面主要是从这个风险上,去评估一些问题。比如说你客户端版本更新不上,造成优惠错误,可能就会产生一些资损。当然这个东西没有绝对一说,比如购物车里的总价要不要到服务端去再绕一圈儿,那我觉得不用。所以我认为这个问题还是要分析一下具体情况。

Q16:老师,请教一下,以前面试的时候听面试官说,前端后期需要了解http协议制定规范,还有需要回设计模式甚至用nodejs成为web全栈,这是成长必须的么?
A:HTTP协议规范,我觉得有用。但是,其实你真要去问的话,一般人知道得HTTP状态码,我认为不会超过10个。所以知道HTTP协议的细节要求是有点偏高得,这不是必要的,但是我觉得它肯定在某些时候会有用。所以从学习的角度来讲,我建议你学。从面试的角度来讲,我觉得这是个无理要求,因为你也不知道他要考哪儿。
关于设计模式,其实你要知道设计模式这本书,其实他是为基于类的面向对象来编写的。所以说很多模式在JS这边,要么不需要,要么不能用。比如说这个工厂模式,它解决new的时候,class的东西没法传的问题。实际上,在JS里有这个问题吗?我们的JS构造器就可以把class当作函数参数往里传,所以根本就不存在工厂模式的这种需求,不管是抽象工厂还是builder,还是不在23模式中的简单工厂模式,都不需要,我们没这需求。

Q17:flutter最近很火的,作为前端有必要学吗?
A:我觉得你要是什么技术火学什么,那估计,你要累死。其实最火的不是flutter,应该是区块链和AI,但你不太可能往那边转。学flutter也是一样的道理。如果觉得你很喜欢flutter,你就去学。但是你要知道flutter是一个全新的体系,它其实跟我们传统的前端开发已经有一定的隔离了。我认为它会是一个比较小众的东西,因为我觉得它确实做的很好,所以它能够有自己的一席之地。

Q18:前端是否应该有一套自己的设计模式?
A:前端的设计模式是有的,之前有一个比较火的石川就讲了一些前端自己的设计模式。http://shichuan.github.io/javascript-patterns/
但是我们前端其实不太有这个像JOF思维大神一样的这种人物,所以说我们可能总结的模式都比较弱。其实前端还是一个很年轻的行业,所以有一些小的模式——微模式,其实已经是很成熟了。但是你说达到JOF总结的23模式这种水平的,我觉得客观上会存在,但是咱们水平不够,总结不出来。

Q19:关于数据结构和算法。
A:我觉得大家把数据结构和算法想复杂了。你说for循环是不是算法,它就是一种简单的算法。你说数组是不是数据结构,它当然是数据。在数据结构里,数组叫做顺序表,如果你有数据结构的书,你就可以找到,它跟链表相对。我们去学数据结构和算法学的是什么呢?这个其实叫做经典数据结构和经典算法。所以我不特别建议你去一个一个的去啃这些经典数据结构和经典算法。但是我建议你提高自己的数据结构和算法的水平。
另一方面,大家有时候认为公司出的算法题,觉得是不应该,都不需要考前端。但其实,在我看来,我们大部分公司考的那个所谓的算法题,它都是一个简单的小程序。实际上,我们的代码跟算法其实没那么容易分开,他们的边界是很模糊的。你说你写一个复杂一点的小程序,它就是一个算法,你写一个简单一点儿的算法,它就是我们日常写的一段代码,两个没区别。广义的算法就是你写的,只要是有逻辑的代码都叫算法。

Q20:webgl会火吗?感觉现在招webgl的也不多,学webgl光学three.js可以吗?
A:我觉得这块作为一种知识储备,可以去学一下。当然我是比较推荐资深一点的前端,希望寻求突破的时候往这个方向去走一走。如果你本身在前端的基础,CSS、HTML和JS这些方面都没有拿到优势的话,你并不面临瓶颈,你没有必要往这个方向去发展。因为你的竞争对手有很多,还有c++一堆大神可能对这边有兴趣,要转过来。

Q21:请问老师,自己公司的app 能在chrome 上模拟注入某段代码判断是这个app 么,类似于判断是微信,是支付宝,是浏览器这种😂,因为我们很多h5页面都是内嵌在自己app 中的
A:这个问题特别的具体。如果你的webview是你自己写的,那一定是能。如果你是要到浏览器里的话呢,这个也是可以的,你可以注册一段协议,然后拉起自己的APP,然后打开了之后,再回来。所以,这个综上所述就是能,不过你们要是在自己的webview里打开,那就很方便,你只需要用密码学手段注册一个特殊的字符串儿进去,然后比如说跟时间有关的,就加个签名,然后让这个去网页的代码去判断这个签名就可以了。
当然呢,大部分的时候,其实我们没有这么高的要求,比如说淘宝,我们判断这个页面是不是在自己的APP里面,其实我们就加了一段UA,没有我前面说的那个什么签名之类的奇特的手段。因为我们不需要这种安全性检查,我们只需要判断一下是不是。如果你骗我了,就骗我了,也无所谓。

Q22:webgpu要是出来的话webgl现在还有接触的必要吗?
A:这个问题,其实我觉得真的挺好的啊。但这个问题,其实我也不太能够回答。webgpu和webgl的这个事儿背后有很多政治因素,很难研究明白,主要看两大组织如何角力。所以我的建议是去学计算机图形学,不要把宝押在他们两个身上。

Q23:webassembly老师讲过吗?怎么看这个东西?
A:webassembly我讲过。webassembly现在有很多的问题,但是总体来说,我认为是看好的。但是看好到什么程度?其实我刚才说了,我没法判断他会不会取代整个JS,虽然我觉得这可能性不大,但也不是没有。总的来说,我觉得如果你认为这个方面能够解决你的问题,你就可以多投资一点。webassembly涨肯定是会涨,但能涨到多少钱我就不知道了,就像大家买股票一样的。

Q24:带前端技术团队的时候,如何科学的做绩效评定?或者制定一种和公司员工等级独立开的技术等级的评定机制?
A:如何评估绩效这个问题,是人类有公司以来的一个千古难题。如何衡量一个创造性工种的产出,我可以介绍一下我自己的方法。其实很简单,就是我们要保证一个初心,在你评估的时候,不要掺杂任何你不应该掺杂的东西。比如这个人跟你关系很好;这个人他最近这个心情不好,刚分手;那个人家里困难...评估绩效的时候,主要根据大家的工作量去评估,事先排好,然后再评估。你不确定的,就互相比比,看看是这个人好还是那个人好。

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