React Native轉(zhuǎn)web方案:react-native-web

在閱讀本篇文章之前,你要有React和React-native相關(guān)經(jīng)驗(yàn)少孝。react-native-web主要將你的react-native應(yīng)用H5化继低,以讓我們能達(dá)到“Write one, run everywhere”。

主要從三個(gè)方面分享 react native 轉(zhuǎn) web 方案:react-native-web

  • react-native-web 的使用
  • react-native-web 源碼分析
  • react-native-web 實(shí)踐

react-naitive: https://github.com/facebook/react-native
react-native-web:https://github.com/necolas/react-native-web

React-native項(xiàng)目引入React-native-web

安裝

npm install react react-dom react-native-web --save

如果使用了 ART稍走,需要安裝 react-art(比如袁翁,使用了 react-native-svg 來(lái)做RN端icon方案,這就是基于 react-art)

npm i react-art --save

安裝好之后婿脸,使用主要分一下兩步:

  • 入口處新增配置
  • webpack配置

入口處新增配置

有兩種方式:

  • 使用 AppRegistry API
  • 使用 render 方法

使用 AppRegistry API

在新增配置之前粱胜,首先看看RN的入口文件:

// index.js
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('rn_web', () => App);

新增配置之后,如下:

// index.web.js
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('rn_web', () => App);

AppRegistry.runApplication('rn_web', {
    rootTag: document.getElementById('react-root')
});

使用 render 方法

使用 render 方法如下:

import { render } from 'react-native';
import App from './App';

render(<App/>, rootTag: document.getElementById('react-root'));

可以看到狐树,AppRegistry API 更貼近RN的寫法焙压,render 方法跟 ReactDOM.render 是一個(gè)意思。

以上抑钟,就能夠?qū)F(xiàn)有RN頁(yè)面轉(zhuǎn)成web頁(yè)面了

接下來(lái)涯曲,以 AppRegistry API 為入口,看看 react-native-web 做了什么

react-native-web 源碼分析

從三部分來(lái)對(duì)源碼進(jìn)行分析:

  • 入口在塔,即 AppRegistry API
  • API幻件,即對(duì) RN API 實(shí)現(xiàn)
  • 組件,即對(duì) RN 組件實(shí)現(xiàn)

入口:AppRegistry API

入口文件代碼:

// index.web.js
import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('rn_web', () => App);

AppRegistry.runApplication('rn_web', {
    rootTag: document.getElementById('react-root')
});

webpack配置

webpack配置就跟普通 React web 應(yīng)用配置一致即可蛔溃,然后新增alias配置绰沥,如下:

// webpack.config.js
module.exports = {
  // ...the rest of your config
  resolve: {
    alias: {
      'react-native$': 'react-native-web'
    }
  }
}

那我們來(lái)來(lái)看看這兩個(gè) API 都做了什么

AppRegistry.registerComponent

const runnables = {};
static registerComponent(appKey: string, componentProvider: ComponentProvider): string {
    runnables[appKey] = {
        getApplication: appParameters => getApplication(componentProviderInstrumentationHook(componentProvider), appParameters ? appParameters.initialProps : emptyObject, wrapperComponentProvider && wrapperComponentProvider(appParameters)),
        run: appParameters => renderApplication(componentProviderInstrumentationHook(componentProvider), appParameters.initialProps || emptyObject, appParameters.rootTag, wrapperComponentProvider && wrapperComponentProvider(appParameters), appParameters.callback)
    };
    return appKey;
}

以例子代碼為例篱蝇,此方法就是定義了 runnables['rn_web'] 對(duì)象,此對(duì)象有 getApplication揪利、run 兩個(gè)方法

AppRegistry.runApplication

static runApplication(appKey: string, appParameters: Object): void {
    runnables[appKey].run(appParameters);
}

以例子代碼為例态兴,此方法就是調(diào)用了

runnables['rn_web'].run({
    rootTag: document.getElementById('react-root')
})

這里的 appParameters 數(shù)據(jù)結(jié)構(gòu)如下:

{
    initialProps, // 初始props
    rootTag, // root DOM節(jié)點(diǎn)
    callback, // 回調(diào)函數(shù)
}

renderApplication

import { render } from 'react-dom';
const renderFn = render;
function renderApplication<Props: Object>(RootComponent: ComponentType<Props>, initialProps: Props, rootTag: any, WrapperComponent?: ?ComponentType<*>, callback?: () => void) {
    renderFn(
        <AppContainer WrapperComponent={WrapperComponent} rootTag={rootTag}>
            <RootComponent {...initialProps} />
        </AppContainer>,
        rootTag,
        callback
    );
}

實(shí)際調(diào)用的是:

ReactDOM.render(
    <AppContainer WrapperComponent={WrapperComponent} rootTag={rootTag}>
        <App {...initialProps} />
    </AppContainer>,
    rootTag,
    callback
);

AppContainer

export default class AppContainer extends Component<Props, State> {
  state = { mainKey: 1 };

  static childContextTypes = {
    rootTag: any
  };

  static propTypes = {
    WrapperComponent: any,
    children: node,
    rootTag: any.isRequired
  };

  getChildContext(): Context {
    return {
      rootTag: this.props.rootTag
    };
  }

  render() {
    const { children, WrapperComponent } = this.props;
    let innerView = (
      <View
        children={children}
        key={this.state.mainKey}
        pointerEvents="box-none"
        style={styles.appContainer}
      />
    );

    if (WrapperComponent) {
      innerView = <WrapperComponent>{innerView}</WrapperComponent>;
    }
    return (
      <View pointerEvents="box-none" style={styles.appContainer}>
        {innerView}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  appContainer: {
    flex: 1
  }
});

API

以 StyleSheet 為例狠持,分析 react-native-web API 源碼

我們都知道疟位,RN中使用的樣式表是CSS的子集,我們來(lái)看看 react-native-web 對(duì)樣式表的處理

StyleSheet

const StyleSheet = {
  absoluteFill,
  absoluteFillObject,
  compose(style1, style2) {
    ...
  },
  create(styles) {
    ...
  },
  flatten: flattenStyle,
  hairlineWidth: 1
};

RN的StyleSheet模塊有以下幾個(gè)方法和常量:

1喘垂、方法:

  • setStyleAttributePreprocessor(此方法存在風(fēng)險(xiǎn))
  • create
  • flatten

2甜刻、常量:

  • hairlineWidth
  • absoluteFill
  • absoluteFillObject

可以發(fā)現(xiàn)忌堂,react-native-web 中 StyleSheet 定義了除 setStyleAttributePreprocessor(此方法存在風(fēng)險(xiǎn))方法之外的所有方法和常量犀忱。此外,還新增了 compose 方法柿扣,此方法在 react-native-web 的組件中使用

首先來(lái)看看 StyleSheet.create 方法

StyleSheet.create
create(styles) {
  const result = {};
  Object.keys(styles).forEach(key => {
    const id = styles[key] && ReactNativePropRegistry.register(styles[key]);
    result[key] = id;
  });
  return result;
}

代碼比較簡(jiǎn)單章贞,主要就是遍歷styles祥绞,對(duì)所有styles調(diào)用 ReactNativePropRegistry.register 獲取對(duì)應(yīng)的id,返回對(duì)應(yīng) key-id 的對(duì)象鸭限。我們先看個(gè)例子:

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  ellipsis: {
    width: 200,
  }
});

console.log(styles);

我們來(lái)看看打印出來(lái)的styles是什么蜕径?

{container: 78, welcome: 79, instructions: 80, ellipsis: 81}

接著來(lái)看看 ReactNativePropRegistry.register 做了什么

ReactNativePropRegistry
const emptyObject = {};
const objects = {};
const prefix = 'r';
let uniqueID = 1;

const createKey = id => `${prefix}-${id}`;

export default class ReactNativePropRegistry {
  static register(object: Object): number {
    const id = uniqueID++;
    if (process.env.NODE_ENV !== 'production') {
      Object.freeze(object);
    }
    const key = createKey(id);
    objects[key] = object;
    return id;
  }

  static getByID(id: number): Object {
    if (!id) {
      return emptyObject;
    }
    const key = createKey(id);
    const object = objects[key];
    if (!object) {
      return emptyObject;
    }
    return object;
  }
}

這個(gè)模塊,定義了兩個(gè)方法:register败京、getByID兜喻,register 是將樣式對(duì)象存入 objects 對(duì)象中,并返回對(duì)應(yīng)的 id赡麦;getByID 則是通過(guò) id 獲取對(duì)應(yīng)的樣式對(duì)象

在react-native-web整個(gè)樣式轉(zhuǎn)換過(guò)程中朴皆,除了StyleSheet.create,還需要關(guān)注一下 StyleSheet.flatten 方法泛粹,即 flattenStyle

flattenStyle
function getStyle(style) {
  if (typeof style === 'number') {
    return ReactNativePropRegistry.getByID(style);
  }
  return style;
}

function flattenStyle(style: ?StyleObj): ?Object {
  if (!style) {
    return undefined;
  }

  if (!Array.isArray(style)) {
    return getStyle(style);
  }

  const result = {};
  for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
    const computedStyle = flattenStyle(style[i]);
    if (computedStyle) {
      for (const key in computedStyle) {
        const value = computedStyle[key];
        result[key] = value;
      }
    }
  }
  return result;
}

flattenStyle 方法接受的 styles 參數(shù)是存有樣式表id的數(shù)組或變量遂铡,通過(guò)遞歸遍歷 styles,調(diào)用上一部分提到的 ReactNativePropRegistry.getByID 方法晶姊,通過(guò)id獲取對(duì)應(yīng)的樣式對(duì)象扒接,并返回。

以上帽借,我們以 StyleSheet 為例分析了 react-native-web 實(shí)現(xiàn) RN API 的源碼珠增。

組件

以 View 組件為例,分析 react-native-web 組件的源碼

const calculateHitSlopStyle = hitSlop => {
  const hitStyle = {};
  for (const prop in hitSlop) {
    if (hitSlop.hasOwnProperty(prop)) {
      const value = hitSlop[prop];
      hitStyle[prop] = value > 0 ? -1 * value : 0;
    }
  }
  return hitStyle;
};

class View extends Component<ViewProps> {
  static displayName = 'View';

  static contextTypes = {
    isInAParentText: bool
  };

  static propTypes = ViewPropTypes;

  render() {
    const hitSlop = this.props.hitSlop;
    const supportedProps = filterSupportedProps(this.props);

    const { isInAParentText } = this.context;

    supportedProps.style = StyleSheet.compose(
      styles.initial,
      StyleSheet.compose(isInAParentText && styles.inline, this.props.style)
    );

    if (hitSlop) {
      const hitSlopStyle = calculateHitSlopStyle(hitSlop);
      const hitSlopChild = createElement('span', { style: [styles.hitSlop, hitSlopStyle] });
      supportedProps.children = React.Children.toArray([hitSlopChild, supportedProps.children]);
    }

    return createElement('div', supportedProps);
  }
}

const styles = StyleSheet.create({
  // https://github.com/facebook/css-layout#default-values
  initial: {
    alignItems: 'stretch',
    borderWidth: 0,
    borderStyle: 'solid',
    boxSizing: 'border-box',
    display: 'flex',
    flexDirection: 'column',
    margin: 0,
    padding: 0,
    position: 'relative',
    zIndex: 0,
    // fix flexbox bugs
    minHeight: 0,
    minWidth: 0
  },
  inline: {
    display: 'inline-flex'
  },
  // this zIndex-ordering positions the hitSlop above the View but behind
  // its children
  hitSlop: {
    ...StyleSheet.absoluteFillObject,
    zIndex: -1
  }
});

export default applyLayout(applyNativeMethods(View));

View 組件就是一個(gè)簡(jiǎn)單的React組件砍艾,首先關(guān)注一下:

export default applyLayout(applyNativeMethods(View));

其中蒂教,applyNativeMethods 方法是將native的方法轉(zhuǎn)換為對(duì)應(yīng)的DOM方法;applyLayout 方法是對(duì)組件的生命周期函數(shù)進(jìn)行重寫脆荷。這部分感興趣的小伙伴自行了解~

接下來(lái)關(guān)注一下 View 組件的 render 方法凝垛,主要是對(duì)組件的 props 做些處理懊悯,包括校驗(yàn) props 是否支持、style 處理梦皮,最后調(diào)用 createElement 方法

createElement

const createElement = (component, props, ...children) => {
  // use equivalent platform elements where possible
  let accessibilityComponent;
  if (component && component.constructor === String) {
    accessibilityComponent = AccessibilityUtil.propsToAccessibilityComponent(props);
  }
  const Component = accessibilityComponent || component;
  const domProps = createDOMProps(Component, props);
  adjustProps(domProps);
  return React.createElement(Component, domProps, ...children);
};

最終是調(diào)用了 React.createElement 方法創(chuàng)建 React Element炭分,在此之前,主要做的事情就是調(diào)用 createDOMProps 方法剑肯,得到 domProps

createDOMProps

const createDOMProps = (component, props, styleResolver) => {
  ...
  const {
    ...
    ...domProps
  } = props;

  // GENERAL ACCESSIBILITY
  ...

  // DISABLED
  ...

  // FOCUS
  // Assume that 'link' is focusable by default (uses <a>).
  // Assume that 'button' is not (uses <div role='button'>) but must be treated as such.
  ...

  // STYLE
  // Resolve React Native styles to optimized browser equivalent
  const reactNativeStyle = [
    component === 'a' && resetStyles.link,
    component === 'button' && resetStyles.button,
    role === 'heading' && resetStyles.heading,
    component === 'ul' && resetStyles.list,
    role === 'button' && !disabled && resetStyles.ariaButton,
    pointerEvents && pointerEventsStyles[pointerEvents],
    providedStyle,
    placeholderTextColor && { placeholderTextColor }
  ];
  const { className, style } = styleResolver(reactNativeStyle);
  if (className && className.constructor === String) {
    domProps.className = props.className ? `${props.className} ${className}` : className;
  }
  if (style) {
    domProps.style = style;
  }

  // OTHER
  // Link security and automation test ids
  ...
  return domProps;
};

createDOMProps 方法代碼較長(zhǎng)捧毛,這里就不全部粘貼,從幾個(gè)注釋可以知道让网,此方法主要是將各 props 轉(zhuǎn)換成對(duì)應(yīng)的 web 端的props呀忧,這里我們以 style 為例,看看是如何做轉(zhuǎn)換的溃睹。

樣式轉(zhuǎn)換工作量主要在 styleResolver 方法而账,即調(diào)用 ReactNativeStyleResolver 實(shí)例的 resolve 方法。此方法最后會(huì)返回 className 和 style因篇,最后會(huì)賦值到 domProps 中

styleResolver

resolve(style) {
  // fast and cachable
  // style: id
  if (typeof style === 'number') {
    this._injectRegisteredStyle(style);
    const key = createCacheKey(style);
    return this._resolveStyleIfNeeded(style, key);
  }
  // resolve a plain RN style object
  // style: 樣式對(duì)象
  if (!Array.isArray(style)) {
    return this._resolveStyleIfNeeded(style);
  }
  // flatten the style array
  // cache resolved props when all styles are registered
  // otherwise fallback to resolving
  // style: 存儲(chǔ)id的數(shù)組
  const flatArray = flattenArray(style);
  let isArrayOfNumbers = true;
  for (let i = 0; i < flatArray.length; i++) {
    const id = flatArray[i];
    if (typeof id !== 'number') {
      isArrayOfNumbers = false;
    } else {
      this._injectRegisteredStyle(id);
    }
  }
  const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
  return this._resolveStyleIfNeeded(flatArray, key);
}

接下來(lái)看看 _injectRegisteredStyle_resolveStyleIfNeeded

_injectRegisteredStyle

_injectRegisteredStyle(id) {
  const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
  const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
  if (!this.injectedCache[dir][id]) {
    // 根據(jù)id獲取對(duì)應(yīng)的樣式對(duì)象
    const style = flattenStyle(id);
    // 對(duì)樣式對(duì)象格式化:各樣式屬性排序泞辐;添加長(zhǎng)度單位;顏色值處理竞滓;特定屬性處理咐吼;返回格式化之后的樣式對(duì)象
    const domStyle = createReactDOMStyle(i18nStyle(style));
    Object.keys(domStyle).forEach(styleProp => {
      const value = domStyle[styleProp];
      if (value != null) {
        // 將樣式插入 WebStyleSheet(domStyleElement.sheet)中
        this.styleSheetManager.injectDeclaration(styleProp, value);
      }
    });
    // 將此樣式標(biāo)記為已插入
    this.injectedCache[dir][id] = true;
  }
}

其中,styleSheetManager.injectDeclaration 是基于 domStyleElement.sheet 對(duì)頁(yè)面樣式進(jìn)行插入操作虽界,我們可以看看轉(zhuǎn)出來(lái)的web頁(yè)面的樣式:

[圖片上傳失敗...(image-5cd020-1551534493771)]

_resolveStyleIfNeeded

_resolveStyleIfNeeded 方法即是調(diào)用 _resolveStyle 方法汽烦,源碼如下:

_resolveStyle(style) {
  // 獲取對(duì)應(yīng)id的樣式對(duì)象
  const flatStyle = flattenStyle(style);
  // 對(duì)樣式對(duì)象格式化:各樣式屬性排序;添加長(zhǎng)度單位莉御;顏色值處理撇吞;特定屬性處理;返回格式化之后的樣式對(duì)象
  const domStyle = createReactDOMStyle(i18nStyle(flatStyle));

  const props = Object.keys(domStyle).reduce(
    (props, styleProp) => {
      const value = domStyle[styleProp];
      if (value != null) {
        // 獲取 WebStyleSheet 中特定樣式屬性及值對(duì)應(yīng)的className
        // 通過(guò) StyleSheet.create 創(chuàng)建的樣式礁叔,會(huì)插入到 WebStyleSheet
        const className = this.styleSheetManager.getClassName(styleProp, value);
        if (className) {
          // 將此className放入props.classList中
          props.classList.push(className);
        } else {
          // Certain properties and values are not transformed by 'createReactDOMStyle' as they
          // require more complex transforms into multiple CSS rules. Here we assume that StyleManager
          // can bind these styles to a className, and prevent them becoming invalid inline-styles.
          // 單條樣式屬性牍颈,如果不是特殊屬性,則直接放進(jìn)props.style中
          // 單條樣式屬性是指未通過(guò) StyleSheet.create 創(chuàng)建的樣式
          if (
            styleProp === 'pointerEvents' ||
            styleProp === 'placeholderTextColor' ||
            styleProp === 'animationName'
          ) {
            const className = this.styleSheetManager.injectDeclaration(styleProp, value);
            if (className) {
              props.classList.push(className);
            }
          } else {
            if (!props.style) {
              props.style = {};
            }
            // 4x slower render
            props.style[styleProp] = value;
          }
        }
      }
      return props;
    },
    { classList: [] }
  );

  props.className = classListToString(props.classList);
  if (props.style) {
    props.style = prefixInlineStyles(props.style);
  }
  return props;
}

此方法主要是獲取所有樣式對(duì)應(yīng)的 className 或者 style琅关,并存入props中返回

以上煮岁,我們以 View 組件為例分析了 react-native-web 實(shí)現(xiàn) RN 組件的源碼。

我們做完源碼分析之后涣易,我們看看如何基于 react-native-web 做一些修改

實(shí)踐

以 Text 組件為例画机,RN Text組件可以設(shè)置 numberOfLines,來(lái)實(shí)現(xiàn)單行或多行省略新症,但是react-native-web只實(shí)現(xiàn)了單行省略步氏,所以我們要把多行省略的功能加上,代碼如下:

class Text extends Component<*> {
  ...
  render() {
    ...
    // allow browsers to automatically infer the language writing direction
    otherProps.dir = dir !== undefined ? dir : 'auto';
    otherProps.style = [
      styles.initial,
      this.context.isInAParentText === true && styles.isInAParentText,
      style,
      selectable === false && styles.notSelectable,
      numberOfLines === 1 && styles.singleLineStyle,
      onPress && styles.pressable
    ];
    // 支持多行省略
    if (numberOfLines > 1) {
      otherProps.style.push({
        display: '-webkit-box',
        WebkitBoxOrient: 'vertical',
        WebkitLineClamp: numberOfLines,
        overflow: 'hidden',
        textOverflow: 'ellipsis',
      });
    }
    const component = isInAParentText ? 'span' : 'div';
    return createElement(component, otherProps);
  }
  ...
}

舉的這個(gè)例子比較簡(jiǎn)單徒爹,想表達(dá)的是我們通過(guò)看react-native-web源碼荚醒,在開(kāi)發(fā)過(guò)程中芋类,遇到了轉(zhuǎn)換web的問(wèn)題,我們可以通過(guò)修改源碼界阁、或者使用它提供的API來(lái)解決侯繁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泡躯,隨后出現(xiàn)的幾起案子贮竟,更是在濱河造成了極大的恐慌,老刑警劉巖精续,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坝锰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡重付,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門凫乖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)确垫,“玉大人,你說(shuō)我怎么就攤上這事帽芽∩鞠疲” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵导街,是天一觀的道長(zhǎng)披泪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)搬瑰,這世上最難降的妖魔是什么款票? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮泽论,結(jié)果婚禮上艾少,老公的妹妹穿的比我還像新娘。我一直安慰自己翼悴,他們只是感情好缚够,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著鹦赎,像睡著了一般谍椅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上古话,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天雏吭,我揣著相機(jī)與錄音,去河邊找鬼煞额。 笑死思恐,一個(gè)胖子當(dāng)著我的面吹牛沾谜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胀莹,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼基跑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了描焰?” 一聲冷哼從身側(cè)響起媳否,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荆秦,沒(méi)想到半個(gè)月后篱竭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡步绸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年掺逼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓤介。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吕喘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刑桑,到底是詐尸還是另有隱情氯质,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布祠斧,位于F島的核電站闻察,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏琢锋。R本人自食惡果不足惜辕漂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吩蔑。 院中可真熱鬧钮热,春花似錦、人聲如沸烛芬。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赘娄。三九已至仆潮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遣臼,已是汗流浹背性置。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留揍堰,地道東北人鹏浅。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓嗅义,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親隐砸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子之碗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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