1. 程式人生 > 其它 >使用Typescript+React hooks實現滑動點讚的元件

使用Typescript+React hooks實現滑動點讚的元件

自從React推出hooks後,函式式元件好像可以配合hooks做任何事了,不用class component也能實現各種功能了,而且這種模式更加靈活,更易於拆分與封裝。一番體驗下來,充分意識到,函數語言程式設計+hooks才是React的王道。今天我們就來使用hooks來實現一下滑動列表項顯示點贊按鈕的功能。頁面如下所示:

梳理一下功能:滑動列表條目時,左滑顯示點贊按鈕,並且關掉其他條目的按鈕顯示;右滑,關掉當前條目的點贊按鈕展示。

其實這個功能實現起來沒有什麼難度,只是給列表條目繫結touchmove事件,左滑的時候,新增樣式,使條目左移,顯示出點贊按鈕。我們直接上程式碼看看:

// index.tsx
import React from 'react';
import './index.less';
import { newsItem } from '../../types';
import useSwipe from '../../hooks/useSwiper';

const newsListData = [
    {
        id: 1,
        title: '菲律賓多位學者發起網上請願',
        src: 'pic.png',
        abstract: '菲律賓多位學者5日發起網上請願活動,希望收集足夠多的簽名。',
        releaseDate: '2021-08-06 15:42',
        like: false,
    },
    {
        id: 2,
        title: '引導粉絲文化步入健康軌道',
        src: 'pic1.png',
        abstract:'各相關方都要負起社會責任,堅持正確導向,自覺做社會正能量的放大器',
        releaseDate: '08-05 06:30',
        like: false,
    },
];

function SwiperItem({
    title,
    src,
    abstract,
    releaseDate,
    isCurrent,
    like,
    swipeEventHandler,
    likeEventHandler,
}: newsItem) {
    return (
        <div className="swiper-list-item" {...swipeEventHandler}>
            <div
                className={[
                    'item-content-wrap',
                    isCurrent ? 'swiper-left' : '',
                ].join(' ')}
            >
                <img className="news-pic" src={src} alt={title} />
                <div className="item-right-content">
                    <p className="news-title">{title}</p>
                    <p className="news-abstract">{abstract}</p>
                    <p className="news-publish-date">釋出時間:{releaseDate}</p>
                </div>
            </div>
            <div className="like-btn" {...likeEventHandler}>
                {like ? '取消' : '點贊'}
            </div>
        </div>
    );
}

export default function SwiperList() {
    const { swipeList, createSwipeProps, createLikeProps } = useSwipe(newsListData);
    return (
        <div className="swiper-list-cpn">
            {swipeList.map((item) => (
                <SwiperItem
                    key={item.id}
                    {...{
                        ...item,
                        ...createSwipeProps(item.id),
                        ...createLikeProps(item.id),
                    }}
                />
            ))}
        </div>
    );
}

// useSwipe.ts
import { useState } from 'react';
import { newsItem } from '../types';

function setSwipeLeftList(list: Array<newsItem>, id: number): Array<newsItem> {
    return list.map((item) => ({
        ...item,
        isCurrent: item.id === id,
    }));
}

function setSwipeRightList(list: Array<newsItem>): Array<newsItem> {
    return list.map((item) => ({
        ...item,
        isCurrent: false,
    }));
}

function setLikeList(list: Array<newsItem>, id: number): Array<newsItem> {
    return list.map((item) => ({
        ...item,
        like: item.id === id ? !item.like : item.like,
    }));
}

export default function useSwipe(list: Array<newsItem>) {
    const [swipeList, setSwipeList] = useState(list);
    const [preId, setPreId] = useState(-1);
    const [startX, setStartX] = useState(0);

    return {
        swipeList,
        createSwipeProps: (id: number) => {
            return {
                swipeEventHandler: {
                    onTouchStart: (e: React.TouchEvent) => {
                        // 滑動初始位置
                        setStartX(e.targetTouches[0].clientX);
                    },
                    onTouchEnd: (e: React.TouchEvent) => {
                        const endX = e.changedTouches[0].clientX;
                        const deltaX = startX - endX;
                        if (deltaX > 50) {
                            // 左滑
                            if (id === preId) return;
                            setSwipeList(setSwipeLeftList(swipeList, id));
                            setPreId(id);
                        } else if (deltaX < -50) {
                            // 右滑
                            if (id !== preId) return;
                            setSwipeList(setSwipeRightList(swipeList));
                            setPreId(-1);
                        }
                    },
                },
            };
        },
        createLikeProps: (id: number) => {
            return {
                likeEventHandler: {
                    onClick: () => {
                        setTimeout(() => {
                            setSwipeList(setLikeList(swipeList, id));
                        }, 100);
                    },
                },
            };
        },
    };
}

// index.less
.swiper-list-cpn {
    padding: 20px;
    .swiper-list-item {
        position: relative;
        height: 140px;
        margin-bottom: 20px;
        border-radius: 4px;
        overflow: hidden;
        box-shadow: 2px 2px 5px grey;
        .item-content-wrap {
            position: absolute;
            left: 0;
            top: 0;
            z-index: 1;
            display: flex;
            align-items: center;
            height: 100%;
            box-sizing: border-box;
            padding: 10px 20px;
            background: #fff;
            transition: transform 0.3s ease-in;
            &.swiper-left {
                transform: translateX(-60px);
            }
            .news-pic {
                width: 80px;
                margin-right: 20px;
            }
            .item-right-content {
                flex: 1;
                .news-title {
                    margin-bottom: 10px;
                    font-size: 16px;
                    font-weight: bold;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                }
                .news-abstract {
                    margin-bottom: 10px;
                    font-size: 14px;
                }
                .news-publish-date {
                    font-size: 12px;
                    color: grey;
                }
            }
        }
        .like-btn {
            position: absolute;
            right: 0;
            top: 0;
            width: 60px;
            line-height: 140px;
            text-align: center;
            background: rgb(255, 66, 66);
        }
    }
}

看程式碼的結構的話,我把整個邏輯層抽離出來一個自定義hook,這個hook很簡單,我的列表是根據列表的資料渲染出來的,所以其實我只需要跟列表的資料打交道就行了,不管是左滑右滑還是點贊取消點贊,都只是改變列表的資料內容,改變後再渲染到頁面上。Hooks很強大,也很靈活,這既是優點也是缺點,如果你能很好進行模組的拆解與封裝,那麼程式碼的可讀性和可維護性會很高,反之,簡單的懟hooks,後期維護將是災難。