{"id":13435159,"url":"https://github.com/runkids/vue-condition-watcher","last_synced_at":"2026-03-04T16:07:33.288Z","repository":{"id":42066403,"uuid":"264894779","full_name":"runkids/vue-condition-watcher","owner":"runkids","description":"🕶 Data fetching with Vue Composition API. Power of conditions to easily control and sync to the URL query string.","archived":false,"fork":false,"pushed_at":"2025-06-04T07:37:44.000Z","size":50495,"stargazers_count":49,"open_issues_count":2,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-04T13:25:10.300Z","etag":null,"topics":["conditions","data-fetching","fetch","query-string","vue","vue-condition-watcher","vue-router","vue-router-sync","watcher"],"latest_commit_sha":null,"homepage":"https://github.com/runkids/vue-condition-watcher","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/runkids.png","metadata":{"files":{"readme":"README-zh_TW.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-05-18T09:42:49.000Z","updated_at":"2025-06-04T07:35:40.000Z","dependencies_parsed_at":"2025-07-17T09:48:25.871Z","dependency_job_id":"20979cbf-1ce6-4498-8920-92f98366b040","html_url":"https://github.com/runkids/vue-condition-watcher","commit_stats":{"total_commits":193,"total_committers":4,"mean_commits":48.25,"dds":0.06217616580310881,"last_synced_commit":"c9a7e686cff8637899d99f948e886f8bd9d86452"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"purl":"pkg:github/runkids/vue-condition-watcher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runkids%2Fvue-condition-watcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runkids%2Fvue-condition-watcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runkids%2Fvue-condition-watcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runkids%2Fvue-condition-watcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/runkids","download_url":"https://codeload.github.com/runkids/vue-condition-watcher/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runkids%2Fvue-condition-watcher/sbom","scorecard":{"id":789805,"data":{"date":"2025-08-11","repo":{"name":"github.com/runkids/vue-condition-watcher","commit":"20e06980aa3d4b3a15ec13eb637298edf8959d53"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 2/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"35 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-67mh-4wv8-2f99","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-6fw4-hr69-g3rv","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-3f95-r44v-8mrg","Warn: Project is vulnerable to: GHSA-28xr-mwxg-3qc8","Warn: Project is vulnerable to: GHSA-9p95-fxvg-qgq2","Warn: Project is vulnerable to: GHSA-9w5j-4mwv-2wj8","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-c24v-8rfc-w8vw","Warn: Project is vulnerable to: GHSA-8jhw-289h-jh2g","Warn: Project is vulnerable to: GHSA-64vr-g452-qvp3","Warn: Project is vulnerable to: GHSA-9cwx-2883-4wfx","Warn: Project is vulnerable to: GHSA-vg6x-rcgg-rjx6","Warn: Project is vulnerable to: GHSA-x574-m823-4x7w","Warn: Project is vulnerable to: GHSA-4r4m-qw57-chr8","Warn: Project is vulnerable to: GHSA-xcj6-pq6g-qj4x","Warn: Project is vulnerable to: GHSA-356w-63v5-8wf4","Warn: Project is vulnerable to: GHSA-859w-5945-r5v3","Warn: Project is vulnerable to: GHSA-5j4c-8p2g-v4jx","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T07:11:21.708Z","repository_id":42066403,"created_at":"2025-08-23T07:11:21.708Z","updated_at":"2025-08-23T07:11:21.708Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30085995,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T15:40:14.053Z","status":"ssl_error","status_checked_at":"2026-03-04T15:40:13.655Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["conditions","data-fetching","fetch","query-string","vue","vue-condition-watcher","vue-router","vue-router-sync","watcher"],"created_at":"2024-07-31T03:00:33.312Z","updated_at":"2026-03-04T16:07:33.266Z","avatar_url":"https://github.com/runkids.png","language":"TypeScript","funding_links":[],"categories":["Packages","TypeScript","✨ Composition API functions","Components \u0026 Libraries","Utilities [🔝](#readme)"],"sub_categories":["Utilities"],"readme":"[English](./README.md) | 中文\n\n# vue-condition-watcher \u003cimg src=\"https://slackmojis.com/emojis/43271-glasses/download\" width=\"40\" /\u003e\n\n[![CircleCI](https://circleci.com/gh/runkids/vue-condition-watcher.svg?style=svg)](https://circleci.com/gh/runkids/vue-condition-watcher) [![vue3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org/) [![vue3](https://img.shields.io/badge/vue-2.x-brightgreen.svg)](https://composition-api.vuejs.org/) [![npm](https://img.shields.io/npm/v/vue-condition-watcher.svg)](https://www.npmjs.com/package/vue-condition-watcher)  [![npm](https://img.shields.io/npm/dt/vue-condition-watcher.svg)](https://www.npmjs.com/package/vue-condition-watcher) [![bundle size](https://badgen.net/bundlephobia/minzip/vue-condition-watcher)](https://bundlephobia.com/result?p=vue-condition-watcher) [![npm](https://img.shields.io/npm/l/vue-condition-watcher.svg)](https://github.com/runkids/vue-condition-watcher/blob/master/LICENSE)\n\n## 介紹\n\n`vue-condition-watcher` 是 Vue 組合 API，以 `conditions` 為核心，可用在請求資料情境，還能簡單地使用 `conditions` 參數來自動獲取資料\n\u003e Node.js 需大於或等於 12.0.0 版本\n\n## 功能\n\n  ✔ 每當 `conditions` 變動，會自動獲取數據\u003cbr\u003e\n  ✔ 送出請求前會自動過濾掉 `null` `undefined` `[]` `''`\u003cbr\u003e\n  ✔ 重新整理網頁會自動依照 URL 的 query string 初始化 `conditions`，且會自動對應型別 ( string, number, array, date )\u003cbr\u003e\n  ✔ 每當 `conditions` 變動，會自動同步 URL query string，並且讓上一頁下一頁都可以正常運作\u003cbr\u003e\n  ✔ 避免 `race condition`，確保請求先進先出，也可以避免重複請求\u003cbr\u003e\n  ✔ 在更新 `data` 前，可做到依賴請求 ( Dependent Request )\u003cbr/\u003e\n  ✔ 輕鬆處理分頁的需求，簡單客製自己的分頁邏輯\u003cbr/\u003e\n  ✔ 當網頁重新聚焦或是網絡斷線恢復自動重新請求資料\u003cbr/\u003e\n  ✔ 支援輪詢，可動態調整輪詢週期\u003cbr/\u003e\n  ✔ 緩存機制讓資料可以更快呈現，不用再等待 loading 動畫\u003cbr/\u003e\n  ✔ 不需要等待回傳結果，可手動改變 `data` 讓使用者體驗更好\u003cbr/\u003e\n  ✔ 支援 TypeScript\u003cbr/\u003e\n  ✔ 支援 Vue 2 \u0026 3，感謝 [vue-demi](https://github.com/vueuse/vue-demi)\n  \n  \u003cimg src=\".github/vue-conditions-watcher.gif\"/\u003e\n\n## Navigation\n\n- [安裝](#installation)\n- [快速開始](#快速開始)\n- [Configs](#configs)\n- [Return Values](#return-values)\n- [執行請求](#執行請求)\n- [阻止預請求](#阻止預請求)\n- [手動觸發請求](#手動觸發請求)\n- [攔截請求](#攔截請求)\n- [變異資料](#變異資料)\n- [Conditions 改變事件](#conditions-改變事件)\n- [請求事件](#請求事件)\n- [輪詢](#輪詢)\n- [緩存](#緩存)\n- [History 模式](#history-模式)\n- [生命週期](#生命週期)\n- [分頁處理](#分頁處理)\n- [Changelog](https://github.com/runkids/vue-condition-watcher/blob/master/CHANGELOG.md)\n\n## Demo\n\n[👉 (推薦) 這邊下載 Vue3 版本範例](https://github.com/runkids/vue-condition-watcher/tree/master/examples/vue3) (使用 [Vite](https://github.com/vuejs/vite))\n\n```bash\ncd examples/vue3\nyarn \nyarn serve\n````\n\n[👉 這邊下載 Vue2 @vue/composition-api 版本範例](https://github.com/runkids/vue-condition-watcher/tree/master/examples/vue2)\n\n```bash\ncd examples/vue2\nyarn \nyarn serve\n````\n\n### 👉 線上 Demo\n\n- [Demo with Vue 3 on StackBlitz](https://stackblitz.com/edit/vitejs-vite-tsvfqu?devtoolsheight=33\u0026embed=1\u0026file=src/views/Home.vue)\n\n## 入門\n\n### 安裝\n\n在你的專案執行 yarn\n\n```bash\nyarn add vue-condition-watcher\n```\n\n或是使用 NPM\n\n```bash\nnpm install vue-condition-watcher\n```\n\nCDN\n\n```javascript\nhttps://unpkg.com/vue-condition-watcher/dist/index.js\n```\n\n### 快速開始\n\n這是一個使用 `vue-next` 和 `vue-router-next` 的簡單範例。\n\n首先建立一個 `fetcher` function, 你可以用原生的 `fetch` 或是 `Axios` 這類的套件。接著  import `useConditionWatcher` 並開始使用它。\n\n```javascript\ncreateApp({\n  template: `\n    \u003cdiv class=\"filter\"\u003e\n      \u003cinput v-model=\"conditions.name\"\u003e\n      \u003cbutton @click=\"execute\"\u003eRefetch\u003c/button\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"container\"\u003e\n      {{ !loading ? data : 'Loading...' }}\n    \u003c/div\u003e\n    \u003cdiv v-if=\"error\"\u003e{{ error }}\u003c/div\u003e\n  `,\n  setup() {\n    const fetcher = params =\u003e axios.get('/user/', {params})\n    const router = useRouter()\n\n    const { conditions, data, loading, error } = useConditionWatcher(\n      {\n        fetcher,\n        conditions: {\n          name: ''\n        },\n        history: {\n          sync: router\n        }\n      }\n    )\n    return { conditions, data, loading, error }\n  },\n})\n.use(router)\n.mount(document.createElement('div'))\n```\n\n您可以使用 `data`、`error` 和 `loading` 的值來確定請求的當前狀態。\n\n當 `conditions.name` 值改變，將會觸發 [生命週期](#lifecycle) 重新發送請求.\n\n你可以在 `config.history` 設定 sync 為 `sync: router`。 這將會同步 `conditions` 的變化到 URL 的 query string。\n\n### 基礎用法\n\n```js\nconst { conditions, data, error, loading, execute, resetConditions, onConditionsChange } = useConditionWatcher(config)\n```\n\n### Configs\n\n- `fetcher`: (⚠️ 必要)  請求資料的 promise function。\n- `conditions`: (⚠️ 必要) `conditions` 預設值。\n- `defaultParams`: 每次請求預設會帶上的參數，不可修改。\n- `initialData`: `data` 預設回傳 null，如果想定義初始的資料可以使用這個參數設定。\n- `immediate`: 如果不想一開始自動請求資料，可以將此參數設定為 `false`，直到 `conditions` 改變或是執行 `execute` 才會執行請求。\n- `manual`: 改為手動執行 `execute` 以觸發請求，就算 `conditions` 改變也不會自動請求。\n- `history`: 基於 vue-router (v3 \u0026 v4)，啟用同步 `conditions` 到 URL 的 Query String。當網頁重新整理後會同步 Query String 至 `conditions`\n- `pollingInterval`: 啟用輪詢，以毫秒為單位可以是 `number` 或是 `ref(number)`\n- `pollingWhenHidden`: 每當離開聚焦畫面繼續輪詢，預設是關閉的\n- `pollingWhenOffline`: 每當網路斷線繼續輪詢，預設是關閉的\n- `revalidateOnFocus`: 重新聚焦畫面後，重新請求一次，預設是關閉的\n- `cacheProvider`: `vue-condition-watch` 背後會緩存資料，可傳入此參數自訂 `cacheProvider`\n- `beforeFetch`: 你可以在請求前最後修改 `conditions`，也可以在此階段終止請求。\n- `afterFetch`: 你可以在 `data` 更新前調整 `data` 的結果\n- `onFetchError`: 當請求發生錯誤觸發，可以在`data` 和 `error` 更新前調整 `error`\u0026 `data`\n\n### Return Values\n\n- `conditions`:\u003cbr/\u003e\n Type: `reactive`\u003cbr/\u003e\n reactive 型態的物件 (基於 config 的 conditions)，是 `vue-conditions-watcher`主要核心，每當 `conditions` 改變都會觸發[生命週期](#lifecycle)。\u003cbr/\u003e\n- `data`:\u003cbr/\u003e\n  Type: `👁‍🗨 readonly \u0026 ⚠️ ref`\u003cbr/\u003e\n  Default Value: `undefined`\u003cbr/\u003e\n  `config.fetcher` 的回傳結果\u003cbr/\u003e\n- `error`:\u003cbr/\u003e\n  Type: `👁‍🗨 readonly \u0026 ⚠️ ref`\u003cbr/\u003e\n  Default Value: `undefined`\u003cbr/\u003e\n  `config.fetcher` 錯誤返回結果\u003cbr/\u003e\n- `isFetching`:\u003cbr/\u003e\n  Type: `👁‍🗨 readonly \u0026 ⚠️ ref`\u003cbr/\u003e\n  Default Value: `false`\u003cbr/\u003e\n  請求正在處理中的狀態\u003cbr/\u003e\n- `loading`: 當 `!data.value \u0026 !error.value` 就會是 `true`\n- `execute`: 基於目前的 `conditions` 和 `defaultParams` 再次觸發請求。\u003cbr/\u003e\n- `mutate`: 可以使用此方法修改 `data` \u003cbr/\u003e\n**🔒 ( `data`預設是唯獨不可修改的 )**\u003cbr/\u003e\n- `resetConditions`: 重置 `conditions` 回初始值\n- `onConditionsChange`: 在 `conditions` 發生變化時觸發，回傳新值以及舊值\n- `onFetchSuccess`: 請求成功觸發，回傳原始的請求結果\n- `onFetchError`: 請求失敗觸發，回傳原始的請求失敗結果\n- `onFetchFinally`: 請求結束時觸發\n\n### 執行請求\n\n`conditions` 是響應式的， 每當 `conditions` 變化將自動觸發請求\n\n```js\nconst { conditions } = useConditionWatcher({\n  fetcher,\n  conditions: {\n    page: 0\n  },\n  defaultParams: {\n    opt_expand: 'date'\n  }\n})\n\nconditions.page = 1 // fetch data with payload { page: 1, opt_expand: 'date' }\n\nconditions.page = 2 // fetch data with payload { page: 2, opt_expand: 'date' }\n```\n\n如果有需要你可以執行 `execute` 這個 function 再次發送請求\n\n```js\nconst { conditions, execute: refetch } = useConditionWatcher({\n  fetcher,\n  conditions: {\n    page: 0\n  },\n   defaultParams: {\n    opt_expand: 'date'\n  }\n})\n\nrefetch() // fetch data with payload { page: 0, opt_expand: 'date' }\n```\n\n一次完整更新 `conditions`，**只會觸發一次請求**\n\n```js\nconst { conditions, resetConditions } = useConditionWatcher({\n  fetcher,\n  immediate: false,\n  conditions: {\n    page: 0,\n    name: '',\n    date: []\n  },\n})\n\n// 初始化 conditions 將會觸發 `onConditionsChange` 事件\nresetConditions({\n  name: 'runkids',\n  date: ['2022-01-01', '2022-01-02']\n})\n\n// 重置 conditions\nfunction reset () {\n  // 直接用 `resetConditions` function 來重置初始值.\n  resetConditions()\n}\n```\n\n### 阻止預請求\n\n`vue-conditions-watcher` 會在一開始先請求一次，如果不想這樣做可以設定 `immediate` 為 `false`，將不會一開始就發送請求直到你呼叫 `execute` function 或是改變 `conditions`\n\n```js\nconst { execute } = useConditionWatcher({\n  fetcher,\n  conditions,\n  immediate: false,\n})\n\nexecute()\n```\n\n### 手動觸發請求\n\n`vue-condition-watcher` 會自動觸發請求. 但是你可以設定 `manual` 為 `true` 來關閉這個功能。接著可以使用 `execute()` 在你想要的時機觸發請求。\n\n```js\nconst { execute } = useConditionWatcher({\n  fetcher,\n  conditions,\n  manual: true,\n})\n\nexecute()\n```\n\n### 攔截請求\n\n`beforeFetch` 可以讓你在請求之前再次修改 `conditions`。\n- 第一個參數回傳一個深拷貝的 `conditions`，你可以任意的修改它且不會影響原本 `conditions`，你可以在這邊調整要給後端的 API 格式。\n- 第二個參數回傳一個 function，執行它將會終止這次請求。這在某些情況會很有用的。\n- `beforeFetch` 可以處理同步與非同步行為。\n- 必須返回修改後的 `conditions`\n\n```js\nuseConditionWatcher({\n  fetcher,\n  conditions: {\n    date: ['2022/01/01', '2022/01/02']\n  },\n  initialData: [],\n  async beforeFetch(conditions, cancel) {\n    // 請求之前先檢查 token\n    await checkToken ()\n\n    // conditions 是一個深拷貝 `config.conditions` 的物件\n    const {date, ...baseConditions} = conditions\n    const [after, before] = date\n    baseConditions.created_at_after = after\n    baseConditions.created_at_before = before\n\n    // 返回修改後的 `conditions`\n    return baseConditions\n  }\n})\n```\n\n`afterFetch` 可以在更新 `data` 前攔截請求，這時候的 `loading` 狀態還是 `true`。\n- 你可以在這邊做依賴請求 🎭，或是處理其他同步與非同步行為\n- 可以在這邊最後修改 `data`，返回的值將會是 `data` 的值\n\n```js\nconst { data } = useConditionWatcher({\n  fetcher,\n  conditions,\n  async afterFetch(response) {\n    //response.data = {id: 1, name: 'runkids'}\n    if(response.data === null) {\n      return []\n    }\n    // 依賴其他請求\n    // `loading` 還是 `true` 直到 `onFetchFinally`\n    const finalResponse = await otherAPIById(response.data.id)\n\n    return finalResponse // [{message: 'Hello', sender: 'runkids'}]\n  }\n})\n\nconsole.log(data) //[{message: 'Hello', sender: 'runkids'}]\n```\n\n`onFetchError` 可以攔截錯誤，可以在 `data` 和 `error` 更新前調整 `error` \u0026 `data`，這時候的 `loading` 狀態還是 `true`。\n- `onFetchError` 可以處理同步與非同步行為。\n- 最後返回格式必須為\n\n```js\n{\n  data: ... ,\n  error: ...\n}\n```\n\n```js\nconst { data, error } = useConditionWatcher({\n  fetcher,\n  conditions,\n  async onFetchError({data, error}) {\n    if(error.code === 401) {\n      await doSomething()\n    }\n\n    return {\n      data: [],\n      error: 'Error Message'\n    }\n  }\n})\n\nconsole.log(data) //[]\nconsole.log(error) //'Error Message'\n```\n\n### 變異資料\n\n在一些情況下, mutations `data` 是提升用戶體驗的好方法，因為不需要等待 API 回傳結果。\n\n使用 `mutate` function, 你可以修改 `data`。 當 `onFetchSuccess` 觸發時會再改變 `data`。\n\n有兩種方式使用 `mutate` function:\n\n- 第一種：完整修改 data.\n\n```js\nmutate(newData)\n```\n\n- 第二種：使用 callback function，會接受一個深拷貝的 `data` 資料，修改完後再返回結果\n\n```js\nconst finalData = mutate((draft) =\u003e {\n  draft[0].name = 'runkids'\n  return draft\n})\n\nconsole.log(finalData[0]name === data.value[0].name) //true\n```\n\n#### 🏄‍♂️ 範例：依據目前的資料來修改部分資料\n\nPOST API 會返回更新後的結果，我們不需要重新執行 `execute` 更新結果。我們可以用 `mutate` 的第二種方式來修改部分改動。\n\n```js\nconst { conditions, data, mutate } = useConditionWatcher({\n  fetcher: api.userInfo,\n  conditions,\n  initialData: []\n})\n\nasync function updateUserName (userId, newName, rowIndex = 0) {\n  console.log(data.value) //before: [{ id: 1, name: 'runkids' }, { id: 2, name: 'vuejs' }]\n\n  const response = await api.updateUer(userId, newName)\n\n  // 🚫 `data.value[0] = response.data`\n  // 沒作用! 因為 `data` 是唯讀不可修改的.\n\n  // Easy to use function will receive deep clone data, and return updated data.\n  mutate(draft =\u003e {\n    draft[rowIndex] = response.data\n    return draft\n  })\n\n  console.log(data.value) //after: [{ id: 1, name: 'mutate name' }, { id: 2, name: 'vuejs' }]\n}\n```\n\n### Conditions 改變事件\n\n`onConditionsChange` 可以幫助你處理 `conditions` 的變化。會回傳新值和舊值\n\n```js\nconst { conditions, onConditionsChange } = useConditionWatcher({\n  fetcher,\n  conditions: {\n    page: 0\n  },\n})\n\nconditions.page = 1\n\nonConditionsChange((conditions, preConditions)=\u003e {\n  console.log(conditions) // { page: 1}\n  console.log(preConditions) // { page: 0}\n})\n```\n\n### 請求事件\n\n`onFetchResponse`, `onFetchError` 和 `onFetchFinally` 會在請求期間觸發。\n\n```ts\nconst { onFetchResponse, onFetchError, onFetchFinally } = useConditionWatcher(config)\n\nonFetchResponse((response) =\u003e {\n  console.log(response)\n})\n\nonFetchError((error) =\u003e {\n  console.error(error)\n})\n\nonFetchFinally(() =\u003e {\n  //todo\n})\n```\n\n## 輪詢\n\n你可以透過設定 `pollingInterval` 啟用輪詢功能（當為 0 時會關閉此功能）\n\n```js\nuseConditionWatcher({\n  fetcher,\n  conditions,\n  pollingInterval: 1000\n})\n```\n\n你還可以使用 `ref` 動態響應輪詢週期。\n\n```js\nconst pollingInterval = ref(0)\n\nuseConditionWatcher({\n  fetcher,\n  conditions,\n  pollingInterval: pollingInterval\n})\n\nfunction startPolling () {\n  pollingInterval.value = 1000\n}\n\nonMounted(startPolling)\n```\n\n`vue-condition-watcher` 預設會在你離開畫面聚焦或是網路斷線時停用輪詢，直到畫面重新聚焦或是網路連線上了才會啟用輪詢。\n\n你可以透過設定關閉預設行為：\n\n- `pollingWhenHidden=true`  離開聚焦後繼續輪詢\n- `pollingWhenOffline=true` 網路斷線還是會繼續輪詢\n\n你也可以啟用聚焦畫面後重打請求，確保資料是最新狀態。\n\n- `revalidateOnFocus=true`\n\n```js\nuseConditionWatcher({\n  fetcher,\n  conditions,\n  pollingInterval: 1000,\n  pollingWhenHidden: true, // pollingWhenHidden default is false\n  pollingWhenOffline: true, // pollingWhenOffline default is false\n  revalidateOnFocus: true // revalidateOnFocus default is false\n})\n```\n\n## 緩存\n\n`vue-condition-watcher` 預設會在當前組件緩存你的第一次數據。接著後面的請求會先使用緩存數據，背後默默請求新資料，等待最新回傳結果並比對緩存資料是否相同，達到類似預加載的效果。\n\n你也可以設定 `cacheProvider` 全局共用或是緩存資料在 `localStorage`，搭配輪詢可以達到分頁同步資料的效果。\n\n###### Global Based\n\n```js\n// App.vue\n\u003cscript lang=\"ts\"\u003e\nconst cache = new Map()\n\nexport default {\n  name: 'App',\n  provide: {\n    cacheProvider: () =\u003e cache\n  }\n}\n\n//Other.vue\nuseConditionWatcher({\n  fetcher,\n  conditions,\n  cacheProvider: inject('cacheProvider')\n})\n\u003c/script\u003e\n```\n\n###### [LocalStorage Based](https://swr.vercel.app/docs/advanced/cache#localstorage-based-persistent-cache)\n\n```js\nfunction localStorageProvider() {\n  const map = new Map(JSON.parse(localStorage.getItem('your-cache-key') || '[]'))\n  window.addEventListener('beforeunload', () =\u003e {\n    const appCache = JSON.stringify(Array.from(map.entries()))\n    localStorage.setItem('your-cache-key', appCache)\n  })\n  return map\n}\n\nuseConditionWatcher({\n  fetcher,\n  conditions,\n  cacheProvider: localStorageProvider\n})\n```\n\n## History 模式\n\n你可以設定 `config.history` 啟用 History 模式，是基於 vue-router 的，支援 v3 和 v4 版本\n\n```js\nconst router = useRouter()\n\nuseConditionWatcher({\n  fetcher,\n  conditions,\n  history: {\n    sync: router\n  }\n})\n```\n\n你還可以設定 `history.ignore` 排除 `conditions` 部分的 `key＆value` 不要同步到 URL query string.\n\n```js\nconst router = useRouter()\n\nuseConditionWatcher({\n  fetcher,\n  conditions: {\n    users: ['runkids', 'hello']\n    limit: 20,\n    offset: 0\n  },\n  history: {\n    sync: router,\n    ignore: ['limit']\n  }\n})\n\n// the query string will be ?offset=0\u0026users=runkids,hello\n```\n\nHistory mode 會轉換 `conditions`預設值的對應型別到 query string 而且會過濾掉 `undefined`, `null`, `''`, `[]` 這些類型的值.\n\n```js\nconditions: {\n  users: ['runkids', 'hello']\n  company: ''\n  limit: 20,\n  offset: 0\n}\n// the query string will be ?offset=0\u0026limit=20\u0026users=runkids,hello\n```\n\n每當你重新整理網頁還會自動同步 query string 到 `conditions`\n\n```\nURL query string: ?offset=0\u0026limit=10\u0026users=runkids,hello\u0026company=vue\n```\n\n`conditions` 將變成\n\n```js\n{\n  users: ['runkids', 'hello']\n  company: 'vue'\n  limit: 10,\n  offset: 0\n}\n```\n\n使用 `navigation` 可以 push 或是 replace 當前的位置. 預設值為 'push'\n```js\nuseConditionWatcher({\n  fetcher,\n  conditions: {\n    limit: 20,\n    offset: 0\n  },\n  history: {\n    sync: router,\n    navigation: 'replace'\n  }\n})\n```\n\n## 生命週期\n\n\u003cimg src=\".github/vue-condition-watcher_lifecycle.jpeg\"/\u003e\n\n- ##### `onConditionsChange`\n\n  `conditions` 變更時觸發，會返回新舊值。\n\n  ```js\n  onConditionsChange((cond, preCond)=\u003e {\n    console.log(cond)\n    console.log(preCond)\n  })\n  ```\n\n- ##### `beforeFetch`\n\n  可以讓你在請求之前再次修改 `conditions`，也可以在這個階段終止請求。\n\n  ```js\n  const { conditions } = useConditionWatcher({\n    fetcher,\n    conditions,\n    beforeFetch\n  })\n\n  async function beforeFetch(cond, cancel){\n    if(!cond.token) {\n      // stop fetch\n      cancel()\n      // will fire onConditionsChange again\n      conditions.token = await fetchToken()\n    }\n    return cond\n  })\n  ```\n\n- ##### `afterFetch` \u0026 `onFetchSuccess`\n\n  `afterFetch` 會在 `onFetchSuccess` 前觸發\u003cbr/\u003e\n  `afterFetch` 可以在`data` 更新前修改 `data`\n  ||Type|Modify data before update| Dependent request |\n  |-----|--------|------|------|\n  |afterFetch| config | ⭕️ | ⭕️ |\n  |onFetchSuccess  | event | ❌ | ❌ |\n\n  ```html\n    \u003ctemplate\u003e \n      {{ data?.detail }} \u003c!-- 'xxx' --\u003e\n    \u003c/template\u003e\n  ```\n\n   ```js\n  const { data, onFetchSuccess } = useConditionWatcher({\n    fetcher,\n    conditions,\n    async afterFetch(response){\n      //response = { id: 1 }\n      const detail = await fetchDataById(response.id)\n      return detail // { id: 1, detail: 'xxx' }\n    })\n  })\n\n  onFetchSuccess((response)=\u003e {\n    console.log(response) // { id: 1, detail: 'xxx' }\n  })\n  ```\n\n- ##### `onFetchError(config)` \u0026 `onFetchError(event)`\n\n  `config.onFetchError` 會在 `event.onFetchError` 前觸發\u003cbr/\u003e\n  `config.onFetchError` 可以攔截錯誤，可以在 `data` 和 `error` 更新前調整 `error` \u0026 `data`。\n  ||Type|Modify data before update|Modify error before update|\n  |-----|--------|------|------|\n  |onFetchError| config | ⭕️ | ⭕️ |\n  |onFetchError  | event | ❌ | ❌ |\n\n   ```js\n  const { onFetchError } = useConditionWatcher({\n    fetcher,\n    conditions,\n    onFetchError(ctx){\n      return {\n        data: [],\n        error: 'Error message.'\n      }\n    })\n  })\n\n  onFetchError((error)=\u003e {\n    console.log(error) // origin error data\n  })\n  ```\n\n- ##### `onFetchFinally`\n\n  請求結束時觸發\n\n  ```js\n  onFetchFinally(async ()=\u003e {\n    //do something\n  })\n  ```\n\n## 重複使用\n\n建立 `vue-condition-watcher` 的可重用的 hook 非常容易。\n\n```js\nfunction useUserExpensesHistory (id) {\n  const { conditions, data, error, loading } = useConditionWatcher({\n    fetcher: params =\u003e api.user(id, { params }),\n    defaultParams: {\n      opt_expand: 'amount,place'\n    },\n    conditions: {\n      daterange: []\n    }\n    immediate: false,\n    initialData: [],\n    beforeFetch(cond, cancel) {\n      if(!id) {\n        cancel()\n      }\n      const { daterange, ...baseCond } = cond\n      if(daterange.length) {\n        [baseCond.created_at_after, baseCond.created_at_before] = [\n          daterange[0],\n          daterange[1]\n        ]\n      }\n      return baseCond\n    }\n  })\n\n  return {\n    histories: data,\n    isFetching: loading,\n    isError: error,\n    daterange: conditions.daterange\n  }\n}\n```\n\n接著在 components 使用:\n\n```js\n\u003cscript setup\u003e\n  const { \n    daterange, \n    histories, \n    isFetching, \n    isError \n  } = useUserExpensesHistory(route.params.id)\n\n  onMounted(() =\u003e {\n    //start first time data fetching after initial date range\n    daterange = [new Date(), new Date()]\n  })\n\u003c/script\u003e\n```\n\n```html\n\u003ctemplate\u003e\n  \u003cel-date-picker\n    v-model=\"daterange\"\n    :disabled=\"isFetching\"\n    type=\"daterange\"\n  /\u003e\n  \u003cdiv v-for=\"history in histories\" :key=\"history.id\"\u003e\n    {{ `${history.created_at}: ${history.amount}` }}\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n恭喜你! 🥳 你已經學會再次包裝 `vue-condition-watcher`.\n\n現在我們來用 `vue-condition-watcher` 做分頁的處理.\n\n## 分頁處理\n\n這個範例適用 Django the limit and offset functions 和 Element UI.\n\n建立 `usePagination`\n\n```js\nfunction usePagination () {\n  let cancelFlag = false // check this to cancel fetch\n\n  const { startLoading, stopLoading } = useLoading()\n  \n  const { conditions, data, execute, resetConditions, onConditionsChange, onFetchFinally } = useConditionWatcher(\n    {\n      fetcher: api.list,\n      conditions: {\n        daterange: [],\n        limit: 20,\n        offset: 0\n      }\n      immediate: true,\n      initialData: [],\n      history: {\n        sync: 'router',\n        // You can ignore the key of URL query string, prevent users from entering unreasonable numbers by themselves.\n        // The URL will look like ?offset=0 not show `limit`\n        ignore: ['limit'] \n      },\n      beforeFetch\n    }, \n  )\n\n  // use on pagination component\n  const currentPage = computed({\n    get: () =\u003e conditions.offset / conditions.limit + 1,\n    set: (page) =\u003e {\n      conditions.offset = (page - 1) * conditions.limit\n    }\n  })\n\n  // onConditionsChange -\u003e beforeFetch -\u003e onFetchFinally\n  onConditionsChange((newCond, oldCond) =\u003e {\n    // When conditions changed, reset offset to 0 and then will fire beforeEach again.\n    if (newCond.offset !== 0 \u0026\u0026 newCond.offset === oldCond.offset) {\n      cancelFlag = true\n      conditions.offset = 0\n    }\n  })\n\n  async function beforeFetch(cond, cancel) {\n    if (cancelFlag) {\n      // cancel fetch when cancelFlag be true\n      cancel()\n      cancelFlag = false // reset cancelFlag \n      return cond\n    }\n    // start loading\n    await nextTick()\n    startLoading()\n    const { daterange, ...baseCond } = cond\n    if(daterange.length) {\n      [baseCond.created_at_after, baseCond.created_at_before] = [\n        daterange[0],\n        daterange[1]\n      ]\n    }\n    return baseCond\n  }\n\n  onFetchFinally(async () =\u003e {\n    await nextTick()\n    // stop loading\n    stopLoading()\n    window.scrollTo(0, 0)\n  })\n\n  return {\n    data,\n    conditions,\n    currentPage,\n    resetConditions,\n    refetch: execute\n  }\n}\n```\n\n接著在 components 使用:\n\n```js\n\u003cscript setup\u003e\n  const { data, conditions, currentPage, resetConditions, refetch } = usePagination()\n\u003c/script\u003e\n```\n\n```html\n\u003ctemplate\u003e\n  \u003cel-button @click=\"refetch\"\u003eRefetch Data\u003c/el-button\u003e\n  \u003cel-button @click=\"resetConditions\"\u003eReset Offset\u003c/el-button\u003e\n\n  \u003cel-date-picker\n    v-model=\"conditions.daterange\"\n    type=\"daterange\"\n  /\u003e\n\n  \u003cdiv v-for=\"info in data\" :key=\"info.id\"\u003e\n    {{ info }}\n  \u003c/div\u003e\n\n  \u003cel-pagination\n    v-model:currentPage=\"currentPage\"\n    v-model:page-size=\"conditions.limit\"\n    :total=\"data.length\"\n  /\u003e\n\u003c/template\u003e\n```\n\n當 daterange or limit 改變時, 會將 offset 設置為 0，接著才會重新觸發請求。\n\n## TDOD List\n\n- [ ] Error Retry\n- [ ] Nuxt SSR SSG Support\n\n## Thanks\n\nThis project is heavily inspired by the following awesome projects.\n\n- [vercel/swr](https://github.com/vercel/swr)\n\n## 📄 License\n\n[MIT License](https://github.com/runkids/vue-condition-watcher/blob/master/LICENSE) © 2020-PRESENT [Runkids](https://github.com/runkids)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunkids%2Fvue-condition-watcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frunkids%2Fvue-condition-watcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunkids%2Fvue-condition-watcher/lists"}