react源碼閱讀筆記(2)組件的渲染

本文使用的是react 15.6.1的代碼

ReactDOM.render


class App extends React.Component {
    constructor(props) {
        super(props)
    }

    render() {
        let {text} = this.props;
        return <div id="app">
            <span>{text}</span>
        </div>
    }
}
App.defaultProps = {
    text: 'hello react'
};



ReactDOM.render((
    <App/>
), document.getElementById('root'));

相信大家在初學(xué)react的時(shí)候都寫(xiě)過(guò)類似上面的代碼盲再,上篇文章中罢防,已經(jīng)介紹了 extends React.Component做了些什么喉酌,這次,我們就來(lái)看看ReactDOM.render中react去做了些什么,ReactDOM.render實(shí)際調(diào)用的是ReactMount.js中的render
代碼地址:[ReactMount]

// nextElement即ReactElement司澎,
// container 具體容器房揭,將由virtualDOM生成的真實(shí)dom映射的位置
// callback 渲染成功后的回調(diào)
  render: function(nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(
      null,
      nextElement,
      container,
      callback,
    );

進(jìn)一步得知,其主要邏輯位于_renderSubtreeIntoContainer下
代碼地址:[ReactMount]

// ReactDom.render調(diào)用后 第一個(gè)參數(shù)默認(rèn)傳遞null氯葬,因?yàn)樵摲椒ǖ谝粋€(gè)參數(shù)是父組件挡篓,剩下三個(gè)參數(shù)和render函數(shù)一致
  _renderSubtreeIntoContainer: function(
    parentComponent,
    nextElement,
    container,
    callback,
  ) {
    //判斷callback是否為函數(shù);
    ReactUpdateQueue.validateCallback(callback, 'ReactDOM.render');


    var nextWrappedElement = React.createElement(TopLevelWrapper, {
      child: nextElement,
    });
//通過(guò)TopLevelWrapper創(chuàng)建一個(gè)ReactElement節(jié)點(diǎn),并且設(shè)置其this.props.child =     render傳入的ReactElement    
     /** 上文TopLevelWrapper代碼
     var topLevelRootCounter = 1;z
     var TopLevelWrapper = function() {
      this.rootID = topLevelRootCounter++;
    };
     TopLevelWrapper.prototype.isReactComponent = {};
     TopLevelWrapper.prototype.render = function() {
      return this.props.child;
    };
     TopLevelWrapper.isReactTopLevelWrapper = true;
     */
      // 可以看出TopLevelWrapper代碼就是一個(gè)簡(jiǎn)單的ReactComponent,類似于 extend React.Component, 并重寫(xiě)了方法render
    

    var nextContext;
    // 如果存在父組件官研,即不是頂級(jí)組件的情況下(在ReactDOM.render時(shí)秽澳,parentComponent為null)
    if (parentComponent) {
      var parentInst = ReactInstanceMap.get(parentComponent);
      nextContext = parentInst._processChildContext(parentInst._context);
    } else {
      nextContext = emptyObject;
    }
    // 這時(shí)候preComponent = null;
    var prevComponent = getTopLevelWrapperInContainer(container);
    if (prevComponent) {
      //
      var prevWrappedElement = prevComponent._currentElement;
      var prevElement = prevWrappedElement.props.child;
      // diff 簡(jiǎn)單概括就是如果渲染的節(jié)點(diǎn)和原節(jié)點(diǎn)type和key(所以像listview可以通過(guò)設(shè)置key來(lái)進(jìn)行優(yōu)化)都不變的時(shí)候,直接更新就好戏羽,不用在去重新渲染一遍
      if (shouldUpdateReactComponent(prevElement, nextElement)) {
        var publicInst = prevComponent._renderedComponent.getPublicInstance();
        var updatedCallback =
          callback &&
          function() {
            callback.call(publicInst);
          };
        ReactMount._updateRootComponent(
          prevComponent,
          nextWrappedElement,
          nextContext,
          container,
          updatedCallback,
        );
        return publicInst;
      } else {
        //否則的話卸載掉該容器的組件
        ReactMount.unmountComponentAtNode(container);
      }
    }

    // 獲取container的跟元素
    var reactRootElement = getReactRootElementInContainer(container);
    // 確定container是否被markup担神,即添加了data-reactid,第一次渲染肯定是false
    var containerHasReactMarkup =
      reactRootElement && !!internalGetID(reactRootElement);
    // 目前為false,因?yàn)镽eactDOM.render調(diào)用時(shí)還沒(méi)有實(shí)例化任何組件
    var containerHasNonRootReactChild = hasNonRootReactChild(container);


    // 目前為false
    var shouldReuseMarkup =
      containerHasReactMarkup &&
      !prevComponent &&
      !containerHasNonRootReactChild;
    // 關(guān)鍵代碼始花,渲染妄讯,插入都在這里面
    var component = ReactMount._renderNewRootComponent(
      nextWrappedElement,
      container,
      shouldReuseMarkup,
      nextContext,
    )._renderedComponent.getPublicInstance();
    if (callback) {
      callback.call(component);
    }
    return component;
  }

根據(jù)代碼我們知道,在_renderSubtreeIntoContainer方法的時(shí)候衙荐,他會(huì)優(yōu)先判斷渲染節(jié)點(diǎn)和原節(jié)點(diǎn)的type和key是否一致捞挥,如果一致,直接調(diào)用_updateRootComponent更新忧吟,否則才會(huì)去重新render新的組件,因此在渲染listview等需要大量刷新的組件時(shí)斩披,可以通過(guò)設(shè)置key去優(yōu)化顯示溜族,減少重新渲染

在看看_renderNewRootComponent的代碼

/**
   * Render a new component into the DOM. Hooked by hooks!
   *
   * @param {ReactElement} nextElement 即將渲染的組件
   * @param {DOMElement} container 容器元素
   * @param {boolean} shouldReuseMarkup 是否需要重新標(biāo)記元素
   * @return {ReactComponent} nextComponent 返回一個(gè)ReactComponent
   */
  _renderNewRootComponent: function(
    nextElement,
    container,
    shouldReuseMarkup,
    context,
  ) {
     //主要和滾動(dòng)條有關(guān),目前不需要太關(guān)心
    ReactBrowserEventEmitter.ensureScrollValueMonitoring();
    //實(shí)例化React Component,
    var componentInstance = instantiateReactComponent(nextElement, false);
    /*
     上文instantiateReactComponent
     function instantiateReactComponent(node, shouldHaveDebugID) {
     var instance;

     if (node === null || node === false) {
     // 如果是空對(duì)象
     instance = ReactEmptyComponent.create(instantiateReactComponent);
     } else if (typeof node === 'object') {
     // 如果是Node,(包括dom節(jié)點(diǎn)以及reactElement)
     var element = node;

     // 原生對(duì)象
     if (typeof element.type === 'string') {
     instance = ReactHostComponent.createInternalComponent(element);
     } else {
     // react組件
     instance = new ReactCompositeComponentWrapper(element);
     }
     //如果元素本來(lái)就是一個(gè)string或者number垦沉,如 <div>111</div>中的111
     } else if (typeof node === 'string' || typeof node === 'number') {
     //創(chuàng)建一個(gè)
     instance = ReactHostComponent.createInstanceForText(node);
     }

     //這兩個(gè)參數(shù)用于dom 和 art diff算法
     instance._mountIndex = 0;
     instance._mountImage = null;
     return instance;
     }
     */


    /* 批量更新方法煌抒,具體實(shí)現(xiàn)可以見(jiàn) ReactDefaultBatchingStrategy.js中 batchedUpdate方法,實(shí)際就是執(zhí)行
     * batchedMountComponentIntoNode方法厕倍,將后面的參數(shù)傳入batchedMountComponentIntoNode中

     */
    ReactUpdates.batchedUpdates(
      batchedMountComponentIntoNode,
      componentInstance,
      container,
      shouldReuseMarkup,
      context,
    );

    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;

    return componentInstance;
  },

batchedMountComponentIntoNode中寡壮,使用了React中大量使用的事務(wù)機(jī)制,調(diào)用了mountComponentIntoNode方法讹弯,事務(wù)機(jī)制后面有空在來(lái)研究况既,我們直接看mountComponentIntoNode


function mountComponentIntoNode(
  wrapperInstance,
  container,
  transaction,
  shouldReuseMarkup,
  context,
) {
 /*調(diào)用剛才ReactComponent instance中mountComponent方法,將React組件解析成對(duì)應(yīng)的html(對(duì)應(yīng)不同ReactComponent instance)mountComponent也是不同的
   <div>hello react</div>, 對(duì)應(yīng)的是ReactDOMTextComponent组民,最終解析成的HTML為<div data-reactroot="x.x.x">hello react</div>
   */
  var markup = ReactReconciler.mountComponent(
    wrapperInstance,
    transaction,
    null,
    ReactDOMContainerInfo(wrapperInstance, container),
    context,
    0 /* parentDebugID */,
  );

  wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
  /**
   * 將解析出來(lái)的html插入到dom中
   */
  ReactMount._mountImageIntoNode(
    markup,
    container,
    wrapperInstance,
    shouldReuseMarkup,
    transaction,
  );
}

看看ReactReconciler中的mountComponent代碼

mountComponent: function(
    internalInstance,
    transaction,
    hostParent,
    hostContainerInfo,
    context,
    parentDebugID, // 0 in production and for roots
  ) {
    var markup = internalInstance.mountComponent(
      transaction,
      hostParent,
      hostContainerInfo,
      context,
      parentDebugID,
    );
    if (
      internalInstance._currentElement &&
      internalInstance._currentElement.ref != null
    ) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    return markup;
  },

由代碼可以知道棒仍,其實(shí)本質(zhì)也是調(diào)用了ReactComponent實(shí)例的mountComponent,剛才我們說(shuō)到,不同的ReactComponent實(shí)例會(huì)有不同的mountComponent方法
大體有這三種實(shí)例:

  1. ReactHostComponent.createInternalComponent(element); 原生組件
  2. new ReactCompositeComponentWrapper(element); 自定義React組件
  3. ReactHostComponent.createInstanceForText(node); 字符串/數(shù)字

我們看一下最復(fù)雜的自定義React組件的mountComponent方法

mountComponent: function(
    transaction,
    hostParent,
    hostContainerInfo,
    context,
  ) {
    this._context = context;
    this._mountOrder = nextMountID++;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

    var publicProps = this._currentElement.props;
    var publicContext = this._processContext(context);

    var Component = this._currentElement.type;

    var updateQueue = transaction.getUpdateQueue();

    // Initialize the public class a
    var doConstruct = shouldConstruct(Component);
    // 本質(zhì)上就是new 了一個(gè)React.createClass(), 即Component組件臭胜,代碼可以自己看看莫其,比較簡(jiǎn)單
    var inst = this._constructComponent(
      doConstruct,
      publicProps,
      publicContext,
      updateQueue,
    );
    var renderedElement;

    // 當(dāng)不存在構(gòu)造函數(shù),并且沒(méi)有new出來(lái)的組件實(shí)例是null或者組件實(shí)例沒(méi)有render方法耸三,那么可以認(rèn)為這是一個(gè)無(wú)狀態(tài)組件
    if (!doConstruct && (inst == null || inst.render == null)) {
      renderedElement = inst;
      warnIfInvalidElement(Component, renderedElement);
      // new 一個(gè)無(wú)狀態(tài)組件
      inst = new StatelessComponent(Component);
      this._compositeType = CompositeTypes.StatelessFunctional;
    } else {
      if (isPureComponent(Component)) {
        this._compositeType = CompositeTypes.PureClass;
      } else {
        this._compositeType = CompositeTypes.ImpureClass;
      }
    }

    // simpler class abstractions, we set them up after the fact.
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = updateQueue;

    this._instance = inst;

    // Store a reference from the instance back to the internal representation
    ReactInstanceMap.set(inst, this);

    var initialState = inst.state;
    if (initialState === undefined) {
      inst.state = initialState = null;
    }

    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    var markup;
    if (inst.unstable_handleError) {
      markup = this.performInitialMountWithErrorHandling(
        renderedElement,
        hostParent,
        hostContainerInfo,
        transaction,
        context,
      );
    } else {
      markup = this.performInitialMount(
        renderedElement,  // 感謝鐘佳鋒同學(xué)的指正乱陡,這里的renderedElement是后面_renderValidatedComponent中調(diào)用內(nèi)部的render方法獲取
        hostParent,
        hostContainerInfo,
        transaction,
        context,
      );
    }

    if (inst.componentDidMount) { //生命周期componentDidMount
      transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }

    return markup;
  },

從上面我們知道自定義React組件真正實(shí)例化(new)的時(shí)候就在mountComponment之中,但是這只是簡(jiǎn)單的實(shí)例化仪壮,返回值html是在performInitialMount中處理的憨颠,在看看performInitialMount中的代碼

performInitialMount: function(
    renderedElement,
    hostParent,
    hostContainerInfo,
    transaction,
    context,
  ) {
    var inst = this._instance;

    var debugID = 0;

    if (inst.componentWillMount) {
      //該實(shí)體如果有componentWillMount方法,生命周期之一
      inst.componentWillMount();
      // When mounting, calls to `setState` by `componentWillMount` will set
      // `this._pendingStateQueue` without triggering a re-render.
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }

    // If not a stateless component, we now render
    // 獲取子組件睛驳,實(shí)際是調(diào)用React.createClass中的render方法烙心,因?yàn)閞ender方法會(huì)返回一個(gè)ReactElement對(duì)象
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

    var nodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedNodeType = nodeType;
    //再次調(diào)用instantiateReactComponent初始化
    var child = this._instantiateReactComponent(
      renderedElement,
      nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
    );
    this._renderedComponent = child;
    
    //再一次調(diào)用mountComponent
    var markup = ReactReconciler.mountComponent(
      child,
      transaction,
      hostParent,
      hostContainerInfo,
      this._processChildContext(context),
      debugID,
    );


    return markup;
  },

在這段代碼中膜廊,renderedElement被_renderValidatedComponent重新進(jìn)行了賦值,根據(jù)后面child這個(gè)變量命名淫茵,我們猜測(cè)爪瓜,這個(gè)方法會(huì)返回子組件,最后匙瘪,又在一次調(diào)用了ReactReconciler.mountComponent方法铆铆,和mountComponentIntoNode方法調(diào)用手段一致,只是第一個(gè)參數(shù)變成了child,簡(jiǎn)單猜測(cè)一下丹喻,這個(gè)時(shí)候在渲染子組件薄货。為了證實(shí)猜測(cè),在來(lái)看看_renderValidatedComponent方法

_renderValidatedComponent: function() {
    var renderedElement;
    renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();

    return renderedElement;
  },

對(duì)應(yīng)的_renderValidatedComponentWithoutOwnerOrContext方法

/**
   * @protected
   */
  _renderValidatedComponentWithoutOwnerOrContext: function() {
    var inst = this._instance;
    var renderedElement;
    //回想一下我們React.createClass中render方法里面被babel解析后是什么
    //render中{return React.createElement}
    //所以他又是一個(gè)ReactElement
    renderedElement = inst.render();

    return renderedElement;
  },

果然碍论,是調(diào)用了render方法谅猾,也就是render中的子組件。隨后組件又調(diào)用了_instantiateReactComponentfan方法初始化最后再一次mountComponent去實(shí)例化子組件鳍悠,形成遞歸去渲染税娜,直到得到最后的markup,同時(shí)不難想象藏研,隨著不斷的遞歸只有下面的2類會(huì)返回正常的mask對(duì)象

  1. ReactHostComponent.createInternalComponent(element); 原生組件
  2. ReactHostComponent.createInstanceForText(node); 字符串/數(shù)字

我們?cè)趤?lái)看看createInternalComponent方法敬矩,該方法會(huì)new一個(gè)ReactDOMComponent(指代原生組件),我們來(lái)看看ReactDOMComponent的mountComponent有什么

mountComponent: function(
    transaction,
    hostParent,
    hostContainerInfo,
    context,
  ) {
    this._rootNodeID = globalIdCounter++;
    this._domID = hostContainerInfo._idCounter++;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

    var props = this._currentElement.props;
    //針對(duì)不同的element蠢挡,進(jìn)行不同的處理弧岳,在構(gòu)造函數(shù)中this._tag=element.tag.toLowCase()
    switch (this._tag) {
      case 'audio':
      case 'form':
      case 'iframe':
      case 'img':
      case 'link':
      case 'object':
      case 'source':
      case 'video':
        this._wrapperState = {
          listeners: null,
        };
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
      case 'input':
        ReactDOMInput.mountWrapper(this, props, hostParent);
        props = ReactDOMInput.getHostProps(this, props);
        transaction.getReactMountReady().enqueue(trackInputValue, this);
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
      case 'option':
        ReactDOMOption.mountWrapper(this, props, hostParent);
        props = ReactDOMOption.getHostProps(this, props);
        break;
      case 'select':
        ReactDOMSelect.mountWrapper(this, props, hostParent);
        props = ReactDOMSelect.getHostProps(this, props);
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
      case 'textarea':
        ReactDOMTextarea.mountWrapper(this, props, hostParent);
        props = ReactDOMTextarea.getHostProps(this, props);
        transaction.getReactMountReady().enqueue(trackInputValue, this);
        transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
        break;
    }

    assertValidProps(this, props);

    // We create tags in the namespace of their parent container, except HTML
    // tags get no namespace.
    var namespaceURI;
    var parentTag;
    if (hostParent != null) {
      namespaceURI = hostParent._namespaceURI;
      parentTag = hostParent._tag;
    } else if (hostContainerInfo._tag) {
      namespaceURI = hostContainerInfo._namespaceURI;
      parentTag = hostContainerInfo._tag;
    }
    if (
      namespaceURI == null ||
      (namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject')
    ) {
      namespaceURI = DOMNamespaces.html;
    }
    if (namespaceURI === DOMNamespaces.html) {
      if (this._tag === 'svg') {
        namespaceURI = DOMNamespaces.svg;
      } else if (this._tag === 'math') {
        namespaceURI = DOMNamespaces.mathml;
      }
    }
    this._namespaceURI = namespaceURI;

    var mountImage;
    // 調(diào)用render時(shí),useCreateElement為true
    if (transaction.useCreateElement) {
      var ownerDocument = hostContainerInfo._ownerDocument;
      var el;
      if (namespaceURI === DOMNamespaces.html) {
        if (this._tag === 'script') {
          // Create the script via .innerHTML so its "parser-inserted" flag is
          // set to true and it does not execute
          var div = ownerDocument.createElement('div');
          var type = this._currentElement.type;
          div.innerHTML = `<${type}></${type}>`;
          el = div.removeChild(div.firstChild);
        } else if (props.is) {
          el = ownerDocument.createElement(this._currentElement.type, props.is);
        } else {
          // Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
          // See discussion in https://github.com/facebook/react/pull/6896
          // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
          // 創(chuàng)建標(biāo)簽
          el = ownerDocument.createElement(this._currentElement.type);
        }
      } else {
        el = ownerDocument.createElementNS(
          namespaceURI,
          this._currentElement.type,
        );
      }
      ReactDOMComponentTree.precacheNode(this, el);
      this._flags |= Flags.hasCachedChildNodes;
      if (!this._hostParent) {
        DOMPropertyOperations.setAttributeForRoot(el);
      }
      this._updateDOMProperties(null, props, transaction);
      var lazyTree = DOMLazyTree(el);
      // 準(zhǔn)備實(shí)例化子組件
      this._createInitialChildren(transaction, props, context, lazyTree);
      mountImage = lazyTree;
    } else {
      var tagOpen = this._createOpenTagMarkupAndPutListeners(
        transaction,
        props,
      );
      var tagContent = this._createContentMarkup(transaction, props, context);
      if (!tagContent && omittedCloseTags[this._tag]) {
        mountImage = tagOpen + '/>';
      } else {
        mountImage =
          tagOpen + '>' + tagContent + '</' + this._currentElement.type + '>';
      }
    }

    switch (this._tag) {
      case 'input':
        transaction.getReactMountReady().enqueue(inputPostMount, this);
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'textarea':
        transaction.getReactMountReady().enqueue(textareaPostMount, this);
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'select':
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'button':
        if (props.autoFocus) {
          transaction
            .getReactMountReady()
            .enqueue(AutoFocusUtils.focusDOMComponent, this);
        }
        break;
      case 'option':
        transaction.getReactMountReady().enqueue(optionPostMount, this);
        break;
    }

    return mountImage;
  }

由代碼可以知道react通過(guò)type對(duì)input业踏,select禽炬,script等特殊元素進(jìn)行單獨(dú)的處理,最后調(diào)用createElement來(lái)生成一個(gè)普通的元素堡称,同時(shí)調(diào)用了this._createInitialChildren(transaction, props, context, lazyTree)方法去實(shí)例化其中的子組件瞎抛,同原生組件一下,形成遞歸返回到tree中却紧,最后mountImage = lazyTree;拿到生成的節(jié)點(diǎn)桐臊,這里就不在看原生組件_createInitialChildren的實(shí)現(xiàn)了

回到開(kāi)mountCompouentIntoNode的代碼,最后調(diào)用了ReactMount._mountImageIntoNode方法晓殊,這個(gè)方法就是真真的把節(jié)點(diǎn)插入帶容器中去了断凶,看一下實(shí)現(xiàn);

_mountImageIntoNode: function(
    markup,
    container,
    instance,
    shouldReuseMarkup,
    transaction,
  ) {

    //暫時(shí)不用關(guān)心
    if (shouldReuseMarkup) {
     ......
    }

    //render的時(shí)候該值為true
    if (transaction.useCreateElement) {
      //移除container中的節(jié)點(diǎn)
      while (container.lastChild) {
        container.removeChild(container.lastChild);
      }
      DOMLazyTree.insertTreeBefore(container, markup, null);
    } else {
      setInnerHTML(container, markup);
      ReactDOMComponentTree.precacheNode(instance, container.firstChild);
    }
  },

最后實(shí)際上是調(diào)用DOMLazyTree.insertTreeBefore完成節(jié)點(diǎn)插入,這一段代碼很簡(jiǎn)單巫俺,就不在單獨(dú)分析了

總結(jié)

終于走完了React整個(gè)渲染流程认烁,我們可以發(fā)現(xiàn)React抽象程度非常的高,源碼讀起來(lái)特別的復(fù)雜,這里也只是簡(jiǎn)單的閱讀整個(gè)流程却嗡,細(xì)節(jié)太多舶沛,沒(méi)有深究

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市窗价,隨后出現(xiàn)的幾起案子如庭,更是在濱河造成了極大的恐慌,老刑警劉巖撼港,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坪它,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡帝牡,警方通過(guò)查閱死者的電腦和手機(jī)往毡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)靶溜,“玉大人开瞭,你說(shuō)我怎么就攤上這事≌窒ⅲ” “怎么了惩阶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)扣汪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锨匆,這世上最難降的妖魔是什么崭别? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮恐锣,結(jié)果婚禮上茅主,老公的妹妹穿的比我還像新娘。我一直安慰自己土榴,他們只是感情好诀姚,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著玷禽,像睡著了一般赫段。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矢赁,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天糯笙,我揣著相機(jī)與錄音,去河邊找鬼撩银。 笑死给涕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播够庙,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼恭应,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了耘眨?” 一聲冷哼從身側(cè)響起昼榛,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毅桃,沒(méi)想到半個(gè)月后褒纲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钥飞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年莺掠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片读宙。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡彻秆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出结闸,到底是詐尸還是另有隱情唇兑,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布桦锄,位于F島的核電站扎附,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏结耀。R本人自食惡果不足惜留夜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望图甜。 院中可真熱鬧碍粥,春花似錦、人聲如沸黑毅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)矿瘦。三九已至枕面,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匪凡,已是汗流浹背膊畴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留病游,地道東北人唇跨。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓稠通,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親买猖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子改橘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 原教程內(nèi)容詳見(jiàn)精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過(guò)程中的一些閱讀筆記玉控,個(gè)人覺(jué)得該教程講解深入淺出飞主,比目前大...
    leonaxiong閱讀 2,813評(píng)論 1 18
  • It's a common pattern in React to wrap a component in an ...
    jplyue閱讀 3,259評(píng)論 0 2
  • [原創(chuàng)] 作者/張慶九 我把心給了你 你說(shuō)愛(ài)是什么 我說(shuō)愛(ài)就是把心給了你 你說(shuō)愛(ài)是什么 我說(shuō)愛(ài)就是讓你幸...
    9陸海空9閱讀 590評(píng)論 4 14
  • 意識(shí):人的頭腦對(duì)客觀物質(zhì)世界的反映,是各種心理過(guò)程的總和虱而,是覺(jué)察筏餐、是感覺(jué)、佛教六識(shí)之一牡拇,心理學(xué)的定義是人所特有的一...
    夢(mèng)的精靈解夢(mèng)客閱讀 287評(píng)論 0 0