最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React DOM-diff 節(jié)點(diǎn)源碼解析

 更新時(shí)間:2023年02月27日 10:55:10   作者:朔望  
這篇文章主要為大家介紹了React DOM-diff節(jié)點(diǎn)源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

這篇文章幫助大家梳理一下React中的dom-diff。在React中,根據(jù)新的虛擬DOM的不同,分為單節(jié)點(diǎn)(指的是同層級(jí)只有一個(gè)子節(jié)點(diǎn)),和多節(jié)點(diǎn)(指的是同層級(jí)有多個(gè)子節(jié)點(diǎn)),分別是在reconcileSingleElementreconcileChildrenArray中進(jìn)行的。下面結(jié)合源碼和原理圖進(jìn)行詳解。

單節(jié)點(diǎn)

單節(jié)點(diǎn)的dom-diff是在reconcileSingleElement中進(jìn)行的,而能否復(fù)用的判斷依據(jù)就是將要更新的虛擬DOM的key和HTML元素的類型(即divp的區(qū)別)是否和當(dāng)前(頁(yè)面上正在渲染的)真實(shí)DOM的fiber一致。

如圖所示,對(duì)于單節(jié)點(diǎn)的diff我們按照?qǐng)D中的流程,結(jié)合源碼進(jìn)行一一解讀

/**
   * 
   * @param {*} returnFiber 根fiber div#root對(duì)應(yīng)的fiber
   * @param {*} currentFirstChild 老的FunctionComponent對(duì)應(yīng)的fiber
   * @param {*} element 新的虛擬DOM對(duì)象
   * @returns 返回新的第一個(gè)子fiber
   */
  function reconcileSingleElement(returnFiber, currentFirstChild, element) {
    //新的虛擬DOM的key,也就是唯一標(biāo)準(zhǔn)
    const key = element.key;        // null
    let child = currentFirstChild; //老的FunctionComponent對(duì)應(yīng)的fiber
    while (child !== null) {
      //判斷此老fiber對(duì)應(yīng)的key和新的虛擬DOM對(duì)象的key是否一樣 null===null
      if (child.key === key) {
        //判斷老fiber對(duì)應(yīng)的類型和新虛擬DOM元素對(duì)應(yīng)的類型是否相同
        if (child.type === element.type) {// p div
          deleteRemainingChildren(returnFiber, child.sibling);
          //如果key一樣,類型也一樣,則認(rèn)為此節(jié)點(diǎn)可以復(fù)用
          const existing = useFiber(child, element.props);
          existing.ref = element.ref;
          existing.return = returnFiber;
          return existing;
        } else {
          //如果找到一key一樣老fiber,但是類型不一樣,不能此老fiber,把剩下的全部刪除
          deleteRemainingChildren(returnFiber, child);
        }
      } else {
        deleteChild(returnFiber, child);
      }
      child = child.sibling;
    }
    //因?yàn)槲覀儸F(xiàn)實(shí)的初次掛載,老節(jié)點(diǎn)currentFirstChild肯定是沒(méi)有的,所以可以直接根據(jù)虛擬DOM創(chuàng)建新的Fiber節(jié)點(diǎn)
    const created = createFiberFromElement(element);
    created.ref = element.ref;
    created.return = returnFiber;
    return created;
  }

key相同,類型相同

<div>
  <div key='A'>A</div> 
  <div key='B'>B</div>
</div>
<!-- 變化到 -->
<div>
  <div key='A'>C</div>
</div>

對(duì)于上面列舉到的情況,新的虛擬DOM匹配到第一個(gè)即為相同key和type,我們首先通過(guò)deleteRemainingChildren方法刪除掉其它的多余的子節(jié)點(diǎn)(上面的 <div key='B'>B</div>),然后通過(guò)useFiber方法來(lái)復(fù)用老fiber產(chǎn)生新的fiber,這樣就完成我們的復(fù)用。

key不同,類型相同

<div>
  <div key='A'>A</div> 
  <div key='B'>B</div>
</div>
<!-- 變化到 -->
<div>
  <div key='C'>C</div>
</div>

對(duì)于上面列舉到的情況,新的虛擬DOM匹配到第一個(gè)即為不同key即使type相同也不會(huì)往下進(jìn)行,通過(guò)deleteChild方法刪掉第一個(gè)子節(jié)點(diǎn),即<div key='A'>A</div>對(duì)應(yīng)的fiber,然后再對(duì)第二個(gè)子節(jié)點(diǎn)<div key='B'>B</div>進(jìn)行對(duì)比,發(fā)現(xiàn)key依然不同,繼續(xù)刪除,刪除完成之后child === null成立,跳出while循環(huán),通過(guò)createFiberFromElement方法根據(jù)新的虛擬DOM創(chuàng)建新的fiber。

key相同,類型不同

<div>
  <div key='A'>A</div> 
  <div key='B'>B</div>
</div>
<!-- 變化到 -->
<div>
  <p key='A'>C</p>
</div>

對(duì)于上面列舉的情況,第一次匹配到了相同的key但是type不同,依舊是不符合復(fù)用的條件,而且此時(shí)會(huì)通過(guò)deleteRemainingChildren方法刪除掉所有子節(jié)點(diǎn),即不會(huì)再進(jìn)行第二次比較,直接就跳出循環(huán),通過(guò)createFiberFromElement方法根據(jù)新的虛擬DOM創(chuàng)建新的fiber。

多節(jié)點(diǎn)

多節(jié)點(diǎn)的diff相對(duì)于單節(jié)點(diǎn)的diff來(lái)說(shuō)更加復(fù)雜一些。這里主要是在方法reconcileChildrenArray中進(jìn)行,這個(gè)過(guò)程最多會(huì)經(jīng)歷三次遍歷,每次完成相應(yīng)的功能,下面我們結(jié)合源碼來(lái)具體探究一下。

  function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
    let resultingFirstChild = null; //返回的第一個(gè)新兒子
    let previousNewFiber = null; //上一個(gè)的一個(gè)新的兒fiber
    let newIdx = 0;//用來(lái)遍歷新的虛擬DOM的索引
    let oldFiber = currentFirstChild;//第一個(gè)老fiber
    let nextOldFiber = null;//下一個(gè)第fiber
    let lastPlacedIndex = 0;//上一個(gè)不需要移動(dòng)的老節(jié)點(diǎn)的索引
    // 開(kāi)始第一輪循環(huán) 如果老fiber有值,新的虛擬DOM也有值
    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
      //先暫下一個(gè)老fiber
      nextOldFiber = oldFiber.sibling;
      //試圖更新或者試圖復(fù)用老的fiber
      const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
      if (newFiber === null) {
        break;
      }
      //如果有老fiber,但是新的fiber并沒(méi)有成功復(fù)用老fiber和老的真實(shí)DOM,那就刪除老fiber,在提交階段會(huì)刪除真實(shí)DOM
      if (oldFiber && newFiber.alternate === null) {
        deleteChild(returnFiber, oldFiber);
      }
      //指定新fiber的位置
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;//li(A).sibling=p(B).sibling=>li(C)
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
      oldFiber = nextOldFiber
    }
    //新的虛擬DOM已經(jīng)循環(huán)完畢
    if (newIdx === newChildren.length) {
      //刪除剩下的老fiber
      deleteRemainingChildren(returnFiber, oldFiber);
      return resultingFirstChild;
    }
    if (oldFiber === null) {
      //如果老的 fiber已經(jīng)沒(méi)有了, 新的虛擬DOM還有,進(jìn)入插入新節(jié)點(diǎn)的邏輯
      for (; newIdx < newChildren.length; newIdx++) {
        const newFiber = createChild(returnFiber, newChildren[newIdx]);
        if (newFiber === null) continue;
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        //如果previousNewFiber為null,說(shuō)明這是第一個(gè)fiber
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber; //這個(gè)newFiber就是大兒子
        } else {
          //否則說(shuō)明不是大兒子,就把這個(gè)newFiber添加上一個(gè)子節(jié)點(diǎn)后面
          previousNewFiber.sibling = newFiber;
        }
        //讓newFiber成為最后一個(gè)或者說(shuō)上一個(gè)子fiber
        previousNewFiber = newFiber;
      }
    }
    // 開(kāi)始處理移動(dòng)的情況
    const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
    //開(kāi)始遍歷剩下的虛擬DOM子節(jié)點(diǎn)
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx]);
      if (newFiber !== null) {
      //如果要跟蹤副作用,并且有老fiber
       if (newFiber.alternate !== null) {
         existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);
       }
        //指定新的fiber存放位置 ,并且給lastPlacedIndex賦值
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber; //這個(gè)newFiber就是大兒子
        } else {
          //否則說(shuō)明不是大兒子,就把這個(gè)newFiber添加上一個(gè)子節(jié)點(diǎn)后面
          previousNewFiber.sibling = newFiber;
        }
        //讓newFiber成為最后一個(gè)或者說(shuō)上一個(gè)子fiber
        previousNewFiber = newFiber;
      }
    }
    //等全部處理完后,刪除map中所有剩下的老fiber
    existingChildren.forEach(child => deleteChild(returnFiber, child));
    return resultingFirstChild;
  }

這段代碼是比較長(zhǎng)的,這里全部貼出來(lái)就是體現(xiàn)其完整性。下面幫助大家逐步的分析。

<ul key="container">
  <li key="A">A</li>
  <li key="B">B</li>
  <li key="C">C</li>
  <li key="D">D</li>
  <li key="E">E</li>
  <li key="F">F</li>
</ul>
<!-- 變化到 -->
<ul key="container">
  <li key="A">A2</li>
  <li key="C">C2</li>
  <li key="E">E2</li>
  <li key="B">B2</li>
  <li key="G">G</li>
  <li key="D">D2</li>
</ul>

第一次遍歷

for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
      //先暫下一個(gè)老fiber
      nextOldFiber = oldFiber.sibling;
      //試圖更新或者試圖復(fù)用老的fiber
      const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
      if (newFiber === null) {
        break;
      }
      if (shouldTrackSideEffects) {
        //如果有老fiber,但是新的fiber并沒(méi)有成功復(fù)用老fiber和老的真實(shí)DOM,那就刪除老fiber,在提交階段會(huì)刪除真實(shí)DOM
        if (oldFiber && newFiber.alternate === null) {
          deleteChild(returnFiber, oldFiber);
        }
      }
      //指定新fiber的位置
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;//li(A).sibling=p(B).sibling=>li(C)
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
      oldFiber = nextOldFiber
    }

我們所有的對(duì)比都是基于新節(jié)點(diǎn)的虛擬DOM和老節(jié)點(diǎn)的fiber,當(dāng)我們對(duì)比A1和A2時(shí),會(huì)根據(jù)updateSlot方法進(jìn)行條件判斷,發(fā)現(xiàn)他們的key和type相同,符合復(fù)用條件返回創(chuàng)建好的fiber,我們的操作指針都指向下一個(gè)操作節(jié)點(diǎn),開(kāi)始對(duì)下一個(gè)節(jié)點(diǎn)進(jìn)行第一次遍歷。

當(dāng)我們對(duì)比C2和B時(shí),因?yàn)镃2和B的key并不相同,updateSlot返回null,第一次遍歷break開(kāi)始進(jìn)入第二次遍歷。

第二次遍歷

if (oldFiber === null) {
  //如果老的 fiber已經(jīng)沒(méi)有了, 新的虛擬DOM還有,進(jìn)入插入新節(jié)點(diǎn)的邏輯
  for (; newIdx < newChildren.length; newIdx++) {
    const newFiber = createChild(returnFiber, newChildren[newIdx]);
    if (newFiber === null) continue;
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    //如果previousNewFiber為null,說(shuō)明這是第一個(gè)fiber
    if (previousNewFiber === null) {
      resultingFirstChild = newFiber; //這個(gè)newFiber就是大兒子
    } else {
      //否則說(shuō)明不是大兒子,就把這個(gè)newFiber添加上一個(gè)子節(jié)點(diǎn)后面
      previousNewFiber.sibling = newFiber;
    }
    //讓newFiber成為最后一個(gè)或者說(shuō)上一個(gè)子fiber
    previousNewFiber = newFiber;
  }
}

然而oldFiber依舊是存在的,會(huì)直接進(jìn)入到第三次遍歷,但是我們這里帶大家梳理一下,看看是如何操作的。這里的遍歷主要是針對(duì)新節(jié)點(diǎn)還存在,但是老fiber已經(jīng)沒(méi)有了,即新更新的節(jié)點(diǎn)要多余老節(jié)點(diǎn)的情況,我們這里需要做的就是將剩下的新節(jié)點(diǎn)的fiber通過(guò)createChild創(chuàng)造出來(lái)。

第三次遍歷

// 開(kāi)始處理移動(dòng)的情況
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
//開(kāi)始遍歷剩下的虛擬DOM子節(jié)點(diǎn)
for (; newIdx < newChildren.length; newIdx++) {
  const newFiber = updateFromMap(
    existingChildren,
    returnFiber,
    newIdx,
    newChildren[newIdx],
  );
  if (newFiber !== null) {
  //如果要跟蹤副作用,并且有老fiber
  if (newFiber.alternate !== null) {
    existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);
  }
    //指定新的fiber存放位置 ,并且給lastPlacedIndex賦值
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    if (previousNewFiber === null) {
      resultingFirstChild = newFiber; //這個(gè)newFiber就是大兒子
    } else {
      //否則說(shuō)明不是大兒子,就把這個(gè)newFiber添加上一個(gè)子節(jié)點(diǎn)后面
      previousNewFiber.sibling = newFiber;
    }
    //讓newFiber成為最后一個(gè)或者說(shuō)上一個(gè)子fiber
    previousNewFiber = newFiber;
  }
}
function mapRemainingChildren(returnFiber, currentFirstChild) {
  const existingChildren = new Map();
  let existingChild = currentFirstChild;
  while (existingChild != null) {
    //如果有key用key,如果沒(méi)有key使用索引
    if (existingChild.key !== null) {
      existingChildren.set(existingChild.key, existingChild);
    } else {
      existingChildren.set(existingChild.index, existingChild);
    }
    existingChild = existingChild.sibling;
  }
  return existingChildren;
}

接下來(lái)我們進(jìn)行第三次遍歷,也就是我們節(jié)點(diǎn)移動(dòng)的情況,這里的復(fù)用是比較復(fù)雜了。

首先我們會(huì)創(chuàng)造一個(gè)Map來(lái)承接所有的剩余的老節(jié)點(diǎn),接下來(lái)我們會(huì)根據(jù)key,或者index,來(lái)挑選老節(jié)點(diǎn)以供復(fù)用。找到一個(gè)能復(fù)用的節(jié)點(diǎn),就會(huì)在Map中刪除對(duì)應(yīng)的節(jié)點(diǎn),如果有對(duì)應(yīng)的點(diǎn)就復(fù)用,沒(méi)有就新創(chuàng)建節(jié)點(diǎn)。

  • 多個(gè)節(jié)點(diǎn)數(shù)量不同、key 不同;
  • 第一輪比較 A 和 A,相同可以復(fù)用,更新,然后比較 B 和 C,key 不同直接跳出第一個(gè)循環(huán);
  • 把剩下 oldFiber 的放入 existingChildren 這個(gè) map 中;
  • 然后聲明一個(gè)lastPlacedIndex變量,表示不需要移動(dòng)的老節(jié)點(diǎn)的索引;
  • 繼續(xù)循環(huán)剩下的虛擬 DOM 節(jié)點(diǎn);
  • 如果能在 map 中找到相同 key 相同 type 的節(jié)點(diǎn)則可以復(fù)用老 fiber,并把此老 fiber 從 map 中刪除;
  • 如果能在 map 中找不到相同 key 相同 type 的節(jié)點(diǎn)則創(chuàng)建新的 fiber;
  • 如果是復(fù)用老的 fiber,則判斷老 fiber 的索引是否小于 lastPlacedIndex,如果是要移動(dòng)老 fiber,不變;
  • 如果是復(fù)用老的 fiber,則判斷老 fiber 的索引是否小于 lastPlacedIndex,如果否則更新 lastPlacedIndex 為老 fiber 的 index;
  • 把所有的 map 中剩下的 fiber 全部標(biāo)記為刪除;
  • (刪除#li#F)=>(添加#li#B)=>(添加#li#G)=>(添加#li#D)=>null;

總結(jié)

DOM DIFF 的三個(gè)規(guī)則

  • 只對(duì)同級(jí)元素進(jìn)行比較,不同層級(jí)不對(duì)比
  • 不同的類型對(duì)應(yīng)不同的元素
  • 可以通過(guò) key 來(lái)標(biāo)識(shí)同一個(gè)節(jié)點(diǎn)

第 1 輪遍歷

  • 如果 key 不同則直接結(jié)束本輪循環(huán)
  • newChildren 或 oldFiber 遍歷完,結(jié)束本輪循環(huán)
  • key 相同而 type 不同,標(biāo)記老的 oldFiber 為刪除,繼續(xù)循環(huán)
  • key 相同而 type 也相同,則可以復(fù)用老節(jié) oldFiber 節(jié)點(diǎn),繼續(xù)循環(huán)

第 2 輪遍歷

  • newChildren 遍歷完而 oldFiber 還有,遍歷剩下所有的 oldFiber 標(biāo)記為刪除,DIFF 結(jié)束
  • oldFiber 遍歷完了,而 newChildren 還有,將剩下的 newChildren 標(biāo)記為插入,DIFF 結(jié)束
  • newChildren 和 oldFiber 都同時(shí)遍歷完成,diff 結(jié)束
  • newChildren 和 oldFiber 都沒(méi)有完成,則進(jìn)行節(jié)點(diǎn)移動(dòng)的邏輯
  • 第 3 輪遍歷

處理節(jié)點(diǎn)移動(dòng)的情況

以上就是React DOM-diff 節(jié)點(diǎn)源碼解析的詳細(xì)內(nèi)容,更多關(guān)于React DOM-diff 的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React中的頁(yè)面跳轉(zhuǎn)方式示例詳解

    React中的頁(yè)面跳轉(zhuǎn)方式示例詳解

    React Router提供了幾種不同的跳轉(zhuǎn)方式,包括使用組件進(jìn)行頁(yè)面跳轉(zhuǎn)、使用組件進(jìn)行重定向,以及使用編程式導(dǎo)航進(jìn)行跳轉(zhuǎn),這篇文章主要介紹了React中的頁(yè)面跳轉(zhuǎn)方式詳解,需要的朋友可以參考下
    2023-09-09
  • ReactHook使用useState更新變量后,如何拿到變量更新后的值

    ReactHook使用useState更新變量后,如何拿到變量更新后的值

    這篇文章主要介紹了ReactHook使用useState更新變量后,如何拿到變量更新后的值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React useCallback詳細(xì)使用教程

    React useCallback詳細(xì)使用教程

    useCallback是react中比較重要的一個(gè)hook,useCallback 用來(lái)返回一個(gè)函數(shù),在父子組件傳參或者通用函數(shù)封裝中,起到舉足輕重的作用
    2022-11-11
  • React的組件協(xié)同使用實(shí)現(xiàn)方式

    React的組件協(xié)同使用實(shí)現(xiàn)方式

    這篇文章主要介紹了React的組件協(xié)同使用,文中給大家提到在React開(kāi)發(fā)中,有哪些場(chǎng)景的組件協(xié)同?又如何去實(shí)現(xiàn)組件的協(xié)同使用呢?本文都給大家提到,感興趣的朋友跟隨小編一起看看吧
    2021-09-09
  • React中使用Workbox進(jìn)行預(yù)緩存的實(shí)現(xiàn)代碼

    React中使用Workbox進(jìn)行預(yù)緩存的實(shí)現(xiàn)代碼

    Workbox是Google Chrome團(tuán)隊(duì)推出的一套 PWA 的解決方案,這套解決方案當(dāng)中包含了核心庫(kù)和構(gòu)建工具,因此我們可以利用Workbox實(shí)現(xiàn)Service Worker的快速開(kāi)發(fā),本文小編給大家介紹了React中使用Workbox進(jìn)行預(yù)緩存的實(shí)現(xiàn),需要的朋友可以參考下
    2023-11-11
  • React+TypeScript+webpack4多入口配置詳解

    React+TypeScript+webpack4多入口配置詳解

    這篇文章主要介紹了React+TypeScript+webpack4多入口配置詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • react如何向數(shù)組中追加值

    react如何向數(shù)組中追加值

    這篇文章主要介紹了react如何向數(shù)組中追加值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • React如何避免子組件無(wú)效刷新

    React如何避免子組件無(wú)效刷新

    這篇文章主要介紹了React幾種避免子組件無(wú)效刷新的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React使用context進(jìn)行跨級(jí)組件數(shù)據(jù)傳遞

    React使用context進(jìn)行跨級(jí)組件數(shù)據(jù)傳遞

    這篇文章給大家介紹了React使用context進(jìn)行跨級(jí)組件數(shù)據(jù)傳遞的方法步驟,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)React context組件數(shù)據(jù)傳遞有一定的幫助,感興趣的小伙伴跟著小編一起來(lái)學(xué)習(xí)吧
    2024-01-01
  • react項(xiàng)目自行配置熱更新的實(shí)現(xiàn)

    react項(xiàng)目自行配置熱更新的實(shí)現(xiàn)

    本文主要介紹了react項(xiàng)目自行配置熱更新的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11

最新評(píng)論

襄汾县| 安宁市| 格尔木市| 石泉县| 衡山县| 韩城市| 恭城| 龙井市| 道孚县| 始兴县| 武汉市| 台前县| 永吉县| 土默特右旗| 东乌| 苍溪县| 宜阳县| 瑞安市| 广平县| 临夏市| 民县| 顺义区| 平远县| 马鞍山市| 芜湖市| 高陵县| 大埔县| 乌鲁木齐市| 北京市| 长泰县| 湘潭市| 年辖:市辖区| 平山县| 凤台县| 仁怀市| 台中市| 讷河市| 海盐县| 菏泽市| 公安县| 许昌市|