Skip to content

Commit

Permalink
feat(useEvent): add useEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
lmhcoding committed Sep 19, 2020
1 parent d06332a commit 8db2731
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 0 deletions.
89 changes: 89 additions & 0 deletions docs/useEvent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# ``useEvent``
---

Sensor hook that subscribes a ``handler`` to events

## Usage

1. 当不传target时,target 默认为 ``window``

```vue
import { useEvent } from 'composition-fn'
export default {
setup () {
useEvent('resize', () => {
console.log('window resize')
})
}
}
```

2. target 可为 CSS 选择器

```vue
<template>
<div id="test">test</test>
</template>
<script>
import { useEvent } from 'composition-fn'
export default {
setup () {
useEvent('click', () => {
console.log('window resize')
}, true, '#test')
}
}
</script>
```

3. target 为 Ref<EventTarget>

```vue
<template>
<div ref="target">test</test>
</template>
<script>
import { ref } from 'vue'
import { useEvent } from 'composition-fn'
export default {
setup () {
const target = ref(null)
useEvent('click', () => {
console.log('window resize')
}, true, '#test')
return {
target
}
}
}
</script>
```

4. target 为 EventTarget

```vue
<template>
<div id="target">test</test>
</template>
<script>
import { ref, onMounted } from 'vue'
import { useEvent } from 'composition-fn'
export default {
setup () {
const target = ref(null)
onMounted(() => {
useEvent('click', () => {
console.log('window resize')
}, true, document.querySelector('#target))
})
return {
target
}
}
}
</script>
```

1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './useTitle'
export * from './useEvent'
94 changes: 94 additions & 0 deletions src/useEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-disable no-redeclare */
import { Ref, onMounted, onUnmounted, isRef, getCurrentInstance } from 'vue'

type Target = Ref<EventTarget> | EventTarget | string

interface WindowEventHandler<T extends keyof WindowEventMap> {
(this: Window, e: WindowEventMap[T]): any
}

interface DocumentEventHandler<T extends keyof DocumentEventMap> {
(this: Document, e: DocumentEventMap[T]): any
}

type HandlerOptions = boolean | AddEventListenerOptions
type DocumentEvents = keyof DocumentEventMap
type WindowEvents = keyof WindowEventMap

function getTarget(target: Target): EventTarget | null {
if (!target) {
return window
}
if (typeof target === 'string') {
const dom = document.querySelector(target)
if (!dom && process.env.NODE_ENV !== 'production') {
console.error('target is not found')
return null
}
return dom
}
if (isRef(target)) {
return target.value
}
return target
}

function registerEvent(
target: Target,
event: string,
cb: EventListenerOrEventListenerObject,
options?: HandlerOptions
) {
const eventTarget = getTarget(target)
if (eventTarget) {
eventTarget.addEventListener(event, cb, options)
}
return eventTarget
}

export function useEvent<T extends WindowEvents>(
event: T,
handler: WindowEventHandler<T>,
options?: HandlerOptions
): void
export function useEvent<T extends DocumentEvents>(
event: T,
handler: DocumentEventHandler<T>,
options?: HandlerOptions,
target?: Target
): void
export function useEvent<T extends DocumentEvents>(
event: T,
handler: DocumentEventHandler<T>,
options?: HandlerOptions,
target?: string
): void
export function useEvent(
event: string,
cb: EventListenerOrEventListenerObject,
options?: HandlerOptions,
target: Target = window
) {
if (!event || !cb) {
return
}
let eventTarget: EventTarget | null
function register() {
eventTarget = registerEvent(target, event, cb, options)
}
const currentInstance = getCurrentInstance()
if (currentInstance) {
if (currentInstance.isMounted) {
register()
} else {
onMounted(() => {
register()
})
}
onUnmounted(() => {
if (eventTarget) {
eventTarget.removeEventListener(event, cb)
}
})
}
}
68 changes: 68 additions & 0 deletions tests/useEvent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { mount, VueWrapper } from '@vue/test-utils'
import { defineComponent, ref, Ref } from 'vue'
import { useEvent } from '../src/useEvent'

const props = {
name: 'resize' as 'resize',
handler: jest.fn(),
target: {
addEventListener: jest.fn(),
removeEventListener: jest.fn()
} as unknown as EventTarget
}

const props1 = {
...props,
target: ref({
addEventListener: jest.fn(),
removeEventListener: jest.fn()
}) as unknown as Ref<EventTarget>
}

describe('test useEvent when target is an EventTarget', () => {
let wrapper: VueWrapper<any>
beforeEach(() => {
const comp = defineComponent({
template: '<div>test</div>',
setup () {
useEvent(props.name, props.handler, true, props.target)
}
})
wrapper = mount(comp)
})
test('should call addEventListener on mount', () => {
expect(props.target.addEventListener).toHaveBeenCalledTimes(1)
expect(props.target.addEventListener).toHaveBeenCalledWith(props.name, props.handler, true)
expect((props.target.addEventListener as any).mock.instances).toEqual([props.target])
})
test('should call removeEventListener on unmount', () => {
wrapper.unmount()
expect(props.target.removeEventListener).toHaveBeenCalledTimes(1)
expect(props.target.removeEventListener).toHaveBeenCalledWith(props.name, props.handler)
expect((props.target.removeEventListener as any).mock.instances).toEqual([props.target])
})
})

describe('test useEvent when target is a Ref', () => {
let wrapper: VueWrapper<any>
beforeEach(() => {
const comp = defineComponent({
template: '<div>test</div>',
setup () {
useEvent(props1.name, props1.handler, true, props1.target)
}
})
wrapper = mount(comp)
})
test('should call addEventListener on mount', () => {
expect(props1.target.value.addEventListener).toHaveBeenCalledTimes(1)
expect(props1.target.value.addEventListener).toHaveBeenCalledWith(props1.name, props1.handler, true)
expect((props1.target.value.addEventListener as any).mock.instances).toEqual([props1.target.value])
})
test('should call removeEventListener on unmount', () => {
wrapper.unmount()
expect(props1.target.value.removeEventListener).toHaveBeenCalledTimes(1)
expect(props1.target.value.removeEventListener).toHaveBeenCalledWith(props1.name, props1.handler)
expect((props1.target.value.removeEventListener as any).mock.instances).toEqual([props1.target.value])
})
})

0 comments on commit 8db2731

Please sign in to comment.