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

詳解為什么Vue中不要用index作為key(diff算法)

 更新時(shí)間:2020年04月04日 10:35:01   作者:晨曦時(shí)夢(mèng)見(jiàn)兮  
這篇文章主要介紹了詳解為什么Vue中不要用index作為key(diff算法),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

Vue 中的 key 是用來(lái)做什么的?為什么不推薦使用 index 作為 key?常常聽(tīng)說(shuō)這樣的問(wèn)題,本篇文章帶你從原理來(lái)一探究竟。
另外本文的結(jié)論對(duì)于性能的毀滅是針對(duì)列表子元素順序會(huì)交換、或者子元素被刪除的特殊情況,提前說(shuō)明清楚,噴子繞道。

本篇已經(jīng)收錄在 Github 倉(cāng)庫(kù),歡迎 Star:
https://github.com/sl1673495/blogs/issues/39

示例

以這樣一個(gè)列表為例:

<ul>
 <li>1</li>
 <li>2</li>
</ul>

那么它的 vnode 也就是虛擬 dom 節(jié)點(diǎn)大概是這樣的。

{
 tag: 'ul',
 children: [
  { tag: 'li', children: [ { vnode: { text: '1' }}] },
  { tag: 'li', children: [ { vnode: { text: '2' }}] },
 ]
}

假設(shè)更新以后,我們把子節(jié)點(diǎn)的順序調(diào)換了一下:

{
 tag: 'ul',
 children: [
+  { tag: 'li', children: [ { vnode: { text: '2' }}] },
+  { tag: 'li', children: [ { vnode: { text: '1' }}] },
 ]
}

很顯然,這里的 children 部分是我們本文 diff 算法要講的重點(diǎn)(敲黑板)。

首先響應(yīng)式數(shù)據(jù)更新后,觸發(fā)了 渲染 Watcher  的回調(diào)函數(shù) vm._update(vm._render())去驅(qū)動(dòng)視圖更新,vm._render() 其實(shí)生成的就是 vnode,而 vm._update 就會(huì)帶著新的 vnode 去走觸發(fā) __patch__ 過(guò)程。

我們直接進(jìn)入 ul 這個(gè) vnode 的 patch 過(guò)程。

對(duì)比新舊節(jié)點(diǎn)是否是相同類型的節(jié)點(diǎn):

1. 不是相同節(jié)點(diǎn):
isSameNode為false的話,直接銷毀舊的 vnode,渲染新的 vnode。這也解釋了為什么 diff 是同層對(duì)比。

2. 是相同節(jié)點(diǎn),要盡可能的做節(jié)點(diǎn)的復(fù)用(都是 ul,進(jìn)入👈)。

會(huì)調(diào)用src/core/vdom/patch.js下的patchVNode方法。

如果新 vnode 是文字 vnode

就直接調(diào)用瀏覽器的 dom api 把節(jié)點(diǎn)的直接替換掉文字內(nèi)容就好。

如果新 vnode 不是文字 vnode
如果有新 children 而沒(méi)有舊 children

說(shuō)明是新增 children,直接 addVnodes 添加新子節(jié)點(diǎn)。

如果有舊 children 而沒(méi)有新 children

說(shuō)明是刪除 children,直接 removeVnodes 刪除舊子節(jié)點(diǎn)

如果新舊 children 都存在(都存在 li 子節(jié)點(diǎn)列表,進(jìn)入👈)

那么就是我們 diff算法 想要考察的最核心的點(diǎn)了,也就是新舊節(jié)點(diǎn)的 diff 過(guò)程。

通過(guò)

 // 舊首節(jié)點(diǎn)
 let oldStartIdx = 0
 // 新首節(jié)點(diǎn)
 let newStartIdx = 0
 // 舊尾節(jié)點(diǎn)
 let oldEndIdx = oldCh.length - 1
 // 新尾節(jié)點(diǎn)
 let newEndIdx = newCh.length - 1

這些變量分別指向舊節(jié)點(diǎn)的首尾、新節(jié)點(diǎn)的首尾。

根據(jù)這些指針,在一個(gè) while 循環(huán)中不停的對(duì)新舊節(jié)點(diǎn)的兩端的進(jìn)行對(duì)比,直到?jīng)]有節(jié)點(diǎn)可以對(duì)比。

在講對(duì)比過(guò)程之前,要講一個(gè)比較重要的函數(shù):sameVnode:

function sameVnode (a, b) {
 return (
  a.key === b.key && (
   (
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
   )
  )
 )
}

它是用來(lái)判斷節(jié)點(diǎn)是否可用的關(guān)鍵函數(shù),可以看到,判斷是否是 sameVnode,傳遞給節(jié)點(diǎn)的 key 是關(guān)鍵。
然后我們接著進(jìn)入 diff 過(guò)程,每一輪都是同樣的對(duì)比,其中某一項(xiàng)命中了,就遞歸的進(jìn)入 patchVnode 針對(duì)單個(gè) vnode 進(jìn)行的過(guò)程(如果這個(gè) vnode 又有 children,那么還會(huì)來(lái)到這個(gè) diff children 的過(guò)程 ):

  1. 舊首節(jié)點(diǎn)和新首節(jié)點(diǎn)用 sameNode 對(duì)比。
  2. 舊尾節(jié)點(diǎn)和新首節(jié)點(diǎn)用 sameNode 對(duì)比
  3. 舊首節(jié)點(diǎn)和新尾節(jié)點(diǎn)用 sameNode 對(duì)比
  4. 舊尾節(jié)點(diǎn)和新尾節(jié)點(diǎn)用 sameNode 對(duì)比
  5. 如果以上邏輯都匹配不到,再把所有舊子節(jié)點(diǎn)的 key 做一個(gè)映射表,然后用新 vnode 的 key 去找出在舊節(jié)點(diǎn)中可以復(fù)用的位置。

然后不停的把匹配到的指針向內(nèi)部收縮,直到新舊節(jié)點(diǎn)有一端的指針相遇(說(shuō)明這個(gè)端的節(jié)點(diǎn)都被patch過(guò)了)。
在指針相遇以后,還有兩種比較特殊的情況:

  • 有新節(jié)點(diǎn)需要加入。如果更新完以后,oldStartIdx > oldEndIdx,說(shuō)明舊節(jié)點(diǎn)都被 patch 完了,但是有可能還有新的節(jié)點(diǎn)沒(méi)有被處理到。接著會(huì)去判斷是否要新增子節(jié)點(diǎn)。
  • 有舊節(jié)點(diǎn)需要?jiǎng)h除。如果新節(jié)點(diǎn)先patch完了,那么此時(shí)會(huì)走 newStartIdx > newEndIdx  的邏輯,那么就會(huì)去刪除多余的舊子節(jié)點(diǎn)。

為什么不要以index作為key?

節(jié)點(diǎn)reverse場(chǎng)景

假設(shè)我們有這樣的一段代碼:

  <div id="app">
   <ul>
    <item
     :key="index"
     v-for="(num, index) in nums"
     :num="num"
     :class="`item${num}`"
    ></item>
   </ul>
   <button @click="change">改變</button>
  </div>
  <script src="./vue.js"></script>
  <script>
   var vm = new Vue({
    name: "parent",
    el: "#app",
    data: {
     nums: [1, 2, 3]
    },
    methods: {
     change() {
      this.nums.reverse();
     }
    },
    components: {
     item: {
      props: ["num"],
      template: `
          <div>
            {{num}}
          </div>
        `,
      name: "child"
     }
    }
   });
  </script>

其實(shí)是一個(gè)很簡(jiǎn)單的列表組件,渲染出來(lái) 1 2 3 三個(gè)數(shù)字。我們先以 index 作為key,來(lái)跟蹤一下它的更新。

我們接下來(lái)只關(guān)注 item 列表節(jié)點(diǎn)的更新,在首次渲染的時(shí)候,我們的虛擬節(jié)點(diǎn)列表 oldChildren 粗略表示是這樣的:

[
 {
  tag: "item",
  key: 0,
  props: {
   num: 1
  }
 },
 {
  tag: "item",
  key: 1,
  props: {
   num: 2
  }
 },
 {
  tag: "item",
  key: 2,
  props: {
   num: 3
  }
 }
];

在我們點(diǎn)擊按鈕的時(shí)候,會(huì)對(duì)數(shù)組做 reverse 的操作。那么我們此時(shí)生成的 newChildren 列表是這樣的:

[
 {
  tag: "item",
  key: 0,
  props: {
+   num: 3
  }
 },
 {
  tag: "item",
  key: 1,
  props: {
+   num: 2
  }
 },
 {
  tag: "item",
  key: 2,
  props: {
+   num: 1
  }
 }
];

發(fā)現(xiàn)什么問(wèn)題沒(méi)有?key的順序沒(méi)變,傳入的值完全變了。這會(huì)導(dǎo)致一個(gè)什么問(wèn)題?

本來(lái)按照最合理的邏輯來(lái)說(shuō),舊的第一個(gè)vnode 是應(yīng)該直接完全復(fù)用 新的第三個(gè)vnode的,因?yàn)樗鼈儽緛?lái)就應(yīng)該是同一個(gè)vnode,自然所有的屬性都是相同的。

但是在進(jìn)行子節(jié)點(diǎn)的 diff 過(guò)程中,會(huì)在 舊首節(jié)點(diǎn)和新首節(jié)點(diǎn)用sameNode對(duì)比。 這一步命中邏輯,因?yàn)楝F(xiàn)在新舊兩次首部節(jié)點(diǎn) 的 key 都是 0了,

然后把舊的節(jié)點(diǎn)中的第一個(gè) vnode 和 新的節(jié)點(diǎn)中的第一個(gè) vnode 進(jìn)行 patchVnode 操作。

這會(huì)發(fā)生什么呢?我可以大致給你列一下:

首先,正如我之前的文章props的更新如何觸發(fā)重渲染?里所說(shuō),在進(jìn)行 patchVnode 的時(shí)候,會(huì)去檢查 props 有沒(méi)有變更,如果有的話,會(huì)通過(guò) _props.num = 3 這樣的邏輯去更新這個(gè)響應(yīng)式的值,觸發(fā) dep.notify,觸發(fā)子組件視圖的重新渲染等一套很重的邏輯。

然后,還會(huì)額外的觸發(fā)以下幾個(gè)鉤子,假設(shè)我們的組件上定義了一些dom的屬性或者類名、樣式、指令,那么都會(huì)被全量的更新。

  1. updateAttrs
  2. updateClass
  3. updateDOMListeners
  4. updateDOMProps
  5. updateStyle
  6. updateDirectives

而這些所有重量級(jí)的操作(虛擬dom發(fā)明的其中一個(gè)目的不就是為了減少真實(shí)dom的操作么?),都可以通過(guò)直接復(fù)用 第三個(gè)vnode 來(lái)避免,是因?yàn)槲覀兺祽袑?xiě)了 index 作為 key,而導(dǎo)致所有的優(yōu)化失效了。

節(jié)點(diǎn)刪除場(chǎng)景

另外,除了會(huì)導(dǎo)致性能損耗以外,在刪除子節(jié)點(diǎn)的場(chǎng)景下還會(huì)造成更嚴(yán)重的錯(cuò)誤,

可以看sea_ljf同學(xué)提供的這個(gè)demo。

假設(shè)我們有這樣的一段代碼:

<body>
 <div id="app">
  <ul>
   <li v-for="(value, index) in arr" :key="index">
    <test />
   </li>
  </ul>
  <button @click="handleDelete">delete</button>
 </div>
 </div>
</body>
<script>
 new Vue({
  name: "App",
  el: '#app',
  data() {
   return {
    arr: [1, 2, 3]
   };
  },
  methods: {
   handleDelete() {
    this.arr.splice(0, 1);
   }
  },
  components: {
   test: {
    template: "<li>{{Math.random()}}</li>"
   }
  }
 })
</script>

那么一開(kāi)始的 vnode列表是:

[
 {
  tag: "li",
  key: 0,
  // 這里其實(shí)子組件對(duì)應(yīng)的是第一個(gè) 假設(shè)子組件的text是1
 },
 {
  tag: "li",
  key: 1,
  // 這里其實(shí)子組件對(duì)應(yīng)的是第二個(gè) 假設(shè)子組件的text是2
 },
 {
  tag: "li",
  key: 2,
  // 這里其實(shí)子組件對(duì)應(yīng)的是第三個(gè) 假設(shè)子組件的text是3
 }
];

有一個(gè)細(xì)節(jié)需要注意,正如我上一篇文章中所提到的為什么說(shuō) Vue 的響應(yīng)式更新比 React 快?,Vue 對(duì)于組件的 diff 是不關(guān)心子組件內(nèi)部實(shí)現(xiàn)的,它只會(huì)看你在模板上聲明的傳遞給子組件的一些屬性是否有更新。

也就是和v-for平級(jí)的那部分,回顧一下判斷 sameNode 的時(shí)候,只會(huì)判斷key、 tag、是否有data的存在(不關(guān)心內(nèi)部具體的值)、是否是注釋節(jié)點(diǎn)、是否是相同的input type,來(lái)判斷是否可以復(fù)用這個(gè)節(jié)點(diǎn)。

<li v-for="(value, index) in arr" :key="index"> // 這里聲明的屬性
 <test />
</li>

有了這些前置知識(shí)以后,我們來(lái)看看,點(diǎn)擊刪除子元素后,vnode 列表 變成什么樣了。

[
 // 第一個(gè)被刪了
 {
  tag: "li",
  key: 0,
  // 這里其實(shí)上一輪子組件對(duì)應(yīng)的是第二個(gè) 假設(shè)子組件的text是2
 },
 {
  tag: "li",
  key: 1,
  // 這里其實(shí)子組件對(duì)應(yīng)的是第三個(gè) 假設(shè)子組件的text是3
 },
];

雖然在注釋里我們自己清楚的知道,第一個(gè) vnode 被刪除了,但是對(duì)于 Vue 來(lái)說(shuō),它是感知不到子組件里面到底是什么樣的實(shí)現(xiàn)(它不會(huì)深入子組件去對(duì)比文本內(nèi)容),那么這時(shí)候 Vue 會(huì)怎么 patch 呢?

由于對(duì)應(yīng)的 key使用了 index導(dǎo)致的錯(cuò)亂,它會(huì)把

  1. 原來(lái)的第一個(gè)節(jié)點(diǎn)text: 1直接復(fù)用。
  2. 原來(lái)的第二個(gè)節(jié)點(diǎn)text: 2直接復(fù)用。
  3. 然后發(fā)現(xiàn)新節(jié)點(diǎn)里少了一個(gè),直接把多出來(lái)的第三個(gè)節(jié)點(diǎn)text: 3 丟掉。

至此為止,我們本應(yīng)該把 text: 1節(jié)點(diǎn)刪掉,然后text: 2、text: 3 節(jié)點(diǎn)復(fù)用,就變成了錯(cuò)誤的把 text: 3 節(jié)點(diǎn)給刪掉了。

為什么不要用隨機(jī)數(shù)作為key?

<item
 :key="Math.random()"
 v-for="(num, index) in nums"
 :num="num"
 :class="`item${num}`"
/>

其實(shí)我聽(tīng)過(guò)一種說(shuō)法,既然官方要求一個(gè) 唯一的key,是不是可以用 Math.random() 作為 key 來(lái)偷懶?這是一個(gè)很雞賊的想法,看看會(huì)發(fā)生什么吧。

首先 oldVnode 是這樣的:

[
 {
  tag: "item",
  key: 0.6330715699108844,
  props: {
   num: 1
  }
 },
 {
  tag: "item",
  key: 0.25104533240710514,
  props: {
   num: 2
  }
 },
 {
  tag: "item",
  key: 0.4114769152411637,
  props: {
   num: 3
  }
 }
];

更新以后是:

[
 {
  tag: "item",
+  key: 0.11046018699748683,
  props: {
+   num: 3
  }
 },
 {
  tag: "item",
+  key: 0.8549799545696619,
  props: {
+   num: 2
  }
 },
 {
  tag: "item",
+  key: 0.18674467938937478,
  props: {
+   num: 1
  }
 }
];

可以看到,key 變成了完全全新的 3 個(gè)隨機(jī)數(shù)。

上面說(shuō)到,diff 子節(jié)點(diǎn)的首尾對(duì)比如果都沒(méi)有命中,就會(huì)進(jìn)入 key 的詳細(xì)對(duì)比過(guò)程,簡(jiǎn)單來(lái)說(shuō),就是利用舊節(jié)點(diǎn)的 key -> index 的關(guān)系建立一個(gè) map 映射表,然后用新節(jié)點(diǎn)的 key 去匹配,如果沒(méi)找到的話,就會(huì)調(diào)用 createElm 方法 重新建立 一個(gè)新節(jié)點(diǎn)。

具體代碼在這:

// 建立舊節(jié)點(diǎn)的 key -> index 映射表
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);

// 去映射表里找可以復(fù)用的 index
idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
// 一定是找不到的,因?yàn)樾鹿?jié)點(diǎn)的 key 是隨機(jī)生成的。
if (isUndef(idxInOld)) {
 // 完全通過(guò) vnode 新建一個(gè)真實(shí)的子節(jié)點(diǎn)
 createElm();
}

也就是說(shuō),咱們的這個(gè)更新過(guò)程可以這樣描述:

123 -> 前面重新創(chuàng)建三個(gè)子組件 -> 321123  -> 刪除、銷毀后面三個(gè)子組件 -> 321。
發(fā)現(xiàn)問(wèn)題了吧?這是毀滅性的災(zāi)難,創(chuàng)建新的組件和銷毀組件的成本你們曉得的伐……本來(lái)僅僅是對(duì)組件移動(dòng)位置就可以完成的更新,被我們毀成這樣了。

總結(jié)

經(jīng)過(guò)這樣的一段旅行,diff 這個(gè)龐大的過(guò)程就結(jié)束了。

我們收獲了什么?

  1. 用組件唯一的 id(一般由后端返回)作為它的 key,實(shí)在沒(méi)有的情況下,可以在獲取到列表的時(shí)候通過(guò)某種規(guī)則為它們創(chuàng)建一個(gè) key,并保證這個(gè) key 在組件整個(gè)生命周期中都保持穩(wěn)定。
  2. 如果你的列表順序會(huì)改變,別用 index 作為 key,和沒(méi)寫(xiě)基本上沒(méi)區(qū)別,因?yàn)椴还苣銛?shù)組的順序怎么顛倒,index 都是 0, 1, 2 這樣排列,導(dǎo)致 Vue 會(huì)復(fù)用錯(cuò)誤的舊子節(jié)點(diǎn),做很多額外的工作。列表順序不變也盡量別用,可能會(huì)誤導(dǎo)新人。
  3. 千萬(wàn)別用隨機(jī)數(shù)作為 key,不然舊節(jié)點(diǎn)會(huì)被全部刪掉,新節(jié)點(diǎn)重新創(chuàng)建,你的老板會(huì)被你氣死。

到此這篇關(guān)于詳解為什么Vue中不要用index作為key(diff算法)的文章就介紹到這了,更多相關(guān)Vue不要用index作為key內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue-pdf如何通過(guò)文件流預(yù)覽pdf文件

    vue-pdf如何通過(guò)文件流預(yù)覽pdf文件

    這篇文章主要介紹了vue-pdf如何通過(guò)文件流預(yù)覽pdf文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue-router傳參用法詳解

    vue-router傳參用法詳解

    今天小編就為大家分享一篇關(guān)于vue-router傳參用法詳解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Vue性能優(yōu)化的方法

    Vue性能優(yōu)化的方法

    這篇文章主要介紹了Vue性能優(yōu)化的方法,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí)vue,感興趣的朋友可以了解下
    2020-07-07
  • vue后臺(tái)返回base64圖片無(wú)法顯示的解決

    vue后臺(tái)返回base64圖片無(wú)法顯示的解決

    這篇文章主要介紹了vue后臺(tái)返回base64圖片無(wú)法顯示的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Vue實(shí)現(xiàn)仿iPhone懸浮球的示例代碼

    Vue實(shí)現(xiàn)仿iPhone懸浮球的示例代碼

    這篇文章主要介紹了Vue實(shí)現(xiàn)仿iPhone懸浮球的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • flv.js在vue中的使用方法

    flv.js在vue中的使用方法

    flv.js是一個(gè)用于在瀏覽器中解碼和播放FLV視頻的JavaScript庫(kù),它可以將FLV視頻流解碼并顯示在HTML5的video元素中,從而實(shí)現(xiàn)在瀏覽器中直接播放FLV格式的視頻文件,本文給大家介紹flv.js在vue中的使用,感興趣的朋友一起看看吧
    2023-11-11
  • vue3中路由傳參query、params及動(dòng)態(tài)路由傳參詳解

    vue3中路由傳參query、params及動(dòng)態(tài)路由傳參詳解

    vue3中的傳參方式和vue2中一樣,都可以用query和params傳參,下面這篇文章主要給大家介紹了關(guān)于vue3中路由傳參query、params及動(dòng)態(tài)路由傳參的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • vue多頁(yè)面項(xiàng)目實(shí)現(xiàn)版本快照功能示例詳解

    vue多頁(yè)面項(xiàng)目實(shí)現(xiàn)版本快照功能示例詳解

    這篇文章主要為大家介紹了vue多頁(yè)面項(xiàng)目實(shí)現(xiàn)版本快照功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • vue-cli+webpack在生成的項(xiàng)目中使用bootstrap實(shí)例代碼

    vue-cli+webpack在生成的項(xiàng)目中使用bootstrap實(shí)例代碼

    本篇文章主要介紹了vue-cli+webpack在生成的項(xiàng)目中使用bootstrap實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-05-05
  • 前端通過(guò)vue調(diào)用后端接口導(dǎo)出excel表格基本步驟

    前端通過(guò)vue調(diào)用后端接口導(dǎo)出excel表格基本步驟

    在Vue前端項(xiàng)目中,可通過(guò)axios庫(kù)發(fā)送請(qǐng)求至后端獲取Excel下載鏈接,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-10-10

最新評(píng)論

延安市| 瓮安县| 南丰县| 永州市| 明溪县| 南城县| 汾阳市| 绥芬河市| 中卫市| 龙泉市| 磐石市| 濮阳市| 岳普湖县| 湖州市| 新昌县| 怀安县| 于都县| 砚山县| 茌平县| 招远市| 江源县| 台南市| 茶陵县| 五华县| 屏南县| 达日县| 商洛市| 开封县| 兰溪市| 陇西县| 二手房| 竹山县| 固原市| 交口县| 镇远县| 武夷山市| 七台河市| 哈巴河县| 普安县| 海城市| 广元市|