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

Vue響應(yīng)式原理深入分析

 更新時(shí)間:2022年12月30日 09:19:20   作者:volit_  
響應(yīng)式就是當(dāng)對(duì)象本身(對(duì)象的增刪值)或者對(duì)象屬性(重新賦值)發(fā)生變化時(shí),將會(huì)運(yùn)行一些函數(shù),最常見(jiàn)的就是render函數(shù),下面這篇文章主要給大家介紹了關(guān)于Vue3響應(yīng)式原理的相關(guān)資料,需要的朋友可以參考下

1.響應(yīng)式數(shù)據(jù)和副作用函數(shù)

(1)副作用函數(shù)

副作用函數(shù)就是會(huì)產(chǎn)生副作用的函數(shù)。

function effect() {
    document.body.innerText = 'hello world.'
}

? 當(dāng)effect函數(shù)執(zhí)行時(shí),它會(huì)設(shè)置body的內(nèi)容,而body是一個(gè)全局變量,除了effect函數(shù)外任何地方都可以訪問(wèn)到,也就是說(shuō)effect函數(shù)的執(zhí)行會(huì)對(duì)其他操作產(chǎn)生影響,即effect函數(shù)是一個(gè)副作用函數(shù)。

(2)響應(yīng)式數(shù)據(jù)

const obj = { text: 'hello world.'};
function effect() {
    document.body.innerText = obj.text;
}
obj.text = 'text';

? 當(dāng) obj.text = 'text' 這條語(yǔ)句執(zhí)行之后,我們期望 document.body.innerText 的值也能夠隨之修改,這就是通常意義上的響應(yīng)式數(shù)據(jù)。

2.響應(yīng)式數(shù)據(jù)的基本實(shí)現(xiàn)

? 上文中,對(duì)響應(yīng)式數(shù)據(jù)進(jìn)行描述的代碼段,并未實(shí)現(xiàn)真正的響應(yīng)式數(shù)據(jù)。而通過(guò)觀察我們可以發(fā)現(xiàn),要實(shí)現(xiàn)真正的響應(yīng)式數(shù)據(jù),我們需要對(duì)數(shù)據(jù)的讀取和設(shè)置進(jìn)行攔截。當(dāng)有操作對(duì)響應(yīng)式數(shù)據(jù)進(jìn)行讀取中,我們將其添加至一個(gè)依賴(lài)隊(duì)列,當(dāng)修改響應(yīng)式數(shù)據(jù)的值時(shí),將依賴(lài)隊(duì)列中的操作依次取出,并執(zhí)行。以下使用Proxy對(duì)該思路進(jìn)行實(shí)現(xiàn)。

const bucket = new Set();
const data = { text: "hello world." };
const obj = new Proxy(data, {
  get(target, key) {
    bucket.add(effect);
    return target[key];
  },
  set(target, key, newVal) {
    target[key] = newVal;
    bucket.forEach((fn) => fn());
    return true;
  },
});
const body = {
  innerText: "",
};
function effect() {
  body.innerText = obj.text;
}
effect();
console.log(body.innerText); // hello world
obj.text = "text";
console.log(body.innerText); // text

? 但是,該實(shí)現(xiàn)仍然存在缺陷,比如說(shuō)只能通過(guò)effect函數(shù)的名字實(shí)現(xiàn)依賴(lài)收集。

3.設(shè)計(jì)一個(gè)完善的響應(yīng)式系統(tǒng)

(1)消除依賴(lài)收集的硬綁定

? 這里我們使用一個(gè)active變量來(lái)保存當(dāng)前需要進(jìn)行依賴(lài)收集的函數(shù)。

const bucket = new Set();
const data = { text: "hello world." };
let activeEffect; // 新增一個(gè)active變量
const obj = new Proxy(data, {
  get(target, key) {
    if (activeEffect) {
      bucket.add(activeEffect); // 添加active變量保存的函數(shù)
    }
    return target[key];
  },
  set(target, key, newVal) {
    target[key] = newVal;
    bucket.forEach((fn) => fn());
    return true;
  },
});
function effect(fn) {
  activeEffect = fn; // 將當(dāng)前函數(shù)賦值給active變量
  fn();
}
const body = {
  innerText: "",
};
effect(() => {
  body.innerText = obj.text;
});
console.log(body.innerText); // hello world
obj.text = "text";
console.log(body.innerText); // text

? 但是該設(shè)計(jì)仍然存在很多問(wèn)題,比如說(shuō),當(dāng)訪問(wèn)一個(gè)obj對(duì)象上并不存在的屬性假設(shè)為val時(shí),邏輯上并沒(méi)有存在對(duì)obj.val的訪問(wèn),因此該操作不會(huì)產(chǎn)生任何響應(yīng),但實(shí)際上,當(dāng)val的值被修改后,傳入effect的匿名函數(shù)會(huì)再次執(zhí)行。

(2)基于屬性的依賴(lài)收集

? 上一個(gè)版本的響應(yīng)式系統(tǒng)只能對(duì)攔截對(duì)象所有的get和set操作進(jìn)行響應(yīng),并不能做到細(xì)粒度的控制。考慮針對(duì)屬性進(jìn)行依賴(lài)攔截,主要有三個(gè)角色,對(duì)象、屬性和依賴(lài)方法。因此考慮修改bucket的結(jié)構(gòu),由原來(lái)的Set修改為WeakMap(target,Map(key,activeEffect));這樣就可以針對(duì)屬性進(jìn)行細(xì)粒度的依賴(lài)收集了。

ps.使用WeakMap是因?yàn)閃eakMap是對(duì)key的弱引用,不會(huì)影響垃圾回收機(jī)制的工作,當(dāng)target對(duì)象不存在任何引用時(shí),說(shuō)明target對(duì)象已不被需要,這時(shí)target對(duì)象將會(huì)被垃圾回收。如果換成Map,即時(shí)target不存在任何引用,Map已然會(huì)保持對(duì)target的引用,容易造成內(nèi)存泄露。

// bucket的數(shù)據(jù)結(jié)構(gòu)修改為WeakMap
const bucket = new WeakMap();
const data = { text: "hello world." };
let activeEffect;
const obj = new Proxy(data, {
  get(target, key) {
    track(target, key);
    return target[key];
  },
  set(target, key, newVal) {
    target[key] = newVal;
    trigger(target, key);
  },
});
function track(target, key) {
  if (!activeEffect) {
    return;
  }
  let depsMap = bucket.get(target);
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()));
  }
  let deps = depsMap.get(key);
  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }
  deps.add(activeEffect);
}
function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  effects && effects.forEach((fn) => fn());
}
function effect(fn) {
  activeEffect = fn;
  fn();
}
const body = {
  innerText: "",
};
effect(() => {
  body.innerText = obj.text;
});
console.log(body.innerText); // hello world
obj.text = "text";
console.log(body.innerText); // text

(3)分支切換和cleanup

? 對(duì)于一段三元運(yùn)算符 obj.flag? obj.text : 'text',我們所期望的結(jié)果是,當(dāng)obj.flag的值為false時(shí),不會(huì)對(duì)obj.text屬性進(jìn)行響應(yīng)操作。 如果是上面那段程序,當(dāng)obj.flag的值為false時(shí),操作obj.text仍然會(huì)進(jìn)行相應(yīng)操作,因?yàn)閛bj.text對(duì)應(yīng)的依賴(lài)仍然存在。對(duì)此如果我們能夠在每次的函數(shù)執(zhí)行之前,將其從之前相關(guān)聯(lián)的依賴(lài)集合中移除,就可以達(dá)到目的了。這里通過(guò)修改副作用函數(shù)來(lái)實(shí)現(xiàn):

function effect(fn) {
  const effectFn = () => {
    // 在依賴(lài)函數(shù)執(zhí)行之前,清除依賴(lài)函數(shù)之前的依賴(lài)項(xiàng)
    cleanup(effectFn);
    activeEffect = effectFn;
    fn();
  };
  // 給副作用函數(shù)添加一個(gè)deps數(shù)組用來(lái)收集和該副作用函數(shù)相關(guān)聯(lián)的依賴(lài)
  effectFn.deps = [];
  effectFn();
}
// cleanup函數(shù)實(shí)現(xiàn)
function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i];
    deps.delete(effectFn);
  }
  effectFn.deps.length = 0;
}
function track(target, key) {
  if (!activeEffect) {
    return;
  }
  let depsMap = bucket.get(target);
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()));
  }
  let deps = depsMap.get(key);
  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }
  deps.add(activeEffect);
  activeEffect.deps.push(deps); // 在這里收集相關(guān)聯(lián)的依賴(lài)
}
function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  const effectToRun = new Set(effects); // 這里需要?jiǎng)?chuàng)建一個(gè)新集合來(lái)遍歷,因?yàn)閒oreach循環(huán)會(huì)對(duì)新加入序列的元素也執(zhí)行遍歷,若遍歷直接原集合,會(huì)出現(xiàn)死循環(huán)。
  effectToRun.forEach((fn) => fn());
}

(4)嵌套effect

? 雖然我們已經(jīng)解決了很多問(wèn)題,但是作為響應(yīng)式系統(tǒng)中比較常見(jiàn)的場(chǎng)景之一的嵌套,我們還不能實(shí)現(xiàn)。因?yàn)槲覀兌x的activeEffect是一個(gè)變量,當(dāng)嵌套操作時(shí),無(wú)論怎樣,最后activeEffect變量中存放的都是操作的最后一個(gè)副作用函數(shù)。這里,我們通過(guò)加入一個(gè)effect棧的方式,來(lái)給這套響應(yīng)式系統(tǒng)添加嵌套功能。

// 定義一個(gè)effect棧
const effectStack = [];
function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn);
    activeEffect = effectFn;
    effectStack.push(effectFn); // 在effect執(zhí)行之前,放入棧中
    fn();
    effectStack.pop(); // 執(zhí)行完畢立即彈出
    activeEffect = effectStack[effectStack.length - 1]; // activeEffect指向新的effect
  };
  effectFn.deps = [];
  effectFn();
}

(5)避免產(chǎn)生死循環(huán)

? 試看obj.val++這條語(yǔ)句,它實(shí)際上相當(dāng)于obj.val = obj.val+1,也就是進(jìn)行了一次讀取操作和一次賦值操作,共兩次操作。而若將該操作運(yùn)行在我們前面的響應(yīng)式系統(tǒng)中,它將會(huì)產(chǎn)生死循環(huán),因?yàn)楫?dāng)我們進(jìn)行了讀取操作后,會(huì)立即進(jìn)行賦值操作,而在賦值操作中,讀取操作再次被觸發(fā),然后循環(huán)的執(zhí)行這一系列操作。這里我們?cè)趖rigger函數(shù)中判斷trigger觸發(fā)的副作用函數(shù),是否與當(dāng)前正在執(zhí)行的副作用函數(shù)相同,若相同,則不執(zhí)行當(dāng)前副作用函數(shù)。這樣就能避免無(wú)限遞歸調(diào)用,避免內(nèi)存溢出。

function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  const effectToRun = new Set();
  effects &&
    effects.forEach((fn) => {
      // 若正在執(zhí)行的副作用函數(shù)與當(dāng)前觸發(fā)的副作用函數(shù)相同,則不執(zhí)行
      if (fn !== activeEffect) {
        effectToRun.add(fn);  
      }
    });
  effectToRun.forEach((fn) => fn());
}

(6)實(shí)現(xiàn)調(diào)度實(shí)行

現(xiàn)在要實(shí)現(xiàn)一個(gè)這樣的效果:

effect(()=> {
    console.log(obj.val);
});
obj.val ++;
console.log("結(jié)束");

// 這段代碼本來(lái)會(huì)輸出的結(jié)果是:
/**
    1
    2
    結(jié)束
 **/
// 現(xiàn)在我們想讓它變成
/**
     1
     結(jié)束
     2
 **/

這里我們可以通過(guò)給effect函數(shù)添加一個(gè)配置項(xiàng)來(lái)實(shí)現(xiàn):

effect(
  ()=> {
    console.log(obj.val);
  },
  {
      scheduler(fn) {
          setTimeout(fn);
      }
  }
function effect(fn,options = {}) {
    const effectFn = ()=> {
       ...
    }
    effectFn.deps = [];
    effectFn.options = options; // 為副作用函數(shù)添加配置項(xiàng)
    effectFn();
}
function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  const effectToRun = new Set();
  effects &&
    effects.forEach((fn) => {
      if (fn !== activeEffect) {
        effectToRun.add(fn);
      }
    });
  effectToRun.forEach((fn) => {
    // 若當(dāng)前依賴(lài)函數(shù)含有調(diào)度執(zhí)行,將當(dāng)前函數(shù)傳遞給調(diào)度函數(shù)執(zhí)行
    if (fn.options.scheduler) {
      fn.options.scheduler(fn); //將當(dāng)前函數(shù)傳遞給調(diào)度函數(shù)
    } else {
      fn();
    }
  });
}

如果還要實(shí)現(xiàn)一下效果:

effect(()=> {
    console.log(obj.val);
});
obj.val ++;
obj.val ++;
// 這段代碼本來(lái)會(huì)輸出的結(jié)果是:
/**
	1
	2
	3
 **/
// 現(xiàn)在我們想讓它變成
/**
 	1
 	3
 **/

這里通過(guò)添加一個(gè)任務(wù)執(zhí)行隊(duì)列來(lái)實(shí)現(xiàn):

const jobQueue = new Set();
const p = Promise.resolve(); 
let isFlushing = false;
effect(
    ()=> {
        console.log(obj.val);
    },
    {
        scheduler(fn){
            jobQueue.add(fn);
            flushJob();
        }
    }
);
function flushJob() {
    if(isFlushing) return;
    isFlushing = true;
    p.then(()=> {
        jobQueue.forEach(job=>job());
    }).finally(()=> {
        isFlushing = false;
    })
}

? 像這樣,由于Set保證了任務(wù)的唯一性,也就是jobQueue中只會(huì)保存唯一的一個(gè)任務(wù),即當(dāng)前執(zhí)行的任務(wù)。而isFlushing標(biāo)記則保證任務(wù)只會(huì)執(zhí)行一次。而因?yàn)橥ㄟ^(guò)Promise將任務(wù)添加到了微任務(wù)隊(duì)列中,當(dāng)任務(wù)最后執(zhí)行的時(shí)候,obj.val的值已經(jīng)是3了。

(7)computed和lazy

? 計(jì)算屬性是vue中一個(gè)比較有特色的屬性,它會(huì)緩存表達(dá)式的計(jì)算結(jié)果,只有當(dāng)表達(dá)式依賴(lài)的變量發(fā)生變化時(shí),它才會(huì)進(jìn)行重新計(jì)算。實(shí)現(xiàn)計(jì)算屬性的前提是實(shí)現(xiàn)懶加載標(biāo)記,這里我們可以通過(guò)之前effect函數(shù)的配置項(xiàng)來(lái)實(shí)現(xiàn)。

effect(
	()=> {
        return ()=>obj.val * 2;
    },
    {
        lazy: true; // 設(shè)置 lazy 標(biāo)記
    }
);
effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn);
    activeEffect = effectFn;
    effectStack.push(effectFn);
    const res = fn();
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
    return res;
  };
  effectFn.deps = [];
  effectFn.options = options;
  if (!effectFn.options.lazy) { // 若副作用函數(shù)持有l(wèi)azy標(biāo)記,則直接將副作用函數(shù)返回
    effectFn();
  }
  return effectFn;
}

通過(guò)上面對(duì)lazy標(biāo)記的設(shè)置,現(xiàn)在可以實(shí)現(xiàn)下面的效果:

const effectFn = effect(
	()=> {
        return ()=>obj.val * 2;
    },
    {
        lazy: true; // 設(shè)置 lazy 標(biāo)記
    }
)();
console.log(effectFn); // 2

在此基礎(chǔ)上,我們來(lái)實(shí)現(xiàn)computed

function computed(getter) {
    let value;
    let dirty = false;
    const effectFn = effect(getter, {
        lazy: true,
        scheduler(){
        	if(!dirty) {
                dirty = true;
                tirgger(obj, 'value');
            }
    	}
    });
    const obj = {
        get value() {
            if(!dirty) {
                value = effectFn();
                dirty = true;
            }
            track(target, 'value');
            return value;
        }
    };
    return obj;
}

(8)watch

? 想要實(shí)現(xiàn)watch,其實(shí)只需要添加一個(gè)scheduler(),像是這樣:

effect(
	()=> {
        consoloe.log(obj.val);
    },
    {
        scheduler() {
            console.log("數(shù)值發(fā)生了變化");
        }
    }
)

就可以實(shí)現(xiàn)一個(gè)基本的watch效果,現(xiàn)在來(lái)編寫(xiě)一個(gè)功能完整的watch函數(shù)

function watch(source, cb) {
    let getter;
    if(typeof source === "function") { //若傳入()=> obj.val,則直接使用該匿名函數(shù)
        getter = source;
    } else {
        getter = traverse(source); // 否則遞歸遍歷該對(duì)象的所有屬性,從而達(dá)到監(jiān)聽(tīng)所有屬性的目的
    }
    let oldValue, newValue; // 保存新舊值
    const effectFn = effect(getter, {
        lazy: true,
        scheduler() {
            newValue = effectFn(); // 獲取新值
            cb(oldValue, newValue);
            oldValue = newValue; // 函數(shù)執(zhí)行完后,更新舊值。
        }
    });
    oldValue = effectFn(); // 獲取初始舊值
}
function traverse(value, seen = new Set()) {
    if(typeof value !== 'object' || value !== null || seen.has(value)) return ;
    seen.add(value);
    for(const k in seen) {
        traverse(seen[k],seen);
    }
}

到此這篇關(guān)于Vue響應(yīng)式原理深入分析的文章就介紹到這了,更多相關(guān)Vue響應(yīng)式原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue.js表單驗(yàn)證插件(vee-validate)的使用教程詳解

    vue.js表單驗(yàn)證插件(vee-validate)的使用教程詳解

    這篇文章主要介紹了vue.js表單驗(yàn)證插件(vee-validate)的使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-05-05
  • Vue3中Element-Plus分頁(yè)(Pagination)組件的使用

    Vue3中Element-Plus分頁(yè)(Pagination)組件的使用

    Element-Plus分頁(yè)(Pagination)組件在開(kāi)發(fā)過(guò)程中數(shù)據(jù)展示會(huì)經(jīng)常使用到,同時(shí)分頁(yè)功能也會(huì)添加到頁(yè)面中,下面我們就來(lái)學(xué)習(xí)一下它的具體使用,需要的可以參考一下
    2023-11-11
  • Vue插槽簡(jiǎn)介和使用示例詳解

    Vue插槽簡(jiǎn)介和使用示例詳解

    插槽就是子組件中的提供給父組件使用的一個(gè)占位符,用<slot></slot>?表示,父組件可以在這個(gè)占位符中填充任何模板代碼,如?HTML、組件等,填充的內(nèi)容會(huì)替換子組件的<slot></slot>標(biāo)簽,這篇文章主要介紹了Vue插槽的理解和使用,需要的朋友可以參考下
    2023-03-03
  • Vue3+Vite如何解決“找不到模塊“@/components/xxx.vue”或其相應(yīng)的類(lèi)型聲明ts(2307)”

    Vue3+Vite如何解決“找不到模塊“@/components/xxx.vue”或其相應(yīng)的類(lèi)型聲明ts(2307)”

    在Vue項(xiàng)目中使用Vite時(shí),可能因版本差異而需修改tsconfig.app.json而非tsconfig.json以解決編譯錯(cuò)誤,本文分享個(gè)人解決經(jīng)驗(yàn),供參考
    2024-10-10
  • 去除Element-Plus下拉菜單邊框的實(shí)現(xiàn)步驟

    去除Element-Plus下拉菜單邊框的實(shí)現(xiàn)步驟

    Element-Plus 是 Element UI 的 Vue 3 版本,它提供了一套完整的組件庫(kù),在使用 Element-Plus 進(jìn)行開(kāi)發(fā)時(shí),我們可能會(huì)遇到需要自定義組件樣式的情況,本文將介紹如何使用 CSS 來(lái)去除 Element-Plus 下拉框的邊框,需要的朋友可以參考下
    2024-03-03
  • Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流

    Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流

    本文主要介紹了Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-12-12
  • vuex頁(yè)面刷新數(shù)據(jù)丟失解決方法詳解

    vuex頁(yè)面刷新數(shù)據(jù)丟失解決方法詳解

    這篇文章主要為大家介紹了vuex頁(yè)面刷新數(shù)據(jù)丟失解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Vue Element UI + OSS實(shí)現(xiàn)上傳文件功能

    Vue Element UI + OSS實(shí)現(xiàn)上傳文件功能

    這篇文章主要為大家詳細(xì)介紹了Vue Element UI + OSS實(shí)現(xiàn)上傳文件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • 詳解如何使用Vue實(shí)現(xiàn)圖像識(shí)別和人臉對(duì)比

    詳解如何使用Vue實(shí)現(xiàn)圖像識(shí)別和人臉對(duì)比

    隨著人工智能的發(fā)展,圖像識(shí)別和人臉識(shí)別技術(shù)已經(jīng)被廣泛應(yīng)用于各種應(yīng)用程序中,Vue為我們提供了許多實(shí)用工具和庫(kù),可以幫助我們?cè)趹?yīng)用程序中進(jìn)行圖像識(shí)別和人臉識(shí)別,在本文中,我們將介紹如何使用Vue進(jìn)行圖像識(shí)別和人臉對(duì)比,需要的朋友可以參考下
    2023-06-06
  • 簡(jiǎn)單了解vue 插值表達(dá)式Mustache

    簡(jiǎn)單了解vue 插值表達(dá)式Mustache

    這篇文章主要介紹了vue 插值表達(dá)式Mustache的相關(guān)資料,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07

最新評(píng)論

承德县| 保康县| 夏邑县| 天长市| 五华县| 孝昌县| 嘉义县| 康平县| 抚宁县| 高唐县| 弥勒县| 连山| 新丰县| 阳曲县| 巴林左旗| 河南省| 永春县| 乐清市| 临清市| 双鸭山市| 东乡族自治县| 澄迈县| 砚山县| 保定市| 格尔木市| 榆林市| 睢宁县| 彝良县| 庆元县| 商南县| 萨嘎县| 昂仁县| 寿光市| 全州县| 湖州市| 富民县| 竹北市| 区。| 文水县| 华池县| 禄劝|