Flutter中的Widget瘦身

一切皆是Widget淮椰,但是不要把所有內(nèi)容都放置在一個(gè)Widget中

這是一篇翻譯文章:
原文:Everything is a Widget, but don’t put everything in one Widget!
作者:Romain Rastel

讀后感:
移動(dòng)端原生Android、iOS通常使用一種命令式的編程風(fēng)格來完成UI編程,這可能是大家最熟悉的風(fēng)格。Flutter則不同宠叼,讓開發(fā)人員只是描述當(dāng)前的UI狀態(tài)妈橄,并將轉(zhuǎn)換交給框架。在Flutter開發(fā)中寫的最多的就是各式各樣的Widget了富蓄,想必大家都有寫過很長(zhǎng)的Widget的經(jīng)驗(yàn),在同一個(gè)build函數(shù)里慢逾,Widget可以通過child屬性一層層的嵌套立倍,在相同的作用域下共享變量,寫起來很方便侣滩。但是當(dāng)需要狀態(tài)刷新的時(shí)候口注,則會(huì)刷新整個(gè)層級(jí),導(dǎo)致性能損失君珠,想必大家也想過該怎么組織Flutter代碼寝志,這篇文章將會(huì)給你帶來一些參考。

這篇文章在medium上有800多贊,對(duì)于Flutter社群是相當(dāng)高的數(shù)量了材部,說明大家比較認(rèn)可這種方式毫缆。文章以一個(gè)UI頁面為例,介紹了怎么組織代碼乐导,為什么要這樣組織代碼苦丁,以及如何通過代碼塊進(jìn)一步提升開發(fā)效率與性能。


正文:
作為一個(gè)Flutter開發(fā)者物臂,我肯定在你的職業(yè)生涯中至少聽過一次這樣的話:"一切都是Widget"旺拉,這是Flutter的一種口頭禪,也揭示出了這個(gè)優(yōu)秀的SDK的內(nèi)在力量棵磷。

當(dāng)我們深入進(jìn)catalog這個(gè)Widget中蛾狗,我們可以看到有很多Widget做著單一的工作,比如Padding仪媒、Align沉桌、 SizedBox等,我們通過組合這些小的Widge來創(chuàng)建其他Widget规丽,我發(fā)現(xiàn)這種方式可擴(kuò)展蒲牧、功能強(qiáng)大且容易理解。

但是當(dāng)我閱讀一些在網(wǎng)上找到的源碼或者新手編寫的代碼后赌莺,我發(fā)現(xiàn)一件事情使我震驚:build 構(gòu)造方法有越來越大的趨勢(shì),并且在其中實(shí)例化了很多Widget松嘶,我發(fā)現(xiàn)這樣很難閱讀艘狭、理解和維護(hù)。

···

作為軟件開發(fā)人員翠订,我們必須記住巢音,軟件的生命起始于當(dāng)它第一次發(fā)布給其他用戶。該軟件的源碼也將會(huì)由他人(包括將來的你自己)來閱讀尽超、維護(hù)官撼,這就是為什么我們的代碼應(yīng)該保持簡(jiǎn)單、易讀和理解的非常重要的原因似谁。

···

我們能在Flutter官網(wǎng)上找到一個(gè)一切皆是Widget的例子傲绣,本教程的目的就是展示如何構(gòu)建此布局:

layout.png

下面的代碼達(dá)到了展示如何簡(jiǎn)單的創(chuàng)建上述布局目的:正如我們看到的,代碼里面甚至有一些變量和方法巩踏,可以為布局的各個(gè)部分賦予語義秃诵,這點(diǎn)做得很好,因?yàn)樗苁勾a更容易理解塞琼。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = Container(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.only(bottom: 8),
                  child: Text(
                    'Oeschinen Lake Campground',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                Text(
                  'Kandersteg, Switzerland',
                  style: TextStyle(
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ),
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          Text('41'),
        ],
      ),
    );

    Color color = Theme.of(context).primaryColor;

    Widget buttonSection = Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          _buildButtonColumn(color, Icons.call, 'CALL'),
          _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
          _buildButtonColumn(color, Icons.share, 'SHARE'),
        ],
      ),
    );

    Widget textSection = Container(
      padding: const EdgeInsets.all(32),
      child: Text(
        'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
        softWrap: true,
      ),
    );

    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        ),
      ),
    );
  }

  Column _buildButtonColumn(Color color, IconData icon, String label) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}

實(shí)際上菠净,代碼可以寫的更糟糕。下面是是我所不喜歡的典型的,所有代碼在一個(gè)Widget中的版本:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            Container(
              padding: const EdgeInsets.all(32),
              child: Row(
                children: [
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          padding: const EdgeInsets.only(bottom: 8),
                          child: Text(
                            'Oeschinen Lake Campground',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                        Text(
                          'Kandersteg, Switzerland',
                          style: TextStyle(
                            color: Colors.grey[500],
                          ),
                        ),
                      ],
                    ),
                  ),
                  Icon(
                    Icons.star,
                    color: Colors.red[500],
                  ),
                  Text('41'),
                ],
              ),
            ),
            Container(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.call, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'CALL',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.near_me, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'ROUTE',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.share, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'SHARE',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            Container(
              padding: const EdgeInsets.all(32),
              child: Text(
                'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
                'Alps. Situated 1,578 meters above sea level, it is one of the '
                'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
                'half-hour walk through pastures and pine forest, leads you to the '
                'lake, which warms to 20 degrees Celsius in the summer. Activities '
                'enjoyed here include rowing, and riding the summer toboggan run.',
                softWrap: true,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

在第二段代碼中毅往,我們書寫的這個(gè)Widget有一個(gè)大大的build方法牵咙,這樣很難閱讀、理解和維護(hù)攀唯。

現(xiàn)在洁桌,讓我們看看如何將其重寫吧:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter layout demo',
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter layout demo'),
      ),
      body: ListView(
        children: [
          const _Header(),
          const _SubHeader(),
          const _Buttons(),
          const _Description(),
        ],
      ),
    );
  }
}

class _Header extends StatelessWidget {
  const _Header({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Image.asset(
      'images/lake.jpg',
      width: 600,
      height: 240,
      fit: BoxFit.cover,
    );
  }
}

class _SubHeader extends StatelessWidget {
  const _SubHeader({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const _Title(),
                const _SubTitle(),
              ],
            ),
          ),
          const _Likes(),
        ],
      ),
    );
  }
}

class _Title extends StatelessWidget {
  const _Title({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        'Oeschinen Lake Campground',
        style: TextStyle(
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

class _SubTitle extends StatelessWidget {
  const _SubTitle({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      'Kandersteg, Switzerland',
      style: TextStyle(
        color: Colors.grey[500],
      ),
    );
  }
}

class _Likes extends StatelessWidget {
  const _Likes({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        Icon(
          Icons.star,
          color: Colors.red[500],
        ),
        Text('41'),
      ],
    );
  }
}

class _Buttons extends StatelessWidget {
  const _Buttons({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          const _Button(icon: Icons.call, text: 'CALL'),
          const _Button(icon: Icons.share, text: 'ROUTE'),
          const _Button(icon: Icons.share, text: 'SHARE'),
        ],
      ),
    );
  }
}

class _Button extends StatelessWidget {
  const _Button({
    Key key,
    @required this.icon,
    @required this.text,
  })  : assert(icon != null),
        assert(text != null),
        super(key: key);

  final IconData icon;
  final String text;

  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;

    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            text,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}

class _Description extends StatelessWidget {
  const _Description({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(32),
      child: Text(
        'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
        softWrap: true,
      ),
    );
  }
}

您不覺得這樣更具有可讀性嗎?

這樣寫有什么好處革答?

我理解為什么教程不經(jīng)常請(qǐng)這樣做:它需要更多行的代碼(在這個(gè)示例中多了100行)战坤,并且人們可能想知道為什么要?jiǎng)?chuàng)建這么多的Widget。由于教程旨在專注于一個(gè)概念残拐,這樣編寫可能和他們的目標(biāo)適得其反途茫。這樣教學(xué)的后果就是,新手可能傾向于在他們的build方法中放置一個(gè)大的Widget樹溪食,讓我們看看對(duì)布局的每個(gè)部分囊卜,使用一個(gè)單獨(dú)的Widget有什么好處:

可讀性

我們?yōu)椴季值拿總€(gè)部分創(chuàng)建一個(gè)Widget,每個(gè)Widget有他們自己的一個(gè)較小的build方法错沃。由于您不必滾動(dòng)到小部件的末尾才能看到所有代碼栅组,因此這樣更易閱讀。

易理解

每個(gè)Widget都有與之角色相匹配的名字枢析,這被稱為語義命名玉掸。這樣,當(dāng)我們?cè)陂喿x代碼時(shí)醒叁,能夠更容易的在腦海中映射出代碼的哪一部分和我們?cè)贏PP中所看到的內(nèi)容相匹配司浪。這里,我看到了兩個(gè)在易理解性方面的改進(jìn):
1.當(dāng)我們閱讀到某處引用這種Widget的地方時(shí)把沼,我們幾乎無需知道其實(shí)現(xiàn)啊易,即可知道它的功能。
2.在閱讀使用語義命名的Widget的build方法前饮睬,我們已經(jīng)對(duì)它的內(nèi)容有了一個(gè)大致了解租谈。

可維護(hù)性

假如你必須更換一個(gè)組件或者更改某個(gè)部分,則該組件將僅位于一個(gè)地方捆愁,由于每個(gè)組件都得到了很好的定義割去,與其他小組件分開,因此更改不易出錯(cuò)牙瓢。在你APP中的另一個(gè)頁面甚至另一個(gè)APP中·共享布局組件劫拗,也將變得更加容易。

性能

前面的所有原因應(yīng)該都足夠讓您采用這種方式來創(chuàng)建Flutter應(yīng)用程序矾克,除此之外還有一個(gè)優(yōu)勢(shì):提升了應(yīng)用程序的性能页慷,因?yàn)槊總€(gè)Widget都可以獨(dú)立進(jìn)行rebuild(在之前的示例中憔足,如果我們僅用方法method隔離布局的部分,則不會(huì)這樣)酒繁。例如滓彰,假設(shè)我們點(diǎn)擊圖中紅色星星時(shí)需要增加數(shù)字,在重構(gòu)版本中州袒,我們可以將_Likes制作成一個(gè)StatefulWidget揭绑,并且在其內(nèi)部處理數(shù)字增加,當(dāng)我們點(diǎn)擊星星時(shí)郎哭,只有_LikesWidget會(huì)被rebuild他匪。而在第一個(gè)版本中,假如將MyApp制作成一個(gè)StatefulWidget夸研,整個(gè)Widget都將會(huì)被rebuild邦蜜。

Flutter官方文檔中也對(duì)最佳實(shí)線做了說明。

State中調(diào)用setState()時(shí)亥至,所有子節(jié)點(diǎn)都將會(huì)被重建悼沈。因此將setState()的調(diào)用放置到真正需要改變UI的子樹上。如果只更改包含子樹的一小部分姐扮,避免在子樹的上層調(diào)用setState()絮供。

When setState() is called on a State, all descendent widgets will rebuild. Therefore, localize the setState() call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree.

另一個(gè)優(yōu)勢(shì)是可以更多的使用const關(guān)鍵字的功能,Widgets可以被被緩存和重用茶敏,如Flutetr文檔所述:

與重新創(chuàng)建新的Widget(配置相同)相比壤靶,重用Widget效率要高的多。

It is massively more efficient for a widget to be re-used than for a new (but identically-configured) widget to be created.

如何進(jìn)一步提升生產(chǎn)力

如您所見惊搏,在布局的每個(gè)語義部分創(chuàng)建一個(gè)Widget萍肆,我們編寫了很多代碼。我們可以使用Visual Studio Code中Dart擴(kuò)展提供的stlessstful代碼段胀屿,但是它們不會(huì)生成const構(gòu)造函數(shù)。

為了滿足我自己的需求包雀,我創(chuàng)建了新的代碼段宿崭,稱之為slesssful,這樣我的生產(chǎn)力比以往任何時(shí)候都更高才写。如果你想要在Visual Studio Code中使用它們葡兑,則必須遵從此文檔并添加一下內(nèi)容:

{
  "Flutter stateless widget": {
        "scope": "dart",
        "prefix": "sless",
        "description": "Insert a StatelessWidget",
        "body": [
            "class $1 extends StatelessWidget {",
            "  const $1({",
            "    Key key,",
            "  }) : super(key: key);",
            "",
            "  @override",
            "  Widget build(BuildContext context) {",
            "    return Container(",
            "      $2",
            "    );",
            "  }",
            "}"
        ]
    },
    "Flutter stateful widget": {
        "scope": "dart",
        "prefix": "sful",
        "description": "Insert a StatefulWidget",
        "body": [
            "class $1 extends StatefulWidget {",
            "  const $1({",
            "    Key key,",
            "  }) : super(key: key);",
            "",
            "  @override",
            "  _$1State createState() => _$1State();",
            "}",
            "",
            "class _$1State extends State<$1> {",
            "  @override",
            "  Widget build(BuildContext context) {",
            "    return Container(",
            "      $2",
            "    );",
            "  }",
            "}"
        ]
    },
}

如何將這種做法與狀態(tài)管理結(jié)合起來?

如您所知赞草,在Flutter中有很多種狀態(tài)管理解決方案讹堤。我并不會(huì)列出哪些可以和這種編碼方式相結(jié)合的很好,而是會(huì)列出您在選擇最適合您的狀態(tài)管理方案時(shí)應(yīng)該知道的一些關(guān)鍵概念厨疙。

  1. 狀態(tài)對(duì)于Widget來說應(yīng)該是可直接獲取的洲守,而不應(yīng)該通過它的構(gòu)造函數(shù)來傳遞。否則,你將不得不通過一些原本不應(yīng)該關(guān)注的Widget來傳遞它梗醇。
  2. 僅當(dāng)與當(dāng)前Widget有關(guān)的狀態(tài)發(fā)生改變時(shí)知允,當(dāng)前Widget才應(yīng)該被重新rebuild。如果不是這種情況叙谨,Widget可能會(huì)被重建太多次温鸽,可能會(huì)有損性能。

我認(rèn)為手负,效果最好的解決方案是基于InheritedWidget或者相同概念的解決方案涤垫。例如,你可以參考Provider+X(X是能夠通知狀態(tài)更改的類)或者Maestro竟终。

結(jié)論

我堅(jiān)信這是編寫Flutter應(yīng)用程序的好方法蝠猬,希望您也堅(jiān)信。如果不是這樣衡楞,我對(duì)你的意見很感興趣吱雏!

從現(xiàn)在開始,記住下面口頭禪:“一切皆是Widget瘾境,但是不要把所有內(nèi)容都放置在一個(gè)Widget中歧杏!”。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迷守,一起剝皮案震驚了整個(gè)濱河市犬绒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兑凿,老刑警劉巖凯力,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異礼华,居然都是意外死亡咐鹤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門圣絮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祈惶,“玉大人,你說我怎么就攤上這事扮匠∨跚耄” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵棒搜,是天一觀的道長(zhǎng)疹蛉。 經(jīng)常有香客問我,道長(zhǎng)力麸,這世上最難降的妖魔是什么可款? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任育韩,我火速辦了婚禮,結(jié)果婚禮上筑舅,老公的妹妹穿的比我還像新娘座慰。我一直安慰自己,他們只是感情好翠拣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布版仔。 她就那樣靜靜地躺著,像睡著了一般误墓。 火紅的嫁衣襯著肌膚如雪蛮粮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天谜慌,我揣著相機(jī)與錄音然想,去河邊找鬼。 笑死欣范,一個(gè)胖子當(dāng)著我的面吹牛变泄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播恼琼,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼妨蛹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了晴竞?” 一聲冷哼從身側(cè)響起蛙卤,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎噩死,沒想到半個(gè)月后颤难,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡已维,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年行嗤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垛耳。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昂验,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出艾扮,到底是詐尸還是另有隱情,我是刑警寧澤占婉,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布泡嘴,位于F島的核電站,受9級(jí)特大地震影響逆济,放射性物質(zhì)發(fā)生泄漏酌予。R本人自食惡果不足惜磺箕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抛虫。 院中可真熱鬧松靡,春花似錦、人聲如沸建椰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棉姐。三九已至屠列,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伞矩,已是汗流浹背笛洛。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乃坤,地道東北人苛让。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像湿诊,于是被迫代替她去往敵國(guó)和親狱杰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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