<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React 性能優(yōu)化的那些事兒

          共 28922字,需瀏覽 58分鐘

           ·

          2022-10-25 08:58


          要講清楚性能優(yōu)化的原理,就需要知道它的前世今生,需要回答如下的問題:

          • React 是如何進行頁面渲染的?
          • 造成頁面的卡頓的罪魁禍首是什么呢?
          • 我們?yōu)槭裁葱枰阅軆?yōu)化?
          • React 有哪些場景會需要性能優(yōu)化?
          • React 本身的性能優(yōu)化手段?
          • 還有哪些工具可以提升性能呢?

          為什么頁面會出現(xiàn)卡頓的現(xiàn)象?

          為什么瀏覽器會出現(xiàn)頁面卡頓的問題?是不是瀏覽器不夠先進?這都 2202 年了,怎么還會有這種問題呢?

          實際上問題的根源來源于瀏覽器的刷新機制。

          我們人類眼睛的刷新率是 60Hz,瀏覽器依據(jù)人眼的刷新率 計算出了

          1000 Ms / 60 = 16.6ms

          也就是說,瀏覽器要在16.6Ms 進行一次刷新,人眼就不會感覺到卡頓,而如果超過這個時間進行刷新,就會感覺到卡頓。

          而瀏覽器的主進程在僅僅需要頁面的渲染,還需要做解析執(zhí)行Js,他們運行在一個進程中。

          如果js的在執(zhí)行的長時間占用主進程的資源,就會導致沒有資源進行頁面的渲染刷新,進而導致頁面的卡頓。

          那么這個又和 React 的性能優(yōu)化又有什么關系呢?

          React 到底是在哪里出現(xiàn)了卡頓?

          基于我們上的知識,js 長期霸占瀏覽器主線程造成無法刷新而造成卡頓。

          那么 React 的卡頓也是基于這個原因。

          React 在render的時候,會根據(jù)現(xiàn)有render產生的新的jsx的數(shù)據(jù)和現(xiàn)有fiberRoot 進行比對,找到不同的地方,然后生成新的workInProgress,進而在掛載階段把新的workInProgress交給服務器渲染。

          在這個過程中,React 為了讓底層機制更高效快速,進行了大量的優(yōu)化處理,如設立任務優(yōu)先級、異步調度、diff算法、時間分片等。

          整個鏈路就是了高效快速的完成從數(shù)據(jù)更新到頁面渲染的整體流程。

          為了不讓遞歸遍歷尋找所有更新節(jié)點太大而占用瀏覽器資源,React 升級了fiber架構,時間分片,讓其可以增量更新。

          為了找出所有的更新節(jié)點,設立了diff算法,高效的查找所有的節(jié)點。

          為了更高效的更新,及時響應用戶的操作,設計任務調度優(yōu)先級。

          而我們的性能優(yōu)化就是為了不給 React 拖后腿,讓其更快,更高效的遍歷。

          那么性能優(yōu)化的奧義是什么呢??

          就是控制刷新渲染的波及范圍,我們只讓改更新的更新,不該更新的不要更新,讓我們的更新鏈路盡可能的短的走完,那么頁面當然就會及時刷新不會卡頓了。

          React 有哪些場景會需要性能優(yōu)化?

          • 父組件刷新,而不波及子組件
          • 組件自己控制自己是否刷新
          • 減少波及范圍,無關刷新數(shù)據(jù)不存入state中
          • 合并 state,減少重復 setState 的操作
          • 如何更快的完成diff的比較,加快進程

          我們分別從這些場景說一下:·

          一:父組件刷新,而不波及子組件。

          我們知道 React 在組件刷新判定的時候,如果觸發(fā)刷新,那么它會深度遍歷所有子組件,查找所有更新的節(jié)點,依據(jù)新的jsx數(shù)據(jù)和舊的 fiber ,生成新的workInProgress,進而進行頁面渲染。

          所以父組件刷新的話,子組件必然會跟著刷新,但是假如這次的刷新,和我們子組件沒有關系呢?怎么減少這種波及呢?

          如下面這樣:

          export default function Father1 (){
              let [name,setName] = React.useState('');

              return (
                  <div>
                      <button onClick={()=>setName("獲取到的數(shù)據(jù)")}>點擊獲取數(shù)據(jù)</button>
                      {name}
                      <Children/>
                  </div>

              )
          }

          function Children(){
              return (
                  <div>
                      這里是子組件
                  </div>

              )
          }

          復制代碼

          運行結果:

          可以看到我們的子組件被波及了,解決辦法有很多,總體來說分為兩種。

          • 子組件自己判斷是否需要更新 ,典型的就是 PureComponent,shouldComponentUpdate,memo
          • 父組件對子組件做個緩沖判斷

          第一種:使用 PureComponent

          使用 PureComponent 的原理就是它會對state 和props進行淺比較,如果發(fā)現(xiàn)并不相同就會更新。

          export default function Father1 (){
              let [name,setName] = React.useState('');
              return (
                  <div>
                      <button onClick={()=>setName("父組件的數(shù)據(jù)")}>點擊刷新父組件</button>
                      {name}
                    
                      <Children1/>
                  </div>

              )
          }

          class Children extends React.PureComponent{
              render() {
                  return (
                      <div>這里是子組件</div>
                  )
              }
          }
          復制代碼

          執(zhí)行結果:

          04.jpg

          實際上PureComponent就是在內部更新的時候調用了會調用如下方法來判斷 新舊state和props

          function shallowEqual(objA: mixed, objB: mixed): boolean {
          if (is(objA, objB)) {
          return true;
          }
          if (
          typeof objA !== 'object' ||
          objA === null ||
          typeof objB !== 'object' ||
          objB === null
          ) {
          return false;
          }
          const keysA = Object.keys(objA);
          const keysB = Object.keys(objB);
          if (keysA.length !== keysB.length) {
          return false;
          }
          // Test for A's keys different from B.
          for (let i = 0; i < keysA.length; i++) {
          const currentKey = keysA[i];
          if (
          !hasOwnProperty.call(objB, currentKey) ||
          !is(objA[currentKey], objB[currentKey])
          ) {
          return false;
          }
          }
          return true;
          }
          復制代碼

          它的判斷步驟如下:

          • 第一步,首先會直接比較新老 props 或者新老 state 是否相等。如果相等那么不更新組件。
          • 第二步,判斷新老 state 或者 props ,有不是對象或者為 null 的,那么直接返回 false ,更新組件。
          • 第三步,通過 Object.keys 將新老 props 或者新老 state 的屬性名 key 變成數(shù)組,判斷數(shù)組的長度是否相等,如果不相等,證明有屬性增加或者減少,那么更新組件。
          • 第四步,遍歷老 props 或者老 state ,判斷對應的新 props 或新 state ,有沒有與之對應并且相等的(這個相等是淺比較),如果有一個不對應或者不相等,那么直接返回 false ,更新組件。 到此為止,淺比較流程結束, PureComponent 就是這么做渲染節(jié)流優(yōu)化的。
          在使用PureComponent時需要注意的細節(jié):

          由于PureComponent 使用的是淺比較判斷stateprops,所以如果我們在父子組件中,子組件使用PureComponent,在父組件刷新的過程中不小心把傳給子組件的回調函數(shù)變了,就會造成子組件的誤觸發(fā),這個時候PureComponent就失效了。

          細節(jié)一:函數(shù)組件中,匿名函數(shù),箭頭函數(shù)和普通函數(shù)都會重新聲明

          下面這些情況都會造成函數(shù)的重新聲明:

          箭頭函數(shù)
           <Children1 callback={(value)=>setValue(value)}/>
          復制代碼
          匿名函數(shù)
          <Children1 callback={function (value){setValue(value)}}/>
          復制代碼
          普通函數(shù)
          export default function Father1 (){
              let [name,setName] = React.useState('');
              let [value,setValue] = React.useState('')
              const setData=(value)=>{
                  setValue(value)
              }
              return (
                  <div>
                      <button onClick={()=>setName("父組件的數(shù)據(jù)"+Math.random())}>點擊刷新父組件</button>
                      {name}
                      <Children1 callback={setData}/>
                  </div>

              )
          }
          class Children1 extends React.PureComponent{
              render() {
                  return (
                      <div>這里是子組件</div>
                  )
              }
          }
          復制代碼

          執(zhí)行結果:

          05.jpg

          可以看到子組件的 PureComponent 完全失效了。這個時候就可以使用useMemo或者 useCallback 出馬了,利用他們緩沖一份函數(shù),保證不會出現(xiàn)重復聲明就可以了。

          export default function Father1 (){
              let [name,setName] = React.useState('');
              let [value,setValue] = React.useState('')
              const setData= React.useCallback((value)=>{
                  setValue(value)
              },[])
              
              return (
                  <div>
                      <button onClick={()=>setName("父組件的數(shù)據(jù)"+Math.random())}>點擊刷新父組件</button>
                      {name}
                      <Children1 callback={setData}/>
                  </div>

              )
          }
          復制代碼

          看結果:

          可以看到我們的子組件這次并沒有參與父組件的刷新,在React Profiler中也提示,Children1并沒有渲染。

          細節(jié)二:class組件中不使用箭頭函數(shù),匿名函數(shù)

          原理和函數(shù)組件中的一樣,class 組件中每一次刷新都會重復調用render函數(shù),那么render函數(shù)中使用的匿名函數(shù),箭頭函數(shù)就會造成重復刷新的問題。

          export default class Father extends React.PureComponent{
              constructor(props) {
                  super(props);
                  this.state = {
                      name:"",
                      count:"",
                  }
              }
              render() {
                  return (
                      <div>
                          <button onClick={()=>this.setState({name:"父組件的數(shù)據(jù)"+Math.random()})}>點擊獲取數(shù)據(jù)</button>
                          {this.state.name}
                          <Children1 callback={()=>this.setState({count:11})}/>
                      </div>
                  )
              }
          }
          復制代碼

          執(zhí)行結果:

          image.png

          而優(yōu)化這個非常簡單,只需要把函數(shù)換成普通函數(shù)就可以。

          export default class Father extends React.PureComponent{
              constructor(props) {
                  super(props);
                  this.state = {
                      name:"",
                      count:"",
                  }
              }
              setCount=(count)=>{
                  this.setState({count})
              }
              render() {
                  return (
                      <div>
                          <button onClick={()=>this.setState({name:"父組件的數(shù)據(jù)"+Math.random()})}>點擊獲取數(shù)據(jù)</button>
                          {this.state.name}
                          <Children1 callback={this.setCount(111)}/>
                      </div>
                  )
              }
          }
          復制代碼

          執(zhí)行結果:

          image.png
          細節(jié)三:在 class 組件的render函數(shù)中調用bind 函數(shù)

          這個細節(jié)是我們在class組件中,沒有在constructor中進行bind的操作,而是在render函數(shù)中,那么由于bind函數(shù)的特性,它的每一次調用都會返回一個新的函數(shù),所以同樣會造成PureComponent的失效

          export default class Father extends React.PureComponent{
              //...
              setCount(count){
                  this.setCount({count})
              }
              render() {
                  return (
                      <div>
                          <button onClick={()=>this.setState({name:"父組件的數(shù)據(jù)"+Math.random()})}>點擊獲取數(shù)據(jù)</button>
                          {this.state.name}
                          <Children1 callback={this.setCount.bind(this,"11111")}/>
                      </div>
                  )
              }
          }
          復制代碼

          看執(zhí)行結果:

          image.png

          優(yōu)化的方式也很簡單,把bind操作放在constructor中就可以了。

          constructor(props) {
              super(props);
              this.state = {
                  name:"",
                  count:"",
              }
              this.setCount= this.setCount.bind(this);
          }
          復制代碼

          執(zhí)行結果就不在此展示了。

          而實際上上訴所說的三個細節(jié)同樣對React.memo有效,它同樣也會淺比較傳入的props.

          第二種:shouldComponentUpdate

          class 組件中 使用 shouldComponentUpdate 是主要的優(yōu)化方式,它不僅僅可以判斷來自父組件的nextprops,還可以根據(jù)nextState和最新的nextContext來決定是否更新。

          class Children2 extends ReactPureComponent{
              shouldComponentUpdate(nextProps, nextState, nextContext) {
                  //判斷只有偶數(shù)的時候,子組件才會更新
                  if(nextProps !== this.props && nextProps.count  % 2 === 0){
                      return true;
                  }else{
                      return false;
                  }
              }
              render() {
                  return (
                      <div>
                          只有父組件傳入的值等于 2的時候才會更新
                          {this.props.count}
                      </div>
                  )
              }
          }
          復制代碼

          它的用法也是非常簡單,就是如果需要更新就返回true,不需要更新就返回false.

          第三種:函數(shù)組件如何判斷props的變化的更新呢? 使用 React.memo函數(shù)

          React.memo的規(guī)則是如果想要復用最后一次渲染結果,就返回true,不想復用就返回false。 所以它和shouldComponentUpdate的正好相反,false才會更新,true就返回緩沖。

          const Children3 = React.memo(function ({count}){
              return (
                  <div>
                      只有父組件傳入的值是偶數(shù)的時候才會更新
                      {count}
                  </div>

              )
          },(prevProps, nextProps)=>{
              if(nextProps.count % 2 === 0){
                  return false;
              }else{
                  return true;
              }
          })
          復制代碼

          如果我們不傳入第二個函數(shù),而是默認讓 React.memo包裹一下,那么它只會對props淺比較一下,并不會有比較state之類的邏輯。

          以上三種都是我們?yōu)榱藨獙Ω附M件更新觸發(fā)子組件,子組件決定是否更新的實現(xiàn)。 下面我們講一下父組件對子組件緩沖實現(xiàn)的情況:

          使用 React.useMemo來實現(xiàn)對子組件的緩沖

          看下面這段邏輯,我們的子組件只關心count數(shù)據(jù),當我們刷新name數(shù)據(jù)的時候,并不會觸發(fā)刷新 Children1子組件,實現(xiàn)了我們對組件的緩沖控制。

          export default function Father1 (){
              let [count,setCount] = React.useState(0);
              let [name,setName] = React.useState(0);
              const render = React.useMemo(()=><Children1 count = {count}/>,[count])
              return (
                  <div>
                      <button onClick={()=>setCount(++count)}>點擊刷新count</button>
                      <br/>
                      <button onClick={()=>setName(++name)}>點擊刷新name</button>
                      <br/>
                      {"count"+count}
                      <br/>
                      {"name"+name}
                      <br/>
                      {render}
                  </div>

              )
          }
          class Children1 extends React.PureComponent{
              render() {
                  return (
                      <div>
                          子組件只關系count 數(shù)據(jù)
                          {this.props.count}
                      </div>

                  )
              }
          }
          復制代碼

          執(zhí)行結果: 當我們點擊刷新name數(shù)據(jù)時,可以看到沒有子組件參與刷新

          當我們點擊刷新count 數(shù)據(jù)時,子組件參與了刷新

          image.png

          二:組件自己控制自己是否刷新

          這里就需要用到上面提到的shouldComponentUpdate以及PureComponent,這里不再贅述。

          三:減少波及范圍,無關刷新數(shù)據(jù)不存入state中

          這種場景就是我們有意識的控制,如果有一個數(shù)據(jù)我們在頁面上并沒有用到它,但是它又和我們的其他的邏輯有關系,那么我們就可以把它存儲在其他的地方,而不是state中。

          場景一:無意義重復調用setState,合并相關的state

          export default class Father extends React.Component{
              state = {
                  count:0,
                  name:"",
              }
              getData=(count)=>{
                  this.setState({count});
                  //依據(jù)異步獲取數(shù)據(jù)
                  setTimeout(()=>{
                      this.setState({
                          name:"異步獲取回來的數(shù)據(jù)"+count
                      })
                  },200)
              }
              componentDidUpdate(prevProps, prevState, snapshot) {
                  console.log("渲染次數(shù),",++count,"次")
              }
              render() {
                  return (
                      <div>
                          <button onClick={()=>this.getData(++this.state.count)}>點擊獲取數(shù)據(jù)</button>
                          {this.state.name}
                      </div>

                  )
              }
          }
          復制代碼

          React Profiler的執(zhí)行結果:

          01.jpg

          可以看到我們的父組件執(zhí)行了兩次。 其中的一次是無意義的先setState保存一次數(shù)據(jù),然后又根據(jù)這個數(shù)據(jù)異步獲取了數(shù)據(jù)以后又調用了一次setState,造成了第二次的數(shù)據(jù)刷新.

          而解決辦法就是把這個數(shù)據(jù)合并到異步數(shù)據(jù)獲取完成以后,一起更新到state中。

          getData=(count)=>{
                  //依據(jù)異步獲取數(shù)據(jù)
                  setTimeout(()=>{
                      this.setState({
                          name:"異步獲取回來的數(shù)據(jù)"+count,
                          count
                      })
                  },200)
          }
          復制代碼

          看執(zhí)行結果:只渲染了一次。

          02.jpg

          場景二:和頁面刷新沒有相關的數(shù)據(jù),不存入state中

          實際上我們發(fā)現(xiàn)這個數(shù)據(jù)在頁面上并沒有展示,我們并不需要把他們都存放在state 中,所以我們可以把這個數(shù)據(jù)存儲在state之外的地方。


          export default class Father extends React.Component{
              constructor(props) {
                  super(props);
                  this.state = {
                      name:"",
                  }
                  this.count = 0;
              }
              getData=(count)=>{
                  this.count = count;
                  //依據(jù)異步獲取數(shù)據(jù)
                  setTimeout(()=>{
                      this.setState({
                          name:"異步獲取回來的數(shù)據(jù)"+count,
                      })
                  },200)
              }
              componentDidUpdate(prevProps, prevState, snapshot) {
                  console.log("渲染次數(shù),",++count,"次")
              }
              render() {
                  return (
                      <div>
                          <button onClick={()=>this.getData(++this.count)}>點擊獲取數(shù)據(jù)</button>
                          {this.state.name}
                      </div>

                  )
              }
          }
          復制代碼

          這樣的操作并不會影響我們對它的使用。 在class組件中我們可以把數(shù)據(jù)存儲在this上面,而在Function中,則我們可以通過利用 useRef 這個 Hooks 來實現(xiàn)同樣的效果。

          export default function Father1 (){
              let [name,setName] = React.useState('');
              const countContainer = React.useRef(0);
              const getData=(count)=>{
                  //依據(jù)異步獲取數(shù)據(jù)
                  setTimeout(()=>{
                      setName("異步獲取回來的數(shù)據(jù)"+count)
                      countContainer.current = count++;
                  },200)
              }
              return (
                  <div>
                      <button onClick={()=>getData(++countContainer.current)}>點擊獲取數(shù)據(jù)</button>
                      {name}
                  </div>

              )
          }
          復制代碼

          場景三:通過存入useRef的數(shù)據(jù)中,避免父子組件的重復刷新

          假設父組件中有需要用到子組件的數(shù)據(jù),子組件需要把數(shù)據(jù)回到返回給父組件,而如果父組件把這份數(shù)據(jù)存入到了 state 中,那么父組件刷新,子組件也會跟著刷新。 這種的情況我們就可以把數(shù)據(jù)存入到 useRef 中,以避免無意義的刷新出現(xiàn)。或者把數(shù)據(jù)存入到class的 this 下。

          四:合并 state,減少重復 setState 的操作

          合并 state ,減少重復 setState 的操作,實際上 React已經幫我們做了,那就是批量更新,在React18 之前的版本中,批量更新只有在 React自己的生命周期或者點擊事件中有提供,而異步更新則沒有,例如setTimeout,setInternal等。

          所以如果我們想在React18 之前的版本中也想在異步代碼添加對批量更新的支持,就可以使用React給我們提供的api。

          import ReactDOM from 'react-dom';
          const { unstable_batchedUpdates } = ReactDOM;
          復制代碼

          使用方法如下:

          componentDidMount() {
              setTimeout(()=>{
                  unstable_batchedUpdates(()=>{
                      this.setState({ number:this.state.number + 1 })
                      console.log(this.state.number)
                      this.setState({ number:this.state.number + 1})
                      console.log(this.state.number)
                      this.setState({ number:this.state.number + 1 })
                      console.log(this.state.number)
                  })
              })
          }
          復制代碼

          而在 React 18中的話,就不需要我們這樣做了,它 對settimeout、promise、原生事件、react事件、外部事件處理程序進行自動批量處理。

          五:如何更快的完成diff的比較,加快進程

          diff算法就是為了幫助我們找到需要更新的異同點,那么有什么辦法可以讓我們的diff算法更快呢?

          那就是合理的使用key

          diff的調用是在reconcileChildren中的reconcileChildFibers,當沒有可以復用current fiber節(jié)點時,就會走mountChildFibers,當有的時候就走reconcileChildFibers

          reconcilerChildFibers的函數(shù)中則會針render函數(shù)返回的新的jsx數(shù)據(jù)進行判斷,它是否是對象,就會判斷它的newChild.$$typeof是否是REACT_ELEMENT_TYPE,如果是就按單節(jié)點處理。 如果不是繼續(xù)判斷是否是REACT_PORTAL_TYPE或者REACT_LAZY_TYPE。

          繼續(xù)判斷它是否為數(shù)組,或者可迭代對象。

          而在單節(jié)點處理函數(shù)reconcileSingleElement中,會執(zhí)行如下邏輯:

          • 通過 key,判斷上次更新的時候的 Fiber 節(jié)點是否存在對應的 DOM 節(jié)點。 如果沒有 則直接走創(chuàng)建流程,新生成一個 Fiber 節(jié)點,并返回
          • 如果有,那么就會繼續(xù)判斷,DOM 節(jié)點是否可以復用?
          • 如果有,就將上次更新的 Fiber 節(jié)點的副本作為本次新生的Fiber 節(jié)點并返回

          • 如果沒有,那么就標記 DOM 需要被刪除,新生成一個 Fiber 節(jié)點并返回。

          function reconcileSingleElement(
              returnFiber: Fiber,
              currentFirstChild: Fiber | null,
              element: ReactElement
          )
          Fiber 
          {
              const key = element.key; //jsx 虛擬 DOM 返回的數(shù)據(jù)
              let child = currentFirstChild;//當前的fiber 
              
              // 首先判斷是否存在對應DOM節(jié)點
              while (child !== null) {
                  // 上一次更新存在DOM節(jié)點,接下來判斷是否可復用
                  
                  // 首先比較key是否相同
                  if (child.key === key) {
                      
                      // key相同,接下來比較type是否相同
                      
                      switch (child.tag) {
                          // ...省略case
                          
                          default: {
                              if (child.elementType === element.type) {
                                  // type相同則表示可以復用
                                  // 返回復用的fiber
                                  return existing;
                              }
                              
                              // type不同則跳出switch
                              break;
                          }
                      }
                      // 代碼執(zhí)行到這里代表:key相同但是type不同
                      // 將該fiber及其兄弟fiber標記為刪除
                      deleteRemainingChildren(returnFiber, child);
                      break;
                  } else {
                      // key不同,將該fiber標記為刪除
                      deleteChild(returnFiber, child);
                  }
                  child = child.sibling;
              }
              
              // 創(chuàng)建新Fiber,并返回 ...省略
          }
          復制代碼

          從上面的代碼就可以看出,React 是如何判斷一個 Fiber 節(jié)點是否可以被復用的。

          • 第一步:判斷elementkeyfiberkey 是否相同
          • 如果不相同,就會創(chuàng)建新的 Fiber,并返回

          • 第二步:如果相同,就判斷element.typefibertype 是否相同,type 就是他們的類型,比如p標簽就是p,div 標簽就是div.如果 type 不相同,那么就會標識刪除。

          • 如果相同,那就可以可以判斷可以復用了,返回existing。

          而在多節(jié)點更新的時候,key的作用則更加重要,React 會通過遍歷新舊數(shù)據(jù),數(shù)組和鏈表來通過按個判斷它們的keytype 來決定是否復用。

          所以我們需要合理的使用key來加快diff算法的比對和fiber的復用。

          那么如何合理使用key呢。

          其實很簡單,只需要每一次設置的值和我們的數(shù)據(jù)一直就可以了。不要使用數(shù)組的下標,這種key和數(shù)據(jù)沒有關聯(lián),我們的數(shù)據(jù)發(fā)生了更新,結果 React 還指望著復用。

          還有哪些工具可以提升性能呢?

          實際的開發(fā)中還有其他的很多場景需要進行優(yōu)化:

          • 頻繁輸入或者滑動滾動的防抖節(jié)流
          • 針對大數(shù)據(jù)展示的虛擬列表,虛擬表格
          • 針對大數(shù)據(jù)展示的時間分片 等等等等 后面再補充吧!

          感謝大佬的文章:

          React進階實踐指南-渲染控制篇[2]

          over...

          關于本文

          作者:雨飛飛雨

          https://juejin.cn/post/7146846541846675492

          最后


          歡迎關注【前端瓶子君】??ヽ(°▽°)ノ?
          回復「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認真的解答喲!
          回復「交流」,吹吹水、聊聊技術、吐吐槽!
          回復「閱讀」,每日刷刷高質量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉發(fā)”就是最大的支持


          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  2014天堂网 | 日日干夜夜拍 | 久久国产精品久久久久久电车 | 亚洲成人网站在线观看 | 另类视频网站 |