不積跬步之怎么升級(jí)react生命周期到16.4以后

timg (1).jpg

為什么要升級(jí)你的代碼?

這一次生命周期的升級(jí)非常重要,由于react官方即將發(fā)布的異步渲染機(jī)制,也就是React Fiber是什么.造成原來只會(huì)調(diào)用一次的生命周期會(huì)有可能多次調(diào)用谱邪。甚至是調(diào)用到一半然后作廢重新調(diào)用盲镶。所以在原來的reader之前的生命周期都不在安全。如果你在render之前的生命周期中進(jìn)行副作用的操作,如異步請(qǐng)求接口馅而,訂閱,耗時(shí)操作等其他的譬圣,都有可能出現(xiàn)意想不到的bug瓮恭。所以我們要把這些原來的操作放到render完成之后的生命周期,這樣可以保證我們進(jìn)行的副作用只會(huì)調(diào)用一次厘熟,下面我們來說一下之前我們的一些操作怎么升級(jí)到新的生命周期里屯蹦。那么現(xiàn)在開始吧。

關(guān)于 componentWillMount

初始化操作

有的人可能喜歡在componentWillMount進(jìn)行初始化操作绳姨。例如:

class ExampleComponent extends React.Component {
  state = {};

  componentWillMount() {
    this.setState({
      currentColor: this.props.defaultColor,
      palette: 'rgb',
    });
  }
}

這種操作最簡(jiǎn)單的方式就是把它放到構(gòu)造函數(shù)里或者屬性初始值設(shè)定項(xiàng)中:

// After
class ExampleComponent extends React.Component {
  state = {
    currentColor: this.props.defaultColor,
    palette: 'rgb',
  };
}

//或者:

class ExampleComponent extends React.Component {
 constructor(props) {
    super(props);
    currentColor: props.defaultColor,
    palette: 'rgb',
  };
}

獲取外部數(shù)據(jù)--如異步調(diào)用的

有在componentWillMount生命周期中進(jìn)行異步調(diào)用獲取數(shù)據(jù)的登澜,例如:

// Before
class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };

  componentWillMount() {
    this._asyncRequest = loadMyAsyncData().then(
      externalData => {
        this._asyncRequest = null;
        this.setState({externalData});
      }
    );
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }
}

在即將到來的異步渲染模式里,有可能會(huì)多次調(diào)用這個(gè)生命周期飘庄,如果你在這里進(jìn)行接口請(qǐng)求脑蠕,就有可能多次調(diào)用接口。所以這里的建議是把請(qǐng)求接口的操作放到渲染完畢以后的componentDidMount里跪削。

// After
class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };

  componentDidMount() {
    this._asyncRequest = loadMyAsyncData().then(
      externalData => {
        this._asyncRequest = null;
        this.setState({externalData});
      }
    );
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }
}

很多人以為componentWillMountcomponentWillUnmount是配對(duì)的谴仙。但是其實(shí)并不是。反而是componentDidMountcomponentWillUnmount是一對(duì)碾盐。他們特別適合在訂閱/取消的操作。例如:setTimeout廓旬、setInterval;上面例子中請(qǐng)求接口/取消接口等哼审。我們這里舉一個(gè)setInterval的例子:

class Clock extends React.Component {
    constructor(props){
        super(props)
        this.state = getClockTime();
    }
    
    componentDidMount(){
        this.ticking  = setInterval(()=>{
            this.setState(getClockTime();
        },1000);
    }
    
    componentWillUnmount(){
        clearInterval(this.ticking);
    }
    
    render(){
        ...
    }
}

關(guān)于componentWillReceiveProps的升級(jí)

這個(gè)方法的升級(jí)是重中之重。在以前我們通過該方法來實(shí)現(xiàn)更新state孕豹。以達(dá)到受控組件的目的,父組件可以控制子組件的更新十气,獲取數(shù)據(jù)励背,重新渲染等的操作。在使用新的getDerivedStateFromProps方法來代替原來的componentWillReceiveProps方法時(shí)砸西,原來的一些寫法也不在適用叶眉。

當(dāng)新的props到來時(shí)更新state

下面是使用componentWillReceiverProps來實(shí)現(xiàn)基于新的props來更新state.

class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.currentRow !== nextProps.currentRow) {
      this.setState({
        isScrollingDown:
          nextProps.currentRow > this.props.currentRow,
      });
    }
  }
}

雖然上面的代碼并沒有問題,但是componentWillReceiveProps經(jīng)常被錯(cuò)誤的使用芹枷。因此該方法將被廢棄!

從版本16.3開始衅疙,我們推薦使用stattic getDerivedStateFromProps(props,state)來實(shí)現(xiàn)基于props對(duì)state的更新。即將廢棄的三個(gè)方法的功能都會(huì)被該方法替代鸳慈。也即是說饱溢,組件在掛載時(shí),更新props時(shí)走芋,更新state時(shí)绩郎,forceUpdate時(shí)潘鲫,都會(huì)調(diào)用該方法。
換一句話說肋杖。只要你render就會(huì)調(diào)用該方法溉仑。由于該方法是一個(gè)靜態(tài)方法,無法拿到該組件的實(shí)例状植。
因?yàn)槠浔活l繁的調(diào)用浊竟。同樣在這個(gè)方法里也不適合進(jìn)行耗時(shí)的操作。

我們看一下官方給出的例子:

class ExampleComponent extends React.Component {
    state = {
        isScrollingDown:false,
        lastRow:null;
    }
    
    static getDerivedStateFromProps(props,state){
        if(props.currentRow 津畸!== state.lastRow){
            return {
                isScrollingDown:props.currentRow > state.lastRow,
                lastRow:props.currentRow,
            }
        }
        
        return null;
    }
}

在上面的例子中:static getDerivedStateFromProps(props,state)由于是靜態(tài)方法振定,因?yàn)槟貌坏浇M件的實(shí)例。所以在這里也就不能通過this.props.currentRow來拿到當(dāng)前實(shí)例對(duì)象中保存的currentRow數(shù)據(jù)洼畅。

在上面的例子中是通過把鏡像數(shù)據(jù)保存到state里吩案,然后在新的props到來時(shí)進(jìn)行比較來達(dá)到同樣的效果的。

當(dāng)新的props到來時(shí)帝簇,進(jìn)行異步接口請(qǐng)求.

我們有時(shí)候也會(huì)有這種需求徘郭。當(dāng)新的props更新時(shí),我們需要重新獲取數(shù)據(jù)來更新頁面丧肴,例如:

class ExampleComponent extends React.Component {
    state = {
        externalData:null,
    }
    
    componentDidMount(){
        this._loadAsyncData(this.props.id);
    }
    
    componentWillReceiveProps(nextProps){
        if(nextProps.id !== this.props.id){
            this.setState({externalData:null});
            this._loadAsyncData(nextProps.id)
        }
    }
    
    componentWillUnmount(){
        if(this._asyncRequest){
            this._asyncRequest.cancel();
        }
    }
    
    render(){
        if(this.state.externalData === null){
            //...
        }else {
            //...
        }
    }
    
    _laodAsyncData(id){
        this._asyncRequest = loadMyAsyncData(id).then(
            externalData => {
                this._asyncRequest = null;
                this.setState({externalData});
            }
        );
    }
}

在上面的例子中残揉,我們獲取數(shù)據(jù)的依據(jù)是propsid。所以我們需要在componentWillReceiveProps中監(jiān)聽id的變化芋浮。當(dāng)變化發(fā)生時(shí)觸發(fā)異步獲取數(shù)據(jù)抱环。

而這里的代碼升級(jí)可以說秉承著一個(gè)原則,就是凡是異步獲取纸巷,或者有副作用的操作镇草,通通扔到生命周期的后面去,也就是render完以后瘤旨,以保證數(shù)據(jù)只執(zhí)行一次梯啤。

在這里就是把數(shù)據(jù)獲取放到componentDidUpdate中。而我們的getDerivedStateFromProps生命周期干嘛呢存哲?

更新props.id的數(shù)據(jù)到state里因宇。置空externalDatanull。我們通過判斷externalData來獲取數(shù)據(jù)祟偷。

class ExampleComponent extends React.Component {
    state = {
        externalData:null,
    }
    
    static getDerivedStateFromProps(props,state){
         //我們把props.id的值保存為state.prevID察滑。當(dāng)新的props.id到來時(shí),我們用來做比較
        // 清除原來的數(shù)據(jù)修肠,這樣我們可以基于此來判斷是否需要重新獲取數(shù)據(jù)
        if(props.id 1== state.prevID){
            return {
                externalData:null,
                prevID:props.id
            }
        }
        return null;
    }
    
    componentDidMount(){
        this._loadAsyncData(this.props.id);
    }
    //在更新完成以后贺辰,通過externalData是否為空來判斷是否需要更新數(shù)據(jù)
    componentDidUpdate(prevProps,prevState){
        if(this.state.externalData === null){
            this._loadAsyncData(this.props.id);
        }
    }
    
    componentWillUnmount(){
        if(this._asyncRequest){
            this._asyncRequest.cancel();
        }
    }
    
    render(){
        if(this.state.externalData === null){
            //...
        }else {
            //...
        }
    }
    
    _laodAsyncData(id){
        this._asyncRequest = loadMyAsyncData(id).then(
            externalData => {
                this._asyncRequest = null;
                this.setState({externalData});
            }
        );
    }
}

現(xiàn)在我們總結(jié)一下getDerivedStateFromPropscomponentWillReceiveProps使用上的差異:

角標(biāo) componentWillReceiveProps getDerivedStateFromProps
如何確定是否更新state if(nextProps.id !== this.props.id){} 把鏡像保存到state里。通過保存在state里的鏡像來進(jìn)行比較判斷if(props.id 1== state.prevID){
如何確定是否需要重新獲取數(shù)據(jù) 通過 if(nextProps.id !== this.props.id){}直接就可以知道需要重新獲取數(shù)據(jù) 通過保存在state里的鏡像來進(jìn)行比較判斷,在上面的例子是通過把數(shù)據(jù)置空魂爪,我們也可以設(shè)置一個(gè)loadingtrue先舷。然后在componentDidUpdate(prevProps,prevState){}判斷來觸發(fā)獲取數(shù)據(jù)的操作。

想比較于原來的做法確實(shí)要繁瑣一點(diǎn)滓侍。

關(guān)于componentWillUpdate

關(guān)于這個(gè)的生命周期蒋川,我在實(shí)際開發(fā)中基本沒有用到過。官網(wǎng)例子中舉了一個(gè)render之前獲取DOM屬性的操作撩笆。

然后使用新的生命周期getSnapshotBeforeUpdate來取代一下:

class ScrollingList extends React.Component {
    listRef = null;

    getSnapshotBeforeUpdate(prevProps, prevState) {
        // Are we adding new items to the list?
        // Capture the scroll position so we can adjust scroll later.
        if (prevProps.list.length < this.props.list.length) {
            return (
                this.listRef.scrollHeight - this.listRef.scrollTop
            );
        }
        return null;
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        // If we have a snapshot value, we've just added new items.
        // Adjust scroll so these new items don't push the old ones out of view.
        // (snapshot here is the value returned from getSnapshotBeforeUpdate)
        if (snapshot !== null) {
            this.listRef.scrollTop =
                this.listRef.scrollHeight - snapshot;
        }
    }

    render() {
        return (
            <div ref={this.setListRef}>
                {/* ...contents... */}
            </div>
        );
    }

    setListRef = ref => {
        this.listRef = ref;
    };
}

通過getSnapshotBeforeUpdate拿到數(shù)據(jù)以后捺球,把數(shù)據(jù)返回,然后在componentDidUpdate(prevProps, prevState, snapshot)的第三個(gè)參數(shù)拿到我們之前返回的數(shù)據(jù)夕冲,然后進(jìn)行一些操作氮兵。

到這里就寫完了。新的生命周期getDerivedStateFromProps我把它放在了關(guān)于componentWillReceiveProps的升級(jí)章節(jié)歹鱼。

over....

參考學(xué)習(xí)的文章:

Update on Async Rendering

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泣栈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弥姻,更是在濱河造成了極大的恐慌南片,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庭敦,死亡現(xiàn)場(chǎng)離奇詭異疼进,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)秧廉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門伞广,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疼电,你說我怎么就攤上這事嚼锄。” “怎么了蔽豺?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵灾票,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我茫虽,道長(zhǎng),這世上最難降的妖魔是什么既们? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任濒析,我火速辦了婚禮,結(jié)果婚禮上啥纸,老公的妹妹穿的比我還像新娘号杏。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布盾致。 她就那樣靜靜地躺著主经,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庭惜。 梳的紋絲不亂的頭發(fā)上罩驻,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音护赊,去河邊找鬼惠遏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骏啰,可吹牛的內(nèi)容都是我干的节吮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼判耕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼透绩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起壁熄,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤帚豪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后请毛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體志鞍,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年方仿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了固棚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仙蚜,死狀恐怖此洲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情委粉,我是刑警寧澤呜师,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站贾节,受9級(jí)特大地震影響汁汗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜栗涂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一知牌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斤程,春花似錦角寸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沮峡。三九已至,卻和暖如春亿柑,著一層夾襖步出監(jiān)牢的瞬間邢疙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工橄杨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秘症,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓式矫,卻偏偏與公主長(zhǎng)得像乡摹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子采转,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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