摘要:發布了新的,并且已經確認了將在下一個版本廢棄老的。所以大家更新到新的是無可厚非的事情。
React16.3發布了新的Context API,并且已經確認了將在下一個版本廢棄老的Context API。所以大家更新到新的Context API是無可厚非的事情。而這篇文章會從原理的角度為大家分析為什么要用新的API--不僅僅是因為React官方要更新,畢竟更新了你也可以用16版本的React來使用老的API--而是因為新的API性能比老API 高出太多用法
我們先來看一下兩個版本的Context API如何使用
// old version class Parent extends Component{ getChildContext() { return {type: 123} } } Parent.childContextType = { type: PropTypes.number } const Child = (props, context) => ({context.type}
) Child.contextTypes = { type: PropTypes.number }
通過在父組件上聲明getChildContext方法為其子孫組件提供context,我們稱其ProviderComponent。注意必須要聲明Parent.childContextType才會生效,而子組件如果需要使用context,需要顯示得聲明Child.contextTypes
// new version const { Provider, Consumer } = React.createContext("defaultValue") const Parent = (props) => ({props.children} ) const Child = () => {{ (value) => }{value}
}
新版本的API,React提供了createContext方法,這個方法會返回兩個組件:Provider和Consumber,Provider用來提供context的內容,通過向Provider傳遞value這個prop,而在需要用到對應context的地方,用相同來源的Consumer來獲取context,Consumer有特定的用法,就是他的children必須是一個方法,并且context的值使用參數傳遞給這個方法。
性能對比正好前幾天React devtool發布了Profiler功能,就用這個新功能來查看一下兩個API的新能有什么差距吧,先看一下例子
不知道Profiler的看這里
// old api demo import React from "react" import PropTypes from "prop-types" export default class App extends React.Component { state = { type: 1, } getChildContext() { return { type: this.state.type } } componentDidMount() { setInterval(() => { this.setState({ type: this.state.type + 1 }) }, 500) } render() { return this.props.children } } App.childContextTypes = { type: PropTypes.number } export const Comp = (props, context) => { const arr = [] for (let i=0; i<100; i++) { arr.push({i}
) } return () } Comp.contextTypes = { type: PropTypes.number }{context.type}
{arr}
// new api demo import React, { Component, createContext } from "react" const { Provider, Consumer } = createContext(1) export default class App extends Component { state = { type: 1 } componentDidMount() { setInterval(() => { this.setState({ type: this.state.type + 1 }) }, 500) } render () { return ({this.props.children} ) } } export const Comp = () => { const arr = [] for (let i=0; i<100; i++) { arr.push({i}
) } return () }{(type) => {arr}{type}
}
// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App, {Comp} from "./context/OldApi" // import App, { Comp } from "./context/NewApi" ReactDOM.render(, document.getElementById("root") )
代碼基本相同,主要變動就是一個interval,每500毫秒給type加1,然后我們來分別看一下Profiler的截圖
不知道Profiler的看這里
可見這兩個性能差距是非常大的,老的API需要7點幾毫秒,而新的API只需要0.4毫秒,而且新的API只有兩個節點重新渲染了,而老的API所有節點都重新渲染了(下面還有很多節點沒截圖進去,雖然每個可能只有0.1毫秒或者甚至不到,但是積少成多,導致他們的父組件Comp渲染時間很長)
進一步舉例在這里可能有些同學會想,新老API的用法不一樣,因為老API的context是作為Comp這個functional Component的參數傳入的,所以肯定會影響該組件的所有子元素,所以我在這個基礎上修改了例子,把數組從Comp組件中移除,放到一個新的組件Comp2中
// Comp2 export class Comp2 extends React.Component { render() { const arr = [] for (let i=0; i<100; i++) { arr.push({i}
) } return arr } } // new old api Comp export const Comp = (props, context) => { return () } // new new api Comp export const Comp = () => { return ({context.type}
) }{(type) => {type}
}
現在受context影響的渲染內容新老API都是一樣的,只有
{type}
,我們再來看一下情況忽視比demo1時間長的問題,應該是我電腦運行時間長性能下降的問題,只需要橫向對比新老API就可以了
從這里可以看出來,結果跟Demo1沒什么區別,老API中我們的arr仍然都被重新渲染了,導致整體的渲染時間被拉長很多。
事實上,這可能還不是最讓你震驚的地方,我們再改一下例子,我們在App中不再修改type,而是新增一個state叫num,然后對其進行遞增
// App export default class App extends React.Component { state = { type: 1, num: 1 } getChildContext() { return { type: this.state.type } } componentDidMount() { setInterval(() => { this.setState({ num: this.state.num + 1 }) }, 500) } render() { return () } }inside update {this.state.num}
{this.props.children}
可以看到老API依然沒有什么改觀,他依然重新渲染所有子節點。
再進一步我給Comp2增加componentDidUpdate生命周期鉤子
componentDidUpdate() { console.log("update") }
在使用老API的時候,每次App更新都會打印
而新API則不會
總結從上面測試的結果大家應該可以看出來結果了,這里簡單的講一下原因,因為要具體分析會很長并且要涉及到源碼的很多細節,所以有空再寫一片續,來詳細得講解源碼,大家有興趣的可以關注我。
要分析原理要了解React對于每次更新的處理流程,React是一個樹結構,要進行更新只能通過某個節點執行setState、forceUpdate等方法,在某一個節點執行了這些方法之后,React會向上搜索直到找到root節點,然后把root節點放到更新隊列中,等待更新。
所以React的更新都是從root往下執行的,他會嘗試重新構建一個新的樹,在這個過程中能復用之前的節點就會復用,而我們現在看到的情況,就是因為復用算法根據不同的情況而得到的不同的結果
我們來看一小段源碼
if ( !hasLegacyContextChanged() && (updateExpirationTime === NoWork || updateExpirationTime > renderExpirationTime) ) { // ... return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); }
如果能滿足這個判斷條件并且進入bailoutOnAlreadyFinishedWork,那么有極高的可能這個節點以及他的子樹都不需要更新,React會直接跳過,我們使用新的context API的時候就是這種情況,但是使用老的context API是永遠不可能跳過這個判斷的
老的context API使用過程中,一旦有一個節點提供了context,那么他的所有子節點都會被視為有side effect的,因為React本身并不判斷子節點是否有使用context,以及提供的context是否有變化,所以一旦檢測到有節點提供了context,那么他的子節點在執行hasLegacyContextChanged的時候,永遠都是true的,而沒有進入bailoutOnAlreadyFinishedWork,就會變成重新reconcile子節點,雖然最終可能不需要更新DOM節點,但是重新計算生成Fiber對象的開銷還是又得,一兩個還好,數量多了時間也是會被拉長的。
以上就是使用老的context API比新的API要慢很多的原因,大家可以先不深究得理解一下,在我之后的源碼分析環節會有更詳細的講解。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97980.html
摘要:作為數據的發布方,它擁有一個名為的屬性,用于維護數據內容,通過傳遞給的數據會被發布出去。最后,組件將自己的原封不動的傳遞給。但是通過這次的實踐,也算是熟悉的的用法,對也加深了了解吧。 這是一篇我發在掘金上的文章,原文有一個我沒有解決的問題,在網友的解答下我找到了答案,我把文章重新修改編輯后,同步發送到這里,希望能對大家有所幫助。本文原發布于掘金:https://juejin.im/po...
摘要:第一個功能是普通經典類組件,也就是眾所周知的有狀態組件。我們準備創建一個上下文環境來存放全局狀態,然后把它的包裹在一個有狀態組件中,然后用來管理狀態。接下來我們需要用有狀態組件包裹我們的,利用它進行應用狀態的管理。 原文地址對于想要跳過文章直接看結果的人,我已經把我寫的內容制作成了一個庫:use-simple-state,無任何依賴(除了依賴 react ),只有3kb,相當輕量。 ...
摘要:但是,有一件事是肯定的年對全棧開發者的需求量很大。有一些方法可以解決這個問題,例如模式,或者你可以這么想,其實谷歌機器人在抓取單頁應用程序時沒有那么糟糕。谷歌正在這方面努力推進,但不要指望在年會看到任何突破。 對于什么是全棧開發者并沒有一個明確的定義。但是,有一件事是肯定的:2019 年對全棧開發者的需求量很大。在本文中,我將向你概述一些趨勢,你可以嘗試根據這些趨勢來確定你可能要投入的...
摘要:但是,有一件事是肯定的年對全棧開發者的需求量很大。有一些方法可以解決這個問題,例如模式,或者你可以這么想,其實谷歌機器人在抓取單頁應用程序時沒有那么糟糕。谷歌正在這方面努力推進,但不要指望在年會看到任何突破。 對于什么是全棧開發者并沒有一個明確的定義。但是,有一件事是肯定的:2019 年對全棧開發者的需求量很大。在本文中,我將向你概述一些趨勢,你可以嘗試根據這些趨勢來確定你可能要投入的...
摘要:但是,有一件事是肯定的年對全棧開發者的需求量很大。有一些方法可以解決這個問題,例如模式,或者你可以這么想,其實谷歌機器人在抓取單頁應用程序時沒有那么糟糕。谷歌正在這方面努力推進,但不要指望在年會看到任何突破。 對于什么是全棧開發者并沒有一個明確的定義。但是,有一件事是肯定的:2019 年對全棧開發者的需求量很大。在本文中,我將向你概述一些趨勢,你可以嘗試根據這些趨勢來確定你可能要投入的...
閱讀 2166·2021-10-08 10:15
閱讀 1194·2019-08-30 15:52
閱讀 523·2019-08-30 12:54
閱讀 1541·2019-08-29 15:10
閱讀 2693·2019-08-29 12:44
閱讀 3015·2019-08-29 12:28
閱讀 3362·2019-08-27 10:57
閱讀 2222·2019-08-26 12:24