{"id":16500341,"url":"https://github.com/caoxiemeihao/vite-vue2-tsx-composition-api","last_synced_at":"2025-10-19T22:03:58.606Z","repository":{"id":106046441,"uuid":"353725791","full_name":"caoxiemeihao/vite-vue2-tsx-composition-api","owner":"caoxiemeihao","description":"Vite、vue2、tsx、composition-api、vant intergration :)","archived":false,"fork":false,"pushed_at":"2021-09-07T09:40:43.000Z","size":3717,"stargazers_count":31,"open_issues_count":2,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-01T04:17:21.963Z","etag":null,"topics":["composition-api","vant","vite","vue-tsx"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/caoxiemeihao.png","metadata":{"files":{"readme":"README.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":"2021-04-01T14:24:03.000Z","updated_at":"2024-02-03T16:16:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"56ad4fcc-65d4-4cf6-899a-1b633377fe43","html_url":"https://github.com/caoxiemeihao/vite-vue2-tsx-composition-api","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caoxiemeihao%2Fvite-vue2-tsx-composition-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caoxiemeihao%2Fvite-vue2-tsx-composition-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caoxiemeihao%2Fvite-vue2-tsx-composition-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caoxiemeihao%2Fvite-vue2-tsx-composition-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/caoxiemeihao","download_url":"https://codeload.github.com/caoxiemeihao/vite-vue2-tsx-composition-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244121586,"owners_count":20401279,"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":["composition-api","vant","vite","vue-tsx"],"created_at":"2024-10-11T14:57:02.984Z","updated_at":"2025-10-19T22:03:58.596Z","avatar_url":"https://github.com/caoxiemeihao.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 前言\n\n1. 我猜你可能是一个更偏向于个 `React` 开发者\n2. 我猜你可能深深的感受到了 `Hooks` 带来的 `逻辑复用、响应式编程` 的种种便利\n3. 我猜你可能深度依赖 `Typescript`，又很喜欢 `JSX` 的强大表现力\n4. 我估计你公司用的 `Vue2`，虽然出了 `Vue3` 可以用上面种种好处；可是你还得维护 `Vue2` 老项目\n5. 我估计你心里很不爽，又想学习 `Vue3` 带来新特性，又想重构已有 `Vue2` 项目但是成本太大\n6. 我估计你再这么下去，可能是想跑路了都~ 👻\n7. ...\n\n- **目的** 本文是从 `React` 使用者视角带你解锁 `Vue2` 中开启 `Composition API` 🖖 并使用 `TSX` 🚀 地开发模式 🎉\n- **希望** 本文能助你一臂之力 💪 不要慌，你还可以继续治疗 💊\n\n## 目标\n\n- 使用 `Vue2` 工程模板加入 `Composition API` 插件\n- 开启 `.ts`、`.tsx` 支持\n\n### 初始化项目\n\n\u003e 略 ~\n\n### 加入 Composition API plugin\n\n- 添加依赖\n\n```bash\nyarn add @vue/composition-api\n```\n\n### 开启 ts、tsx 支持\n\n\u003e 本项目使用的 Babel@7.x\n\n- 添加依赖\n\n```bash\nyarn add -D @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props @babel/plugin-transform-typescript\n```\n\n- 修改配置文件\n1. 修改根目录 `babel.config.js`\n\n```diff\nmodule.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset',\n+   ['@vue/babel-preset-jsx', { compositionAPI: true }], // 开启 jsx\n  ],\n+ plugins: [\n+   ['@babel/plugin-transform-typescript', { isTSX: true }], // 开启 typescript\n+ ],\n}\n```\n\n2. 根目录新建 `vue.config.js`\n\n```diff\n+ const path = require('path')\n\n+ module.exports = {\n+   configureWebpack: {\n+     resolve: {\n+       alias: {\n+         '@': path.resolve(__dirname, './src'),\n+       },\n+       extensions: ['.ts', '.tsx'],\n+     },\n+     module: {\n+       rules: [\n+         {\n+           test: /\\.(jsx|tsx|ts)$/,\n+           loader: 'babel-loader',\n+         },\n+       ],\n+     },\n+   },\n+ }\n```\n\n3. 根目录新建 `tsconfig.json`\n\n```diff\n+ {\n+   \"compilerOptions\": {\n+     \"target\": \"ES2017\",\n+     \"module\": \"UMD\",\n+     \"allowJs\": true,\n+     \"jsx\": \"preserve\",\n+     \"moduleResolution\": \"Node\",\n+     \"allowSyntheticDefaultImports\": true,\n+     \"importHelpers\": true,\n+     \"baseUrl\": \"./\",\n+     \"paths\": {\n+       \"@/*\": [\"src/*\"]\n+     }\n+   }\n+ }\n```\n\n## 修改 App.vue -\u003e App.tsx\n\n- `.vue` 文件和 `.tsx` 可以看成是等价的，直接使用即可；注意 `jsx` 中组件标签首字母一定要大写！\n- `.png` 静态资源用法就是直接引入，底层通过 `url-loader` 处理；@vue/cli 集成了静态资源配置\n\n```jsx\nimport { defineComponent, onMounted } from '@vue/composition-api'\nimport HelloWorld from './components/HelloWorld.vue'\nimport logo from '@/assets/logo.png'\n\nexport default defineComponent({\n  setup() {\n    onMounted(() =\u003e {})\n  },\n  render() {\n    return (\n      \u003cdiv class=\"app\"\u003e\n        \u003cimg alt=\"Vue logo\" src={logo} /\u003e\n        \u003cHelloWorld msg=\"Welcome to Your Vue.js App\"/\u003e\n      \u003c/div\u003e\n    )\n  },\n})\n\n```\n- **注意！**`setup` 中不支持 (setup 中没有 this) `tsx`, 必须写在 `render` 中\n- 启动下项目看看效果吧~ 😄\n\n```bash\nyarn serve\n```\n\n---\n\n\u003e Notes\n- [完整 Demo 戳 github](https://github.com/caoxiemeihao/vue2-tsx-composition-api)\n- Demo 使用API `https://api.github.com/users`\n- User 定义如下\n\n```typescript\nexport interface User {\n  avatar_url: string\n  events_url: string\n  followers_url: string\n  following_url: string\n  gists_url: string\n  gravatar_id: string\n  html_url: string\n  id: number\n  login: string\n  node_id: string\n  organizations_url: string\n  received_events_url: string\n  repos_url: string\n  site_admin: boolean\n  starred_url: string\n  subscriptions_url: string\n  type: string\n  url: string\n}\n```\n\n## 写一个 Component\n\n```jsx\nimport { Row, Col, Image } from 'vant'\n\nexport default defineComponent({\n  props: {\n    users: {\n      type: Array as { (): User[] }, // 提供调用处的类型推到\n      default: [],\n    },\n  },\n  render() {\n    return (\n      \u003cRow class=\"img-box\"\u003e\n        {this.users.map((user, idx) =\u003e (\n          \u003cCol\n            class=\"img-item\"\n            key={idx}\n            span={8}\n            // onclick 大写小写都可以的\n            // 这里直接用 Vue2 内置的 $emit 要比 React ”稍微“ 方便点\n            onclick={() =\u003e this.$emit('click', user)}\n          \u003e\n            \u003cImage src={user.avatar_url} /\u003e\n            \u003cdiv\u003e{user.login}\u003c/div\u003e\n          \u003c/Col\u003e\n        ))}\n      \u003c/Row\u003e\n    )\n  },\n})\n\n```\n\n## 写一个 Hooks\n\n- 如果你写过 React Hooks 那么你已经大概知道怎么写的了\n- 它看起来像个普通的 `函数式` 组件，一般意义的函数式组件返回 `VNode`，Hooks 返回 `状态`\n\n```jsx\n// React Hooks\nimport { useState, useEffect } from 'react'\n\nexport function useRequestWithReact() {\n  const [users, setUsers] = useState([])\n  useEffect(() =\u003e {\n    fetch('https://api.github.com/users')\n      .then(_ =\u003e _.json())\n      .then(data =\u003e {\n        setUsers(users)\n      })\n  }, [])\n\n  return { users }\n}\n\n// usage\nexport default () =\u003e {\n  const { users } = useRequestWithReact()\n  \n  return users.map(user =\u003e\n    \u003cdiv key={user.id}\u003e{user.login}\u003c/div\u003e\n  )\n}\n```\n\n```jsx\n// Vue2 Composition API\nimport { ref } from '@vue/composition-api'\n\nexport function useRequestWithVue2() {\n  const users = ref([])\n  fetch('https://api.github.com/users')\n    .then(_ =\u003e _.json())\n    .then(data =\u003e {\n      users.value = data\n    })\n\n  return { users }\n}\n\n// usage\nexport default () =\u003e {\n  const { users } = useRequestWithVue2()\n  \n  return users.map(user =\u003e\n    \u003cdiv key={user.id}\u003e{user.login}\u003c/div\u003e\n  )\n}\n```\n\n- 这俩个框架的 Hooks/Composition API 设计真的很相似，就像现在的 `Android` 和 `iOS` 系统 😂\n\n#### [与 React Hooks 的对比](https://zhuanlan.zhihu.com/p/68477600)\n\n```\n整体上更符合 JavaScript 的直觉；\n不受调用顺序的限制，可以有条件地被调用；\n不会在后续更新时不断产生大量的内联函数而影响引擎优化或是导致 GC 压力；\n不需要总是使用 useCallback 来缓存传给子组件的回调以防止过度更新；\n不需要担心传了错误的依赖数组给 useEffect/useMemo/useCallback 从而导致回调中使用了过期的值 —— Vue 的依赖追踪是全自动的。\n```\n\n## 写一个 Hooks with Component\n\n#### How and why?\n\n- 试想一下我们写一个 Component\n  * 收益: 能复用 UI 展示，内部能够有自己的逻辑、状态\n  * 弊端: 内部逻辑、状态对外通讯 **需要有一定的手段** 处理，Vue、React 均是如此\n- 试想一下我们写一个 Hooks\n  * 收益: 提供逻辑复用、调用处状态自动更新\n\n- 能不能把 Hooks、Component 的收益放到一起呢？(成年人的世界选择全要😏)\n  Function Component、Hooks 只是返回值看起来不一样而已；要是我们 “两者都返回” 怎么样？\n  * 收益: 能复用 UI 展示，内部能够有自己的逻辑、状态\n  1. 借助响应式设计会 **自动吐出来** 内部状态\n  2. 而不再是使用组件的种种通讯方式去 **手动捞出来** 内部状态\n\n- 基于 vant 设计一个 `useCalendar`\n```jsx\n// src/hooks/useCalendar.tsx\nimport { ref } from '@vue/composition-api'\nimport { Calendar } from 'vant'\n\nexport default function () {\n  const visble = ref(false)\n  const date = ref()\n\n  const onConfirm = (val) =\u003e {\n    visble.value = false\n    date.value = val\n  }\n\n  // 这里必须要用函数包裹\n  // 因为 setup 中无法使用 h 函数\n  // component 需要传递到 dome.tsx 的 render 中调用，render 中可以使用 h 函数，及正确的 this 指向\n  // 不使用箭头函数的目的也是为了 this 的正确指向\n  // ！！！\n  // 这种写法在 Vue3.x、React 要更简单、容易理解一些\n  // 毕竟这是 vue2 的插件，还是有些写法限制的\n  // ！！！\n  const component = function () {\n    return \u003cCalendar\n      vModel={visble.value}\n      onConfirm={onConfirm}\n    /\u003e\n  }\n\n  return {\n    visble,\n    date,\n    component,\n  }\n}\n\n// src/views/dome.tsx\nimport { defineComponent, watch, computed } from '@vue/composition-api'\nimport { Button } from 'vant'\n\nexport default defineComponent({\n  setup() {\n    const { date, visble, component } = useCalendar()\n\n    watch(date, =\u003e {\n      // 永远在选择日期后自动执行 wath 的 callback\n      // 而不是通过通讯的手段去手动捞出\n      console.log('你选择的日期是:', date)\n    })\n\n    // 或者 computed(() =\u003e date -\u003e do something...)\n\n    return {\n      visble,\n      component,\n    }\n  },\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cButton onclick={() =\u003e this.visble = true}\u003e点击唤起日期\u003c/Button\u003e\n        \u003chr/\u003e\n        {this.component()}\n      \u003c/div\u003e\n    )\n  },\n})\n```\n\n![Hooks with Component](./screenshots/HC-page.png)\n\n- 至此，我们可以在回答 Hooks 的面试题时候来一句：“Hooks 除了逻辑复用，还能实现 UI 复用”\n  相信你对工作中一些场景是该封装 `Component` 还是 `Hooks` 会有了一个新的参考 🖖\n\n#### 个人关于 Vue 的 Composition API 看法\n\n* 设计更贴近 `响应式编程` 就像没有 `operator` 的 `rxjs`；有那味儿 ~\n* `ref、reactive` 数据源为 **可观察对象**；`模板`、`component`、`watch` 这些可以视为订阅者\n* `watch、watchEffect` 设计又和 `mobx` 中的 `reaction、autorun` 行为基本一致\n\n#### 个人关于 React 的 Hooks 看法\n\n* React 的 Hooks 在 setState 后重复调用当前组件(函数)，通过执行当前组件内的 useEffect、useMemo 并对比 deps 决定是否 “响应”； 模拟了 响应式 设计\n* 引用官网的一句话 ~ (可能你木有注意到哦)\n![React Hooks](./screenshots/react-hooks.png)\n\n## CSS 模块化\n\n- `Vue Cli` 已经内置了 `xxxx.module.(css|less|sass|scss|styl)` 的 `webpack loader` 配置；用于开启 `style-loader` 的模块化\n```less\n// foo.module.less\n.foo {\n  color: aliceblue;\n\n  :global {\n    .bar {\n      color: skyblue;\n    }\n  }\n}\n```\n\n```jsx\n// foo.tsx\nimport { defineComponent } from '@vue/composition-api'\nimport styles from './foo.module.less'\n\nexport default defineComponent({\n  render() {\n    return (\n      \u003cdiv class={styles.foo}\u003e\n        Foo Component.\n        \u003cdiv class=\"bar\"\u003e\n          Bar Component.\n        \u003c/div\u003e\n      \u003c/div\u003e\n    )\n  },\n})\n```\n\n## VSCode 配置\n- `emmet` 生成 `class` 而不是 `className`\n```json\n{\n  \"emmet.includeLanguages\": {\n    \"javascriptreact\": \"html\",\n    \"typescriptreact\": \"html\"\n  }\n}\n```\n\n## 参考文章\n- [用于提供 组合式 API 的 Vue 2 插件](https://github.com/vuejs/composition-api/blob/HEAD/README.zh-CN.md)\n- [Configurable preset for Vue JSX plugins](https://github.com/vuejs/jsx/tree/dev/packages/babel-preset-jsx)\n- [在Vue中使用JSX的正确姿势](https://zhuanlan.zhihu.com/p/37920151)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaoxiemeihao%2Fvite-vue2-tsx-composition-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcaoxiemeihao%2Fvite-vue2-tsx-composition-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaoxiemeihao%2Fvite-vue2-tsx-composition-api/lists"}