{"id":14977019,"url":"https://github.com/geekskai/vue3-jd-h5","last_synced_at":"2025-05-14T23:07:43.407Z","repository":{"id":37723919,"uuid":"190781132","full_name":"geekskai/vue3-jd-h5","owner":"geekskai","description":":fire: Based on vue3.x,vite5.x, vant3.0.0, vue-router v4.0.0-0, vuex^4.0.0-0, vue-cli3, mockjs, imitating Jingdong Taobao, mobile H5 e-commerce platform! 基于vue3.0.0 ,vant3.0.0,vue-router v4.0.0-0, vuex^4.0.0-0,vue-cli3,mockjs,仿京东淘宝的,移动端H5电商平台!","archived":false,"fork":false,"pushed_at":"2025-02-13T10:26:56.000Z","size":60785,"stargazers_count":894,"open_issues_count":0,"forks_count":247,"subscribers_count":12,"default_branch":"vue-next","last_synced_at":"2025-04-13T20:39:15.316Z","etag":null,"topics":["css3","html5","jsx","tsx","vite","vitejs","vue-cli","vue-cli3","vue-next","vue-router","vue3","vuex"],"latest_commit_sha":null,"homepage":"https://geekskai.github.io/vue3-jd-h5/","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/geekskai.png","metadata":{"files":{"readme":"README-zh_CN.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2019-06-07T17:09:06.000Z","updated_at":"2025-04-11T07:42:56.000Z","dependencies_parsed_at":"2022-07-11T02:51:21.284Z","dependency_job_id":"d30e483a-17c6-438e-bf2b-51f8442fab1a","html_url":"https://github.com/geekskai/vue3-jd-h5","commit_stats":{"total_commits":212,"total_committers":6,"mean_commits":"35.333333333333336","dds":0.3867924528301887,"last_synced_commit":"bb2a039969129571d179042deba696568a11ec28"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Fvue3-jd-h5","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Fvue3-jd-h5/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Fvue3-jd-h5/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekskai%2Fvue3-jd-h5/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/geekskai","download_url":"https://codeload.github.com/geekskai/vue3-jd-h5/tar.gz/refs/heads/vue-next","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254243362,"owners_count":22038046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["css3","html5","jsx","tsx","vite","vitejs","vue-cli","vue-cli3","vue-next","vue-router","vue3","vuex"],"created_at":"2024-09-24T13:54:52.955Z","updated_at":"2025-05-14T23:07:38.399Z","avatar_url":"https://github.com/geekskai.png","language":"Vue","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\n * @version: v 1.0.0\n * @Github: https://github.com/GitHubGanKai\n * @Author: GitHubGanKai\n * @Date: 2024-01-01 11:51:26\n * @LastEditors: gankai\n * @LastEditTime: 2024-01-02 20:55:34\n * @FilePath: /vue-jd-h5/README_CN.md\n --\u003e\n\n\u003cdiv style=\"text-align:center\"\u003e\n\u003cimg width='110px' height='200px'  src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/home_img.jpg' /\u003e\n\u003cimg width='110px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/cart_home.jpg' /\u003e\n\u003cimg width='110px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/me_home.jpg' /\u003e\n\u003cimg width='110px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/WechatIMG15.png' /\u003e\n\u003cimg width='110px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/shal_home.jpg' /\u003e\n\u003cimg width='110px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/buy_home.jpg' /\u003e\n\u003cimg width='110px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/hotal_home.jpg' /\u003e\n\u003cimg width='110px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/search_img.jpg' /\u003e\n\u003c/div\u003e\n\n个人博客网站与需要的可以参考：https://geekskai.com/\n\nGitHub 地址：https://github.com/geekskai/blog\n\n# vue3-jd-h5\n\n\u003cp align='center'\u003e\n\t\u003ca href=\"https://twitter.com/intent/tweet?text=Wow:\u0026url=https%3A%2F%2Fgithub.com%2FGitHubGanKai%2Fvue3-jd-h5\"\u003e\u003cimg alt=\"Twitter\" src=\"https://img.shields.io/twitter/url/https/github.com/GitHubGanKai/vue3-jd-h5.svg?style=social\"\u003e\u003c/a\u003e\n\t\u003ca href=\"https://github.com/GitHubGanKai/vue3-jd-h5/issues\"\u003e\u003cimg alt=\"GitHub issues\" src=\"https://img.shields.io/github/issues/GitHubGanKai/vue3-jd-h5.svg\"\u003e\u003c/a\u003e\n\t\u003ca href=\"https://github.com/GitHubGanKai/vue3-jd-h5/network\"\u003e\u003cimg alt=\"GitHub forks\" src=\"https://img.shields.io/github/forks/GitHubGanKai/vue3-jd-h5.svg\"\u003e\u003c/a\u003e\n\t\u003ca href=\"https://github.com/GitHubGanKai/vue3-jd-h5/stargazers\"\u003e\u003cimg alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/GitHubGanKai/vue3-jd-h5.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align='center'\u003e\n  \u003ca target='_blank' href='https://github.com/GitHubGanKai/vue3-jd-h5/blob/vue-next/README.md'\u003e English\u003c/a\u003e|\u003ca target='_blank' href='https://github.com/GitHubGanKai/vue3-jd-h5/blob/vue-next/README-zh_CN.md'\u003e 简体中文\u003c/a\u003e\n\u003c/p\u003e\n\n## 项目介绍\n\n`vue3-jd-h5`是一个电商 H5 页面前端项目，基于**Vue 3.0.0** + **Vant 3.0.0** 实现，主要包括首页、分类页面、我的页面、购物车等。\n\n📖 本地线下代码**vue2.6**在分支\u003ca target='_blank' href='https://github.com/GitHubGanKai/vue-jd-h5/blob/demo/README.md'\u003edemo\u003c/a\u003e中，使用**mockjs**数据进行开发，效果图请点击 🔗\u003ca target='_blank' href='http://gankai.gitee.io/vue-jd-h5/index'\u003e这里\u003c/a\u003e\n\n⚠️master 分支是线上生产环境代码，因为部分后台接口已经挂了 😫，可能无法看到实际效果。\n\n📌 本项目还有很多不足之处，如果有想为此做贡献的伙伴，也欢迎给我们提出 PR，或者 issue ；\n\n🔑 本项目是免费开源的，如果有伙伴想要在次基础上进行二次开发，可以 clone 或者 fork 整个仓库，如果能帮助到您，我将感到非常高兴，如果您觉得这个项目不错还请给个 start！🙏\n\n💥 2025 年 Vue3.x ,Vite5.x, Vant,TypeScript 最新版本更新分支：👉 \u003ca target='_blank' href='https://github.com/geekskai/vue3-jd-h5/tree/vue3-vite-vant-ts'\u003evue3-vite-vant-ts\u003c/a\u003e\n\n🎉 如果您乐意捐赠，我们也非常欢迎，可以通过以下渠道进行捐款：\n\n\u003cp style=\"text-align:center;margin:0\"\u003e\n\u003cimg width='200px' height='250px' src='https://gitee.com/gankai/vue-jd-h5/raw/vue-next/src/assets/WechatIMG117.jpeg' /\u003e\n\u003c/p\u003e\n\nhttps://user-images.githubusercontent.com/37830362/173991179-71272ccb-bf69-441f-8f15-46e8da403d2a.mov\n\n## vue3 搭建步骤\n\n1. 首先,在本地选择一个文件，将代码 clone 到本地：\n\n```bash\ngit clone https://github.com/GitHubGanKai/vue-jd-h5.git\n```\n\n2. 查看所有分支：\n\n```bash\ngankaideMacBook-Pro:vue-jd-h5 gankai$ git branch -a\n  demo\n  vue-next\n  dev\n  feature\n  gh-pages\n* master\n  remotes/origin/HEAD -\u003e origin/master\n  remotes/origin/demo\n  remotes/origin/vue-next\n  remotes/origin/dev\n  remotes/origin/feature\n  remotes/origin/gh-pages\n  remotes/origin/master\n```\n\n3. 切换到分支**vue-next**开始进行开发！\n\n4. 在 IDEA 命令行中运行命令：npm install,下载相关依赖;\n\n5. 🔧 开发环境 在 IDEA 命令行中运行命令：`npm run dev`,运行项目;\n\n6. 📦 在 IDEA 命令行中运行命令：`npm run dll:build`,打包项目,📱 手机扫描下面 👇 二维码进行查看！\n\n\u003cdiv style=\"text-align:center\"\u003e\n\u003cimg width='200px' height='200px' src='https://gitee.com/gankai/vue-jd-h5/raw/master/src/assets/image/qrcode.png' /\u003e\n\u003c/div\u003e\n\n## 项目的初始化\n\n### 安装 node v16.20.2\n\n进入刚才 clone 下来的项目根目录，安装相关依赖，体验 vue3 新特性。\n\n`npm`安装：\n\n```javascript\nnpm install\n```\n\n`yarn`安装：\n\n```javascript\nyarn;\n```\n\n**CDN**\n\n```html\n\u003cscript src=\"https://unpkg.com/vue@next\"\u003e\u003c/script\u003e\n```\n\n### 使用\n\n在入口文件`main.js`中：\n\n```javascript\nimport Vue from \"vue\";\nimport VueCompositionApi from \"@vue/composition-api\";\n\nVue.use(VueCompositionApi);\n```\n\n安装插件后，您就可以使用新的 [Composition API](https://vue-composition-api-rfc.netlify.com/) 来开发组件了。\n\n⚠️ 目前 vue 官方为 vue-cli 提供了一个插件[vue-cli-plugin-vue-next](https://github.com/vuejs/vue-cli-plugin-vue-next)，你也可以直接在项目中直接添加最新的版本！\n\n```bash\n# in an existing Vue CLI project\nvue add vue-next\n```\n\n\u003cblockquote style='background-color: #ffffcc;border-left: 4px solid #ffeb3b;padding:10px 20px 10px 20px;box-shadow: 0 2px 4px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)!important;'\u003e\n\n如果有想从零开始体验新版本的小伙伴可以采用这种方法进行安装，由于我们这个项目有依赖第三方库，如果全局安装，整个项目第三方 UI 库都无法运行！所以我们还是选择采用安装`@vue/composition-api`来进行体验，从而慢慢过渡到 vue3 最新版本\n\n\u003c/blockquote\u003e\n\n## Vue 3.0 Composition-API 基本特性体验\n\n### setup 函数\n\n`setup()` 函数是 vue3 中专门为组件提供的新属性，相当于 2.x 版本中的`created`函数,之前版本的组件逻辑选项，现在都统一放在这个函数中处理。它为我们使用 vue3 的 `Composition API` 新特性提供了统一的入口,**setup** 函数会在相对于 2.x 来说，会在 **beforeCreate** 之后、**created** 之前执行！具体可以参考如下：\n\n|      vue2.x      |      vue3       |\n| :--------------: | :-------------: |\n| ~~beforeCreate~~ |   setup(替代)   |\n|   ~~created~~    |   setup(替代)   |\n|   beforeMount    |  onBeforeMount  |\n|     mounted      |    onMounted    |\n|   beforeUpdate   | onBeforeUpdate  |\n|     updated      |    onUpdated    |\n|  beforeDestroy   | onBeforeUnmount |\n|    destroyed     |   onUnmounted   |\n|  errorCaptured   | onErrorCaptured |\n\n### 新钩子\n\n除了 2.x 生命周期等效项之外，Composition API 还提供了以下 debug hooks：\n\n- `onRenderTracked`\n- `onRenderTriggered`\n\n两个钩子都收到`DebuggerEvent`类似于`onTrack`和`onTrigger`观察者的选项：\n\n```javascript\nexport default {\n  onRenderTriggered(e) {\n    debugger;\n    // inspect which dependency is causing the component to re-render\n  },\n};\n```\n\n### 依赖注入\n\n`provide`和`inject`启用类似于 2.x `provide/inject`选项的依赖项注入。两者都只能在`setup()`当前活动实例期间调用。\n\n```javascript\nimport { provide, inject } from \"@vue/composition-api\";\n\nconst ThemeSymbol = Symbol();\n\nconst Ancestor = {\n  setup() {\n    provide(ThemeSymbol, \"dark\");\n  },\n};\n\nconst Descendent = {\n  setup() {\n    const theme = inject(ThemeSymbol, \"light\" /* optional default value */);\n    return {\n      theme,\n    };\n  },\n};\n```\n\n`inject`接受可选的默认值作为第二个参数。如果未提供默认值，并且在 Provide 上下文中找不到该属性，则`inject`返回`undefined`。\n\n**注入响应式数据**\n\n为了保持提供的值和注入的值之间的响应式，可以使用`ref`\n\n```javascript\n// 在父组件中\nconst themeRef = ref(\"dark\");\nprovide(ThemeSymbol, themeRef);\n\n// 组件中\nconst theme = inject(ThemeSymbol, ref(\"light\"));\nwatchEffect(() =\u003e {\n  console.log(`theme set to: ${theme.value}`);\n});\n```\n\n1. 因为`setup`函数接收 2 个形参，第一个是`initProps`，即父组件传送过来的值！，第二个形参是一个**上下文对象**\n\n`setupContext`，这个对象的主要属性有 ：\n\n```javascript\nattrs: Object; // 等同 vue 2.x中的 this.$attrs\nemit: ƒ(); // 等同 this.$emit()\nisServer: false; // 是否是服务端渲染\nlisteners: Object; // 等同 vue2.x中的this.$listeners\nparent: VueComponent; // 等同 vue2.x中的this.$parent\nrefs: Object; // 等同 vue2.x中的this.$refs\nroot: Vue; // 这个root是我们在main.js中，使用newVue()的时候，返回的全局唯一的实例对象，注意别和单文件组件中的this混淆了\nslots: {\n} // 等同 vue2.x中的this.$slots\nssrContext: {\n} // 服务端渲染相关\n```\n\n⚠️**注意**：在 `setup()` 函数中无法访问到 `this`的，不管这个`this`指的是全局的的 vue 对象(即：在 main.js 中使用 new 生成的那个全局的 vue 实例对象)，还是指单文件组件的对象。\n\n但是，如果我们想要访问当前组件的实例对象，那该怎么办呢？我们可以引入`getCurrentInstance`这个 api,返回值就是当前组件的实例！\n\n```javascript\nimport { computed, getCurrentInstance } from \"@vue/composition-api\";\nexport default {\n  name: \"svg-icon\",\n  props: {\n    iconClass: {\n      type: String,\n      required: true\n    },\n    className: {\n      type: String\n    }\n  },\n  setup(initProps,setupContext) {\n\n    const { ctx } = getCurrentInstance();\n    const iconName = computed(() =\u003e {\n      return `#icon-${initProps.iconClass}`;\n    });\n    const svgClass = computed(() =\u003e {\n      if (initProps.className) {\n        return \"svg-icon \" + initProps.className;\n      } else {\n        return \"svg-icon\";\n      }\n    });\n    return {\n      iconName,\n      svgClass\n    };\n  }\n};\n\u003c/script\u003e\n\n```\n\n### Ref 自动展开（unwrap）\n\n`ref()` 函数用来根据给定的值创建一个**响应式**的**数据对象**，`ref()` 函数调用的返回值是一个被包装后的对象（RefImpl），这个对象上只有一个 `.value` 属性，如果我们在`setup`函数中，想要访问的对象的值，可以通过`.value`来获取，但是如果是在`\u003ctemplate\u003e`**模板中**，直接访问即可，不需要`.value`！\n\n```javascript\nimport { ref } from '@vue/composition-api'\n\nsetup() {\n    const active = ref(\"\");\n    const timeData = ref(36000000);\n    console.log('输出===\u003e',timeData.value)\n    return {\n       active,\n       timeData\n    }\n}\n```\n\n```html\n\u003ctemplate\u003e\n  \u003cp\u003e活动状态：{{active}}\u003c/p\u003e\n  \u003cp\u003e活动时间：{{timeData}}\u003c/p\u003e\n\u003c/template\u003e\n```\n\n⚠️ 注意：不要将`Array`放入`ref`中，数组索引属性无法进行自动展开，也**不要**使用 `Array` 直接存取 `ref` 对象:\n\n```javascript\nconst state = reactive({\n  list: [ref(0)],\n});\n// 不会自动展开, 须使用 `.value`\nstate.list[0].value === 0; // true\n\nstate.list.push(ref(1));\n// 不会自动展开, 须使用 `.value`\nstate.list[1].value === 1; // true\n```\n\n当我们需要操作 DOM 的时候，比如我们在项目中使用`swiper`需要获取 DOM,那么我们还可以这样 👇！\n\n```javascript\n  \u003cdiv class=\"swiper-cls\"\u003e\n      \u003cswiper :options=\"swiperOption\" ref=\"mySwiper\"\u003e\n        \u003cswiper-slide v-for=\"(img ,index) in tabImgs.value\" :key=\"index\"\u003e\n          \u003cimg class=\"slide_img\" @click=\"handleClick(img.linkUrl)\" :src=\"img.imgUrl\" /\u003e\n        \u003c/swiper-slide\u003e\n      \u003c/swiper\u003e\n   \u003c/div\u003e\n```\n\n然后在`setup`函数中定义一个`const mySwiper = ref(null);`，之前在 vue2.x 中，我们是通过`this.$refs.mySwiper`来获取 DOM 对象的，现在也可以使用`ref`函数代替，返回的`mySwiper`要和`template`中绑定的`ref`相同！\n\n```javascript\nimport { ref, onMounted } from \"@vue/composition-api\";\nsetup(props, { attrs, slots, parent, root, emit, refs }) {\n\tconst mySwiper = ref(null);\n  onMounted(() =\u003e {\n    // 通过mySwiper.value 即可获取到DOM对象！\n    // 同时也可以使用vue2.x中的refs.mySwiper ，他其实mySwiper.value 是同一个DOM对象！\n    mySwiper.value.swiper.slideTo(3, 1000, false);\n  });\n  return {\n    mySwiper\n  }\n}\n```\n\n### reactive\n\n`reactive()` 函数接收一个普通对象，返回一个响应式的数据对象，等价于 `vue 2.x` 中的 `Vue.observable()` 函数，`vue 3.x` 中提供了 `reactive()` 函数，用来创建响应式的数据对象`Observer`，`ref`中我们一般存放的是**基本类型数据**，如果是引用类型的我们可以使用`reactive`函数。\n\n当`reactive`函数中，接收的类型是一个`Array`数组的时候，我们可以在这个`Array`外面在用对象包裹一层，然后给对象添加一个属性比如：`value`（这个属性名你可以自己随便叫什么），他的值就是这个数组！\n\n```javascript\n\u003cscript\u003e\n// 使用相关aip之前必须先引入\nimport { ref, reactive } from \"@vue/composition-api\";\nexport default {\n  name: \"home\",\n  setup(props, { attrs, slots, parent, root, emit, refs }) {\n\n    const active = ref(\"\");\n    const timeData = ref(36000000);\n    // 将tabImgs数组中每个对象都变成响应式的对象\n    const tabImgs = reactive({\n      value: []\n    });\n    const ball = reactive({\n      show: false,\n      el: \"\"\n    });\n    return {\n      active,\n      timeData,\n      tabImgs,\n      ...toRefs(ball),\n    };\n  }\n};\n\u003c/script\u003e\n```\n\n那么在`template`模板中我们想要访问这个数组的时候就是需要使用`.value`的形式来获取这个数组的值。\n\n```html\n\u003ctemplate\u003e\n  \u003cdiv class=\"swiper-cls\"\u003e\n    \u003cswiper :options=\"swiperOption\" ref=\"mySwiper\"\u003e\n      \u003cswiper-slide v-for=\"(img ,index) in tabImgs.value\" :key=\"index\"\u003e\n        \u003cimg\n          class=\"slide_img\"\n          @click=\"handleClick(img.linkUrl)\"\n          :src=\"img.imgUrl\"\n        /\u003e\n      \u003c/swiper-slide\u003e\n    \u003c/swiper\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n### isRef\n\n`isRef()` 用来判断某个值是否为 `ref()` 创建出来的对象；当需要展开某个可能为 `ref()` 创建出来的值的时候，可以使用`isRef`来判断！\n\n```javascript\nimport { isRef } from '@vue/composition-api'\n\nsetup(){\n  const headerActive = ref(false);\n  // 在setup函数中，如果是响应式的对象，在访问属性的时候，一定要加上.value来访问！\n  const unwrapped = isRef(headerActive) ? headerActive.value : headerActive\n  return {}\n}\n```\n\n### toRefs\n\n`toRefs`函数会将**响应式对象**转换为**普通对象**，其中返回的对象上的每个属性都是指向原始对象中相应属性的`ref`，将一个对象上的所有属性转换成响应式的时候，将会非常有用！\n\n```javascript\nimport { reactive,toRefs } from '@vue/composition-api'\nsetup(){\n  // ball 是一个 Observer\n  const ball = reactive({\n    show: false,\n    el: \"\"\n  });\n  // ballToRefs 就是一个普通的Object，但是ballToRefs里面的所有属性都是响应式的（RefImpl）\n  const ballToRefs  = toRefs(ball)\n  // ref和原始属性是“链接的”\n  ball.show = true\n  console.log(ballToRefs.show) // true\n  ballToRefs.show.value = false\n  console.log(ballToRefs.show) // false\n  return {\n    ...ballToRefs    // 将ballToRefs对象展开，我们就可以直接在template模板中直接这样使用这个对象上的所有属性！\n  }\n}\n\n```\n\n点击添加按钮，小球飞入购物车动画：\n\n```html\n\u003ctemplate\u003e\n  \u003cdiv class=\"ballWrap\"\u003e\n    \u003ctransition\n      @before-enter=\"beforeEnter\"\n      @enter=\"enter\"\n      @afterEnter=\"afterEnter\"\n    \u003e\n      \u003c!-- 可以直接使用show--\u003e\n      \u003cdiv class=\"ball\" v-if=\"show\"\u003e\n        \u003cli class=\"inner\"\u003e\n          \u003cspan class=\"cubeic-add\" @click=\"addToCart($event,item)\"\u003e\n            \u003csvg-icon class=\"add-icon\" icon-class=\"add\"\u003e\u003c/svg-icon\u003e\n          \u003c/span\u003e\n        \u003c/li\u003e\n      \u003c/div\u003e\n    \u003c/transition\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n### computed\n\n`computed`函数的第一个参数，可以接收一个函数，或者是一个对象！如果是函数默认是`getter`函数，并为`getter`返回的值返回一个只读的`ref`对象。\n\n```javascript\nimport { computed } from \"@vue/composition-api\";\n\nconst count = ref(1);\n// computed接收一个函数作为入参\nconst plusOne = computed(() =\u003e count.value + 1);\n\nconsole.log(plusOne.value); // 2\n\nplusOne.value++; // 错误，plusOne是只读的！\n```\n\n或者也可以是个对象，可以使用具有`get`和`set`功能的对象来创建可写`ref`对象。\n\n```javascript\nconst count = ref(1);\n// computed接收一个对象作为入参\nconst plusOne = computed({\n  get: () =\u003e count.value + 1,\n  set: (val) =\u003e {\n    count.value = val - 1;\n  },\n});\n\nplusOne.value = 1;\nconsole.log(count.value); // 0\n```\n\n### watch\n\n`watch(source, cb, options?)`\n\n该`watch`API 与 2.x `this.$watch`（以及相应的`watch`选项）完全等效。\n\n#### 观察单一来源\n\n观察者数据源可以是返回值的 getter 函数，也可以直接是 ref：\n\n```javascript\n// watching a getter函数\nconst state = reactive({ count: 0 });\nwatch(\n  () =\u003e state.count, // 返回值的getter函数\n  (count, prevCount, onCleanup) =\u003e {\n    /* ... */\n  }\n);\n\n// directly watching a ref\nconst count = ref(0);\nwatch(\n  count, // 也可以直接是ref\n  (count, prevCount, onCleanup) =\u003e {\n    /* ... */\n  }\n);\n```\n\n#### watch 多个来源\n\n观察者还可以使用数组同时监视多个源：\n\n```javascript\nconst me = reactive({ age: 24, name: \"gk\" });\n// reactive类型的\nwatch(\n  [() =\u003e me.age, () =\u003e me.name], // 监听reactive多个数据源，可以传入一个数组类型，返回getter函数\n  ([age, name], [oldAge, oldName]) =\u003e {\n    console.log(age); // 新的 age 值\n    console.log(name); // 新的 name 值\n    console.log(oldAge); // 旧的 age 值\n    console.log(oldName); // 新的 name 值\n  },\n  // options\n  {\n    lazy: true, //默认 在 watch 被创建的时候执行回调函数中的代码，如果lazy为true ，怎创建的时候，不执行！\n  }\n);\n\nsetInterval(() =\u003e {\n  me.age++;\n  me.name = \"oldMe\";\n}, 7000000);\n\n// ref类型的\nconst work = ref(\"web\");\nconst addres = ref(\"sz\");\nwatch(\n  [work, address], // 监听多个ref数据源\n  ([work, addres], [oldwork, oldaddres]) =\u003e {\n    //......\n  },\n  {\n    lazy: true,\n  }\n);\n```\n\n`watch`和组件的生命周期绑定，当组件卸载后，watch 也将自动停止。在其他情况下，它返回停止句柄，可以调用该句柄以显式停止观察程序：\n\n```javascript\n// watch 返回一个函数句柄，我们可以决定该watch的停止和开始！\nconst stopWatch = watch(\n  [work, address], // 监听多个ref数据源\n  ([work, addres], [oldwork, oldaddres]) =\u003e {\n    //......\n  },\n  {\n    lazy: true,\n  }\n);\n\n// 调用停止函数，清除对work和address的监视\nstopWatch();\n```\n\n#### 在 watch 中清除无效的异步任务\n\n```html\n\u003cdiv class=\"search-con\"\u003e\n  \u003csvg-icon class=\"search-icon\" icon-class=\"search\"\u003e\u003c/svg-icon\u003e\n  \u003cinput v-focus placeholder=\"搜索、关键词\" v-model=\"searchText\" /\u003e\n\u003c/div\u003e\n```\n\n```javascript\nsetup(props, { attrs, slots, parent, root, emit, refs }){\n  const CancelToken = root.$http.CancelToken\n  const source = CancelToken.source()\n  // 定义响应式数据 searchText\n  const searchText = ref('')\n\n  // 向后台发送异步请求\n  const getSearchResult = searchText =\u003e {\n   root.$http.post(\"http://test.happymmall.com/search\",{text:searchText}, {\n     cancelToken: source.token\n   }).then(res =\u003e {\n    // .....\n   });\n  return source.cancel\n}\n\n// 定义 watch 监听\nwatch(\n  searchText,\n  (searchText, oldSearchText, onCleanup) =\u003e {\n    // 发送axios请求，并得到取消axios请求的 cancel函数\n    const cancel = getSearchResult(searchText)\n\n    // 若 watch 监听被重复执行了，则会先清除上次未完成的异步请求\n    onCleanup(cancel)\n  },\n  // watch 刚被创建的时候不执行\n  { lazy: true }\n)\n\n  return {\n    searchText\n  }\n}\n```\n\n## 最后\n\nvue3 新增 Composition API。新的 API 兼容 Vue2.x，只需要在项目中单独引入 @vue/composition-api 这个包就能够解决我们目前 Vue2.x 中存在的个别难题。比如：如何组织逻辑，以及如何在多个组件之间抽取和复用逻辑。基于 Vue 2.x 目前的 API 我们有一些常见的逻辑复用模式，但都或多或少存在一些问题：\n\n这些模式包括：\n\n1. Mixins\n2. 高阶组件 (Higher-order Components, aka HOCs)\n3. Renderless Components (基于 scoped slots / 作用域插槽封装逻辑的组件）\n\n总体来说，以上这些模式存在以下问题：\n\n1. 模板中的数据来源不清晰。举例来说，当一个组件中使用了多个 mixin 的时候，光看模板会很难分清一个属性到底是来自哪一个 mixin。HOC 也有类似的问题。\n2. 命名空间冲突。由不同开发者开发的 mixin 无法保证不会正好用到一样的属性或是方法名。HOC 在注入的 props 中也存在类似问题。\n3. 性能。HOC 和 Renderless Components 都需要额外的组件实例嵌套来封装逻辑，导致无谓的性能开销。\n\nvue3 中，新增 `Composition API`。而且新的`API`兼容 `Vue2.x`，只需要在项目中，单独引入 `@vue/composition-api` 这个包就可以，就能够解决我们目前 以上大部分问题。同时，如果我直接升级到 `Vue3.x`，我要做的事情会更多，只要当前项目中使用到的第三方 ui 库，都需要重新改造，以及升级后的诸多坑要填！刚开始的时候，我就是直接在当前脚手架的基础上 `vue add vue-next` 安装升级，但是只要是有依赖第三方生态库的地方，就有许多的坑。。。\n\n`Vue3.x` 没有导出默认对象 `export default`，在第三方生态中，常用`Vue.xxx()`来进行依赖，现在这些语法需要重写，工作量可不小啊！\n\n如果是新团队、小型的项目，可以尝试使用 vue3 进行尝试开发，慢慢过度，当 `Vue3.x` 正式 发布 后，而且周边生态跟上来了，就可以直接用 vue3 了！\n\n在[bilibili](https://search.bilibili.com/all?keyword=VUE3.0\u0026from_source=nav_search\u0026spm_id_from=333.851.b_696e7465726e6174696f6e616c486561646572.10)直播的时候，Evan You 也说了，目前 vue3 beta 版本，最重要的是**提升稳定性**，和对**第三方工具库的支持**，如果你是第三方库的作者，可以现在开始，熟悉熟悉源码了，我们开发者可以先读懂所有 API 的使用。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekskai%2Fvue3-jd-h5","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeekskai%2Fvue3-jd-h5","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekskai%2Fvue3-jd-h5/lists"}