簡版React實(shí)現(xiàn)

1 基本概念:Component(組件)肯夏、instance(組件實(shí)例)惕橙、 element锐想、jsx、dom

Component(組件)

Component就是我們經(jīng)常實(shí)現(xiàn)的組件悍赢,可以是類組件(class component)或者函數(shù)式組件(functional component)

1.而類組件又可以分為普通類組件(React.Component)以及純類組件(React.PureComponent),總之這兩類都屬于類組件,只不過PureComponent基于shouldComponentUpdate做了一些優(yōu)化左权。

2.函數(shù)式組件則用來簡化一些簡單組件的實(shí)現(xiàn)皮胡,用起來就是寫一個(gè)函數(shù),
入?yún)⑹墙M件屬性props赏迟,出參與類組件的render方法返回值一樣屡贺,
是react element(注意這里已經(jīng)出現(xiàn)了接下來要介紹的element哦)。

下面我們分別按三種方式實(shí)現(xiàn)下Welcome組件:

// Component
class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
}
// PureComponent
class Welcome extends React.PureComponent {
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
}

// functional component
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}
instance(組件實(shí)例)

熟悉面向?qū)ο缶幊?/code>的人肯定知道實(shí)例的關(guān)系锌杀,這里也是一樣的甩栈,組件實(shí)例其實(shí)就是一個(gè)組件類實(shí)例化的結(jié)果,概念雖然簡單糕再,但是在react這里卻容易弄不明白量没,為什么這么說呢?因?yàn)榇蠹以?code>react的使用過程中并不會自己去實(shí)例化一個(gè)組件實(shí)例亿鲜,這個(gè)過程其實(shí)是react內(nèi)部幫我們完成的允蜈,因此我們真正接觸組件實(shí)例的機(jī)會并不多。我們更多接觸到的是下面要介紹的element蒿柳,因?yàn)槲覀兺ǔ懙?code>jsx其實(shí)就是element的一種表示方式而已(后面詳細(xì)介紹)饶套。雖然組件實(shí)例用的不多,但是偶爾也會用到垒探,其實(shí)就是ref妓蛮。ref可以指向一個(gè)dom節(jié)點(diǎn)或者一個(gè)類組件(class component)的實(shí)例,但是不能用于函數(shù)式組件圾叼,因?yàn)?code>函數(shù)式組件不能實(shí)例化蛤克。這里簡單介紹下ref,我們只需要知道ref可以指向一個(gè)組件實(shí)例即可夷蚊,更加詳細(xì)的介紹大家可以看react官方文檔Refs and the DOM构挤。

前面已經(jīng)提到了element,即類組件render方法以及函數(shù)式組件的返回值均為
element惕鼓。那么這里的element到底是什么呢筋现?其實(shí)很簡單,就是一個(gè)純對象(plain object)箱歧,而且這個(gè)純對象包含兩個(gè)屬性:type:(string|ReactClass)props:Object矾飞,注意element并不是組件實(shí)例,而是一個(gè)純對象呀邢。雖然element不是組件實(shí)例洒沦,但是又跟組件實(shí)例有關(guān)系,element是對組件實(shí)例或者dom節(jié)點(diǎn)的描述价淌。如果type是string類型申眼,則表示dom節(jié)點(diǎn)瞒津,如果type是function或者class類型,則表示組件實(shí)例括尸。比如下面兩個(gè)element分別描述了一個(gè)dom節(jié)點(diǎn)和一個(gè)組件實(shí)例

// 描述dom節(jié)點(diǎn)
{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

function Button(props){
  // ...
}

// 描述組件實(shí)例
{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}
jsx

只要弄明白了element仲智,那么jsx就不難理解了,jsx只是換了一種寫法姻氨,方便我們來創(chuàng)建element而已钓辆,想想如果沒有jsx那么我們開發(fā)效率肯定會大幅降低,而且代碼肯定非常不利于維護(hù)肴焊。比如我們看下面這個(gè)jsx的例子

const foo = <div id="foo">Hello!</div>;

其實(shí)說白了就是定義了一個(gè)dom節(jié)點(diǎn)div前联,并且該節(jié)點(diǎn)的屬性集合是{id: 'foo'},children是Hello!娶眷,就這點(diǎn)信息量而已似嗤,因此完全跟下面這種純對象的表示是等價(jià)的:

{
  type: 'div',
  props: {
    id: 'foo',
    children: 'Hello!'
  }
}

那么React是如何將jsx語法轉(zhuǎn)換為純對象的呢?其實(shí)就是利用Babel編譯生成的届宠,我們只要在使用jsx的代碼里加上個(gè)編譯指示(pragma)即可烁落,可以參考這里Babel如何編譯jsx。比如我們將編譯指示設(shè)置為指向createElement函數(shù):/** @jsx createElement */豌注,那么前面那段jsx代碼就會編譯為:

var foo = createElement('div', {id:"foo"}, 'Hello!');

可以看出伤塌,jsx的編譯過程其實(shí)就是從<、>這種標(biāo)簽式寫法到函數(shù)調(diào)用式寫法的一種轉(zhuǎn)化而已轧铁。有了這個(gè)前提每聪,我們只需要簡單實(shí)現(xiàn)下createElement函數(shù)不就可以構(gòu)造出element了嘛,我們后面自己實(shí)現(xiàn)簡版react也會用到這個(gè)函數(shù):

function createElement(type, props, ...children) {
    props = Object.assign({}, props);
    props.children = [].concat(...children)
      .filter(child => child != null && child !== false)
      .map(child => child instanceof Object ? child : createTextElement(child));
    return {type, props};
}

dom
dom我們這里也簡單介紹下齿风,作為一個(gè)前端研發(fā)人員药薯,想必大家對這個(gè)概念應(yīng)該再熟悉不過了。我們可以這樣創(chuàng)建一個(gè)dom節(jié)點(diǎn)div:

const divDomNode = window.document.createElement('div');

其實(shí)所有dom節(jié)點(diǎn)都是HTMLElement類的實(shí)例救斑,我們可以驗(yàn)證下:

window.document.createElement('div') instanceof window.HTMLElement;
// 輸出 true

關(guān)于HTMLElementAPI可以參考這里:HTMLElement介紹童本。因此,dom節(jié)點(diǎn)是HTMLElement類的實(shí)例脸候;同樣的穷娱,在react里面,組件實(shí)例組件類的實(shí)例纪他,而element又是對組件實(shí)例dom節(jié)點(diǎn)的描述鄙煤,現(xiàn)在這些概念之間的關(guān)系大家應(yīng)該都清楚了吧晾匠。介紹完了這幾個(gè)基本概念茶袒,我們畫個(gè)圖來描述下這幾個(gè)概念之間的關(guān)系:

2 虛擬dom與diff算法

相信使用過react的同學(xué)都多少了解過這兩個(gè)概念:虛擬dom以及diff算法。這里的虛擬dom其實(shí)就是前面介紹的element凉馆,為什么說是虛擬dom呢薪寓,前面咱們已經(jīng)介紹過了亡资,element只是dom節(jié)點(diǎn)或者組件實(shí)例的一種純對象描述而已,并不是真正的dom節(jié)點(diǎn)向叉,因此是虛擬dom锥腻。react給我們提供了聲明式的組件寫法當(dāng)組件的props或者state變化時(shí)組件自動更新母谎。整個(gè)頁面其實(shí)可以對應(yīng)到一棵dom節(jié)點(diǎn)樹瘦黑,每次組件props或者state變更首先會反映到虛擬dom樹,然后最終反應(yīng)到頁面dom節(jié)點(diǎn)樹的渲染奇唤。

那么虛擬dom跟diff算法又有什么關(guān)系呢幸斥?之所以有diff算法其實(shí)是為了提升渲染效率,試想下咬扇,如果每次組件的state或者props變化后都把所有相關(guān)dom節(jié)點(diǎn)刪掉再重新創(chuàng)建甲葬,那效率肯定非常,所以在react內(nèi)部存在兩棵虛擬dom樹懈贺,分別表示現(xiàn)狀及下一個(gè)狀態(tài)经窖,setState調(diào)用后就會觸發(fā)diff算法的執(zhí)行,而好的diff算法肯定是盡可能復(fù)用已有的dom節(jié)點(diǎn)梭灿,避免重新創(chuàng)建的開銷画侣。我用下圖來表示虛擬dom和diff算法的關(guān)系:

react組件最初渲染到頁面后先生成第1幀虛擬dom,這時(shí)current指針指向該第一幀堡妒。setState調(diào)用后會生成第2幀虛擬dom棉钧,這時(shí)next指針指向第二幀,接下來diff算法通過比較第2幀和第1幀的異同來將更新應(yīng)用到真正的dom樹以完成頁面更新涕蚤。
這里再次強(qiáng)調(diào)一下setState后具體怎么生成虛擬dom宪卿,因?yàn)檫@點(diǎn)很重要,而且容易忽略万栅。其實(shí)剛剛已經(jīng)介紹過什么是虛擬dom了佑钾,其實(shí)就是element樹而已。那element樹是怎么來的呢烦粒?其實(shí)就是render方法返回的嘛休溶,下面的流程圖再加深下印象:

react組件最初渲染到頁面后先生成第1幀虛擬dom,這時(shí)current指針指向該第一幀扰她。setState調(diào)用后會生成第2幀虛擬dom兽掰,這時(shí)next指針指向第二幀,接下來diff算法通過比較第2幀和第1幀的異同來將更新應(yīng)用到真正的dom樹以完成頁面更新徒役。
這里再次強(qiáng)調(diào)一下setState后具體怎么生成虛擬dom孽尽,因?yàn)檫@點(diǎn)很重要,而且容易忽略忧勿。其實(shí)剛剛已經(jīng)介紹過什么是虛擬dom了杉女,其實(shí)就是element樹而已瞻讽。那element樹是怎么來的呢?其實(shí)就是render方法返回的嘛熏挎,下面的流程圖再加深下印象:

其實(shí)react官方對diff算法有另外一個(gè)稱呼速勇,大家肯定會在react相關(guān)資料中看到,叫Reconciliation坎拐,我個(gè)人認(rèn)為這個(gè)詞有點(diǎn)晦澀難懂烦磁,不過后來又重新翻看了下詞典,發(fā)現(xiàn)其實(shí)跟diff算法一個(gè)意思:

可以看到reconcile有消除分歧哼勇、核對的意思个初,在react語境下就是對比虛擬dom異同的意思,其實(shí)就是說的diff算法猴蹂。這里強(qiáng)調(diào)下院溺,我們后面實(shí)現(xiàn)部實(shí)現(xiàn)reconcile函數(shù),其實(shí)就是實(shí)現(xiàn)diff算法磅轻。

3 生命周期與diff算法

生命周期與diff算法又有什么關(guān)系呢珍逸?這里我們以componentDidMountcomponentWillUnmount聋溜、ComponentWillUpdate以及componentDidUpdate為例說明下二者的關(guān)系谆膳。我們知道,setState調(diào)用后會接著調(diào)用render生成新的虛擬dom樹撮躁,而這個(gè)虛擬dom樹與上一幀可能會產(chǎn)生如下區(qū)別:

1.新增了某個(gè)組件漱病;
2.刪除了某個(gè)組件;
3.更新了某個(gè)組件的部分屬性把曼。

因此杨帽,我們在實(shí)現(xiàn)diff算法的過程會在相應(yīng)的時(shí)間節(jié)點(diǎn)調(diào)用這些生命周期函數(shù)。
這里需要重點(diǎn)說明下前面提到的第1幀嗤军,我們知道每個(gè)react應(yīng)用的入口都是:

ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
);

ReactDom.render也會生成一棵虛擬dom樹注盈,但是這棵虛擬dom樹是開天辟地生成的``第一幀,沒有前一幀用來做diff叙赚,因此這棵虛擬dom樹對應(yīng)的所有組件都只會調(diào)用掛載期的生命周期函數(shù)老客,比如componentDidMount,componentWillUnmount`。

4 實(shí)現(xiàn)

掌握了前面介紹的這些概念震叮,實(shí)現(xiàn)一個(gè)簡版react也就不難了胧砰。首先看一下我們要實(shí)現(xiàn)哪些API,我們最終會以如下方式使用:

// 聲明編譯指示
/** @jsx DiyReact.createElement */

// 導(dǎo)入我們下面要實(shí)現(xiàn)的API
const DiyReact = importFromBelow();

// 業(yè)務(wù)代碼
const randomLikes = () => Math.ceil(Math.random() * 100);
const stories = [
    {name: "DiyReact介紹", url: "http://google.com", likes: randomLikes()},
    {name: "Rendering DOM elements ", url: "http://google.com", likes: randomLikes()},
    {name: "Element creation and JSX", url: "http://google.com", likes: randomLikes()},
    {name: "Instances and reconciliation", url: "http://google.com", likes: randomLikes()},
    {name: "Components and state", url: "http://google.com", likes: randomLikes()}
];

class App extends DiyReact.Component {
    render() {
        return (
            <div>
                <h1>DiyReact Stories</h1>
                <ul>
                    {this.props.stories.map(story => {
                        return <Story name={story.name} url={story.url} />;
                    })}
                </ul>
            </div>
        );
    }

    componentWillMount() {
        console.log('execute componentWillMount');
    }

    componentDidMount() {
        console.log('execute componentDidMount');
    }

    componentWillUnmount() {
        console.log('execute componentWillUnmount');
    }
}

class Story extends DiyReact.Component {
    constructor(props) {
        super(props);
        this.state = {likes: Math.ceil(Math.random() * 100)};
    }
    like() {
        this.setState({
            likes: this.state.likes + 1
        });
    }
    render() {
        const {name, url} = this.props;
        const {likes} = this.state;
        const likesElement = <span />;
        return (
            <li>
                <button onClick={e => this.like()}>{likes}<b>??</b></button>
                <a href={url}>{name}</a>
            </li>
        );
    }

    // shouldcomponentUpdate() {
    //   return true;
    // }

    componentWillUpdate() {
        console.log('execute componentWillUpdate');
    }

    componentDidUpdate() {
        console.log('execute componentDidUpdate');
    }
}

// 將組件渲染到根dom節(jié)點(diǎn)
DiyReact.render(<App stories={stories} />, document.getElementById("root"));

我們在這段業(yè)務(wù)代碼里面使用了render苇瓣、createElement以及Component三個(gè)API尉间,因此后面的任務(wù)就是實(shí)現(xiàn)這三個(gè)API并包裝到一個(gè)函數(shù)importFromBelow內(nèi)即可。

4.1 實(shí)現(xiàn)createElement

createElement函數(shù)的功能跟jsx是緊密相關(guān)的,前面介紹jsx的部分已經(jīng)介紹過了乌妒,其實(shí)就是把類似html的標(biāo)簽式寫法轉(zhuǎn)化為純對象element,具體實(shí)現(xiàn)如下:

function createElement(type, props, ...children) {
    props = Object.assign({}, props);
    props.children = [].concat(...children)
        .filter(child => child != null && child !== false)
        .map(child => child instanceof Object ? child : createTextElement(child));
    return {type, props};
}

// rootInstance用來緩存一幀虛擬dom
let rootInstance = null;
function render(element, parentDom) {
    // prevInstance指向前一幀
    const prevInstance = rootInstance;
    // element參數(shù)指向新生成的虛擬dom樹
    const nextInstance = reconcile(parentDom, prevInstance, element);
    // 調(diào)用完reconcile算法(即diff算法)后將rooInstance指向最新一幀
    rootInstance = nextInstance;
}

render函數(shù)實(shí)現(xiàn)很簡單外邓,只是進(jìn)行了兩幀虛擬dom的對比(reconcile)撤蚊,然后將rootInstance指向新的虛擬dom。細(xì)心點(diǎn)會發(fā)現(xiàn)损话,新的虛擬dom為element侦啸,即最開始介紹的element,而reconcile后的虛擬dom是instance丧枪,不過這個(gè)instance并不是組件實(shí)例光涂,這點(diǎn)看后面instantiate的實(shí)現(xiàn)∨》常總之render方法其實(shí)就是調(diào)用了reconcile方法進(jìn)行了兩幀虛擬dom的對比而已忘闻。

4.3 實(shí)現(xiàn)instantiate

那么前面的instance到底跟element有什么不同呢?其實(shí)instance指示簡單的是把element重新包了一層恋博,并把對應(yīng)的dom也給包了進(jìn)來齐佳,這也不難理解,畢竟我們調(diào)用reconcile進(jìn)行diff比較的時(shí)候需要把跟新應(yīng)用到真實(shí)的dom上债沮,因此需要跟dom關(guān)聯(lián)起來炼吴,下面實(shí)現(xiàn)的instantiate函數(shù)就干這個(gè)事的。注意由于element包括dom類型和Component類型(由type字段判斷疫衩,不明白的話可以回過頭看一下第一節(jié)的element相關(guān)介紹)硅蹦,因此需要分情況處理:
dom類型的element.type為string類型,對應(yīng)的instance結(jié)構(gòu)為{element, dom, childInstances}闷煤。
Component類型的element.type為ReactClass類型童芹,對應(yīng)的instance結(jié)構(gòu)為{dom, element, childInstance, publicInstance},注意這里的publicInstance就是前面介紹的組件實(shí)例鲤拿。

function instantiate(element) {
    const {type, props = {}} = element;

    const isDomElement = typeof type === 'string';

    if (isDomElement) {
        // 創(chuàng)建dom
        const isTextElement = type === TEXT_ELEMENT;
        const dom = isTextElement ? document.createTextNode('') : document.createElement(type);

        // 設(shè)置dom的事件辐脖、數(shù)據(jù)屬性
        updateDomProperties(dom, [], element.props);
        const children = props.children || [];
        const childInstances = children.map(instantiate);
        const childDoms = childInstances.map(childInstance => childInstance.dom);
        childDoms.forEach(childDom => dom.appendChild(childDom));
        const instance = {element, dom, childInstances};
        return instance;
    } else {
        const instance = {};
        const publicInstance = createPublicInstance(element, instance);
        const childElement = publicInstance.render();
        const childInstance = instantiate(childElement);
        Object.assign(instance, {dom: childInstance.dom, element, childInstance, publicInstance});
        return instance;
    }
}

需要注意,由于dom節(jié)點(diǎn)組件實(shí)例都可能有孩子節(jié)點(diǎn)皆愉,因此instantiate函數(shù)中有遞歸實(shí)例化的邏輯嗜价。

4.4 實(shí)現(xiàn)reconcile(diff算法)

重點(diǎn)來了,reconcile是react的核心幕庐,顯然如何將新設(shè)置的state快速的渲染出來非常重要久锥,因此react會盡量復(fù)用已有節(jié)點(diǎn),而不是每次都動態(tài)創(chuàng)建所有相關(guān)節(jié)點(diǎn)异剥。但是react強(qiáng)大的地方還不僅限于此瑟由,react16reconcile算法由之前的stack架構(gòu)升級成了fiber架構(gòu),更近一步做的性能優(yōu)化冤寿。fiber相關(guān)的內(nèi)容下一節(jié)再介紹歹苦,這里為了簡單易懂青伤,仍然使用類似stack架構(gòu)的算法來實(shí)現(xiàn),對于fiber現(xiàn)在只需要知道其調(diào)度原理即可殴瘦,當(dāng)然后面有時(shí)間可以再實(shí)現(xiàn)一版基于fiber架構(gòu)的狠角。

首先看一下整個(gè)reconcile算法的處理流程

可以看到,我們會根據(jù)不同的情況做不同的處理:

1.如果是新增instance蚪腋,那么需要實(shí)例化一個(gè)instance并且appendChild丰歌;
2.如果是不是新增instance,而是刪除instance屉凯,那么需要removeChild立帖;
3.如果既不是新增也不是刪除instance,那么需要看instancetype是否變化悠砚,如果有變化晓勇,那節(jié)點(diǎn)就無法復(fù)用了,也需要實(shí)例化instance灌旧,然后replaceChild宵蕉;
4.如果type沒變化就可以復(fù)用已有節(jié)點(diǎn)了,這種情況下要判斷是原生dom節(jié)點(diǎn)還是我們自定義實(shí)現(xiàn)的react節(jié)點(diǎn)节榜,兩種情況下處理方式不同羡玛。

大流程了解后,我們只需要在對的時(shí)間點(diǎn)執(zhí)行生命周期函數(shù)即可宗苍,下面看具體實(shí)現(xiàn)

function reconcile(parentDom, instance, element) {
    if (instance === null) {
        const newInstance = instantiate(element);
        // componentWillMount
        newInstance.publicInstance
            && newInstance.publicInstance.componentWillMount
            && newInstance.publicInstance.componentWillMount();
        parentDom.appendChild(newInstance.dom);
        // componentDidMount
        newInstance.publicInstance
            && newInstance.publicInstance.componentDidMount
            && newInstance.publicInstance.componentDidMount();
        return newInstance;
    } else if (element === null) {
        // componentWillUnmount
        instance.publicInstance
            && instance.publicInstance.componentWillUnmount
            && instance.publicInstance.componentWillUnmount();
        parentDom.removeChild(instance.dom);
        return null;
    } else if (instance.element.type !== element.type) {
        const newInstance = instantiate(element);
        // componentDidMount
        newInstance.publicInstance
            && newInstance.publicInstance.componentDidMount
            && newInstance.publicInstance.componentDidMount();
        parentDom.replaceChild(newInstance.dom, instance.dom);
        return newInstance;
    } else if (typeof element.type === 'string') {
        updateDomProperties(instance.dom, instance.element.props, element.props);
        instance.childInstances = reconcileChildren(instance, element);
        instance.element = element;
        return instance;
    } else {
        if (instance.publicInstance
            && instance.publicInstance.shouldcomponentUpdate) {
            if (!instance.publicInstance.shouldcomponentUpdate()) {
                return;
            }
        }
        // componentWillUpdate
        instance.publicInstance
            && instance.publicInstance.componentWillUpdate
            && instance.publicInstance.componentWillUpdate();
        instance.publicInstance.props = element.props;
        const newChildElement = instance.publicInstance.render();
        const oldChildInstance = instance.childInstance;
        const newChildInstance = reconcile(parentDom, oldChildInstance, newChildElement);
        // componentDidUpdate
        instance.publicInstance
            && instance.publicInstance.componentDidUpdate
            && instance.publicInstance.componentDidUpdate();
        instance.dom = newChildInstance.dom;
        instance.childInstance = newChildInstance;
        instance.element = element;
        return instance;
    }
}

function reconcileChildren(instance, element) {
    const {dom, childInstances} = instance;
    const newChildElements = element.props.children || [];
    const count = Math.max(childInstances.length, newChildElements.length);
    const newChildInstances = [];
    for (let i = 0; i < count; i++) {
        newChildInstances[i] = reconcile(dom, childInstances[i], newChildElements[i]);
    }
    return newChildInstances.filter(instance => instance !== null);
}

看完reconcile算法后肯定有人會好奇稼稿,為什么這種算法叫做stack算法,這里簡單解釋一下讳窟。從前面的實(shí)現(xiàn)可以看到让歼,每次組件的state更新都會觸發(fā)reconcile的執(zhí)行,而reconcile的執(zhí)行也是一個(gè)遞歸過程丽啡,而且一開始直到遞歸執(zhí)行完所有節(jié)點(diǎn)才停止谋右,因此成為stack算法。由于是個(gè)遞歸過程补箍,因此該diff算法一旦開始就必須執(zhí)行完改执,因此可能會阻塞線程,又由于js是單線程的坑雅,因此這時(shí)就可能會影響用戶的輸入或者ui的渲染幀頻辈挂,降低用戶體驗(yàn)。不過react16中升級為了fiber架構(gòu)裹粤,這一問題得到了解決终蒂。

把前面實(shí)現(xiàn)的所有這些代碼組合起來就是完整的簡版react,不到200行代碼,希望大家多度指教

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拇泣,隨后出現(xiàn)的幾起案子噪叙,更是在濱河造成了極大的恐慌,老刑警劉巖霉翔,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睁蕾,死亡現(xiàn)場離奇詭異,居然都是意外死亡早龟,警方通過查閱死者的電腦和手機(jī)惫霸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門猫缭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葱弟,“玉大人,你說我怎么就攤上這事猜丹≈ゼ樱” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵射窒,是天一觀的道長藏杖。 經(jīng)常有香客問我,道長脉顿,這世上最難降的妖魔是什么蝌麸? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮艾疟,結(jié)果婚禮上来吩,老公的妹妹穿的比我還像新娘。我一直安慰自己蔽莱,他們只是感情好弟疆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盗冷,像睡著了一般怠苔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仪糖,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天柑司,我揣著相機(jī)與錄音,去河邊找鬼锅劝。 笑死帜羊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸠天。 我是一名探鬼主播讼育,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奶段?” 一聲冷哼從身側(cè)響起饥瓷,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痹籍,沒想到半個(gè)月后呢铆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹲缠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年棺克,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片线定。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娜谊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斤讥,到底是詐尸還是另有隱情纱皆,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布芭商,位于F島的核電站派草,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铛楣。R本人自食惡果不足惜近迁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望簸州。 院中可真熱鬧鉴竭,春花似錦、人聲如沸勿侯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽助琐。三九已至祭埂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兵钮,已是汗流浹背蛆橡。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掘譬,地道東北人泰演。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像葱轩,于是被迫代替她去往敵國和親睦焕。 傳聞我的和親對象是個(gè)殘疾皇子藐握,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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

  • 1.(Didact)一個(gè)DIY教程:創(chuàng)建你自己的react1.1 引言 2.渲染dom元素2.1 什么是DOM2....
    johnzhu12閱讀 776評論 0 51
  • 參考文章:深度剖析:如何實(shí)現(xiàn)一個(gè)Virtual DOM 算法 作者:戴嘉華React中一個(gè)沒人能解釋清楚的問題——...
    waka閱讀 5,951評論 0 21
  • 秋雨隨風(fēng)更肆虐,秋風(fēng)颯爽雨絲飛垃喊。 荒涼一片足蹤滅猾普,天地蒼茫處處灰。
    徐一村閱讀 180評論 0 4
  • NoSQL(Redis)秒殺 概念 秒殺 并發(fā) MySQL負(fù)庫存(秒殺可能出現(xiàn)的問題) 修改mysql.ini m...
    空留燈半盞閱讀 622評論 1 5
  • 等雨落下的這段時(shí)間 江水只流動了一次 江邊的塔本谜、古寺和江對岸的 高樓大廈初家,江中心吃水已到甲板的貨船 都保持了靜止 ...
    伏櫪齋閱讀 154評論 0 0