前言

一直想做一个滑动验证码的组件。

html 结构

<div className={styles.container}>
    <div className={styles.slider}>
        <!-- 滑块 -->
        <div className={styles.sliderHandle} onMouseDown={mouseDown} style={sliderHandleStyle} > >> </div>
        <!-- 轨道 -->
        <div className={styles.sliderRail}>
            <div className={styles.text}>向右滑动验证</div>
        </div>
        <!-- 滑块路线 -->
        <div className={styles.sliderTrack} style={{width: ((offset.x) / 250 * 100 + "%"), borderRadius: (offset.x < 250) ? 'none' : '45px' }}></div>
    </div>
</div>

less 结构

主要是一些定位, 相对定位中的绝对定位

.container {
    width: 400px;
    height: 400px;
    margin: 50px auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    user-select: none;

    .slider {
        width: 250px;
        height: 45px;
        position: relative;
        border-radius: 45px;

        .sliderHandle {
            width: 45px;
            height: 45px;
            border-radius: 50%;
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
            background-color: #fff;
            z-index: 99;
            position: absolute;
            left: 0;
            top: 0;
            right: auto;
            transform: translateX(-50%);
            box-shadow:  1px 5px 2px #eee;
            cursor: pointer;
        }

        .sliderRail {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            width: 100%;
            border-radius: 45px;
            background-color: #f5f5f5;
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }

        .sliderTrack {
            position: absolute;
            width: 0%;
            top: 0;
            left: 0;
            z-index: 89;
            right: 0;
            bottom: 0;
            border-top-left-radius: 45px;
            border-bottom-left-radius: 45px ;
            background-color: #91d5ff;
        }
    }
}

代码逻辑

代码中主要使用的是 react Hook, 主要的逻辑是使用了 mouseup, mouseover, mouseDown

const Block: React.FC = () => {
    const [dragging, setDragging] = useState(false);
    const [origin, setOrigin] = useState({ x: 0, y: 0 });
    const [offset, setOffset] = useState({ x: 0, y: 0});

    const mouseDown = useCallback(({clientX, clientY}) => {
        console.log("mouseDown", clientX);
        setOrigin((state) => ({ x: clientX, y: clientY}))
        setDragging(true);
    }, []);

    const mouseMove = useCallback(({clientX, clientY}) => {
        let x =  clientX - origin.x;
        if( x >= 250 ) {
            x = 250
        } else if(x / 250 * 100 <= 0 ) {
            x = 0;
        }
        const transition =  { x, y: clientY - origin.y }; 

        if(x >= 250) {
            setDragging(() => false)
        }
        setOffset(state => {
            return  {
                ...state,
                ...transition
            }
        });
      }, [origin]);

    const mouseUp = useCallback(() => {
        console.log("up")

        setDragging(() => false)

      }, []);

    useEffect(() => {
      if (dragging) {
        window.addEventListener('mousemove', mouseMove);
        window.addEventListener('mouseup', mouseUp);
      } else {
        window.removeEventListener('mousemove', mouseMove);
        window.removeEventListener('mouseup', mouseUp);

        setOffset((state) => {
            let x = state.x
            if(x>= 250) {
                console.log("验证通过")            
            } else if(x< 250) {
                x = 0
            }
            return {
                x: x,
                y: state.y
            }
        })
      }
    }, [dragging]);

    const sliderHandleStyle = useMemo(() => ({
        left: (offset.x) / 250 * 100 + "%",
        transition: (!dragging ? 'left 500ms' : 'none' )
    }), [offset, dragging])

    return (
        <div>
            <div className={styles.container}>
                <div className={styles.slider}>
                    <div className={styles.sliderHandle} onMouseDown={mouseDown} style={sliderHandleStyle} ><DoubleRightOutlined /></div>
                    <div className={styles.sliderRail}>
                        <div className={styles.text}>向右滑动验证</div>
                    </div>
                    <div className={styles.sliderTrack} style={{width: ((offset.x) / 250 * 100 + "%"), borderRadius: (offset.x < 250) ? 'none' : '45px' }}></div>
                </div>
            </div>
        </div>
    );
}

线上仓库

demo

这里记录一下在使用 react hook 的一些问题

先谈谈 useCallback 这个函数吧

useCallback

useCallback 提供了两个参数,一个个回调函数,另一个是依赖数组。
当依赖数组不改变时,此时回调函数不改变,已达到缓存的效果,减少在re-render的时候重新生成函数

案例1

// Child.js

const Child = ({cb}) => {
    return (
        <div>
            <button onClick={cb}>点击</div>
        </div>
    )
}
const App = () => {
    // 此时 当App 重新渲染的时候,会重新生成 memoClick
    // const memoClick = () => console.log("click"); 

    // 用了useCallback 后 无论App 是否重新渲染,传给 Child 的 memoClick 都是之前的引用
    const memoClick = useCallback(() => console.log("click"), [])
    return (
        <div>
            <Child cb={cb} />
        </div>
    )
}

其他

当useCallback 的依赖数组 在useCallback内设置变化会导致无限循环

const App = () => {
    const [a, setA] = useState(1);
    const memoClick = useCallback(() => {
        console.log("click")
        setA((state) => {
            const after = state.a++;
            return after
        })
    }, [a])

    return (
        <div>
            <div>{a}</div>
            <Button onClick={memoClick}>点击</Button>
        </div>
    )
}

然后我找了很多文章,应该只有这篇是讲的比较清晰的, React Hooks(二): useCallback 之痛

后话

验证码组件还没做好,但大体逻辑是这样