所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

React强大且灵活hooks库——ahooks入门实践之场景类(scene)hook详解

什么是 ahooks?

ahooks 是一个 React Hooks 库,提供了大量实用的自定义 hooks,帮助开发者更高效地构建 React 应用。其中场景类 hooks 是 ahooks 的一个重要分类,专门针对特定业务场景提供解决方案。

安装 ahooks

npm install ahooks

场景类 hook 详解

useAntdTable – Antd 表格集成

useAntdTable用于处理表格与表单的联动场景。

import React from "react";
import { useAntdTable } from "ahooks";
import { Form, Input, Button, Table } from "antd";

const UserTable = () => {
  const [form] = Form.useForm();

  const { tableProps, search } = useAntdTable(
    async (params, form) => {
      const { current, pageSize } = params;

      const response = await fetch("/api/users", {
        method: "POST",
        body: JSON.stringify({
          page: current,
          size: pageSize,
          ...form,
        }),
      });
      const data = await response.json();
      return {
        list: data.list,
        total: data.total,
      };
    },
    {
      form,
      defaultPageSize: 10,
    }
  );

  return (
    <div className="container m-4">
      <Form form={form} layout="inline" className="mb-2">
        <Form.Item name="name" label="姓名">
          <Input placeholder="请输入姓名" />
        </Form.Item>
        <Form.Item name="email" label="邮箱">
          <Input placeholder="请输入邮箱" />
        </Form.Item>
        <Form.Item>
          <Button type="primary" onClick={search.submit}>
            搜索
          </Button>
          <Button onClick={search.reset} style={{ marginLeft: 8 }}>
            重置
          </Button>
        </Form.Item>
      </Form>

      <Table
        {...tableProps}
        columns={[
          { title: "姓名", dataIndex: "name" },
          { title: "邮箱", dataIndex: "email" },
          { title: "创建时间", dataIndex: "createTime" },
        ]}
      />
    </div>
  );
};

useFusionTable – Fusion 表格集成

useFusionTable用于处理表格与表单的联动场景。

import React from "react";
import { useFusionTable } from "ahooks";
import { Table, Button, Input } from "@alifd/next";

const UserTable = () => {
  const { tableProps, search, loading } = useFusionTable(
    async (params) => {
      const { current, pageSize, ...rest } = params;
      const response = await fetch("/api/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          page: current,
          size: pageSize,
          ...rest,
        }),
      });
      const data = await response.json();
      return {
        list: data.list,
        total: data.total,
      };
    },
    {
      defaultPageSize: 10,
      defaultParams: [{ current: 1, pageSize: 10 }],
    }
  );

  return (
    <div>
      <div style={{ marginBottom: 16 }}>
        <Input
          placeholder="搜索用户名"
          onChange={(value) => search.setFieldValue("name", value)}
          style={{ width: 200, marginRight: 8 }}
        />
        <Button type="primary" onClick={search.submit}>
          搜索
        </Button>
        <Button onClick={search.reset} style={{ marginLeft: 8 }}>
          重置
        </Button>
      </div>

      <Table {...tableProps} loading={loading}>
        <Table.Column title="姓名" dataIndex="name" />
        <Table.Column title="邮箱" dataIndex="email" />
        <Table.Column title="创建时间" dataIndex="createTime" />
      </Table>
    </div>
  );
};

useInfiniteScroll – 无限滚动

import React from "react";
import { useInfiniteScroll } from "ahooks";

const InfiniteList = () => {
  const { data, loading, noMore } = useInfiniteScroll(
    async (d) => {
      const { list = [], total = 0 } = await fetchData(d?.list?.length || 0);
      return {
        list: [...(d?.list || []), ...list],
        total,
      };
    },
    {
      target: document,
      isNoMore: (d) => d?.list?.length >= d?.total,
      threshold: 100,
    }
  );

  return (
    <div style={{ height: "100vh", overflow: "auto" }}>
      {data?.list?.map((item, index) => (
        <div
          key={`${item.id}-${index}`}
          style={{
            padding: "12px",
            borderBottom: "1px solid #eee",
            backgroundColor: index % 2 === 0 ? "#f9f9f9" : "white",
          }}
        >
          <h3>{item.title}</h3>
          <p>{item.description}</p>
        </div>
      ))}

      {loading && (
        <div style={{ textAlign: "center", padding: "20px" }}>加载中...</div>
      )}

      {noMore && (
        <div style={{ textAlign: "center", padding: "20px", color: "#999" }}>
          没有更多数据了
        </div>
      )}
    </div>
  );
};

// 模拟数据获取函数
const fetchData = async (offset = 0) => {
  // 模拟API调用
  await new Promise((resolve) => setTimeout(resolve, 1000));
  const pageSize = 10;
  const mockData = Array.from({ length: pageSize }, (_, i) => ({
    id: offset + i,
    title: `项目 ${offset + i + 1}`,
    description: `这是第 ${offset + i + 1} 个项目的描述`,
  }));

  return {
    list: mockData,
    total: 50, // 总共50条数据
  };
};

export default InfiniteList;

usePagination – 分页管理

import React from "react";
import { usePagination } from "ahooks";
import { Button, Input } from "antd";

const PaginationExample = () => {
  const { data, loading, pagination, run } = usePagination(
    async ({ current, pageSize }) => {
      const response = await fetch(
        `/api/users?page=${current}&size=${pageSize}`
      );
      return response.json();
    },
    {
      defaultPageSize: 5,
      defaultCurrent: 1,
    }
  );

  const [searchValue, setSearchValue] = React.useState("");

  const handleSearch = () => {
    run({ current: 1, pageSize: pagination.pageSize, search: searchValue });
  };

  return (
    <div>
      <div style={{ marginBottom: 16 }}>
        <Input
          placeholder="搜索用户"
          value={searchValue}
          onChange={(e) => setSearchValue(e.target.value)}
          style={{ width: 200, marginRight: 8 }}
          onPressEnter={handleSearch}
        />
        <Button type="primary" onClick={handleSearch}>
          搜索
        </Button>
      </div>

      {loading ? (
        <div>加载中...</div>
      ) : (
        <div>
          {data?.list?.map((user) => (
            <div
              key={user.id}
              style={{
                padding: "8px",
                border: "1px solid #ddd",
                marginBottom: "8px",
              }}
            >
              <strong>{user.name}</strong> - {user.email}
            </div>
          ))}

          <div style={{ marginTop: 16 }}>
            <Button
              disabled={pagination.current === 1}
              onClick={() => pagination.changeCurrent(pagination.current - 1)}
            >
              上一页
            </Button>
            <span style={{ margin: "0 16px" }}>
              第 {pagination.current} 页,共 {pagination.total} 条
            </span>
            <Button
              disabled={pagination.current >= pagination.totalPages}
              onClick={() => pagination.changeCurrent(pagination.current + 1)}
            >
              下一页
            </Button>
          </div>
        </div>
      )}
    </div>
  );
};

useDynamicList – 动态列表

import React from "react";
import { useDynamicList } from "ahooks";
import { Button, Input, Card } from "antd";

const DynamicListExample = () => {
  const { list, remove, getKey, insert, move, replace, reset } = useDynamicList(
    [
      { name: "张三", age: 25 },
      { name: "李四", age: 30 },
      { name: "王五", age: 28 },
    ]
  );

  const [inputName, setInputName] = React.useState("");
  const [inputAge, setInputAge] = React.useState("");

  const handleAdd = () => {
    if (inputName && inputAge) {
      insert(0, { name: inputName, age: parseInt(inputAge) });
      setInputName("");
      setInputAge("");
    }
  };

  return (
    <div>
      <div style={{ marginBottom: 16 }}>
        <Input
          placeholder="姓名"
          value={inputName}
          onChange={(e) => setInputName(e.target.value)}
          style={{ width: 120, marginRight: 8 }}
        />
        <Input
          placeholder="年龄"
          value={inputAge}
          onChange={(e) => setInputAge(e.target.value)}
          style={{ width: 80, marginRight: 8 }}
        />
        <Button type="primary" onClick={handleAdd}>
          添加到开头
        </Button>
        <Button onClick={reset} style={{ marginLeft: 8 }}>
          重置
        </Button>
      </div>

      {list.map((item, index) => (
        <Card
          key={getKey(index)}
          size="small"
          style={{ marginBottom: 8 }}
          extra={
            <div>
              <Button
                size="small"
                onClick={() => move(index, index - 1)}
                disabled={index === 0}
              >
                上移
              </Button>
              <Button
                size="small"
                onClick={() => move(index, index + 1)}
                disabled={index === list.length - 1}
                style={{ marginLeft: 4 }}
              >
                下移
              </Button>
              <Button
                size="small"
                danger
                onClick={() => remove(index)}
                style={{ marginLeft: 4 }}
              >
                删除
              </Button>
            </div>
          }
        >
          <p>
            <strong>姓名:</strong> {item.name}
          </p>
          <p>
            <strong>年龄:</strong> {item.age}
          </p>
        </Card>
      ))}
    </div>
  );
};

useVirtualList – 虚拟列表

import React, { useMemo, useRef } from "react";
import { useVirtualList } from "ahooks";

export default function Demo() {
  const containerRef = useRef(null);
  const wrapperRef = useRef(null);

  // 生成大量测试数据
  const originalList = useMemo(() => {
    return Array.from({ length: 10000 }, (_, index) => ({
      id: index,
      title: `列表项 ${index + 1}`,
      content: `这是第 ${index + 1} 个列表项的内容,包含一些示例文本。`,
      timestamp: new Date(
        Date.now() - Math.random() * 10000000000
      ).toLocaleString(),
    }));
  }, []);

  // 使用 useVirtualList hook - 正确版本
  const [list] = useVirtualList(originalList, {
    containerTarget: containerRef,
    wrapperTarget: wrapperRef,
    itemHeight: 80,
    overscan: 10,
  });

  console.log("originalList length:", originalList.length);
  console.log("virtual list length:", list.length);
  console.log("containerRef:", containerRef);
  console.log("wrapperRef:", wrapperRef);

  // 计算总高度
  const totalHeight = originalList.length * 80; // 每个项目80px高度
  console.log("totalHeight:", totalHeight);
  console.log("list first item:", list?.[0]);

  // 如果虚拟列表不工作,先显示普通列表
  const showNormalList = !list || list.length === 0;

  return (
    <div className="p-6 max-w-4xl mx-auto">
      <h1 className="text-3xl font-bold text-gray-800 mb-6">
        useVirtualList 虚拟列表示例
      </h1>

      <div className="mb-4 text-sm text-gray-600">
        总共 {originalList.length} 个列表项,但只渲染可见区域的项目
        {showNormalList && (
          <span className="text-orange-600"> (使用普通列表作为备用)</span>
        )}
      </div>

      {/* 虚拟列表容器 */}
      <div
        ref={containerRef}
        className="border border-gray-200 rounded-lg overflow-y-auto bg-white shadow-sm"
        style={{ height: "600px" }}
      >
        <div ref={wrapperRef}>
          {showNormalList
            ? // 备用:普通列表
              originalList.slice(0, 20).map((item) => (
                <div
                  key={item.id}
                  className="border-b border-gray-100 p-4 hover:bg-gray-50 transition-colors"
                  style={{ height: "80px" }}
                >
                  <div className="flex items-center justify-between">
                    <div className="flex-1">
                      <h3 className="font-semibold text-gray-800 mb-1">
                        {item.title}
                      </h3>
                      <p className="text-sm text-gray-600 line-clamp-2">
                        {item.content}
                      </p>
                    </div>
                    <div className="text-xs text-gray-400 ml-4">
                      {item.timestamp}
                    </div>
                  </div>
                </div>
              ))
            : // 虚拟列表
              list.map((item) => (
                <div
                  key={item.index}
                  className="border-b border-gray-100 p-4 hover:bg-gray-50 transition-colors"
                  style={{ height: "80px" }}
                >
                  <div className="flex items-center justify-between">
                    <div className="flex-1">
                      <h3 className="font-semibold text-gray-800 mb-1">
                        {item.data.title}
                      </h3>
                      <p className="text-sm text-gray-600 line-clamp-2">
                        {item.data.content}
                      </p>
                    </div>
                    <div className="text-xs text-gray-400 ml-4">
                      {item.data.timestamp}
                    </div>
                  </div>
                </div>
              ))}
        </div>
      </div>
    </div>
  );
}

6. useHistoryTravel – 历史记录

import React from "react";
import { useHistoryTravel } from "ahooks";
import { Button, Input, Card } from "antd";

const HistoryTravelExample = () => {
  const { value, setValue, backLength, forwardLength, back, forward, reset } =
    useHistoryTravel("");

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  return (
    <div>
      <Card title="历史记录管理" style={{ marginBottom: 16 }}>
        <div style={{ marginBottom: 16 }}>
          <Input
            value={value}
            onChange={handleChange}
            placeholder="输入内容,每次修改都会记录历史"
            style={{ marginBottom: 8 }}
          />
          <div>
            <Button onClick={reset} style={{ marginRight: 8 }}>
              重置
            </Button>
            <span style={{ color: "#666" }}>当前值: {value || "(空)"}</span>
          </div>
        </div>

        <div style={{ marginBottom: 16 }}>
          <Button
            disabled={backLength <= 0}
            onClick={back}
            style={{ marginRight: 8 }}
          >
            后退 ({backLength})
          </Button>
          <Button
            disabled={forwardLength <= 0}
            onClick={forward}
            style={{ marginRight: 8 }}
          >
            前进 ({forwardLength})
          </Button>
        </div>
      </Card>

      <Card title="历史记录信息">
        <p>
          <strong>可后退步数:</strong> {backLength}
        </p>
        <p>
          <strong>可前进步数:</strong> {forwardLength}
        </p>
        <p>
          <strong>总历史记录数:</strong> {backLength + forwardLength + 1}
        </p>
      </Card>
    </div>
  );
};

useNetwork – 网络状态

import React from "react";
import { useNetwork } from "ahooks";
import { Card, Tag, Alert } from "antd";

const NetworkExample = () => {
  const network = useNetwork();

  const getNetworkStatus = () => {
    if (network.online) {
      return <Tag color="green">在线</Tag>;
    }
    return <Tag color="red">离线</Tag>;
  };

  const getNetworkType = () => {
    if (network.effectiveType) {
      return <Tag color="blue">{network.effectiveType}</Tag>;
    }
    return <Tag color="orange">未知</Tag>;
  };

  return (
    <div>
      <Card title="网络状态监控">
        <div style={{ marginBottom: 16 }}>
          <strong>连接状态:</strong> {getNetworkStatus()}
        </div>

        <div style={{ marginBottom: 16 }}>
          <strong>网络类型:</strong> {getNetworkType()}
        </div>

        {!network.online && (
          <Alert
            message="网络连接已断开"
            description="请检查您的网络连接,某些功能可能无法正常使用。"
            type="warning"
            showIcon
            style={{ marginBottom: 16 }}
          />
        )}

        <Card title="详细网络信息" size="small">
          <p>
            <strong>在线状态:</strong> {network.online ? "是" : "否"}
          </p>
          <p>
            <strong>网络类型:</strong> {network.effectiveType || "未知"}
          </p>
          <p>
            <strong>下行速度:</strong>{" "}
            {network.downlink ? `${network.downlink} Mbps` : "未知"}
          </p>
          <p>
            <strong>往返时间:</strong>{" "}
            {network.rtt ? `${network.rtt} ms` : "未知"}
          </p>
          <p>
            <strong>保存数据模式:</strong> {network.saveData ? "是" : "否"}
          </p>
        </Card>
      </Card>
    </div>
  );
};

useSelections – 多选管理

import React, { useMemo } from "react";
import { useSelections } from "ahooks";
import { Checkbox, Button, Card, List } from "antd";

const SelectionsExample = () => {
  const list = useMemo(
    () => [
      { id: 1, name: "苹果", price: 5.5 },
      { id: 2, name: "香蕉", price: 3.2 },
      { id: 3, name: "橙子", price: 4.8 },
      { id: 4, name: "葡萄", price: 8.9 },
      { id: 5, name: "草莓", price: 12.5 },
      { id: 6, name: "蓝莓", price: 15.8 },
    ],
    []
  );

  const {
    selected,
    allSelected,
    isSelected,
    toggle,
    toggleAll,
    partiallySelected,
  } = useSelections(list, {
    defaultSelected: [list[0], list[2]], // 默认选中第一个和第三个项
  });

  const totalPrice = selected
    .map((item) => item.price || 0)
    .reduce((sum, price) => sum + price, 0);

  // 调试信息
  console.log("selected:", selected);
  console.log("allSelected:", allSelected);
  console.log("partiallySelected:", partiallySelected);
  console.log("list:", list);

  return (
    <div>
      <div
        style={{
          marginBottom: 16,
          padding: "10px",
          backgroundColor: "#f5f5f5",
          borderRadius: "4px",
        }}
      >
        <strong>当前选中项 ID:</strong>{" "}
        {selected.map((item) => item.id).join(", ") || "无"}
      </div>

      <Card title="多选管理示例" style={{ marginBottom: 16 }}>
        <div style={{ marginBottom: 16 }}>
          <Checkbox
            checked={allSelected}
            onClick={toggleAll}
            indeterminate={partiallySelected}
            style={{ marginRight: 8 }}
          >
            全选
          </Checkbox>
          <span style={{ color: "#666" }}>
            已选择 {selected.length} 项,总价: ¥{totalPrice.toFixed(2)}
          </span>
        </div>

        <List
          dataSource={list}
          renderItem={(item) => (
            <List.Item
              style={{
                display: "flex",
                justifyContent: "space-between",
                alignItems: "center",
                padding: "8px 0",
                borderBottom: "1px solid #f0f0f0",
              }}
            >
              <div style={{ display: "flex", alignItems: "center" }}>
                <Checkbox
                  checked={isSelected(item)}
                  onClick={() => toggle(item)}
                  style={{ marginRight: 8 }}
                />
                <span>{item.name}</span>
              </div>
              <span style={{ color: "#666" }}>¥{item.price}</span>
            </List.Item>
          )}
        />
      </Card>
    </div>
  );
};

useCountDown – 倒计时

import React, { useState } from "react";
import { useCountDown } from "ahooks";
import { Button, Card, Input, message } from "antd";

const SmsCountDownExample = () => {
  const [phoneNumber, setPhoneNumber] = useState("");
  const [targetDate, setTargetDate] = useState();

  const [countdown] = useCountDown({
    targetDate,
    onEnd: () => {
      console.log("倒计时结束!");
      message.success("倒计时结束,可以重新发送短信");
    },
  });

  const formatTime = (ms) => {
    const seconds = Math.floor(ms / 1000);
    return seconds.toString().padStart(2, "0");
  };

  const handleSendSms = () => {
    if (!phoneNumber) {
      message.error("请输入手机号码");
      return;
    }

    if (phoneNumber.length !== 11) {
      message.error("请输入正确的11位手机号码");
      return;
    }

    // 模拟发送短信
    message.success(`验证码已发送到 ${phoneNumber}`);
    // 开始60秒倒计时
    setTargetDate(Date.now() + 60000);
  };

  const handleReset = () => {
    setTargetDate(undefined);
  };

  return (
    <div>
      <Card title="短信验证码倒计时" style={{ marginBottom: 16 }}>
        <div style={{ marginBottom: 16 }}>
          <Input
            placeholder="请输入手机号码"
            value={phoneNumber}
            onChange={(e) => setPhoneNumber(e.target.value)}
            style={{ marginBottom: 8 }}
            maxLength={11}
          />
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <Button
              type="primary"
              onClick={handleSendSms}
              disabled={countdown !== 0}
              style={{ flex: 1 }}
            >
              {countdown === 0
                ? "发送验证码"
                : `重新发送(${formatTime(countdown)}s)`}
            </Button>
            {countdown !== 0 && (
              <Button onClick={handleReset} size="small">
                重置
              </Button>
            )}
          </div>
        </div>
      </Card>
    </div>
  );
};

useCounter – 计数器

import React from "react";
import { useCounter } from "ahooks";
import { Button, Card, InputNumber, Space } from "antd";

const CounterExample = () => {
  const [current, { inc, dec, set, reset }] = useCounter(1, {
    min: 1,
    max: 10,
  });

  return (
    <div>
      <Card title="useCounter 计数器示例" style={{ marginBottom: 16 }}>
        <div style={{ textAlign: "center", marginBottom: 16 }}>
          <div
            style={{ fontSize: "32px", fontWeight: "bold", color: "#1890ff" }}
          >
            {current}
          </div>
        </div>

        <div style={{ textAlign: "center", marginBottom: 16 }}>
          <Space>
            <Button
              type="primary"
              onClick={() => inc()}
              style={{ marginRight: 8 }}
            >
              inc(1)
            </Button>
            <Button onClick={() => dec()} style={{ marginRight: 8 }}>
              dec(1)
            </Button>
            <Button onClick={() => set(3)} style={{ marginRight: 8 }}>
              set(3)
            </Button>
            <Button onClick={reset} style={{ marginRight: 8 }}>
              reset(0)
            </Button>
          </Space>
        </div>

        <div style={{ textAlign: "center" }}>
          <Space>
            <InputNumber
              min={1}
              max={10}
              value={current}
              onChange={(value) => set(value || 1)}
              style={{ width: 100 }}
            />
            <span style={{ color: "#666" }}>直接输入数值</span>
          </Space>
        </div>
      </Card>
    </div>
  );
};

useTextSelection – 文本选择

import React from "react";
import { useTextSelection } from "ahooks";
import { Card } from "antd";

const TextSelectionExample = () => {
  const selection = useTextSelection();

  return (
    <div>
      <Card title="文本选择监听" style={{ marginBottom: 16 }}>
        <div
          style={{
            padding: "16px",
            border: "1px solid #d9d9d9",
            borderRadius: "6px",
            backgroundColor: "#fafafa",
            lineHeight: "1.8",
          }}
        >
          <p>
            这是一段示例文本,您可以在这里选择任意内容。选择文本后,下方会显示选择的相关信息,
            包括选中的文本内容、选择的位置信息等。这个功能在需要获取用户选择的文本内容时非常有用,
            比如实现文本高亮、复制选中内容等功能。
          </p>
          <p>
            您可以尝试选择单个单词、短语或者整段文本,观察下方信息的变化。
            选择不同的文本内容,会得到不同的选择结果。
          </p>
        </div>
      </Card>

      <Card title="选择信息" size="small" style={{ marginBottom: 16 }}>
        {selection.text ? (
          <div>
            <p>
              <strong>选中的文本:</strong>
            </p>
            <div
              style={{
                padding: "8px",
                backgroundColor: "#f0f0f0",
                borderRadius: "4px",
                marginBottom: "8px",
                wordBreak: "break-all",
              }}
            >
              "{selection.text}"
            </div>

            {selection.rects && selection.rects.length > 0 && (
              <div>
                <p>
                  <strong>选择区域:</strong>
                </p>
                <div style={{ fontSize: "12px", color: "#666" }}>
                  <p>区域数量: {selection.rects.length}</p>
                  {selection.rects.map((rect, index) => (
                    <p key={index}>
                      区域 {index + 1}: x={rect.x.toFixed(0)}, y=
                      {rect.y.toFixed(0)}, 宽={rect.width.toFixed(0)}, 高=
                      {rect.height.toFixed(0)}
                    </p>
                  ))}
                </div>
              </div>
            )}
          </div>
        ) : (
          <div style={{ color: "#999", textAlign: "center" }}>
            请在上方文本中选择内容
          </div>
        )}
      </Card>
    </div>
  );
};

useWebSocket – WebSocket 连接

import React, { useState } from "react";
import { useWebSocket } from "ahooks";
import { Button, Card, Input, List, Tag, Alert } from "antd";

const WebSocketExample = () => {
  const [url, setUrl] = useState("ws://localhost:8080");
  const [message, setMessage] = useState("");

  const { readyState, sendMessage, latestMessage, disconnect, connect } =
    useWebSocket(url, {
      onOpen: () => {
        console.log("WebSocket连接成功");
      },
      onMessage: (message) => {
        console.log("收到消息:", message);
      },
      onError: (error) => {
        console.log("WebSocket错误:", error);
      },
      onClose: () => {
        console.log("WebSocket连接关闭");
      },
      manual: true, // 手动连接
    });

  const [messageHistory, setMessageHistory] = useState([]);

  React.useEffect(() => {
    if (latestMessage) {
      setMessageHistory((prev) => [
        ...prev,
        {
          id: Date.now(),
          content: latestMessage.data,
          type: "received",
          time: new Date().toLocaleTimeString(),
        },
      ]);
    }
  }, [latestMessage]);

  const handleSend = () => {
    if (message.trim()) {
      sendMessage(message);
      setMessageHistory((prev) => [
        ...prev,
        {
          id: Date.now(),
          content: message,
          type: "sent",
          time: new Date().toLocaleTimeString(),
        },
      ]);
      setMessage("");
    }
  };

  const getStatusText = () => {
    switch (readyState) {
      case 0:
        return { text: "连接中", color: "processing" };
      case 1:
        return { text: "已连接", color: "success" };
      case 2:
        return { text: "关闭中", color: "warning" };
      case 3:
        return { text: "已关闭", color: "error" };
      default:
        return { text: "未知", color: "default" };
    }
  };

  const status = getStatusText();

  return (
    <div>
      <Card title="WebSocket 连接管理" style={{ marginBottom: 16 }}>
        <div style={{ marginBottom: 16 }}>
          <Input
            value={url}
            onChange={(e) => setUrl(e.target.value)}
            placeholder="WebSocket URL"
            style={{ marginBottom: 8 }}
          />
          <div>
            <Button
              type="primary"
              onClick={connect}
              disabled={readyState === 1}
              style={{ marginRight: 8 }}
            >
              连接
            </Button>
            <Button
              onClick={disconnect}
              disabled={readyState !== 1}
              style={{ marginRight: 8 }}
            >
              断开
            </Button>
            <Tag color={status.color}>状态: {status.text}</Tag>
          </div>
        </div>

        {readyState === 3 && (
          <Alert
            message="连接已断开"
            description="请点击连接按钮重新建立WebSocket连接"
            type="warning"
            showIcon
            style={{ marginBottom: 16 }}
          />
        )}
      </Card>

      <Card title="消息收发" style={{ marginBottom: 16 }}>
        <div style={{ marginBottom: 16 }}>
          <Input.TextArea
            value={message}
            onChange={(e) => setMessage(e.target.value)}
            placeholder="输入要发送的消息"
            rows={3}
            style={{ marginBottom: 8 }}
            onPressEnter={(e) => {
              if (!e.shiftKey) {
                e.preventDefault();
                handleSend();
              }
            }}
          />
          <Button
            type="primary"
            onClick={handleSend}
            disabled={readyState !== 1 || !message.trim()}
          >
            发送消息
          </Button>
        </div>

        <List
          size="small"
          dataSource={messageHistory}
          renderItem={(item) => (
            <List.Item
              style={{
                padding: "8px 0",
                borderBottom: "1px solid #f0f0f0",
              }}
            >
              <div style={{ width: "100%" }}>
                <div
                  style={{
                    display: "flex",
                    justifyContent: "space-between",
                    marginBottom: "4px",
                  }}
                >
                  <Tag color={item.type === "sent" ? "blue" : "green"}>
                    {item.type === "sent" ? "发送" : "接收"}
                  </Tag>
                  <span style={{ fontSize: "12px", color: "#666" }}>
                    {item.time}
                  </span>
                </div>
                <div
                  style={{
                    padding: "8px",
                    backgroundColor:
                      item.type === "sent" ? "#e6f7ff" : "#f6ffed",
                    borderRadius: "4px",
                    wordBreak: "break-all",
                  }}
                >
                  {item.content}
                </div>
              </div>
            </List.Item>
          )}
        />
      </Card>

      <Card title="连接信息" size="small">
        <p>
          <strong>连接状态:</strong>
          <Tag color={status.color}>{status.text}</Tag>
        </p>
        <p>
          <strong>连接URL:</strong> {url}
        </p>
        <p>
          <strong>消息总数:</strong> {messageHistory.length}
        </p>
        <p>
          <strong>最后消息:</strong> {latestMessage?.data || "无"}
        </p>
      </Card>
    </div>
  );
};

useTheme – 主题

import React from "react";
import { useTheme } from "ahooks";
import { Button, Card, Tag } from "antd";

const ThemeExample = () => {
  const { theme, themeMode, setThemeMode } = useTheme({
    localStorageKey: "themeMode",
  });

  return (
    <div>
      <Card title="useTheme 基础用法" style={{ marginBottom: 16 }}>
        <div style={{ marginBottom: 16 }}>
          <p>
            <strong>theme:</strong> <Tag color="blue">{theme}</Tag>
          </p>
          <p>
            <strong>themeMode:</strong> <Tag color="green">{themeMode}</Tag>
          </p>
        </div>

        <div style={{ marginBottom: 16 }}>
          <Button
            type="primary"
            onClick={() => setThemeMode("dark")}
            style={{ marginRight: 8 }}
          >
            使用深色主题
          </Button>
          <Button
            onClick={() => setThemeMode("light")}
            style={{ marginRight: 8 }}
          >
            使用浅色主题
          </Button>
          <Button
            onClick={() => setThemeMode("system")}
            style={{ marginRight: 8 }}
          >
            跟随系统
          </Button>
        </div>

        <div
          style={{
            padding: "16px",
            border: "1px solid #d9d9d9",
            borderRadius: "6px",
            backgroundColor: theme === "dark" ? "#141414" : "#ffffff",
            color: theme === "dark" ? "#ffffff" : "#000000",
            transition: "all 0.3s ease",
          }}
        >
          <h3 style={{ marginBottom: 12 }}>主题预览区域</h3>
          <p>
            这是一个主题预览区域,展示了当前主题的样式效果。 当前主题: {theme}
            ,主题模式: {themeMode}
          </p>
        </div>
      </Card>
    </div>
  );
};

通过合理使用这些 hooks,可以大大简化 React 应用的开发复杂度,提高代码的可维护性和用户体验。

场景类 hooks 速查表

Hook 名称用途描述
useAntdTableAntd 表格集成专门为 Ant Design Table 组件设计的 hook,简化表格数据获取和分页管理
useFusionTableFusion 表格集成专门为 Fusion Design Table 组件设计的 hook,提供表格数据管理功能
useInfiniteScroll无限滚动实现无限滚动加载,支持触底加载更多数据,自动处理加载状态
usePagination分页管理简化分页数据的获取和管理,自动处理页码和页面大小的状态
useDynamicList动态列表管理动态增减的列表项,支持添加、删除、移动等操作
useVirtualList虚拟列表实现大数据量的虚拟滚动列表,提升性能,减少 DOM 节点数量
useHistoryTravel历史记录管理状态的历史记录,支持前进、后退、跳转到指定历史点
useNetwork网络状态监听网络连接状态变化,包括在线/离线状态和网络类型
useSelections多选管理管理列表的多选状态,支持全选、反选、批量操作等功能
useCountDown倒计时实现倒计时功能,支持自定义格式和回调函数
useCounter计数器管理数字计数器的增删改查操作,支持步长设置
useTextSelection文本选择监听用户文本选择事件,获取选中的文本内容和位置信息
useWebSocketWebSocket 连接管理 WebSocket 连接,处理连接、断开、消息收发等操作
useTheme主题管理管理应用主题状态,支持主题切换和持久化存储
资源下载
下载价格免费
注意:本网站资源属于虚拟产品,不支持退款。请谨慎购买! 购买后资源无法下载,请联系客服QQ:844475003,微信号:th844475003。
原文链接:https://code.ifrontend.net/archives/780,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?