├── .nvmrc ├── .yvmrc ├── .eslintignore ├── jest.config.js ├── .gitignore ├── .prettierignore ├── .storybook ├── addons.js ├── config.js ├── webpack.config.js └── style.css ├── src ├── mocks │ ├── index.ts │ ├── router.ts │ └── store.ts ├── __stories__ │ ├── components │ │ ├── index.ts │ │ └── ShowDocs.tsx │ ├── useWindowSize.story.tsx │ ├── useGetters.story.tsx │ ├── usePrevious.story.tsx │ ├── useDate.story.tsx │ ├── useCounter.story.tsx │ ├── useStore.story.tsx │ ├── useState.story.tsx │ ├── useMutations.story.tsx │ ├── useActions.story.tsx │ └── useRouter.story.tsx ├── useState.ts ├── useActions.ts ├── useGetters.ts ├── useMutations.ts ├── useRouter.ts ├── usePrevious.ts ├── useStore.ts ├── useMountedState.ts ├── useTimeout.ts ├── util │ ├── runtime.ts │ └── renderHook.ts ├── useMedia.ts ├── __tests__ │ ├── useRouter.test.ts │ ├── useMountedState.test.ts │ ├── useState.test.ts │ ├── useGetters.test.ts │ ├── useTimeout.test.ts │ ├── useMutations.test.ts │ ├── useCounter.test.ts │ ├── useActions.test.ts │ ├── useMedia.test.ts │ ├── useDate.test.ts │ ├── usePrevious.test.ts │ ├── useStore.test.ts │ └── useWindowSize.test.ts ├── useCounter.ts ├── useDate.ts ├── useWindowSize.ts ├── index.ts └── helpers │ └── vuex │ ├── index.ts │ └── interface.ts ├── commitlint.config.js ├── .prettierrc ├── .vscode ├── settings.json ├── launch.json ├── hook.code-snippets └── docs.code-snippets ├── .babelrc ├── .travis.yml ├── .eslintrc ├── .all-contributorsrc ├── .editorconfig ├── tsconfig.json ├── LICENSE ├── docs ├── useDate.md ├── useRouter.md ├── useStore.md ├── useWindowSize.md ├── usePrevious.md ├── useGetters.md ├── useCounter.md ├── useState.md ├── useMutations.md └── useActions.md ├── CHANGELOG.md ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.16.0 2 | -------------------------------------------------------------------------------- /.yvmrc: -------------------------------------------------------------------------------- 1 | 1.17.0 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | storybook-static/ 3 | lib/ 4 | esm/ 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverage: true, 3 | }; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | storybook-static/ 3 | coverage/ 4 | lib/ 5 | esm/ 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | storybook-static/ 3 | lib/ 4 | esm/ 5 | package.json 6 | CHANGELOG.md 7 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register'; 2 | import '@storybook/addon-notes/register'; 3 | -------------------------------------------------------------------------------- /src/mocks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as createRouter } from './router'; 2 | export { default as createStore } from './store'; 3 | -------------------------------------------------------------------------------- /src/__stories__/components/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | export { default as ShowDocs } from './ShowDocs'; 3 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | ignores: [(commit) => commit.includes('init')], 4 | }; 5 | -------------------------------------------------------------------------------- /src/useState.ts: -------------------------------------------------------------------------------- 1 | import createVuexHelper, { Helper, useState } from './helpers/vuex'; 2 | 3 | export default createVuexHelper(Helper.State); 4 | -------------------------------------------------------------------------------- /src/useActions.ts: -------------------------------------------------------------------------------- 1 | import createVuexHelper, { Helper, useActions } from './helpers/vuex'; 2 | 3 | export default createVuexHelper(Helper.Actions); 4 | -------------------------------------------------------------------------------- /src/useGetters.ts: -------------------------------------------------------------------------------- 1 | import createVuexHelper, { Helper, useGetters } from './helpers/vuex'; 2 | 3 | export default createVuexHelper(Helper.Getters); 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "quoteProps": "consistent", 4 | "trailingComma": "all", 5 | "arrowParens": "always", 6 | "endOfLine": "lf" 7 | } 8 | -------------------------------------------------------------------------------- /src/useMutations.ts: -------------------------------------------------------------------------------- 1 | import createVuexHelper, { Helper, useMutations } from './helpers/vuex'; 2 | 3 | export default createVuexHelper(Helper.Mutations); 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.autoFixOnSave": true, 3 | "eslint.enable": true, 4 | "npm.packageManager": "yarn", 5 | "typescript.tsdk": "node_modules/typescript/lib" 6 | } 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { "node": "current" } 7 | } 8 | ], 9 | "@babel/preset-typescript" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/useRouter.ts: -------------------------------------------------------------------------------- 1 | import { computed } from '@vue/composition-api'; 2 | import { getRuntimeVM } from './util/runtime'; 3 | 4 | export default function useRouter() { 5 | const vm = getRuntimeVM(); 6 | const route = computed(() => vm.$route); 7 | return { route, router: vm.$router }; 8 | } 9 | -------------------------------------------------------------------------------- /src/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch, Ref } from '@vue/composition-api'; 2 | 3 | export default function usePrevious(state: Ref | (() => T)) { 4 | const previous = ref(); 5 | 6 | watch(state, (_, oldVal) => { 7 | previous.value = oldVal; 8 | }); 9 | 10 | return previous; 11 | } 12 | -------------------------------------------------------------------------------- /src/useStore.ts: -------------------------------------------------------------------------------- 1 | import { computed } from '@vue/composition-api'; 2 | import { Store } from 'vuex'; 3 | import { getRuntimeVM } from './util/runtime'; 4 | 5 | export default function useStore() { 6 | const vm = getRuntimeVM(); 7 | const store = computed(() => vm.$store as Store); 8 | return store; 9 | } 10 | -------------------------------------------------------------------------------- /src/useMountedState.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { ref, onMounted } from '@vue/composition-api'; 3 | 4 | export default function useMountedState() { 5 | const isMounted = ref(false); 6 | 7 | onMounted(async () => { 8 | await Vue.nextTick(); 9 | isMounted.value = true; 10 | }); 11 | 12 | return isMounted; 13 | } 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 10.16.0 4 | 5 | install: 6 | - yarn 7 | 8 | script: 9 | - yarn lint 10 | - yarn lint:types 11 | - yarn lint:prettier 12 | - yarn test 13 | 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) 16 | - yarn release 17 | 18 | cache: 19 | yarn: true 20 | directories: 21 | - node_modules 22 | -------------------------------------------------------------------------------- /src/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api'; 2 | 3 | export default function useTimeout(delay = 0) { 4 | const ready = ref(false); 5 | let timerId: number; 6 | 7 | onMounted(() => { 8 | timerId = window.setTimeout(() => { 9 | ready.value = true; 10 | }, delay); 11 | }); 12 | 13 | onUnmounted(() => { 14 | window.clearTimeout(timerId); 15 | }); 16 | 17 | return ready; 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "extends": [ 5 | "eslint-config-airbnb-base", 6 | "plugin:import/typescript", 7 | "plugin:prettier/recommended" 8 | ], 9 | "env": { 10 | "browser": true, 11 | "es6": true, 12 | "node": true 13 | }, 14 | "rules": { 15 | "no-undef": "off", 16 | "no-unused-vars": "off", 17 | "global-require": "off", 18 | "import/no-extraneous-dependencies": "off" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/util/runtime.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | interface Runtime { 4 | vm?: Vue; 5 | } 6 | 7 | const runtime: Runtime = {}; 8 | 9 | export function getRuntimeVM(): Vue { 10 | if (runtime.vm) return runtime.vm; 11 | throw new ReferenceError('[vue-hooks] Not found vue instance.'); 12 | } 13 | 14 | export function setRuntimeVM(this: Vue, vue?: Vue) { 15 | const vm = this || vue; 16 | if (typeof vm.$options.setup === 'function') { 17 | runtime.vm = vm; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/mocks/router.ts: -------------------------------------------------------------------------------- 1 | import { VueConstructor } from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | 4 | export default function createRouter(Vue?: VueConstructor) { 5 | if (Vue) Vue.use(VueRouter); 6 | return new VueRouter({ 7 | routes: [ 8 | { 9 | path: '/', 10 | name: 'index', 11 | meta: { title: 'Vue Hooks' }, 12 | }, 13 | { 14 | path: '*', 15 | name: '404', 16 | meta: { title: '404 - Not Found' }, 17 | }, 18 | ], 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/__stories__/components/ShowDocs.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import { RenderContext } from 'vue'; 4 | import { ofType } from 'vue-tsx-support'; 5 | 6 | export interface DocsProps { 7 | md: { default: string }; 8 | } 9 | 10 | const ShowDocs = ({ props }: RenderContext) => ( 11 |
12 | ); 13 | 14 | // @ts-ignore 15 | export default ofType().convert(ShowDocs); 16 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "vue-hooks", 3 | "projectOwner": "u3u", 4 | "repoType": "github", 5 | "files": [ 6 | "README.md" 7 | ], 8 | "commit": false, 9 | "commitConvention": "none", 10 | "contributors": [ 11 | { 12 | "login": "u3u", 13 | "name": "u3u", 14 | "avatar_url": "https://avatars2.githubusercontent.com/u/20062482?v=4", 15 | "profile": "https://qwq.cat", 16 | "contributions": [ 17 | "code", 18 | "doc", 19 | "example", 20 | "test" 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/useMedia.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api'; 2 | 3 | export default function useMedia(query, defaultState = false) { 4 | let mql; 5 | const matches = ref(defaultState); 6 | const updateMatches = () => { 7 | if (mql) matches.value = mql.matches; 8 | }; 9 | 10 | onMounted(() => { 11 | mql = window.matchMedia(query); 12 | mql.addListener(updateMatches); 13 | matches.value = mql.matches; 14 | }); 15 | 16 | onUnmounted(() => { 17 | mql.removeListener(updateMatches); 18 | }); 19 | 20 | return matches; 21 | } 22 | -------------------------------------------------------------------------------- /src/__tests__/useRouter.test.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from '..'; 2 | import renderHook from '../util/renderHook'; 3 | 4 | describe('useRouter', () => { 5 | it('should be defined', () => { 6 | expect(useRouter).toBeDefined(); 7 | }); 8 | 9 | it('should update route', () => { 10 | renderHook(() => { 11 | const { route, router } = useRouter(); 12 | expect(route.value.name).toBe('index'); 13 | expect(route.value.meta.title).toBe('Vue Hooks'); 14 | router.push('/test'); 15 | expect(route.value.name).toBe('404'); 16 | expect(route.value.meta.title).toBe('404 - Not Found'); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/useCounter.ts: -------------------------------------------------------------------------------- 1 | /* eslint no-return-assign: off */ 2 | import { ref } from '@vue/composition-api'; 3 | 4 | export default function useCounter(initialValue = 0) { 5 | const count = ref(initialValue); 6 | const inc = (delta = 1) => (count.value += delta); 7 | const dec = (delta = 1) => (count.value -= delta); 8 | const get = () => count.value; 9 | const set = (val: number) => (count.value = val); 10 | const reset = (val = initialValue) => { 11 | initialValue = val; // eslint-disable-line no-param-reassign 12 | return set(val); 13 | }; 14 | const actions = { inc, dec, get, set, reset }; 15 | 16 | return [count, actions] as const; 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "vscode-jest-tests", 10 | "request": "launch", 11 | "args": ["--runInBand"], 12 | "cwd": "${workspaceFolder}", 13 | "console": "integratedTerminal", 14 | "internalConsoleOptions": "neverOpen", 15 | "disableOptimisticBPs": true, 16 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/__tests__/useMountedState.test.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { onMounted } from '@vue/composition-api'; 3 | import { useMountedState } from '..'; 4 | import renderHook from '../util/renderHook'; 5 | 6 | describe('useMountedState', () => { 7 | it('should be defined', () => { 8 | expect(useMountedState).toBeDefined(); 9 | }); 10 | 11 | it('should return true on mounted', () => { 12 | renderHook(() => { 13 | const isMounted = useMountedState(); 14 | expect(isMounted.value).toBe(false); 15 | 16 | onMounted(async () => { 17 | await Vue.nextTick(); 18 | expect(isMounted.value).toBe(true); 19 | }); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/useDate.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api'; 2 | import dayjs from 'dayjs'; 3 | import relativeTime from 'dayjs/plugin/relativeTime'; 4 | 5 | dayjs.extend(relativeTime); 6 | 7 | export default function useDate( 8 | d: dayjs.ConfigType = Date.now(), 9 | timeout: number = 0, 10 | ) { 11 | const date = ref(dayjs(d)); 12 | 13 | if (timeout) { 14 | let timerId: number; 15 | 16 | onMounted(() => { 17 | timerId = window.setInterval(() => { 18 | date.value = dayjs(Date.now()); 19 | }, timeout); 20 | }); 21 | 22 | onUnmounted(() => { 23 | window.clearInterval(timerId); 24 | }); 25 | } 26 | 27 | return date; 28 | } 29 | 30 | export { dayjs }; 31 | -------------------------------------------------------------------------------- /src/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, onUnmounted } from '@vue/composition-api'; 2 | 3 | export default function useWindowSize() { 4 | const width = ref(window.innerWidth); 5 | const height = ref(window.innerHeight); 6 | const update = () => { 7 | width.value = window.innerWidth; 8 | height.value = window.innerHeight; 9 | }; 10 | 11 | const widthPixel = computed(() => `${width.value}px`); 12 | const heightPixel = computed(() => `${height.value}px`); 13 | 14 | onMounted(() => { 15 | window.addEventListener('resize', update); 16 | }); 17 | 18 | onUnmounted(() => { 19 | window.removeEventListener('resize', update); 20 | }); 21 | 22 | return { width, height, widthPixel, heightPixel }; 23 | } 24 | -------------------------------------------------------------------------------- /src/__tests__/useState.test.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '..'; 2 | import renderHook from '../util/renderHook'; 3 | 4 | describe('useState', () => { 5 | it('should be defined', () => { 6 | expect(useState).toBeDefined(); 7 | }); 8 | 9 | it('should update state', () => { 10 | type Inject = { count1: number; count2: number }; 11 | const { vm } = renderHook(() => ({ 12 | ...useState({ count1: 'count' }), 13 | ...useState('test', { count2: 'count' }), 14 | })); 15 | expect(vm.count1).toBe(0); 16 | expect(vm.count2).toBe(0); 17 | 18 | vm.$store.commit('increment'); 19 | vm.$store.commit('test/decrement'); 20 | 21 | expect(vm.count1).toBe(1); 22 | expect(vm.count2).toBe(-1); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/useGetters.test.ts: -------------------------------------------------------------------------------- 1 | import { useGetters } from '..'; 2 | import renderHook from '../util/renderHook'; 3 | 4 | describe('useGetters', () => { 5 | it('should be defined', () => { 6 | expect(useGetters).toBeDefined(); 7 | }); 8 | 9 | it('should update getters', () => { 10 | type Inject = { plusOne: number; minusOne: number }; 11 | const { vm } = renderHook(() => ({ 12 | ...useGetters(['plusOne']), 13 | ...useGetters('test', ['minusOne']), 14 | })); 15 | expect(vm.plusOne).toBe(1); 16 | expect(vm.minusOne).toBe(-1); 17 | 18 | vm.$store.commit('increment'); 19 | vm.$store.commit('test/decrement'); 20 | 21 | expect(vm.plusOne).toBe(2); 22 | expect(vm.minusOne).toBe(-2); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Use 4 spaces for the Python files 13 | [*.py] 14 | indent_size = 4 15 | max_line_length = 80 16 | 17 | # The JSON files contain newlines inconsistently 18 | [*.json] 19 | insert_final_newline = ignore 20 | 21 | # Minified JavaScript files shouldn't be changed 22 | [**.min.js] 23 | indent_style = ignore 24 | insert_final_newline = ignore 25 | 26 | # Makefiles always use tabs for indentation 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # Batch files use tabs for indentation 31 | [*.bat] 32 | indent_style = tab 33 | 34 | [*.md] 35 | trim_trailing_whitespace = false 36 | -------------------------------------------------------------------------------- /.vscode/hook.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "vue-hook": { 3 | "scope": "typescript", 4 | "prefix": "use", 5 | "body": [ 6 | "import { $1 } from '@vue/composition-api';", 7 | "", 8 | "export default function use${2:Hook}() {", 9 | " $3", 10 | "}", 11 | "" 12 | ], 13 | "description": "create a vue hook" 14 | }, 15 | "vue-hook-test": { 16 | "scope": "typescript", 17 | "prefix": "test", 18 | "body": [ 19 | "import { use${1:Hook} } from '..';", 20 | "import renderHook from '../util/renderHook';", 21 | "", 22 | "describe('use${1:Hook}', () => {", 23 | " it('should be defined', () => {", 24 | " expect(use${1:Hook}).toBeDefined();", 25 | " });", 26 | "});", 27 | "" 28 | ], 29 | "description": "test a vue hook" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/__tests__/useTimeout.test.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted } from '@vue/composition-api'; 2 | import { useTimeout } from '..'; 3 | import renderHook from '../util/renderHook'; 4 | 5 | describe('useTimeout', () => { 6 | it('should be defined', () => { 7 | expect(useTimeout).toBeDefined(); 8 | }); 9 | 10 | it('should return true after 3000ms', () => { 11 | const { vm } = renderHook(() => { 12 | jest.useFakeTimers(); 13 | const ready = useTimeout(3000); 14 | expect(ready.value).toBe(false); 15 | 16 | onMounted(() => { 17 | expect(jest.getTimerCount()).toBe(1); 18 | jest.runOnlyPendingTimers(); 19 | expect(ready.value).toBe(true); 20 | }); 21 | 22 | onUnmounted(() => { 23 | expect(jest.getTimerCount()).toBe(0); 24 | }); 25 | }); 26 | 27 | vm.$destroy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "jsx": "preserve", 7 | "allowUmdGlobalAccess": true, 8 | "allowSyntheticDefaultImports": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "esModuleInterop": true, 11 | "declaration": true, 12 | "pretty": true, 13 | "rootDir": "src", 14 | "sourceMap": false, 15 | "strict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noImplicitAny": false, 20 | "noFallthroughCasesInSwitch": true, 21 | "outDir": "lib", 22 | "lib": ["dom", "dom.iterable", "esnext"] 23 | }, 24 | "exclude": [ 25 | "node_modules", 26 | "lib", 27 | "esm", 28 | "**/__tests__/**/*", 29 | "**/__stories__/**/*", 30 | "src/util/renderHook.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addParameters } from '@storybook/vue'; 2 | import { themes } from '@storybook/theming'; 3 | import Vue from 'vue'; 4 | import Vuex from 'vuex'; 5 | import VueRouter from 'vue-router'; 6 | import VueCompositionAPI from '@vue/composition-api'; 7 | import hooks from '../src'; 8 | import 'github-markdown-css'; 9 | import 'prismjs/themes/prism-tomorrow.css'; 10 | import './style.css'; 11 | 12 | addParameters({ 13 | options: { 14 | theme: themes.dark, 15 | panelPosition: 'right', 16 | hierarchySeparator: /\//, 17 | hierarchyRootSeparator: /\|/, 18 | }, 19 | }); 20 | 21 | Vue.use(Vuex); 22 | Vue.use(VueRouter); 23 | Vue.use(VueCompositionAPI); 24 | Vue.use(hooks); 25 | 26 | function loadStories() { 27 | const req = require.context('../src', true, /\.story\.tsx$/); 28 | req.keys().forEach((mod) => req(mod)); 29 | } 30 | 31 | configure(loadStories, module); 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { VueConstructor } from 'vue'; 2 | import { setRuntimeVM } from './util/runtime'; 3 | 4 | export * from './useDate'; 5 | export { default as useDate } from './useDate'; 6 | export { default as useWindowSize } from './useWindowSize'; 7 | export { default as useCounter } from './useCounter'; 8 | export { default as usePrevious } from './usePrevious'; 9 | export { default as useStore } from './useStore'; 10 | export { default as useState } from './useState'; 11 | export { default as useGetters } from './useGetters'; 12 | export { default as useMutations } from './useMutations'; 13 | export { default as useActions } from './useActions'; 14 | export { default as useRouter } from './useRouter'; 15 | export { default as useMountedState } from './useMountedState'; 16 | export { default as useTimeout } from './useTimeout'; 17 | export { default as useMedia } from './useMedia'; 18 | 19 | export default function install(Vue: VueConstructor) { 20 | Vue.mixin({ beforeCreate: setRuntimeVM }); 21 | } 22 | -------------------------------------------------------------------------------- /src/__stories__/useWindowSize.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent } from '@vue/composition-api'; 6 | import { useWindowSize } from '..'; 7 | import { ShowDocs } from './components'; 8 | 9 | type Inject = { 10 | width: number; 11 | height: number; 12 | }; 13 | 14 | const Docs = () => ; 15 | 16 | const Demo = createComponent({ 17 | setup() { 18 | const { width, height } = useWindowSize(); 19 | return { width, height }; 20 | }, 21 | 22 | render(this: Vue & Inject) { 23 | const { width, height } = this; 24 | return ( 25 |
26 |
width: {width}px
27 |
height: {height}px
28 |
29 | ); 30 | }, 31 | }); 32 | 33 | storiesOf('useWindowSize', module) 34 | // @ts-ignore 35 | .add('docs', () => Docs) 36 | .add('demo', () => Demo); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) u3u (https://qwq.cat) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/__tests__/useMutations.test.ts: -------------------------------------------------------------------------------- 1 | import { useMutations } from '..'; 2 | import renderHook from '../util/renderHook'; 3 | 4 | interface InjectMutations { 5 | increment: Function; 6 | decrement: Function; 7 | } 8 | 9 | describe('useMutations', () => { 10 | it('should be defined', () => { 11 | expect(useMutations).toBeDefined(); 12 | }); 13 | 14 | it('should be defined mutations', () => { 15 | const { vm } = renderHook(() => ({ 16 | ...useMutations(['increment']), 17 | ...useMutations('test', ['decrement']), 18 | })); 19 | 20 | expect(vm.increment).toBeDefined(); 21 | expect(vm.decrement).toBeDefined(); 22 | }); 23 | 24 | it('should update count state', () => { 25 | const { vm } = renderHook(() => ({ 26 | ...useMutations(['increment']), 27 | ...useMutations('test', ['decrement']), 28 | })); 29 | 30 | expect(vm.$store.state.count).toBe(0); 31 | expect(vm.$store.state.test.count).toBe(0); 32 | 33 | vm.increment(); 34 | vm.decrement(); 35 | 36 | expect(vm.$store.state.count).toBe(1); 37 | expect(vm.$store.state.test.count).toBe(-1); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/util/renderHook.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import Vue from 'vue'; 3 | import { shallowMount, createLocalVue } from '@vue/test-utils'; 4 | import VueCompositionAPI, { createComponent } from '@vue/composition-api'; 5 | import { SetupFunction, Data } from '@vue/composition-api/dist/component'; 6 | import { createRouter, createStore } from '../mocks'; 7 | import hooks from '..'; 8 | 9 | const localVue = createLocalVue(); 10 | const router = createRouter(localVue); 11 | const store = createStore(localVue); 12 | 13 | localVue.use(hooks); 14 | localVue.use(VueCompositionAPI); 15 | 16 | export default function renderHook( 17 | setup: SetupFunction, 18 | ) { 19 | const App = createComponent({ 20 | template: ` 21 |
22 |
25 | `, 26 | 27 | setup, 28 | }); 29 | 30 | // @ts-ignore 31 | return shallowMount(App, { 32 | localVue, 33 | router, 34 | store, 35 | stubs: ['router-view'], 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const prism = require('markdown-it-prism'); 2 | const highlightLines = require('markdown-it-highlight-lines'); 3 | const linkAttributes = require('markdown-it-link-attributes'); 4 | 5 | module.exports = ({ config }) => { 6 | config.module.rules.push({ 7 | test: /\.md$/, 8 | use: [ 9 | { 10 | loader: require.resolve('markdown-it-loader'), 11 | options: { 12 | use: [ 13 | prism, 14 | highlightLines, 15 | [ 16 | linkAttributes, 17 | { 18 | pattern: /^https?:/, 19 | attrs: { 20 | class: 'external-link', 21 | target: '_blank', 22 | }, 23 | }, 24 | ], 25 | ], 26 | }, 27 | }, 28 | ], 29 | }); 30 | 31 | config.module.rules.push({ 32 | test: /\.(ts|tsx)$/, 33 | use: [ 34 | { 35 | loader: require.resolve('babel-loader'), 36 | options: { 37 | presets: ['@babel/env', '@babel/typescript', '@vue/jsx'], 38 | }, 39 | }, 40 | ], 41 | }); 42 | 43 | config.resolve.extensions.push('.ts', '.tsx'); 44 | return config; 45 | }; 46 | -------------------------------------------------------------------------------- /src/__tests__/useCounter.test.ts: -------------------------------------------------------------------------------- 1 | import { useCounter } from '..'; 2 | import renderHook from '../util/renderHook'; 3 | 4 | describe('useCounter', () => { 5 | it('should be defined', () => { 6 | expect(useCounter).toBeDefined(); 7 | }); 8 | 9 | it('should be update counter', () => { 10 | renderHook(() => { 11 | const [count, { inc, dec, get, set, reset }] = useCounter(); 12 | 13 | expect(count.value).toBe(0); 14 | expect(get()).toBe(0); 15 | inc(); 16 | expect(count.value).toBe(1); 17 | expect(get()).toBe(1); 18 | inc(2); 19 | expect(count.value).toBe(3); 20 | expect(get()).toBe(3); 21 | dec(); 22 | expect(count.value).toBe(2); 23 | expect(get()).toBe(2); 24 | dec(5); 25 | expect(count.value).toBe(-3); 26 | expect(get()).toBe(-3); 27 | set(100); 28 | expect(count.value).toBe(100); 29 | expect(get()).toBe(100); 30 | reset(); 31 | expect(count.value).toBe(0); 32 | expect(get()).toBe(0); 33 | reset(25); 34 | expect(count.value).toBe(25); 35 | expect(get()).toBe(25); 36 | reset(); 37 | expect(count.value).toBe(25); 38 | expect(get()).toBe(25); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/__stories__/useGetters.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent } from '@vue/composition-api'; 6 | import { useStore, useGetters } from '..'; 7 | import { ShowDocs } from './components'; 8 | import { createStore } from '../mocks'; 9 | 10 | type Inject = { 11 | plusOne: number; 12 | minusOne: number; 13 | }; 14 | 15 | const Docs = () => ; 16 | 17 | const Demo = createComponent({ 18 | store: createStore(), 19 | 20 | setup() { 21 | const store = useStore(); 22 | const getters = { 23 | ...useGetters(['plusOne']), 24 | ...useGetters('test', ['minusOne']), 25 | }; 26 | 27 | store.value.dispatch('incrementAsync'); 28 | store.value.dispatch('test/decrementAsync'); 29 | 30 | return { ...getters }; 31 | }, 32 | 33 | render(this: Vue & Inject) { 34 | const { plusOne, minusOne } = this; 35 | return ( 36 |
37 |
plusOne: {plusOne}
38 |
test/minusOne: {minusOne}
39 |
40 | ); 41 | }, 42 | }); 43 | 44 | storiesOf('useGetters', module) 45 | // @ts-ignore 46 | .add('docs', () => Docs) 47 | .add('demo', () => Demo); 48 | -------------------------------------------------------------------------------- /src/__tests__/useActions.test.ts: -------------------------------------------------------------------------------- 1 | import { useActions } from '..'; 2 | import renderHook from '../util/renderHook'; 3 | 4 | interface InjectActions { 5 | incrementAsync: (delay?: number) => void; 6 | decrementAsync: (delay?: number) => void; 7 | } 8 | 9 | describe('useActions', () => { 10 | it('should be defined', () => { 11 | expect(useActions).toBeDefined(); 12 | }); 13 | 14 | it('should be defined actions', () => { 15 | const { vm } = renderHook(() => ({ 16 | ...useActions(['incrementAsync']), 17 | ...useActions('test', ['decrementAsync']), 18 | })); 19 | 20 | expect(vm.incrementAsync).toBeDefined(); 21 | expect(vm.decrementAsync).toBeDefined(); 22 | }); 23 | 24 | it('should async update count state', () => { 25 | const { vm } = renderHook(() => ({ 26 | ...useActions(['incrementAsync']), 27 | ...useActions('test', ['decrementAsync']), 28 | })); 29 | 30 | expect(vm.$store.state.count).toBe(0); 31 | expect(vm.$store.state.test.count).toBe(0); 32 | 33 | jest.useFakeTimers(); 34 | 35 | vm.incrementAsync(0); 36 | vm.decrementAsync(0); 37 | 38 | setTimeout(() => { 39 | expect(vm.$store.state.count).toBe(1); 40 | expect(vm.$store.state.test.count).toBe(-1); 41 | }); 42 | 43 | jest.runAllTimers(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/__tests__/useMedia.test.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted } from '@vue/composition-api'; 2 | import { useMedia } from '..'; 3 | import renderHook from '../util/renderHook'; 4 | 5 | const matchMediaMock = require('match-media-mock').create(); 6 | 7 | matchMediaMock.setConfig({ type: 'screen', width: 1280, height: 800 }); 8 | window.matchMedia = matchMediaMock; 9 | 10 | describe('useMedia', () => { 11 | it('should be defined', () => { 12 | expect(useMedia).toBeDefined(); 13 | }); 14 | 15 | it('should update matches', () => { 16 | const { vm } = renderHook(() => { 17 | const pc = useMedia('(min-width: 768px)'); 18 | const sp = useMedia('(max-width: 768px)'); 19 | expect(pc.value).toBe(false); 20 | expect(sp.value).toBe(false); 21 | 22 | onMounted(() => { 23 | expect(pc.value).toBe(true); 24 | expect(sp.value).toBe(false); 25 | 26 | matchMediaMock.setConfig({ type: 'screen', width: 375, height: 667 }); 27 | 28 | expect(pc.value).toBe(false); 29 | expect(sp.value).toBe(true); 30 | }); 31 | 32 | onUnmounted(() => { 33 | matchMediaMock.setConfig({ type: 'screen', width: 1280, height: 800 }); 34 | 35 | expect(pc.value).toBe(false); 36 | expect(sp.value).toBe(true); 37 | }); 38 | }); 39 | 40 | vm.$destroy(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/__stories__/usePrevious.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint no-plusplus: off, import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent, ref } from '@vue/composition-api'; 6 | import { usePrevious } from '..'; 7 | import { ShowDocs } from './components'; 8 | 9 | type Inject = { 10 | count: number; 11 | prevCount: number; 12 | increment: () => void; 13 | decrement: () => void; 14 | }; 15 | 16 | const Docs = () => ; 17 | 18 | const Demo = createComponent({ 19 | setup() { 20 | const count = ref(0); 21 | const prevCount = usePrevious(count); 22 | 23 | const increment = () => count.value++; 24 | const decrement = () => count.value--; 25 | 26 | return { count, prevCount, increment, decrement }; 27 | }, 28 | 29 | render(this: Vue & Inject) { 30 | const { count, prevCount } = this; 31 | return ( 32 |
33 |
now: {count}
34 |
before: {String(prevCount)}
35 | 36 | 37 |
38 | ); 39 | }, 40 | }); 41 | 42 | storiesOf('usePrevious', module) 43 | // @ts-ignore 44 | .add('docs', () => Docs) 45 | .add('demo', () => Demo); 46 | -------------------------------------------------------------------------------- /docs/useDate.md: -------------------------------------------------------------------------------- 1 | # useDate 2 | 3 | Vue hook that process date via [`dayjs`](https://github.com/iamkun/dayjs). 4 | 5 | ## Usage 6 | 7 | ```jsx {6,11} 8 | import { createComponent } from '@vue/composition-api'; 9 | import { useDate } from '@u3u/vue-hooks'; 10 | 11 | const Demo = createComponent({ 12 | setup() { 13 | const date = useDate(Date.now(), 1000); 14 | return { date }; 15 | }, 16 | 17 | render() { 18 | const { date } = this; 19 | return ( 20 |
    21 |
  • Date: {date.toString()}
  • 22 |
  • Standard Format: {date.format('YYYY-MM-DD HH:mm:ss')}
  • 23 |
  • Relative Time: {date.from(date.add(1, 'd'))}
  • 24 |
25 | ); 26 | }, 27 | }); 28 | ``` 29 | 30 | ## Reference 31 | 32 | ```typescript 33 | function useDate(date?: dayjs.ConfigType, interval?: number): Ref; 34 | ``` 35 | 36 | ### `Arguments` 37 | 38 | - `date` 39 | 40 | Date to process. 41 | 42 | - Type: [`dayjs.ConfigType`](https://github.com/iamkun/dayjs/blob/19affc84bbec84bad840e310b390db5f92b2499a/types/index.d.ts#L5) 43 | - Default: `Date.now()` 44 | 45 | - `interval` 46 | 47 | The update interval of the date, if it is `0`, it will not be updated. 48 | 49 | - Type: `number` 50 | - Default: `0` 51 | 52 | ### `ReturnValue` 53 | 54 | - [`Ref`](https://github.com/iamkun/dayjs/blob/19affc84bbec84bad840e310b390db5f92b2499a/types/index.d.ts#L15-L95) 55 | -------------------------------------------------------------------------------- /docs/useRouter.md: -------------------------------------------------------------------------------- 1 | # useRouter 2 | 3 | > You need to [use a plugin](https://github.com/u3u/vue-hooks#usage) before using this hook. 4 | 5 | Vue hook for [vue-router](https://router.vuejs.org). 6 | 7 | ## Usage 8 | 9 | ```jsx {6,11,19,23} 10 | import { createComponent, onMounted, onUnmounted } from '@vue/composition-api'; 11 | import { useRouter } from '@u3u/vue-hooks'; 12 | 13 | const Demo = createComponent({ 14 | setup() { 15 | const { route, router } = useRouter(); 16 | let timerId; 17 | 18 | onMounted(() => { 19 | timerId = window.setInterval(() => { 20 | router.replace(route.value.meta.next); 21 | }, 5e3); 22 | }); 23 | 24 | onUnmounted(() => { 25 | window.clearInterval(timerId); 26 | }); 27 | 28 | return { route }; 29 | }, 30 | 31 | render() { 32 | const { route } = this; 33 | return ( 34 |
    35 | {Object.keys(route).map((key) => ( 36 |
  • 37 | {key}:
    {JSON.stringify(route[key], null, 2)}
    38 |
  • 39 | ))} 40 |
41 | ); 42 | }, 43 | }); 44 | ``` 45 | 46 | ## Reference 47 | 48 | ```typescript 49 | function useRouter(): { 50 | route: Ref; 51 | router: VueRouter; 52 | }; 53 | ``` 54 | 55 | ### `ReturnValue` 56 | 57 | - `route`: [`Ref`](https://router.vuejs.org/api/#route-object-properties) 58 | - `router`: [`VueRouter`](https://router.vuejs.org/api/#router-instance-methods) 59 | -------------------------------------------------------------------------------- /docs/useStore.md: -------------------------------------------------------------------------------- 1 | # useStore 2 | 3 | > You need to [use a plugin](https://github.com/u3u/vue-hooks#usage) before using this hook. 4 | 5 | Vue hook for [vuex](https://vuex.vuejs.org). 6 | 7 | ## Usage 8 | 9 | ```jsx {6,12,16} 10 | import { createComponent, computed } from '@vue/composition-api'; 11 | import { useStore } from '@u3u/vue-hooks'; 12 | 13 | const Demo = createComponent({ 14 | setup() { 15 | const store = useStore(); 16 | const plusOne = computed(() => store.value.state.count + 1); 17 | 18 | const increment = () => store.value.commit('increment'); 19 | const incrementAsync = () => store.value.dispatch('incrementAsync'); 20 | 21 | return { store, plusOne, increment, incrementAsync }; 22 | }, 23 | 24 | render() { 25 | const { store, plusOne } = this; 26 | return ( 27 |
28 |
count: {store.state.count}
29 |
plusOne: {plusOne}
30 | 31 | 32 |
33 | ); 34 | }, 35 | }); 36 | ``` 37 | 38 | ## Reference 39 | 40 | ```typescript 41 | function useStore(): Ref>; 42 | ``` 43 | 44 | ### `ReturnValue` 45 | 46 | - [`Ref>`](https://vuex.vuejs.org/api/#vuex-store-instance-properties) 47 | 48 | _[`TState`](https://www.typescriptlang.org/docs/handbook/generics.html) is used to specify the type of `store.state`_ 49 | -------------------------------------------------------------------------------- /docs/useWindowSize.md: -------------------------------------------------------------------------------- 1 | # useWindowSize 2 | 3 | Vue hook that tracks dimensions of the browser window. 4 | 5 | ## Usage 6 | 7 | ```jsx {6,11} 8 | import { createComponent } from '@vue/composition-api'; 9 | import { useWindowSize } from '@u3u/vue-hooks'; 10 | 11 | const Demo = createComponent({ 12 | setup() { 13 | const { width, height } = useWindowSize(); 14 | return { width, height }; 15 | }, 16 | 17 | render() { 18 | const { width, height } = this; 19 | return ( 20 |
21 |
width: {width}px
22 |
height: {height}px
23 |
24 | ); 25 | }, 26 | }); 27 | ``` 28 | 29 | ## Reference 30 | 31 | ```typescript 32 | function useWindowSize(): { 33 | width: Ref; 34 | height: Ref; 35 | widthPixel: Ref; 36 | heightPixel: Ref; 37 | }; 38 | ``` 39 | 40 | ### `ReturnValue` 41 | 42 | - `width`: [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) 43 | - `height`: [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) 44 | - `widthPixel`: [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) 45 | - `heightPixel`: [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) 46 | -------------------------------------------------------------------------------- /src/__tests__/useDate.test.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted } from '@vue/composition-api'; 2 | import { useDate, dayjs } from '..'; 3 | import renderHook from '../util/renderHook'; 4 | 5 | describe('useDate', () => { 6 | it('should be defined', () => { 7 | expect(useDate).toBeDefined(); 8 | }); 9 | 10 | it('should be have default date', () => { 11 | renderHook(() => { 12 | const date = useDate(); 13 | expect(date.value).toBeInstanceOf(dayjs); 14 | }); 15 | }); 16 | 17 | it('should be same date', () => { 18 | renderHook(() => { 19 | const date1 = useDate('2019-05-20'); 20 | const date2 = useDate('2019-05-21'); 21 | expect(date2.value.add(-1, 'day').isSame(date1.value)).toBe(true); 22 | }); 23 | }); 24 | 25 | it('should update date', () => { 26 | jest.useFakeTimers(); 27 | const { vm } = renderHook(() => { 28 | const date = useDate(Date.now(), 1000); 29 | let timerId; 30 | 31 | onMounted(() => { 32 | timerId = setInterval(() => { 33 | expect(date.value.isSame(Date.now(), 's')).toBe(true); 34 | }, 1000); 35 | jest.runOnlyPendingTimers(); 36 | }); 37 | 38 | onUnmounted(() => { 39 | clearInterval(timerId); 40 | expect(jest.getTimerCount()).toBe(0); 41 | }); 42 | }); 43 | 44 | setTimeout(() => vm.$destroy(), 3000); 45 | expect(jest.getTimerCount()).toBe(3); 46 | jest.runOnlyPendingTimers(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/mocks/store.ts: -------------------------------------------------------------------------------- 1 | import { VueConstructor } from 'vue'; 2 | import Vuex, { Module } from 'vuex'; 3 | 4 | export interface CounterState { 5 | count: number; 6 | } 7 | 8 | export default function createStore(Vue?: VueConstructor) { 9 | if (Vue) Vue.use(Vuex); 10 | return new Vuex.Store({ 11 | state: { 12 | count: 0, 13 | }, 14 | 15 | actions: { 16 | incrementAsync(context, delay = 1000) { 17 | setTimeout(() => { 18 | context.commit('increment'); 19 | }, delay); 20 | }, 21 | }, 22 | 23 | mutations: { 24 | increment(state) { 25 | Object.assign(state, { count: state.count + 1 }); 26 | }, 27 | }, 28 | 29 | getters: { 30 | plusOne: (state) => state.count + 1, 31 | }, 32 | 33 | modules: { 34 | test: { 35 | namespaced: true, 36 | 37 | state: { 38 | count: 0, 39 | }, 40 | 41 | actions: { 42 | decrementAsync(context, delay = 1000) { 43 | setTimeout(() => { 44 | context.commit('decrement'); 45 | }, delay); 46 | }, 47 | }, 48 | 49 | mutations: { 50 | decrement(state) { 51 | Object.assign(state, { count: state.count - 1 }); 52 | }, 53 | }, 54 | 55 | getters: { 56 | minusOne: (state) => state.count - 1, 57 | }, 58 | } as Module, 59 | }, 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /docs/usePrevious.md: -------------------------------------------------------------------------------- 1 | # usePrevious 2 | 3 | Vue hook that returns the previous value. 4 | 5 | ## Usage 6 | 7 | ```jsx {7,12,16} 8 | import { createComponent, ref } from '@vue/composition-api'; 9 | import { usePrevious } from '@u3u/vue-hooks'; 10 | 11 | const Demo = createComponent({ 12 | setup() { 13 | const count = ref(0); 14 | const prevCount = usePrevious(count); 15 | 16 | const increment = () => count.value++; 17 | const decrement = () => count.value--; 18 | 19 | return { count, prevCount, increment, decrement }; 20 | }, 21 | 22 | render() { 23 | const { count, prevCount } = this; 24 | return ( 25 |
26 |
now: {count}
27 |
before: {String(prevCount)}
28 | 29 | 30 |
31 | ); 32 | }, 33 | }); 34 | ``` 35 | 36 | ## Reference 37 | 38 | ```typescript 39 | function usePrevious(state: Ref | (() => T)): Ref; 40 | ``` 41 | 42 | ### `Arguments` 43 | 44 | - `state` 45 | 46 | `props` or `Ref` 47 | 48 | - Type: [`Ref | (() => T)`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) 49 | 50 | ### `ReturnValue` 51 | 52 | - [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) 53 | 54 | _[`T`](https://www.typescriptlang.org/docs/handbook/generics.html) depends on your `arguments` type_ 55 | -------------------------------------------------------------------------------- /src/__tests__/usePrevious.test.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { ref, reactive } from '@vue/composition-api'; 3 | import { usePrevious } from '..'; 4 | import renderHook from '../util/renderHook'; 5 | 6 | describe('usePrevious', () => { 7 | it('should be defined', () => { 8 | expect(usePrevious).toBeDefined(); 9 | }); 10 | 11 | it('should be previous wrapper count', () => { 12 | renderHook(async () => { 13 | const count = ref(0); 14 | const prevCount = usePrevious(count); 15 | 16 | expect(count.value).toBe(0); 17 | expect(prevCount.value).toBeUndefined(); 18 | 19 | count.value += 1; 20 | 21 | await Vue.nextTick(); 22 | expect(count.value).toBe(1); 23 | expect(prevCount.value).toBe(0); 24 | 25 | count.value -= 1; 26 | 27 | await Vue.nextTick(); 28 | expect(count.value).toBe(0); 29 | expect(prevCount.value).toBe(1); 30 | }); 31 | }); 32 | 33 | it('should be previous state count', () => { 34 | renderHook(async () => { 35 | const state = reactive({ count: 0 }); 36 | const prevCount = usePrevious(() => state.count); 37 | 38 | expect(state.count).toBe(0); 39 | expect(prevCount.value).toBeUndefined(); 40 | 41 | state.count += 1; 42 | 43 | await Vue.nextTick(); 44 | expect(state.count).toBe(1); 45 | expect(prevCount.value).toBe(0); 46 | 47 | state.count -= 1; 48 | 49 | await Vue.nextTick(); 50 | expect(state.count).toBe(0); 51 | expect(prevCount.value).toBe(1); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/__stories__/useDate.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent } from '@vue/composition-api'; 6 | import { useDate, dayjs } from '..'; 7 | import { ShowDocs } from './components'; 8 | 9 | type Inject = { 10 | date: dayjs.Dayjs; 11 | }; 12 | 13 | const Docs = () => ; 14 | 15 | const Demo = createComponent({ 16 | setup() { 17 | const date = useDate(Date.now(), 1000); 18 | return { date }; 19 | }, 20 | 21 | render(this: Vue & Inject) { 22 | const { date } = this; 23 | const symbols = [1, -1]; 24 | const unitTypeShort = ['d', 'M', 'y', 'h', 'm', 's', 'ms']; 25 | const randomSymbolIndex = Math.floor(Math.random() * symbols.length); 26 | const randomUnitTypeIndex = Math.floor(Math.random() * unitTypeShort.length); // prettier-ignore 27 | const inc = (randomUnitTypeIndex + 1) * symbols[randomSymbolIndex]; 28 | const unit = unitTypeShort[randomUnitTypeIndex] as dayjs.UnitTypeShort; 29 | 30 | return ( 31 |
    32 |
  • Date: {date.toString()}
  • 33 |
  • Standard Format: {date.format('YYYY-MM-DD HH:mm:ss')}
  • 34 |
  • 35 | Relative Time ({inc} {unit}): {date.from(date.add(inc, unit))} 36 |
  • 37 |
38 | ); 39 | }, 40 | }); 41 | 42 | storiesOf('useDate', module) 43 | // @ts-ignore 44 | .add('docs', () => Docs) 45 | .add('demo', () => Demo); 46 | -------------------------------------------------------------------------------- /src/__stories__/useCounter.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent } from '@vue/composition-api'; 6 | import { useCounter } from '..'; 7 | import { ShowDocs } from './components'; 8 | 9 | type Inject = { 10 | count: number; 11 | inc: Function; 12 | dec: Function; 13 | set: Function; 14 | reset: Function; 15 | }; 16 | 17 | const Docs = () => ; 18 | 19 | const Demo = createComponent({ 20 | setup() { 21 | const [count, { inc, dec, set, reset }] = useCounter(); 22 | return { 23 | count, 24 | inc, 25 | dec, 26 | set, 27 | reset, 28 | }; 29 | }, 30 | 31 | render(this: Vue & Inject) { 32 | const { count, inc, dec, set, reset } = this; 33 | return ( 34 |
35 |
count: {count}
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | ); 45 | }, 46 | }); 47 | 48 | storiesOf('useCounter', module) 49 | // @ts-ignore 50 | .add('docs', () => Docs) 51 | .add('demo', () => Demo); 52 | -------------------------------------------------------------------------------- /src/helpers/vuex/index.ts: -------------------------------------------------------------------------------- 1 | import { computed } from '@vue/composition-api'; 2 | import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'; 3 | import { useState, useGetters, useMutations, useActions } from './interface'; 4 | import { getRuntimeVM } from '../../util/runtime'; 5 | 6 | export enum Helper { 7 | State, 8 | Getters, 9 | Mutations, 10 | Actions, 11 | } 12 | 13 | type Helpers = useState | useGetters | useMutations | useActions; 14 | 15 | function handleComputed(mappedFn: Function) { 16 | // TypeError: Cannot read property '_modulesNamespaceMap' of undefined 17 | // You must get `runtimeVM` in real time in the calculation properties. 18 | return computed(() => mappedFn.call(getRuntimeVM())); 19 | } 20 | 21 | function handleMethods(mappedFn: Function): T { 22 | return mappedFn.bind(getRuntimeVM()); 23 | } 24 | 25 | const helpers = { 26 | [Helper.State]: { fn: mapState, handler: handleComputed }, 27 | [Helper.Getters]: { fn: mapGetters, handler: handleComputed }, 28 | [Helper.Mutations]: { fn: mapMutations, handler: handleMethods }, 29 | [Helper.Actions]: { fn: mapActions, handler: handleMethods }, 30 | }; 31 | 32 | export default function createVuexHelper(h: Helper) { 33 | const helper = helpers[h]; 34 | 35 | return ((...args) => { 36 | // @ts-ignore 37 | const mapper = (helper.fn as T)(...args); 38 | const dictionary = {}; 39 | Object.keys(mapper).forEach((key) => { 40 | dictionary[key] = helper.handler(mapper[key]); 41 | }); 42 | 43 | return dictionary; 44 | }) as T; 45 | } 46 | 47 | export * from './interface'; 48 | -------------------------------------------------------------------------------- /src/__stories__/useStore.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { Store } from 'vuex'; 5 | import { storiesOf } from '@storybook/vue'; 6 | import { createComponent, computed } from '@vue/composition-api'; 7 | import { useStore } from '..'; 8 | import { ShowDocs } from './components'; 9 | import { createStore } from '../mocks'; 10 | import { CounterState } from '../mocks/store'; 11 | 12 | type Inject = { 13 | store: Store; 14 | plusOne: number; 15 | increment: () => void; 16 | incrementAsync: () => void; 17 | }; 18 | 19 | const Docs = () => ; 20 | 21 | const Demo = createComponent({ 22 | store: createStore(), 23 | 24 | setup() { 25 | const store = useStore(); 26 | const plusOne = computed(() => store.value.state.count + 1); 27 | 28 | const increment = () => store.value.commit('increment'); 29 | const incrementAsync = () => store.value.dispatch('incrementAsync'); 30 | 31 | return { store, plusOne, increment, incrementAsync }; 32 | }, 33 | 34 | render(this: Vue & Inject) { 35 | const { store, plusOne } = this; 36 | return ( 37 |
38 |
count: {store.state.count}
39 |
plusOne: {plusOne}
40 | 41 | 42 |
43 | ); 44 | }, 45 | }); 46 | 47 | storiesOf('useStore', module) 48 | // @ts-ignore 49 | .add('docs', () => Docs) 50 | .add('demo', () => Demo); 51 | -------------------------------------------------------------------------------- /src/__stories__/useState.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent, computed } from '@vue/composition-api'; 6 | import { useStore, useState } from '..'; 7 | import { ShowDocs } from './components'; 8 | import { createStore } from '../mocks'; 9 | 10 | type Inject = { 11 | count: number; 12 | count2: number; 13 | plusOne: number; 14 | minusOne: number; 15 | }; 16 | 17 | const Docs = () => ; 18 | 19 | const Demo = createComponent({ 20 | store: createStore(), 21 | 22 | setup() { 23 | const store = useStore(); 24 | const state = { 25 | ...useState(['count']), 26 | ...useState('test', { count2: 'count' }), 27 | }; 28 | 29 | const plusOne = computed(() => state.count.value + 1); 30 | const minusOne = computed(() => state.count2.value - 1); 31 | 32 | store.value.dispatch('incrementAsync'); 33 | store.value.dispatch('test/decrementAsync'); 34 | 35 | return { ...state, plusOne, minusOne }; 36 | }, 37 | 38 | render(this: Vue & Inject) { 39 | const { count, count2, plusOne, minusOne } = this; 40 | return ( 41 |
42 |
count: {count}
43 |
plusOne: {plusOne}
44 |
test/count: {count2}
45 |
test/minusOne: {minusOne}
46 |
47 | ); 48 | }, 49 | }); 50 | 51 | storiesOf('useState', module) 52 | // @ts-ignore 53 | .add('docs', () => Docs) 54 | .add('demo', () => Demo); 55 | -------------------------------------------------------------------------------- /src/__tests__/useStore.test.ts: -------------------------------------------------------------------------------- 1 | import { Store } from 'vuex'; 2 | import { useStore } from '..'; 3 | import renderHook from '../util/renderHook'; 4 | 5 | describe('useStore', () => { 6 | it('should be defined', () => { 7 | expect(useStore).toBeDefined(); 8 | }); 9 | 10 | it('should have store property', () => { 11 | const { vm } = renderHook(() => ({ store: useStore() })); 12 | expect(vm).toHaveProperty('store'); 13 | }); 14 | 15 | it('should update store', () => { 16 | type Inject = { store: Store }; 17 | const { vm } = renderHook(() => ({ store: useStore() })); 18 | expect(vm.store.state.count).toBe(0); 19 | expect(vm.store.getters.plusOne).toBe(1); 20 | expect(vm.store.state.test.count).toBe(0); 21 | expect(vm.store.getters['test/minusOne']).toBe(-1); 22 | 23 | vm.store.commit('increment'); 24 | vm.store.commit('test/decrement'); 25 | 26 | expect(vm.store.state.count).toBe(1); 27 | expect(vm.store.getters.plusOne).toBe(2); 28 | expect(vm.store.state.test.count).toBe(-1); 29 | expect(vm.store.getters['test/minusOne']).toBe(-2); 30 | 31 | vm.store.dispatch('incrementAsync', 0); 32 | vm.store.dispatch('test/decrementAsync', 0); 33 | 34 | expect(vm.store.state.count).toBe(1); 35 | expect(vm.store.getters.plusOne).toBe(2); 36 | expect(vm.store.state.test.count).toBe(-1); 37 | expect(vm.store.getters['test/minusOne']).toBe(-2); 38 | 39 | setTimeout(() => { 40 | expect(vm.store.state.count).toBe(2); 41 | expect(vm.store.getters.plusOne).toBe(3); 42 | expect(vm.store.state.test.count).toBe(-2); 43 | expect(vm.store.getters['test/minusOne']).toBe(-3); 44 | }, 0); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /docs/useGetters.md: -------------------------------------------------------------------------------- 1 | # useGetters 2 | 3 | > You need to [use a plugin](https://github.com/u3u/vue-hooks#usage) before using this hook. 4 | 5 | Vue hook for [`mapGetters`](https://vuex.vuejs.org/api/#mapgetters). 6 | 7 | ## Usage 8 | 9 | ```jsx {8,9,15,19} 10 | import { createComponent } from '@vue/composition-api'; 11 | import { useStore, useGetters } from '@u3u/vue-hooks'; 12 | 13 | const Demo = createComponent({ 14 | setup() { 15 | const store = useStore(); 16 | const getters = { 17 | ...useGetters(['plusOne']), 18 | ...useGetters('test', ['minusOne']), 19 | }; 20 | 21 | store.value.dispatch('incrementAsync'); 22 | store.value.dispatch('test/decrementAsync'); 23 | 24 | return { ...getters }; 25 | }, 26 | 27 | render() { 28 | const { plusOne, minusOne } = this; 29 | return ( 30 |
31 |
plusOne: {plusOne}
32 |
test/minusOne: {minusOne}
33 |
34 | ); 35 | }, 36 | }); 37 | ``` 38 | 39 | ## Reference 40 | 41 | ```typescript 42 | function useGetters( 43 | namespace?: string, 44 | map: Array | Object, 45 | ): Object>; 46 | ``` 47 | 48 | > The usage of the `useGetters` hook is exactly the same as the usage of [`mapGetters`](https://vuex.vuejs.org/api/#mapgetters) (the same parameters) 49 | > The only difference is that the return value of `useGetters` is the [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) dictionary. For each item in the dictionary, you need to use `.value` to get its actual value. 50 | 51 | _Please refer to the documentation of [`mapGetters`](https://vuex.vuejs.org/api/#mapgetters) for details._ 52 | -------------------------------------------------------------------------------- /docs/useCounter.md: -------------------------------------------------------------------------------- 1 | # useCounter 2 | 3 | Vue hook that tracks a numeric value. 4 | 5 | ## Usage 6 | 7 | ```jsx {6,17} 8 | import { createComponent } from '@vue/composition-api'; 9 | import { useCounter } from '@u3u/vue-hooks'; 10 | 11 | const Demo = createComponent({ 12 | setup() { 13 | const [count, { inc, dec, set, reset }] = useCounter(); 14 | return { 15 | count, 16 | inc, 17 | dec, 18 | set, 19 | reset, 20 | }; 21 | }, 22 | 23 | render() { 24 | const { count, inc, dec, set, reset } = this; 25 | return ( 26 |
27 |
count: {count}
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | ); 37 | }, 38 | }); 39 | ``` 40 | 41 | ## Reference 42 | 43 | ```typescript {6-10} 44 | function useCounter( 45 | initialValue?: number, 46 | ): [ 47 | Ref, 48 | { 49 | inc: (delta?: number) => number; 50 | dec: (delta?: number) => number; 51 | get: () => number; 52 | set: (val: number) => number; 53 | reset: (val?: number) => number; 54 | }, 55 | ]; 56 | ``` 57 | 58 | ### `Arguments` 59 | 60 | - `initialValue` 61 | 62 | Initial value of the counter. 63 | 64 | - Type: `number` 65 | - Default: `0` 66 | 67 | ### `ReturnValue` 68 | 69 | 0. [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) 70 | 1. `Actions` 71 | -------------------------------------------------------------------------------- /docs/useState.md: -------------------------------------------------------------------------------- 1 | # useState 2 | 3 | > You need to [use a plugin](https://github.com/u3u/vue-hooks#usage) before using this hook. 4 | 5 | Vue hook for [`mapState`](https://vuex.vuejs.org/api/#mapstate). 6 | 7 | ## Usage 8 | 9 | ```jsx {7,8,14,18} 10 | import { createComponent, computed } from '@vue/composition-api'; 11 | import { useState } from '@u3u/vue-hooks'; 12 | 13 | const Demo = createComponent({ 14 | setup() { 15 | const state = { 16 | ...useState(['count']), 17 | ...useState('test', { count2: 'count' }), 18 | }; 19 | 20 | const plusOne = computed(() => state.count.value + 1); 21 | const minusOne = computed(() => state.count2.value - 1); 22 | 23 | return { ...state, plusOne, minusOne }; 24 | }, 25 | 26 | render() { 27 | const { count, count2, plusOne, minusOne } = this; 28 | return ( 29 |
30 |
count: {count}
31 |
plusOne: {plusOne}
32 |
test/count: {count2}
33 |
test/minusOne: {minusOne}
34 |
35 | ); 36 | }, 37 | }); 38 | ``` 39 | 40 | ## Reference 41 | 42 | ```typescript 43 | function useState( 44 | namespace?: string, 45 | map: Array | Object, 46 | ): Object>; 47 | ``` 48 | 49 | > The usage of the `useState` hook is exactly the same as the usage of [`mapState`](https://vuex.vuejs.org/api/#mapstate) (the same parameters) 50 | > The only difference is that the return value of `useState` is the [`Ref`](https://github.com/vuejs/composition-api/blob/a7a68bda5d32139c6cf05b45e385cf8d4ce86707/src/reactivity/ref.ts#L8-L10) dictionary. For each item in the dictionary, you need to use `.value` to get its actual value. 51 | 52 | _Please refer to the documentation of [`mapState`](https://vuex.vuejs.org/api/#mapstate) for details._ 53 | -------------------------------------------------------------------------------- /.storybook/style.css: -------------------------------------------------------------------------------- 1 | .markdown-body, 2 | .sb-show-main { 3 | color: rgba(255, 255, 255, 0.8); 4 | } 5 | 6 | .markdown-body { 7 | box-sizing: border-box; 8 | min-width: 200px; 9 | max-width: 980px; 10 | margin: 0 auto; 11 | padding: 45px; 12 | } 13 | 14 | @media (max-width: 767px) { 15 | .markdown-body { 16 | padding: 15px; 17 | } 18 | } 19 | 20 | .markdown-body h1, 21 | .markdown-body h2 { 22 | border-bottom-color: rgba(255, 255, 255, 0.1); 23 | } 24 | 25 | .markdown-body pre { 26 | position: relative; 27 | } 28 | 29 | .sb-show-main a, 30 | .markdown-body a { 31 | color: #1ea7fd; 32 | transition: color 300ms ease; 33 | } 34 | 35 | a.router-link-exact-active { 36 | color: #999; 37 | text-decoration: none; 38 | pointer-events: none; 39 | } 40 | 41 | .fade-enter-active { 42 | animation: fade 300ms ease; 43 | } 44 | 45 | .fade-leave-active { 46 | animation: fade 300ms ease reverse; 47 | } 48 | 49 | @keyframes fade { 50 | from { 51 | opacity: 0; 52 | } 53 | 54 | to { 55 | opacity: 1; 56 | } 57 | } 58 | 59 | .highlighted-line { 60 | display: block; 61 | margin: 0 -1em; 62 | padding: 0 1em; 63 | background-color: #3d3d3d; 64 | } 65 | 66 | pre[class*='language-'], 67 | code[class*='language-'] { 68 | font-family: Menlo, Andale Mono, Consolas, Courier, monospace; 69 | font-size: 85%; 70 | } 71 | 72 | code[class*='language-']::before { 73 | position: absolute; 74 | z-index: 3; 75 | top: 0.8em; 76 | right: 1em; 77 | font-size: 0.75rem; 78 | color: rgba(255, 255, 255, 0.4); 79 | } 80 | 81 | code[class*='language-javascript']::before { 82 | content: 'js'; 83 | } 84 | 85 | code[class*='language-jsx']::before { 86 | content: 'jsx'; 87 | } 88 | 89 | code[class*='language-typescript']::before { 90 | content: 'ts'; 91 | } 92 | 93 | code[class*='language-tsx']::before { 94 | content: 'tsx'; 95 | } 96 | -------------------------------------------------------------------------------- /docs/useMutations.md: -------------------------------------------------------------------------------- 1 | # useMutations 2 | 3 | > You need to [use a plugin](https://github.com/u3u/vue-hooks#usage) before using this hook. 4 | 5 | Vue hook for [`mapMutations`](https://vuex.vuejs.org/api/#mapmutations). 6 | 7 | ## Usage 8 | 9 | ```jsx {17,18,24,29,36,37} 10 | import { createComponent } from '@vue/composition-api'; 11 | import { useState, useGetters, useMutations } from '@u3u/vue-hooks'; 12 | 13 | const Demo = createComponent({ 14 | setup() { 15 | const state = { 16 | ...useState(['count']), 17 | ...useState('test', { count2: 'count' }), 18 | }; 19 | 20 | const getters = { 21 | ...useGetters(['plusOne']), 22 | ...useGetters('test', ['minusOne']), 23 | }; 24 | 25 | const mutations = { 26 | ...useMutations(['increment']), 27 | ...useMutations('test', ['decrement']), 28 | }; 29 | 30 | return { 31 | ...state, 32 | ...getters, 33 | ...mutations, 34 | }; 35 | }, 36 | 37 | render() { 38 | const { count, count2, plusOne, minusOne } = this; 39 | return ( 40 |
41 |
count: {count}
42 |
plusOne: {plusOne}
43 |
test/count: {count2}
44 |
test/minusOne: {minusOne}
45 | 46 | 47 |
48 | ); 49 | }, 50 | }); 51 | ``` 52 | 53 | ## Reference 54 | 55 | ```typescript 56 | function mapMutations( 57 | namespace?: string, 58 | map: Array | Object, 59 | ): Object; 60 | ``` 61 | 62 | > The usage of the `useMutations` hook is exactly the same as the usage of [`mapMutations`](https://vuex.vuejs.org/api/#mapmutations). 63 | 64 | _Please refer to the documentation of [`mapMutations`](https://vuex.vuejs.org/api/#mapmutations) for details._ 65 | -------------------------------------------------------------------------------- /src/__stories__/useMutations.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent } from '@vue/composition-api'; 6 | import { useState, useGetters, useMutations } from '..'; 7 | import { ShowDocs } from './components'; 8 | import { createStore } from '../mocks'; 9 | 10 | type Inject = { 11 | count: number; 12 | count2: number; 13 | plusOne: number; 14 | minusOne: number; 15 | increment: () => void; 16 | decrement: () => void; 17 | }; 18 | 19 | const Docs = () => ; 20 | 21 | const Demo = createComponent({ 22 | store: createStore(), 23 | 24 | setup() { 25 | const state = { 26 | ...useState(['count']), 27 | ...useState('test', { count2: 'count' }), 28 | }; 29 | 30 | const getters = { 31 | ...useGetters(['plusOne']), 32 | ...useGetters('test', ['minusOne']), 33 | }; 34 | 35 | const mutations = { 36 | ...useMutations(['increment']), 37 | ...useMutations('test', ['decrement']), 38 | }; 39 | 40 | return { 41 | ...state, 42 | ...getters, 43 | ...mutations, 44 | }; 45 | }, 46 | 47 | render(this: Vue & Inject) { 48 | const { count, count2, plusOne, minusOne } = this; 49 | return ( 50 |
51 |
count: {count}
52 |
plusOne: {plusOne}
53 |
test/count: {count2}
54 |
test/minusOne: {minusOne}
55 | 56 | 57 |
58 | ); 59 | }, 60 | }); 61 | 62 | storiesOf('useMutations', module) 63 | // @ts-ignore 64 | .add('docs', () => Docs) 65 | .add('demo', () => Demo); 66 | -------------------------------------------------------------------------------- /docs/useActions.md: -------------------------------------------------------------------------------- 1 | # useActions 2 | 3 | > You need to [use a plugin](https://github.com/u3u/vue-hooks#usage) before using this hook. 4 | 5 | Vue hook for [`mapActions`](https://vuex.vuejs.org/api/#mapactions). 6 | 7 | ## Usage 8 | 9 | ```jsx {17,18,24,29,36,37} 10 | import { createComponent } from '@vue/composition-api'; 11 | import { useState, useGetters, useActions } from '@u3u/vue-hooks'; 12 | 13 | const Demo = createComponent({ 14 | setup() { 15 | const state = { 16 | ...useState(['count']), 17 | ...useState('test', { count2: 'count' }), 18 | }; 19 | 20 | const getters = { 21 | ...useGetters(['plusOne']), 22 | ...useGetters('test', ['minusOne']), 23 | }; 24 | 25 | const actions = { 26 | ...useActions(['incrementAsync']), 27 | ...useActions('test', ['decrementAsync']), 28 | }; 29 | 30 | return { 31 | ...state, 32 | ...getters, 33 | ...actions, 34 | }; 35 | }, 36 | 37 | render() { 38 | const { count, count2, plusOne, minusOne } = this; 39 | return ( 40 |
41 |
count: {count}
42 |
plusOne: {plusOne}
43 |
test/count: {count2}
44 |
test/minusOne: {minusOne}
45 | 46 | 49 |
50 | ); 51 | }, 52 | }); 53 | ``` 54 | 55 | ## Reference 56 | 57 | ```typescript 58 | function useActions( 59 | namespace?: string, 60 | map: Array | Object, 61 | ): Object; 62 | ``` 63 | 64 | > The usage of the `useActions` hook is exactly the same as the usage of [`mapActions`](https://vuex.vuejs.org/api/#mapactions). 65 | 66 | _Please refer to the documentation of [`mapActions`](https://vuex.vuejs.org/api/#mapactions) for details._ 67 | -------------------------------------------------------------------------------- /src/__tests__/useWindowSize.test.ts: -------------------------------------------------------------------------------- 1 | import { useWindowSize } from '..'; 2 | import renderHook from '../util/renderHook'; 3 | 4 | interface InjectWindowSize { 5 | width: number; 6 | height: number; 7 | widthPixel: string; 8 | heightPixel: string; 9 | } 10 | 11 | enum SizeType { 12 | width = 'width', 13 | height = 'height', 14 | } 15 | 16 | const resize = { 17 | [SizeType.width](value: number) { 18 | (window.innerWidth as number) = value; 19 | }, 20 | [SizeType.height](value: number) { 21 | (window.innerHeight as number) = value; 22 | }, 23 | }; 24 | 25 | // simulate window resize 26 | function triggerResize(type: SizeType, value = 0) { 27 | const dispatchResize = resize[type]; 28 | dispatchResize(value); 29 | window.dispatchEvent(new Event('resize')); 30 | } 31 | 32 | describe('useWindowSize', () => { 33 | it('should be defined', () => { 34 | expect(useWindowSize).toBeDefined(); 35 | }); 36 | 37 | it('should update width', () => { 38 | const { vm } = renderHook(useWindowSize); 39 | triggerResize(SizeType.width, 1280); 40 | expect(vm.width).toBe(1280); 41 | expect(vm.widthPixel).toBe('1280px'); 42 | triggerResize(SizeType.width, 375); 43 | expect(vm.width).toBe(375); 44 | expect(vm.widthPixel).toBe('375px'); 45 | }); 46 | 47 | it('should update height', () => { 48 | const { vm } = renderHook(useWindowSize); 49 | triggerResize(SizeType.height, 800); 50 | expect(vm.height).toBe(800); 51 | expect(vm.heightPixel).toBe('800px'); 52 | triggerResize(SizeType.height, 667); 53 | expect(vm.height).toBe(667); 54 | expect(vm.heightPixel).toBe('667px'); 55 | }); 56 | 57 | it('should remove the listener', () => { 58 | const { vm } = renderHook(useWindowSize); 59 | triggerResize(SizeType.width, 750); 60 | vm.$destroy(); 61 | triggerResize(SizeType.width, 375); 62 | expect(vm.width).toBe(750); 63 | expect(vm.widthPixel).toBe('750px'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/helpers/vuex/interface.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Dispatch, Commit } from 'vuex'; 3 | import { Ref } from '@vue/composition-api'; 4 | 5 | type Dictionary = { [key: string]: T }; 6 | type Computed = Ref; 7 | type MutationMethod = (...args: any[]) => void; 8 | type ActionMethod = (...args: any[]) => Promise; 9 | type CustomVue = Vue & Dictionary; 10 | 11 | interface Mapper { 12 | (map: string[]): Dictionary; 13 | (map: Dictionary): Dictionary; 14 | } 15 | 16 | interface MapperWithNamespace { 17 | (namespace: string, map: string[]): Dictionary; 18 | (namespace: string, map: Dictionary): Dictionary; 19 | } 20 | 21 | interface FunctionMapper { 22 | ( 23 | map: Dictionary<(this: CustomVue, fn: F, ...args: any[]) => any>, 24 | ): Dictionary; 25 | } 26 | 27 | interface FunctionMapperWithNamespace { 28 | ( 29 | namespace: string, 30 | map: Dictionary<(this: CustomVue, fn: F, ...args: any[]) => any>, 31 | ): Dictionary; 32 | } 33 | 34 | interface MapperForState { 35 | ( 36 | map: Dictionary<(this: CustomVue, state: S, getters: any) => any>, 37 | ): Dictionary; 38 | } 39 | 40 | interface MapperForStateWithNamespace { 41 | ( 42 | namespace: string, 43 | map: Dictionary<(this: CustomVue, state: S, getters: any) => any>, 44 | ): Dictionary; 45 | } 46 | 47 | export type useState = Mapper & 48 | MapperWithNamespace & 49 | MapperForState & 50 | MapperForStateWithNamespace; 51 | 52 | export type useGetters = Mapper & MapperWithNamespace; 53 | 54 | export type useMutations = Mapper & 55 | MapperWithNamespace & 56 | FunctionMapper & 57 | FunctionMapperWithNamespace; 58 | 59 | export type useActions = Mapper & 60 | MapperWithNamespace & 61 | FunctionMapper & 62 | FunctionMapperWithNamespace; 63 | -------------------------------------------------------------------------------- /src/__stories__/useActions.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: off */ 2 | import 'vue-tsx-support/enable-check'; 3 | import Vue from 'vue'; 4 | import { storiesOf } from '@storybook/vue'; 5 | import { createComponent } from '@vue/composition-api'; 6 | import { useState, useGetters, useActions } from '..'; 7 | import { ShowDocs } from './components'; 8 | import { createStore } from '../mocks'; 9 | 10 | type Inject = { 11 | count: number; 12 | count2: number; 13 | plusOne: number; 14 | minusOne: number; 15 | incrementAsync: (delay?: number) => void; 16 | decrementAsync: (delay?: number) => void; 17 | }; 18 | 19 | const Docs = () => ; 20 | 21 | const Demo = createComponent({ 22 | store: createStore(), 23 | 24 | setup() { 25 | const state = { 26 | ...useState(['count']), 27 | ...useState('test', { count2: 'count' }), 28 | }; 29 | 30 | const getters = { 31 | ...useGetters(['plusOne']), 32 | ...useGetters('test', ['minusOne']), 33 | }; 34 | 35 | const actions = { 36 | ...useActions(['incrementAsync']), 37 | ...useActions('test', ['decrementAsync']), 38 | }; 39 | 40 | return { 41 | ...state, 42 | ...getters, 43 | ...actions, 44 | }; 45 | }, 46 | 47 | render(this: Vue & Inject) { 48 | const { count, count2, plusOne, minusOne } = this; 49 | return ( 50 |
51 |
count: {count}
52 |
plusOne: {plusOne}
53 |
test/count: {count2}
54 |
test/minusOne: {minusOne}
55 | 56 | 59 |
60 | ); 61 | }, 62 | }); 63 | 64 | storiesOf('useActions', module) 65 | // @ts-ignore 66 | .add('docs', () => Docs) 67 | .add('demo', () => Demo); 68 | -------------------------------------------------------------------------------- /.vscode/docs.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "storybook": { 3 | "scope": "typescriptreact", 4 | "prefix": "docs", 5 | "body": [ 6 | "/* eslint import/no-extraneous-dependencies: off */", 7 | "import 'vue-tsx-support/enable-check';", 8 | "import Vue from 'vue';", 9 | "import { storiesOf } from '@storybook/vue';", 10 | "import { createComponent } from '@vue/composition-api';", 11 | "import { use${1:Hook} } from '..';", 12 | "import { ShowDocs } from './components';", 13 | "", 14 | "type Inject = {", 15 | " ${2:data}: ${3:any};", 16 | "};", 17 | "", 18 | "const Docs = () => ;", 19 | "", 20 | "const Demo = createComponent({", 21 | " setup() {", 22 | " const ${2:data} = use${1:Hook}();", 23 | " return ${2:data};", 24 | " },", 25 | "", 26 | " render(this: Vue & Inject) {", 27 | " const { ${2:data} } = this;", 28 | " return
{${2:data}}
;", 29 | " },", 30 | "});", 31 | "", 32 | "storiesOf('use${1:Hook}', module)", 33 | " // @ts-ignore", 34 | " .add('docs', () => Docs)", 35 | " .add('demo', () => Demo);", 36 | "" 37 | ], 38 | "description": "storybook for hook" 39 | }, 40 | "docs": { 41 | "scope": "markdown", 42 | "prefix": "docs", 43 | "body": [ 44 | "# use${1:Hook}", 45 | "", 46 | "${2:Introduction...}", 47 | "", 48 | "## Usage", 49 | "", 50 | "```jsx {6,11}", 51 | "import { createComponent } from '@vue/composition-api';", 52 | "import { use${1:Hook} } from '@u3u/vue-hooks';", 53 | "", 54 | "const Demo = createComponent({", 55 | " setup() {", 56 | " const ${3:data} = use${1:Hook}();", 57 | " return ${3:data};", 58 | " },", 59 | "", 60 | " render() {", 61 | " const { ${3:data} } = this;", 62 | " return
{${3:data}}
;", 63 | " },", 64 | "});", 65 | "```", 66 | "" 67 | ], 68 | "description": "docs for hook" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.1](https://github.com/u3u/vue-hooks/compare/v2.0.0...v2.0.1) (2019-08-25) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **release:** semantic release ([c5d2ee8](https://github.com/u3u/vue-hooks/commit/c5d2ee8)) 7 | 8 | # [1.4.0](https://github.com/u3u/vue-hooks/compare/v1.3.0...v1.4.0) (2019-08-16) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **deps:** move to dev dependencies ([8a64b14](https://github.com/u3u/vue-hooks/commit/8a64b14)) 14 | 15 | 16 | ### Features 17 | 18 | * **hooks:** add `useMedia` hook ([dee851e](https://github.com/u3u/vue-hooks/commit/dee851e)) 19 | 20 | # [1.3.0](https://github.com/u3u/vue-hooks/compare/v1.2.0...v1.3.0) (2019-08-07) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * **hooks:** fix `usePrevious` does not work when passing state ([8567a04](https://github.com/u3u/vue-hooks/commit/8567a04)) 26 | 27 | 28 | ### Features 29 | 30 | * **hooks:** add `useTimeout` hook ([61db981](https://github.com/u3u/vue-hooks/commit/61db981)) 31 | 32 | # [1.2.0](https://github.com/u3u/vue-hooks/compare/v1.1.1...v1.2.0) (2019-08-06) 33 | 34 | 35 | ### Features 36 | 37 | * **hooks:** add `useMountedState` hook ([1e70d5e](https://github.com/u3u/vue-hooks/commit/1e70d5e)) 38 | * **hooks:** add `useRef` hook ([f600ab8](https://github.com/u3u/vue-hooks/commit/f600ab8)) 39 | 40 | ## [1.1.1](https://github.com/u3u/vue-hooks/compare/v1.1.0...v1.1.1) (2019-07-26) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * **ts:** forgot to export the type definition file ([5cb25ec](https://github.com/u3u/vue-hooks/commit/5cb25ec)) 46 | 47 | # [1.1.0](https://github.com/u3u/vue-hooks/compare/v1.0.0...v1.1.0) (2019-07-18) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * **hooks:** `vm.$router` should not be computed wrapper ([8483d43](https://github.com/u3u/vue-hooks/commit/8483d43)) 53 | 54 | 55 | ### Features 56 | 57 | * **hooks:** add `usePrevious` hook ([f4d0dc8](https://github.com/u3u/vue-hooks/commit/f4d0dc8)) 58 | 59 | # 1.0.0 (2019-07-17) 60 | 61 | 62 | ### Bug Fixes 63 | 64 | * **hooks:** improve vuex interface ([fdae9b1](https://github.com/u3u/vue-hooks/commit/fdae9b1)) 65 | * **hooks:** improve vuex interface again ([87bd2a5](https://github.com/u3u/vue-hooks/commit/87bd2a5)) 66 | * **hooks:** using runtime plugin ([e0f556d](https://github.com/u3u/vue-hooks/commit/e0f556d)) 67 | * **util:** `vm` must have a `setup` function ([d6ffe8b](https://github.com/u3u/vue-hooks/commit/d6ffe8b)) 68 | 69 | 70 | ### Features 71 | 72 | * **hooks:** add `useActions` hook ([1106ade](https://github.com/u3u/vue-hooks/commit/1106ade)) 73 | * **hooks:** add `useCounter` hook ([200086e](https://github.com/u3u/vue-hooks/commit/200086e)) 74 | * **hooks:** add `useGetters` hook ([49376cb](https://github.com/u3u/vue-hooks/commit/49376cb)) 75 | * **hooks:** add `useMutations` hook ([b38358c](https://github.com/u3u/vue-hooks/commit/b38358c)) 76 | * **hooks:** add `useRouter` hook ([df8bdda](https://github.com/u3u/vue-hooks/commit/df8bdda)) 77 | * **hooks:** add `useState` hook ([607dfd1](https://github.com/u3u/vue-hooks/commit/607dfd1)) 78 | * **hooks:** add `useStore` hook ([109995a](https://github.com/u3u/vue-hooks/commit/109995a)) 79 | * **hooks:** export hooks in entry ([3f0d5dd](https://github.com/u3u/vue-hooks/commit/3f0d5dd)) 80 | * **util:** add vue runtime ([f7ff827](https://github.com/u3u/vue-hooks/commit/f7ff827)) 81 | -------------------------------------------------------------------------------- /src/__stories__/useRouter.story.tsx: -------------------------------------------------------------------------------- 1 | /* eslint spaced-comment: off, import/no-extraneous-dependencies: off */ 2 | /// 3 | import 'vue-tsx-support/enable-check'; 4 | import Vue from 'vue'; 5 | import VueRouter, { Route } from 'vue-router'; 6 | import { storiesOf } from '@storybook/vue'; 7 | import { 8 | createComponent, 9 | ref, 10 | watch, 11 | onMounted, 12 | onUnmounted, 13 | } from '@vue/composition-api'; 14 | import { useRouter } from '..'; 15 | import { ShowDocs } from './components'; 16 | 17 | type Inject = { 18 | time: number; 19 | route: Route; 20 | router: VueRouter; 21 | }; 22 | 23 | const Docs = () => ; 24 | 25 | const Home: any = () => ( 26 | 27 | 32 | 33 | ); 34 | 35 | const About: any = () => ( 36 | 42 | ); 43 | 44 | const NotFound: any = () => ( 45 | 46 | 51 | 52 | ); 53 | 54 | const Demo = createComponent({ 55 | router: new VueRouter({ 56 | routes: [ 57 | { 58 | path: '/', 59 | name: 'index', 60 | meta: { title: 'Home Page', next: '/about' }, 61 | component: Home, 62 | }, 63 | { 64 | path: '/about', 65 | name: 'about', 66 | meta: { title: 'About Page', next: '/github' }, 67 | component: About, 68 | }, 69 | { 70 | path: '*', 71 | name: '404', 72 | meta: { title: '404 - Not Found', next: '/' }, 73 | component: NotFound, 74 | }, 75 | ], 76 | }), 77 | 78 | setup() { 79 | const { route, router } = useRouter(); 80 | const time = ref(5); 81 | let timerId; 82 | 83 | watch(route, () => { 84 | time.value = 5; 85 | }); 86 | 87 | watch(time, () => { 88 | if (time.value <= 0) { 89 | router.replace(route.value.meta.next); 90 | } 91 | }); 92 | 93 | onMounted(() => { 94 | // eslint-disable-next-line no-plusplus 95 | timerId = window.setInterval(() => time.value--, 1e3); 96 | }); 97 | 98 | onUnmounted(() => { 99 | window.clearInterval(timerId); 100 | }); 101 | 102 | return { time, route, router }; 103 | }, 104 | 105 | render(this: Vue & Inject) { 106 | const { time, route } = this; 107 | 108 | return ( 109 |
110 | 119 |
120 |

121 | {route.meta.title} ({time}s) 122 |

123 | 124 | 125 | 126 | 127 | 128 |
129 |
130 | Route Details 131 |
    132 | {Object.keys(route).map((key) => ( 133 |
  • 134 | {key}:
    {JSON.stringify(route[key], null, 2)}
    135 |
  • 136 | ))} 137 |
138 |
139 |
140 | ); 141 | }, 142 | }); 143 | 144 | storiesOf('useRouter', module) 145 | // @ts-ignore 146 | .add('docs', () => Docs) 147 | .add('demo', () => Demo); 148 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@u3u/vue-hooks", 3 | "description": "⚡️ Awesome Vue Hooks", 4 | "version": "2.0.1", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "keywords": [ 9 | "vue", 10 | "vue-use", 11 | "vue-hooks", 12 | "vue-function-api", 13 | "vue-composition-api" 14 | ], 15 | "main": "lib/index.js", 16 | "module": "esm/index.js", 17 | "types": "lib/index.d.ts", 18 | "typings": "lib/index.d.ts", 19 | "files": [ 20 | "lib/", 21 | "esm/" 22 | ], 23 | "scripts": { 24 | "start": "yarn storybook", 25 | "storybook": "start-storybook -p 3000", 26 | "storybook:build": "build-storybook", 27 | "test": "jest", 28 | "test:watch": "jest --watch", 29 | "build:cjs": "tsc", 30 | "build:es": "tsc -m esNext --outDir esm", 31 | "build": "yarn clean && yarn build:cjs && yarn build:es", 32 | "clean": "rimraf lib esm", 33 | "lint": "eslint 'src/**/*.ts'", 34 | "lint:types": "tsc --noEmit", 35 | "lint:prettier": "prettier '**/*.{ts,md,mdx}' --check", 36 | "format": "prettier '**/*.{ts,md,mdx}' --write", 37 | "release": "semantic-release", 38 | "prepublishOnly": "yarn test && yarn build" 39 | }, 40 | "author": { 41 | "name": "u3u", 42 | "email": "qwq@qwq.cat", 43 | "url": "https://qwq.cat" 44 | }, 45 | "license": "MIT", 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/u3u/vue-hooks" 49 | }, 50 | "bugs": { 51 | "url": "https://github.com/u3u/vue-hooks/issues" 52 | }, 53 | "homepage": "https://github.com/u3u/vue-hooks#readme", 54 | "engines": { 55 | "node": ">=10.16.0" 56 | }, 57 | "dependencies": { 58 | "dayjs": "^1.8.15" 59 | }, 60 | "devDependencies": { 61 | "@babel/core": "^7.5.5", 62 | "@babel/preset-env": "^7.5.5", 63 | "@babel/preset-typescript": "^7.3.3", 64 | "@commitlint/cli": "^8.1.0", 65 | "@commitlint/config-conventional": "^8.1.0", 66 | "@semantic-release/changelog": "^3.0.4", 67 | "@semantic-release/git": "^7.0.16", 68 | "@storybook/addon-knobs": "^5.1.11", 69 | "@storybook/addon-notes": "^5.1.11", 70 | "@storybook/theming": "^5.1.11", 71 | "@storybook/vue": "^5.1.11", 72 | "@types/jest": "^24.0.18", 73 | "@types/node": "^12.7.2", 74 | "@types/storybook__vue": "^5.0.2", 75 | "@typescript-eslint/parser": "^2.0.0", 76 | "@vue/babel-preset-jsx": "^1.1.0", 77 | "@vue/composition-api": "^0.2.0", 78 | "@vue/test-utils": "^1.0.0-beta.29", 79 | "all-contributors-cli": "^6.8.1", 80 | "babel-loader": "^8.0.6", 81 | "babel-preset-vue": "^2.0.2", 82 | "eslint": "^6.2.2", 83 | "eslint-config-airbnb-base": "^14.0.0", 84 | "eslint-config-prettier": "^6.1.0", 85 | "eslint-plugin-import": "^2.18.2", 86 | "eslint-plugin-prettier": "^3.1.0", 87 | "github-markdown-css": "^3.0.1", 88 | "jest": "^24.8.0", 89 | "lint-staged": "^9.2.3", 90 | "markdown-it": "^9.1.0", 91 | "markdown-it-highlight-lines": "^1.0.2", 92 | "markdown-it-link-attributes": "^2.1.0", 93 | "markdown-it-loader": "^0.7.0", 94 | "markdown-it-prism": "^2.0.2", 95 | "match-media-mock": "^0.1.1", 96 | "prettier": "^1.18.2", 97 | "prismjs": "^1.17.1", 98 | "rimraf": "^3.0.0", 99 | "semantic-release": "^15.13.24", 100 | "typescript": "^3.5.3", 101 | "vue": "^2.6.10", 102 | "vue-loader": "^15.7.1", 103 | "vue-router": "^3.1.2", 104 | "vue-template-compiler": "^2.6.10", 105 | "vue-tsx-support": "^2.3.1", 106 | "vuex": "^3.1.1", 107 | "yorkie": "^2.0.0" 108 | }, 109 | "release": { 110 | "plugins": [ 111 | "@semantic-release/commit-analyzer", 112 | "@semantic-release/release-notes-generator", 113 | "@semantic-release/changelog", 114 | "@semantic-release/npm", 115 | "@semantic-release/git", 116 | "@semantic-release/github" 117 | ] 118 | }, 119 | "gitHooks": { 120 | "pre-commit": "lint-staged", 121 | "commit-msg": "commitlint -E GIT_PARAMS" 122 | }, 123 | "lint-staged": { 124 | "*.ts": [ 125 | "eslint --fix" 126 | ], 127 | "*.{ts,md,mdx}": [ 128 | "prettier --write", 129 | "prettier --check", 130 | "git add" 131 | ] 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-hooks [![NPM Version](https://img.shields.io/npm/v/@u3u/vue-hooks.svg)](https://www.npmjs.com/package/@u3u/vue-hooks) [![Build Status](https://img.shields.io/travis/u3u/vue-hooks/master.svg)](https://travis-ci.org/u3u/vue-hooks) [![Code Coverage](https://img.shields.io/codecov/c/github/u3u/vue-hooks.svg)](https://codecov.io/gh/u3u/vue-hooks) 2 | 3 | > ⚡️ Awesome Vue Hooks 4 | 5 | Using [`@vue/composition-api`](https://github.com/vuejs/composition-api) to implement useful vue hooks. 6 | Vue 3.0 has not been released yet, it allows you to use functional-based components in advance. 7 | 8 | ⚠️ 2.x has been switched to `@vue/composition-api`, if you are using version 1.x please use [`vue-function-api`](https://npmjs.com/vue-function-api) 9 | 10 | ## Install 11 | 12 | ```sh 13 | yarn add @vue/composition-api @u3u/vue-hooks 14 | ``` 15 | 16 | ## Documentation [![Netlify Status](https://api.netlify.com/api/v1/badges/e93d1698-f766-4529-b8e0-91fa1162d4cb/deploy-status)](https://app.netlify.com/sites/vue-hooks/deploys) 17 | 18 | Docs are available at 19 | 20 | ## Usage 21 | 22 | [![Edit Vue Hooks Examples](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/vue-template-66f2o?expanddevtools=1&fontsize=12&module=%2Fsrc%2FApp.vue) 23 | 24 | ```js 25 | import Vue from 'vue'; 26 | import VueCompositionAPI from '@vue/composition-api'; 27 | import hooks from '@u3u/vue-hooks'; 28 | 29 | Vue.use(hooks); 30 | Vue.use(VueCompositionAPI); // Don't forget to use the plugin! 31 | ``` 32 | 33 | ```jsx 34 | import { createComponent } from '@vue/composition-api'; 35 | import { useWindowSize } from '@u3u/vue-hooks'; 36 | 37 | export default createComponent({ 38 | setup() { 39 | const { width, height, widthPixel, heightPixel } = useWindowSize(); 40 | return { width, height, widthPixel, heightPixel }; 41 | }, 42 | 43 | render() { 44 | const { width, height, widthPixel, heightPixel } = this; 45 | return ( 46 |
47 | dynamic window size: {width}, {height} 48 |
49 | ); 50 | }, 51 | }); 52 | ``` 53 | 54 | ## Hooks 55 | 56 | - [`useDate`](https://vue-hooks.netlify.com/?path=/story/usedate--docs) — Using [`dayjs`](https://github.com/iamkun/dayjs) to process date. 57 | - [`useWindowSize`](https://vue-hooks.netlify.com/?path=/story/usewindowsize--docs) — Tracks `window` dimensions. 58 | - [`useCounter`](https://vue-hooks.netlify.com/?path=/story/usecounter--docs) — Tracks state of a number. 59 | - [`usePrevious`](https://vue-hooks.netlify.com/?path=/story/useprevious--docs) — Returns the previous state or props. 60 | - [`useRouter`](https://vue-hooks.netlify.com/?path=/story/userouter--docs) — A hook for [`vue-router`](https://github.com/vuejs/vue-router). 61 | - [`useStore`](https://vue-hooks.netlify.com/?path=/story/usestore--docs) — A hook for [`vuex`](https://github.com/vuejs/vuex). 62 | - [`useState`](https://vue-hooks.netlify.com/?path=/story/usestate--docs) — A hook for [`mapState`](https://vuex.vuejs.org/api/#mapstate). 63 | - [`useGetters`](https://vue-hooks.netlify.com/?path=/story/usegetters--docs) — A hook for [`mapGetters`](https://vuex.vuejs.org/api/#mapgetters). 64 | - [`useMutations`](https://vue-hooks.netlify.com/?path=/story/usemutations--docs) — A hook for [`mapMutations`](https://vuex.vuejs.org/api/#mapmutations). 65 | - [`useActions`](https://vue-hooks.netlify.com/?path=/story/useactions--docs) — A hook for [`mapActions`](https://vuex.vuejs.org/api/#mapactions). 66 | 67 | ## Contributing 68 | 69 | 1. Fork it! 70 | 2. Create your feature branch: `git checkout -b feat/new-hook` 71 | 3. Commit your changes: `git commit -am 'feat(hooks): add a new hook'` 72 | 4. Push to the branch: `git push origin feat/new-hook` 73 | 5. Submit a pull request :D 74 | 75 | ## Contributors 76 | 77 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
u3u
u3u

💻 📖 💡 ⚠️
86 | 87 | 88 | 89 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind are welcome! 90 | 91 | ## License 92 | 93 | [MIT](./LICENSE) 94 | --------------------------------------------------------------------------------