Skip to content

sanyuankexie/hello.kexie.space

Repository files navigation

一个不像是README的文档

它更像是踩坑日记。

已经遇到过的问题

使用vercel部署,react-router跳转的页面会404

stack overflow上有相关的解决方案:Why does react-router not works at vercel?

只需要将vercel.json对应的配置文件改成这样。通过正则,让所有的路径资源的请求都到/,使路由生效。

{
    "routes": [
        {
            "src": "/[^.]+",
            "dest": "/",
            "status": 200
        }
    ]
}

小球刷新率设置

不同的显示器有不同的刷新率,使用setInterval(callback, ms),60FPS的参数是16.7ms,而144FPS的参数是7ms。在实际渲染的过程中还会出现丢帧的情况。[深入理解 requestAnimationFrame]

为了更好的适应不同显示器的帧数,使用window.requestAnimationFrame(callback)。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次。

Markdown文档解析

基础的文本用markdown-it,代码块的渲染用higtlight.js

代码在:/src/utils/markdown.ts。组件在/src/pages/Article下,样式写得非常的丑,以后有空再改。

主页评论

这个是用本仓库的issues。知道了组织名、项目名和issue编号,通过GitHub的API来获取也不难。

WebScoket

原本页面上的小球是可以在页面上发送消息的,因为没有将用户的登陆进行身份校验,也没有将用户发送的消息储存下来,所以直接砍掉了。

image

/src/hooks/useClient这个hook采用了消息订阅的方法,负责将消息转发给的不同回调。如果服务端传来的消息没有被订阅,那么这个消息将会被忽略。(这个消息订阅实现的时,只允许一个消息对应一个订阅者)

useEffect(() => {
    // 连接上服务器,服务器会主动回传。
    setDeliverier({ "hello": handleHelloAction }); 

    // 当有人连接上服务器,服务器会向其它用户发送这个消息。
    setDeliverier({ "enter": handleEnterAction }); 

    // 当有人发送消息至服务器,服务器会向所有用户广播这个的消息。
    setDeliverier({ "talk": handleTalkAction });

    // 当有人移动的行为发送至服务器,服务器会向其他用户广播这个消息。
    setDeliverier({ "move": handleMoveAction });

    // 客户端可以向服务器请求当前在线用户的信息。
    setDeliverier({ "stand up": handleStandUpAction });

    // 用户下线,服务器会向其他用户广播这个消息。
    setDeliverier({ "leave": handleLeaveAction });
}, []);

函数式组件里的回调函数里无法获取最新的state

/src/component/BallRoom里以各种handle开头的函数都有这个问题。这些回调被执行时,都获取不到最新的userList。这个问题产生的原因是闭包。

那么可以将获取的userList的方法放在ref里,这样每次这个方法都是最新的,获取到的userList也是最新的了。

function BallRoom(){
    ......
    const userListRef = useRef();
    userListRef.current = () => userList;
    ......
    // 获取的方式
    const data = userListRef.current();
}

小球的移动(父组件调用子组件内部的方法)

Ball组件是一个函数式组件,只能用hook的方法。useImperativeHandle与forwardRef

关于音乐播放器

没空,暂时搁置,所以/src/App.tsx中的组合 store 也暂时没用。而且现在的 reducer 设计非常有问题。

渐显和渐隐

用简单的话来说:

  1. 将需要增加这个特效的组件想方设法加入到 redux 里,可以使用useScrollAnimationRefs (一个自定义的hook)的第一个参数,用法与 ref 一样。如果该组件无法 ref ,那只能手动添加。/src/pages/Welcome/ContestList.tsx里只能这样。
  2. 选择一个容器组件,使用useScrollHandler(一个自定义的hook)作为监视器。

嵌入等比缩放的视频

参考博客:使用iframe嵌入等比缩放的哔哩哔哩视频

在父类中,宽度被设为100%,高度被设为0padding-bottom属性(外部下边距)被设为75%。因为当padding-bottom的值为百分比时,百分比计算的基准为父元素的宽。

这样,父类的实际宽高比(包含边距的宽高比)就变为了四比三。

于是子类宽高都100%position:relative在里边撑开就可以了。

antd design修改主题色

这个项目是从 create-react-app 迁移至 vite 的。修改主题色是用新的主题色去替换旧的主题色。(less 全局变量替换)在vue3-vite中配置less的全局变量

需要配置这两个文件。/vite.config.ts/src/theme.lessantd design配置主题

可能想问的问题

如何更换头像?

修改 local storage 中的 useravatar 字段,用其它图片的链接替代。

image

一些过程

小球相关

移动

其实单独抽离了一个 Float 组件出来,这个组件里所有子元素都能像小球一样移动。

Float是用类组件的形式写的,写得非常的丑,有空可以重构一下。

可以注意到,小球不随着滚动条的滚动二变化位置,是因为它 position:absolute;。位置的移动本质上就是 lefttop 的变化。

浏览器自带有 mousedown mousemove mouseup 这三个事件(触摸事件同理),抽象流程图如下。

image

消息显示

当有新消息到来,直接显示消息框。在5s后消息框会消失,若5s内又收到新消息,则推迟5s后再消失(防抖)。

export function debounce<T extends (...args: any[]) => void>(func: T, dalay: number) {
    let timer: any = null
    return (...args: Parameters<T>): void => {
        if (!!timer) clearTimeout(timer)
        timer = setTimeout(() => {
            func(...args)
        }, dalay)
    }
}
const delaySetContent = debounce(setContent, 5000);

位置信息的发送

如果是显示器刷新一次就向服务器发送一次的话也太夸张了,这个发送位置信息的函数在16ms内只会被执行一次(节流)。

16ms如果人少的话应该是不会卡的......卡了再往上调

export function throttle<T extends (...arg: any[]) => void>(func: T, interval: number) {
    let _args: any = null;
    let _timer: any = null;
    return (...args: Parameters<T>) => {
        _args = args;
        if (!_timer) {
            _timer = setTimeout(() => {
                func(..._args);
                _timer = null;
            }, interval);
        }
    }
}
/**
 * 拖动自己的小球,将位置信息发送至服务器
 */
const onMoving = throttle((position: Position) => {
    if (!unique) return;
    const res = { type: "move", data: position, userName: user.name };
    client.send(res);
}, 16);

挂载列表的ref

/src/component/BallRoom 有用到类似的。

const floatRefs = useRef(new Map());
function floatRef(user: AtomUser) {
    return (el) => {
        floatRefs.current.set(user.name, el);
    }
}

使用的时候,通过 current 取 Map 即可得到对应元素。

floatRefs.current.get(user.name);

跳转页面后,返回保留之前的滚动条高度

在学习方向的介绍这,点击跳转页面回来仍然是现在的高度。

image

没有把saveScrollTop写在useEffect销毁的回调中,为了更方便的自定义。

export function usePageJumpSaveScrollTop() {
    useEffect(() => {
        const scrollTop = localStorage.getItem("scrollTop");
        if (scrollTop) {
            localStorage.removeItem("scrollTop");
            document.body.scrollTop = ~~scrollTop;
        }
    }, []);

    function saveScrollTop() {
        const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
        localStorage.setItem("scrollTop", JSON.stringify(scrollTop));
    }

    return saveScrollTop;
}