Vue3学习(六)Vue3 + ts几种写法
前言
官网提到组合式api和选项式api
选项式api其实就是vue2的写法,组合式api是vue3的新写法(组合式api可以在script中使用setup()也可以使用<script setup>,<script setup>是setup()的语法糖,语法糖的写法在vue3.2后才支持)
参考
Vue3官网的一些文章
Vue3语法官网教程
官网迁移教程 (Vue2和Vue3差异点)
组合式api setup()用法
单文件组件 <sctipt setup>用法
Vue3使用setup()函数核心API写法
vue3中使用TypeScript
Vue3结合TS项目开发实践总结
Vue3写法总结
Vue3种使用<script setup>语法糖核心API写法
上手后才知道 ,Vue3 的 script setup 语法糖是真的爽
Vue3 中setup()和<script setup></script>
Vue3 <script setup>语法糖+ts+Hook实践探索
Vue3一些API用法
vue3 ref函数用法
Vue3中ref和reactive的区别
Vue3第一篇之ref和reactive详解扩展
实战
Vue3使用setup()函数
<script lang="ts"> import { defineComponent, onMounted, ref, watch, nextTick, computed, onBeforeUnmount } from '@vue/composition-api'; import { useVisibility } from '@live/hooks/src/useVisibility'; import { toWorkVideoPage, getCookie, parseQuery, toLiveRoomByUserId, openProfile, osName, query } from '@live/actions'; import { throttle, isInLiveRoom, isLowDeviceBiz } from '../../utils/init'; import { BOOKSTATUS, ENROLLSTATUS, EVENTSTATUS } from '../../schemas'; import loadingsvg from './img/loading.svg'; import type { PropType } from '@vue/composition-api'; import type { TTabPhotoList, TEventList, TTabs } from '../../schemas'; // import LoadingIcon from '@/components/LoadingIcon/index.vue'; import { showToast } from '@/utils/init'; import { sendShow, sendClick } from '@/logs/index'; import TabSwiper from '@/components/TabSwiper/index.vue'; // import LottieIcon from '@/components/LottieIcon/index.vue'; export default defineComponent({ components: { Swiper, Sticky, // LottieIcon, TabSwiper, // LoadingIcon, LottieIcon: () => import(/* webpackChunkName:"lottie-icon" */ '@/components/LottieIcon/index.vue'), }, props: { tabPhotoList: { type: Array as PropType<TTabPhotoList[]>, default: () => [], }, currentTabIndex: { type: Number, default: 0, }, bookStatusLoading: { type: Boolean, default: false, }, }, emits: [ 'updateCurrentTabIndex', 'handleEnroll', 'handleBook', ], setup(props, { emit }) { const swiperPanelRef = ref(); const currentSwiperPanelIndex = ref(0); const setTimeoutId = ref(); // 当前页面是否可见,settimeout中需要判断是否播放视频 const isVisibility = ref(true); // 当前video是否可见,只有video可见 && 页面可见才播放视频 const isVisibilityVideo = ref(true); const swiperPannelItemRef = ref(); const laterRender = ref(false); setTimeout(() => { laterRender.value = true; }); watch( [() => props.currentTabIndex], () => { if (currentSwiperPanelIndex.value === 0) { sendShow(getPanelShowLog(0)); } }, { immediate: false, }, ); // 自动播放第一个视频 onMounted(() => { sendShow(getPanelShowLog(0)); handleVideoPlay(); // 离开可视区不播放视频 const io = new IntersectionObserver(res => { if (res[0].intersectionRatio > 0) { isVisibilityVideo.value = true; handleVideoPlay(); } else { isVisibilityVideo.value = false; isVisibility.value = true; videoStop(); } }); io.observe(document.querySelector('.swiper-panel-main')!); }); onBeforeUnmount(() => { videoStop(); }); useVisibility({ visibleHandler: () => { console.log('----visibleHandler-00-0---'); isVisibility.value = true; // 此处安卓端未生效,代码执行到,但是视频没有重新播放 // if (osName !== 'android') { // fix 安卓偶先播放状态下漏出封面图 // pc,ios,android handleVideoPlay(1000, false); // } }, hiddenHandler: () => { isVisibility.value = false; console.log('----hiddenHandler-00-0---'); // 此处安卓端未生效,代码执行到,但是视频没有因此暂停 // if (osName !== 'android') { videoStop(false); // } }, }); // const videoVisibilityCallBack = const eventListByIndex = computed<TEventList[]>(() => { return (props.tabPhotoList[props.currentTabIndex] as TTabPhotoList).eventList || []; }); const tabsByTabPhotoList = computed<TTabs[]>(() => { return props?.tabPhotoList?.map((item: TTabPhotoList) => { return { tabTitle: item?.tabTitle, tabCode: item?.tabCode, selectedTitle: item?.selectedTitle, }; }); }); const onSwiperTabsChange = (index: number) => { // currentSwiperTabsIndex.value = index; emit('updateCurrentTabIndex', index); currentSwiperPanelIndex.value = 0; swiperPanelRef?.value?.to(0); handleVideoPlay(); }; const getPanelShowLog = (index: number) => { const event: any = (props as any).tabPhotoList[props.currentTabIndex].eventList[index]; return { action: 'BLIND_DATE_VENUE_VIDEO_CARD', params: { author_id: event.authorInfo.userInfo.user_id, id: event.eventId, status: event.eventStatus, tab_name: props.tabPhotoList[props.currentTabIndex].tabTitle, video_id: event.photo.photoId, }, }; }; const onSwiperPanelChange = (index, lastIndex) => { sendShow(getPanelShowLog(index)); currentSwiperPanelIndex.value = index; handleVideoPlay(); }; const videoStop = (isInitPoster = true) => { try { if (isInLiveRoom()) { return; } // 暂停隐藏封面 if (isInitPoster) { initPoster(); } const dom = document.querySelectorAll('.swiper-panel-item video'); dom.forEach((item: HTMLVideoElement) => { item?.pause(); // item?.load(); console.log('stop', item.className); }); } catch (e) { console.log('error', 'videostop', e); } }; /** * 播放视频处理:ref不太满足播放控制,采用document.querySelectorAll直接获取dom * 1.涉及到过渡动画,影响getBoundingClientRect计算,所以await nextTick(); * 2.因为支持轮播,可能存在两个activitydom都处于选中状态,因此需要用for循环找到一个距离屏幕中心最近的item * inShowPoster: 安卓端点击视频跳转再次回到h5时候,封面会闪一下(不会暂停1s后播放),次case不隐藏封面 */ const handleVideoPlay = (timeOut = 1000, isInitPoster = true) => { if (isInLiveRoom()) { return; } // fix 快速切换导致业务卡顿 clearTimeout(setTimeoutId.value); videoStop(isInitPoster); setTimeoutId.value = setTimeout(() => { try { videoStop(isInitPoster); // console.error(' settimeout transform------', document.querySelector('.spu-swiper-inner').style.transform); if (isVisibility.value && isVisibilityVideo.value ) { const targetDom = getCurrentVideo(); // play调用失败或者多次调用触发异常(The operation was aborted) targetDom?.play()?.catch(e => { console.log(e); }); } } catch (e) { console.log('error', e); } }, timeOut); }; const getCurrentVideo = () => { const activeDom = document.querySelectorAll('.swiper-panel-item.is-active video'); const target = window.innerWidth / 2; let targetDom = activeDom?.[0] as HTMLVideoElement; let distance = Number.MAX_VALUE; for (const item of activeDom) { const left = item.getBoundingClientRect().left; if (Math.abs(left - target) < distance) { distance = Math.abs(left - target); targetDom = item as HTMLVideoElement; } } return targetDom; }; const initPoster = () => { // const domPoster = document.querySelectorAll<HTMLElement>('.item-video-living.videoPoster'); domPoster.forEach(item => { item && (item.style.opacity = '1'); }); }; const toWorkVideoPageFn = (eventId: number, photoId: string, userId: string, eventStatus: number) => { if (isInLiveRoom()) { return; } sendClick({ action: 'BLIND_DATE_VENUE_VIDEO_CARD', params: { author_id: userId, id: eventId, status: eventStatus, tab_name: props.tabPhotoList[props.currentTabIndex].tabTitle, video_id: photoId, }, }); // videoStop(); toWorkVideoPage(photoId, ''); }; const handleBook = throttle((isCancel:boolean, eventId: number, userId: number, photoId: string) => { console.log('handleBook', isCancel, eventId, userId, photoId); if (!props.bookStatusLoading) { // 网络请求阶段bookStatusLoading为true emit('handleBook', isCancel, eventId, userId, photoId); } }, 1000); const handleEnroll = throttle((isCancel:boolean, eventId: number, userId: number, photoId: string) => { console.log('handleEnroll', isCancel, eventId, userId, photoId); emit('handleEnroll', isCancel, eventId, userId, photoId); }, 1000); const handleOnline = (userId: number) => { // videoStop(); const { authorId } = query; if ( String(authorId) === String(userId)) { showToast('已经在当前直播间'); return; } if (userId) { toLiveRoomByUserId(String(userId), { liveSource: 'BLIND_DATE_CNY_PAGE_BUTTON', sourceType: 269, }); } }; const openProfileFn = (userId: number) => { // videoStop(); openProfile(String(userId)); }; const onVideoWaiting = (id: string) => { // loading const domLoading = document.querySelectorAll<HTMLElement>(`.${id}`); domLoading.forEach(item => { item && (item.style.opacity = '1'); }); console.log('onVideoWaiting', id); }; // 性能考虑,不采用display,用opacity const onVideoPlaying = (id: string) => { try { // 关闭loading const domLoading = document.querySelectorAll<HTMLElement>(`.${id}`); domLoading.forEach(item => { item && (item.style.opacity = '0'); }); // 关闭poster const domPoster = document.querySelectorAll<HTMLElement>(`.${id}_poster`); domPoster.forEach(item => { item && (item.style.opacity = '0'); }); const video = getCurrentVideo(); video && (video.muted = false); } catch (e) { console.log('onVideoPlaying', e); } }; const onVideoOtherEvent = throttle((name: string, id: string) => { if (name === 'timeupdate') { const dom = getCurrentVideo(); if (dom?.readyState < 4 ) { onVideoWaiting(id); } else { onVideoPlaying(id); } } }, 1000); // 不是低端机 && 不在直播间 const isShowVideo = () => { return !isLowDeviceBiz() && !isInLiveRoom(); }; const oneventvideo = (name, id) => { // const dom = getCurrentVideo(); // console.log('name', name, id, dom.readyState); if (name === 'canplaythrough') { try { // 关闭loading const domLoading = document.querySelectorAll<HTMLElement>(`.${id}`); domLoading.forEach(item => { item && (item.style.opacity = '0'); }); } catch (e) { console.log('onVideoPlaying', e); } } }; // 当前dom节点改变:loop模式下,边界场景下视觉上未切换,实际已切换,此时会出现有声音,无画面情况,此case需要重新播放视频 const onDomChangePanel = (index, lastIndex) => { handleVideoPlay(); }; const getTextInfo = (title: string) => { return title.indexOf('「') === 0; }; return { swiperPanelRef, currentSwiperPanelIndex, onSwiperTabsChange, onSwiperPanelChange, toWorkVideoPageFn, eventListByIndex, tabsByTabPhotoList, handleBook, handleEnroll, handleOnline, openProfileFn, isInLiveRoom, BOOKSTATUS, ENROLLSTATUS, EVENTSTATUS, onVideoWaiting, onVideoPlaying, loadingsvg, isShowVideo, swiperPannelItemRef, oneventvideo, onVideoOtherEvent, onDomChangePanel, getTextInfo, laterRender, }; }, }); </script>