Vue-JSX 常用钩子函数
一、getAllElements
用于模拟 React.children 获取所有子元素,在 Arco 组件库中常用。
定义:
import type { Component, Slots, VNode, VNodeTypes } from 'vue'
import _ from 'lodash'
export enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9
}
export const isElement = (vn: VNode) => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.ELEMENT)
}
export const isComponent = (
vn: VNode,
type?: VNodeTypes
): type is Component => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.COMPONENT)
}
export const isTextChildren = (
child: VNode,
children: VNode['children']
): children is string => {
return Boolean(child && child.shapeFlag & 8)
}
export const isArrayChildren = (
vn: VNode,
children: VNode['children']
): children is VNode[] => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.ARRAY_CHILDREN)
}
export const isSlotsChildren = (
vn: VNode,
children: VNode['children']
): children is Slots => {
return Boolean(vn && vn.shapeFlag & ShapeFlags.SLOTS_CHILDREN)
}
export const getAllElements = (
children: VNode[] | undefined,
includeText = false
) => {
const results: VNode[] = []
for (const item of children ?? []) {
if (
isElement(item) ||
isComponent(item) ||
(includeText && isTextChildren(item, item.children))
) {
results.push(item)
} else if (isArrayChildren(item, item.children)) {
results.push(...getAllElements(item.children, includeText))
} else if (isSlotsChildren(item, item.children)) {
results.push(...getAllElements(item.children.default?.(), includeText))
} else if (_.isArray(item)) {
results.push(...getAllElements(item, includeText))
}
}
return results
}
使用:
import { defineComponent, mergeProps } from 'vue';
import { getAllElements } from '../_utils/hook';
export default defineComponent({
name: 'Parent',
setup(props, { slots }) {
return () => {
const children = getAllElements(slots.default?.() ?? []);
const childItems = children.map((child, index) => {
// 这里的顺序决定父子组件属性的优先级谁高谁低
child.props = mergeProps(
child.props ?? {},
{ txt: '传入的值' } // 这里显然父组件传入的属性优先级更高
);
return child;
})
return (
<div>
{childItems}
</div>
);
};
},
});
二、useChildComponentSlots
用于获取指定的子元素,在 TDesign 中常用。
定义:
import { Slots, VNode, Component, getCurrentInstance } from 'vue';
/**
* 渲染 default slot,获取子组件 VNode。处理多种子组件创建场景
* @returns {function(childComponentName: string, slots: Slots): VNode[]}
* @param childComponentName
* @param slots
* @example const getChildByName = useChildComponentSlots()
* @example getChildComponentByName('xxItem')
*/
export const useChildComponentSlots = () => {
const instance = getCurrentInstance();
return (childComponentName: string, slots?: Slots): VNode[] => {
if (!slots) {
slots = instance.slots;
}
const content = slots?.default?.() || [];
return content
.map((item: VNode) => {
if (item.children && Array.isArray(item.children)) return item.children;
return item;
})
.flat()
.filter((item: VNode) =>
(item.type as Component).name?.endsWith(childComponentName)
) as VNode[];
};
};
使用:
import { defineComponent } from 'vue';
import { useChildComponentSlots } from '../_utils/hook';
import Child from './Child.tsx'
export default defineComponent({
name: 'Parent',
setup(props) {
const childItems = () => {
const getChildComponentByName = useChildComponentSlots();
const childrenList = getChildComponentByName('Child')
const carouselItemList = childrenList.map((item: any) => {
return (
// 在这里决定父子组件属性的优先级谁高谁低
<Child txt={item.props?.txt ?? '传入的值'}>
{item.children.default()}
</Child>
);
});
return carouselItemList
}
return () => {
return (
<div>
{childItems()}
</div>
);
};
},
});
三、useWindowResize
用于获取 window 的宽度和高度。
定义:
import { ref, onMounted, onUnmounted } from 'vue';
/**
* 获取 window 的宽度和高度
* @returns width/height
*/
export const useWindowResize = () => {
const width = ref(window.innerWidth);
const height = ref(window.innerHeight);
const handleResize = () => {
width.value = window.innerWidth;
height.value = window.innerHeight;
}
onMounted(() => {
window.addEventListener('resize', handleResize)
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
return {
width,
height
}
}
使用:
const { width, height } = useWindowResize()
四、usePageVisibility
用于判断用户是否聚焦于网站窗口。
定义:
import { onMounted, onUnmounted } from 'vue';
export const usePageVisibility = (callback = () => { }) => {
let hidden, visibilityChange;
if (typeof document.hidden !== "undefined") {
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
const handleVisibilityChange = () => {
callback(document[hidden]);
}
onMounted(() => {
document.addEventListener(visibilityChange, handleVisibilityChange, false);
});
onUnmounted(() => {
document.removeEventListener(visibilityChange, handleVisibilityChange);
});
}
使用:
usePageVisibility((hidden) => {
console.log(`User is${hidden ? ' not' : ''} focus your site`);
});
五、useViewport
用于检测当前设备。
定义:
import { ref, onMounted, onUnmounted } from 'vue';
export const MOBILE = 'MOBILE'
export const TABLET = 'TABLET'
export const DESKTOP = 'DESKTOP'
export const useViewport = (config = {}) => {
const { mobile = null, tablet = null } = config;
let mobileWidth = mobile ? mobile : 768;
let tabletWidth = tablet ? tablet : 922;
let device = ref(getDevice(window.innerWidth));
function getDevice(width) {
if (width < mobileWidth) {
return MOBILE;
} else if (width < tabletWidth) {
return TABLET;
}
return DESKTOP;
}
const handleResize = () => {
device.value = getDevice(window.innerWidth);
}
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
return {
device
}
}
使用:
const { device } = useViewport({ mobile: 700, table: 900 });
六、useScrollToBottom
用于判断用户是否滚动至底部。
定义:
import { onMounted, onUnmounted } from 'vue';
export const useScrollToBottom = (callback = () => { }) => {
const handleScrolling = () => {
if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
callback();
}
}
onMounted(() => {
window.addEventListener('scroll', handleScrolling);
});
onUnmounted(() => {
window.removeEventListener('scroll', handleScrolling);
});
}
使用:
useScrollToBottom(() => { console.log('Scrolled to bottom') })
七、useNetworkStatus
用于检测当前网络状态。
定义:
import { onMounted, onUnmounted } from 'vue';
export const useNetworkStatus = (callback = () => { }) => {
const updateOnlineStatus = () => {
const status = navigator.onLine ? 'online' : 'offline';
callback(status);
}
onMounted(() => {
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
});
onUnmounted(() => {
window.removeEventListener('online', updateOnlineStatus);
window.removeEventListener('offline', updateOnlineStatus);
})
}
使用:
useNetworkStatus((status) => {
console.log(`Your network status is ${status}`);
})
八、useCopyToClipboard
用于获取剪贴板。
定义:
function copyToClipboard(text) {
let input = document.createElement('input');
input.setAttribute('value', text);
document.body.appendChild(input);
input.select();
let result = document.execCommand('copy');
document.body.removeChild(input);
return result;
}
export const useCopyToClipboard = () => {
return (text) => {
if (typeof text === "string" || typeof text == "number") {
return copyToClipboard(text);
}
return false;
}
}
使用:
const copyToClipboard = useCopyToClipboard();
copyToClipboard('just copy');