成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

從零開始實(shí)現(xiàn)一個(gè)React(四):異步的setState

RyanQ / 1289人閱讀

摘要:一個(gè)比較好的做法是利用的事件隊(duì)列機(jī)制。整個(gè)系列大概會(huì)有四篇左右,我每周會(huì)更新一到兩篇,我會(huì)第一時(shí)間在上更新,有問題需要探討也請(qǐng)?jiān)谏匣貜?fù)我博客地址關(guān)注點(diǎn),訂閱點(diǎn)上一篇文章從零開始實(shí)現(xiàn)一個(gè)三算法

前言

在上一篇文章中,我們實(shí)現(xiàn)了diff算法,性能有非常大的改進(jìn)。但是文章末尾也指出了一個(gè)問題:按照目前的實(shí)現(xiàn),每次調(diào)用setState都會(huì)觸發(fā)更新,如果組件內(nèi)執(zhí)行這樣一段代碼:

for ( let i = 0; i < 100; i++ ) {
    this.setState( { num: this.state.num + 1 } );
}

那么執(zhí)行這段代碼會(huì)導(dǎo)致這個(gè)組件被重新渲染100次,這對(duì)性能是一個(gè)非常大的負(fù)擔(dān)。

真正的React是怎么做的

React顯然也遇到了這樣的問題,所以針對(duì)setState做了一些特別的優(yōu)化:React會(huì)將多個(gè)setState的調(diào)用合并成一個(gè)來執(zhí)行,這意味著當(dāng)調(diào)用setState時(shí),state并不會(huì)立即更新,舉個(gè)栗子:

class App extends Component {
    constructor() {
        super();
        this.state = {
            num: 0
        }
    }
    componentDidMount() {
        for ( let i = 0; i < 100; i++ ) {
            this.setState( { num: this.state.num + 1 } );
            console.log( this.state.num );    // 會(huì)輸出什么?
        }
    }
    render() {
        return (
            

{ this.state.num }

); } }

我們定義了一個(gè)App組件,在組件掛載后,會(huì)循環(huán)100次,每次讓this.state.num增加1,我們用真正的React來渲染這個(gè)組件,看看結(jié)果:

組件渲染的結(jié)果是1,并且在控制臺(tái)中輸出了100次0,說明每個(gè)循環(huán)中,拿到的state仍然是更新之前的。

這是React的優(yōu)化手段,但是顯然它也會(huì)在導(dǎo)致一些不符合直覺的問題(就如上面這個(gè)例子),所以針對(duì)這種情況,React給出了一種解決方案:setState接收的參數(shù)還可以是一個(gè)函數(shù),在這個(gè)函數(shù)中可以拿先前的狀態(tài),并通過這個(gè)函數(shù)的返回值得到下一個(gè)狀態(tài)。

我們可以通過這種方式來修正App組件:

componentDidMount() {
    for ( let i = 0; i < 100; i++ ) {
        this.setState( prevState => {
            console.log( prevState.num );
            return {
                num: prevState.num + 1
            }
        } );
    }
}
這種用法是不是很像數(shù)組的reduce方法?

現(xiàn)在來看看App組件的渲染結(jié)果:

現(xiàn)在終于能得到我們想要的結(jié)果了。

所以,這篇文章的目標(biāo)也明確了,我們要實(shí)現(xiàn)以下兩個(gè)功能

異步更新state,將短時(shí)間內(nèi)的多個(gè)setState合并成一個(gè)

為了解決異步更新導(dǎo)致的問題,增加另一種形式的setState:接受一個(gè)函數(shù)作為參數(shù),在函數(shù)中可以得到前一個(gè)狀態(tài)并返回下一個(gè)狀態(tài)

合并setState

回顧一下第二篇文章中對(duì)setState的實(shí)現(xiàn):

setState( stateChange ) {
    Object.assign( this.state, stateChange );
    renderComponent( this );
}

這種實(shí)現(xiàn),每次調(diào)用setState都會(huì)更新state并馬上渲染一次。

setState隊(duì)列

為了合并setState,我們需要一個(gè)隊(duì)列來保存每次setState的數(shù)據(jù),然后在一段時(shí)間后,清空這個(gè)隊(duì)列并渲染組件。

隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu),它的特點(diǎn)是“先進(jìn)先出”,可以通過js數(shù)組的push和shift方法模擬
const queue = [];
function enqueueSetState( stateChange, component ) {
    queue.push( {
        stateChange,
        component
    } );
}

然后修改組件的setState方法

setState( stateChange ) {
    enqueueSetState( stateChange, this );
}

現(xiàn)在隊(duì)列是有了,怎么清空隊(duì)列并渲染組件呢?

清空隊(duì)列

我們定義一個(gè)flush方法,它的作用就是清空隊(duì)列

function flush() {
    let item;
    // 遍歷
    while( item = setStateQueue.shift() ) {

        const { stateChange, component } = item;

        // 如果沒有prevState,則將當(dāng)前的state作為初始的prevState
        if ( !component.prevState ) {
            component.prevState = Object.assign( {}, component.state );
        }

        // 如果stateChange是一個(gè)方法,也就是setState的第二種形式
        if ( typeof stateChange === "function" ) {
            Object.assign( component.state, stateChange( component.prevState, component.props ) );
        } else {
            // 如果stateChange是一個(gè)對(duì)象,則直接合并到setState中
            Object.assign( component.state, stateChange );
        }

        component.prevState = component.state;

    }
}

這只是實(shí)現(xiàn)了state的更新,我們還沒有渲染組件。渲染組件不能在遍歷隊(duì)列時(shí)進(jìn)行,因?yàn)橥粋€(gè)組件可能會(huì)多次添加到隊(duì)列中,我們需要另一個(gè)隊(duì)列保存所有組件,不同之處是,這個(gè)隊(duì)列內(nèi)不會(huì)有重復(fù)的組件。

我們?cè)趀nqueueSetState時(shí),就可以做這件事

const queue = [];
const renderQueue = [];
function enqueueSetState( stateChange, component ) {
    queue.push( {
        stateChange,
        component
    } );
    // 如果renderQueue里沒有當(dāng)前組件,則添加到隊(duì)列中
    if ( !renderQueue.some( item => item === component ) ) {
        renderQueue.push( component );
    }
}

在flush方法中,我們還需要遍歷renderQueue,來渲染每一個(gè)組件

function flush() {
    let item, component;
    while( item = queue.shift() ) {
        // ...
    }
    // 渲染每一個(gè)組件
    while( component = renderQueue.shift() ) {
        renderComponent( component );
    }

}
延遲執(zhí)行

現(xiàn)在還有一件最重要的事情:什么時(shí)候執(zhí)行flush方法。
我們需要合并一段時(shí)間內(nèi)所有的setState,也就是在一段時(shí)間后才執(zhí)行flush方法來清空隊(duì)列,關(guān)鍵是這個(gè)“一段時(shí)間“怎么決定。

一個(gè)比較好的做法是利用js的事件隊(duì)列機(jī)制。

先來看這樣一段代碼:

setTimeout( () => {
    console.log( 2 );
}, 0 );
Promise.resolve().then( () => console.log( 1 ) );
console.log( 3 );

你可以打開瀏覽器的調(diào)試工具運(yùn)行一下,它們打印的結(jié)果是:

3
1
2

具體的原理可以看阮一峰的這篇文章,這里就不再贅述了。

我們可以利用事件隊(duì)列,讓flush在所有同步任務(wù)后執(zhí)行

function enqueueSetState( stateChange, component ) {
    // 如果queue的長(zhǎng)度是0,也就是在上次flush執(zhí)行之后第一次往隊(duì)列里添加
    if ( queue.length === 0 ) {
        defer( flush );
    }
    queue.push( {
        stateChange,
        component
    } );
    if ( !renderQueue.some( item => item === component ) ) {
        renderQueue.push( component );
    }
}

定義defer方法,利用剛才題目中出現(xiàn)的Promise.resolve

function defer( fn ) {
    return Promise.resolve().then( fn );
}

這樣在一次“事件循環(huán)“中,最多只會(huì)執(zhí)行一次flush了,在這個(gè)“事件循環(huán)”中,所有的setState都會(huì)被合并,并只渲染一次組件。

別的延遲執(zhí)行方法

除了用Promise.resolve().then( fn ),我們也可以用上文中提到的setTimeout( fn, 0 ),setTimeout的時(shí)間也可以是別的值,例如16毫秒。

16毫秒的間隔在一秒內(nèi)大概可以執(zhí)行60次,也就是60幀,人眼每秒只能捕獲60幅畫面

另外也可以用requestAnimationFrame或者requestIdleCallback

function defer( fn ) {
    return requestAnimationFrame( fn );
}
試試效果

就試試渲染上文中用React渲染的那兩個(gè)例子:

class App extends Component {
    constructor() {
        super();
        this.state = {
            num: 0
        }
    }
    componentDidMount() {
        for ( let i = 0; i < 100; i++ ) {
            this.setState( { num: this.state.num + 1 } );
            console.log( this.state.num ); 
        }
    }
    render() {
        return (
            

{ this.state.num }

); } }

效果和React完全一樣

同樣,用第二種方式調(diào)用setState:

componentDidMount() {
    for ( let i = 0; i < 100; i++ ) {
        this.setState( prevState => {
            console.log( prevState.num );
            return {
                num: prevState.num + 1
            }
        } );
    }
}

結(jié)果也完全一樣:

后話

在這篇文章中,我們又實(shí)現(xiàn)了一個(gè)很重要的優(yōu)化:合并短時(shí)間內(nèi)的多次setState,異步更新state。
到這里我們已經(jīng)實(shí)現(xiàn)了React的大部分核心功能和優(yōu)化手段了,所以這篇文章也是這個(gè)系列的最后一篇了。

這篇文章的所有代碼都在這里:https://github.com/hujiulong/...

從零開始實(shí)現(xiàn)React系列

React是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個(gè)角度去解讀React:從零開始實(shí)現(xiàn)一個(gè)React,從API層面實(shí)現(xiàn)React的大部分功能,在這個(gè)過程中去探索為什么有虛擬DOM、diff、為什么setState這樣設(shè)計(jì)等問題。

整個(gè)系列大概會(huì)有四篇左右,我每周會(huì)更新一到兩篇,我會(huì)第一時(shí)間在github上更新,有問題需要探討也請(qǐng)?jiān)趃ithub上回復(fù)我~

博客地址: https://github.com/hujiulong/...
關(guān)注點(diǎn)star,訂閱點(diǎn)watch
上一篇文章

從零開始實(shí)現(xiàn)一個(gè)React(三):diff算法

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/94331.html

相關(guān)文章

  • 從零開始編寫React-Express單頁博客應(yīng)用(學(xué)習(xí)總結(jié))

    摘要:?jiǎn)雾摬┛蛻?yīng)用編寫總結(jié)很久之前就想寫一個(gè)博客應(yīng)用在一開始想要直接用和模板直接寫但是暑假一開始的時(shí)候不小心入了的坑所以就一不做二不休直接用寫那既然用了不寫個(gè)單頁應(yīng)用也過意不去了不前前后后寫了將近兩個(gè)星期現(xiàn)在看來這其實(shí)是一個(gè)很容易的應(yīng)用但是鑒于 React-Express單頁博客應(yīng)用編寫總結(jié) 很久之前就想寫一個(gè)博客應(yīng)用.在一開始想要直接用express和ejs模板直接寫, 但是暑假一開始的時(shí)...

    Jioby 評(píng)論0 收藏0
  • 金三銀,前端同學(xué)快來補(bǔ)補(bǔ)React原理吧

    摘要:無疑是一個(gè)非常值得學(xué)習(xí)其原理的框架,它設(shè)計(jì)簡(jiǎn)單,沒有引入任何新的概念,一個(gè)組件就是一個(gè)方法或一個(gè)類。 這是我?guī)讉€(gè)月前寫的文章,在前端面試中原理相關(guān)的問題是問的最多的,所以重新推薦下這幾篇文章 深入學(xué)習(xí)一個(gè)框架最直接的方式,就是弄明白框架的原理。React無疑是一個(gè)非常值得學(xué)習(xí)其原理的框架,它設(shè)計(jì)簡(jiǎn)單,沒有引入任何新的概念,一個(gè)組件就是一個(gè)方法或一個(gè)類。 但是要完整弄明白R(shí)eact的源碼...

    dcr309duan 評(píng)論0 收藏0
  • 從零開始實(shí)現(xiàn)一個(gè)React(二):實(shí)現(xiàn)組件功能

    摘要:在這篇文章中,我們就要實(shí)現(xiàn)的組件功能。這篇文章的代碼從零開始實(shí)現(xiàn)系列是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個(gè)角度去解讀從零開始實(shí)現(xiàn)一個(gè),從層面實(shí)現(xiàn)的大部分功能,在這個(gè)過程中去探索為什么有虛擬為什么這樣設(shè)計(jì)等問題。 前言 在上一篇文章JSX和虛擬DOM中,我們實(shí)現(xiàn)了基礎(chǔ)的JSX渲染功能,但是React的意義在于組件化。在這篇文章中,我們就要實(shí)現(xiàn)React的組件功...

    vslam 評(píng)論0 收藏0
  • 從零自己編寫一個(gè)React框架 【中高級(jí)前端殺手锏級(jí)別技能】

    摘要:想要自己實(shí)現(xiàn)一個(gè)簡(jiǎn)易版框架,并不是非常難。為了防止出現(xiàn)這種情況,我們需要改變整體的策略。上面這段話,說的就是版本和架構(gòu)的區(qū)別。 showImg(https://segmentfault.com/img/bVbwfRh); 想要自己實(shí)現(xiàn)一個(gè)React簡(jiǎn)易版框架,并不是非常難。但是你需要先了解下面這些知識(shí)點(diǎn)如果你能閱讀以下的文章,那么會(huì)更輕松的閱讀本文章: 優(yōu)化你的超大型React應(yīng)用 ...

    hot_pot_Leo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<