你好,我是Sakuraseven。在上一篇教程中,我们搭建了随机图片API。今天我们来让这个API真正"活"起来——用React构建一个动态图片展示墙!最终效果如下:

[此处可放GIF演示图或截图]

你将学到

  • ✅ React基础组件设计

  • ✅ 异步请求API数据

  • ✅ 响应式图片布局

  • ✅ 无限滚动加载

  • ✅ 添加加载动画

一、项目初始化

使用Create React App快速搭建环境:

npx create-react-app image-wall
cd image-wall
npm start

二、核心组件设计

文件结构

src/
├── components/
│   ├── ImageCard.js   # 单张图片卡片
│   └── ImageWall.js   # 图片墙主组件
├── App.js
└── index.js

1. 图片卡片组件 (ImageCard.js)

import React from 'react';

const ImageCard = ({ imgUrl, altText }) => {
  return (
    <div className="image-card">
      <img 
        src={imgUrl} 
        alt={altText || '随机图片'} 
        loading="lazy"  // 原生懒加载
      />
    </div>
  );
};

export default ImageCard;

2. 图片墙主组件 (ImageWall.js)

import React, { useState, useEffect } from 'react';
import ImageCard from './ImageCard';

const ImageWall = () => {
  const [images, setImages] = useState([]);
  const [loading, setLoading] = useState(true);

  // 初始加载图片
  useEffect(() => {
    fetchImages(10); // 首次加载10张
  }, []);

  // 调用API函数
  const fetchImages = async (count = 5) => {
    setLoading(true);
    const baseUrl = 'http://your-api.com/random-img'; // 替换为你的API地址
    
    try {
      // 并行请求多张图片
      const requests = Array(count).fill().map(() => fetch(baseUrl));
      const responses = await Promise.all(requests);
      const newImages = await Promise.all(
        responses.map(res => res.url) // 获取图片URL
      );
      
      setImages(prev => [...prev, ...newImages]);
    } catch (error) {
      console.error('加载图片失败:', error);
    } finally {
      setLoading(false);
    }
  };

  // 无限滚动处理
  useEffect(() => {
    const handleScroll = () => {
      if (
        window.innerHeight + document.documentElement.scrollTop >=
        document.documentElement.offsetHeight - 500
      ) {
        !loading && fetchImages(5); // 每次滚动加载5张
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [loading]);

  return (
    <div className="image-wall">
      <div className="grid-container">
        {images.map((imgUrl, index) => (
          <ImageCard key={`${imgUrl}-${index}`} imgUrl={imgUrl} />
        ))}
      </div>
      {loading && <div className="loader">加载中...</div>}
    </div>
  );
};

export default ImageWall;

三、关键功能实现技巧

1. 响应式网格布局(纯CSS实现)

/* src/App.css */
.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
  padding: 20px;
}

.image-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  transition: transform 0.3s ease;
}

.image-card img:hover {
  transform: scale(1.03);
}

2. 优雅的加载状态

// 在ImageWall组件中添加
{loading && (
  <div className="loader-container">
    <div className="spinner"></div>
    <p>正在加载更多美图...</p>
  </div>
)}
/* 加载动画CSS */
.loader-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid rgba(0, 0, 0, 0.1);
  border-left-color: #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

3. 图片懒加载优化

// 在ImageCard组件中使用Intersection Observer
import React, { useRef, useState, useEffect } from 'react';

const ImageCard = ({ imgUrl }) => {
  const imgRef = useRef();
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        setIsVisible(true);
        observer.unobserve(imgRef.current);
      }
    }, { threshold: 0.1 });

    observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, []);

  return (
    <div ref={imgRef} className="image-card">
      {isVisible ? (
        <img src={imgUrl} alt="随机图片" />
      ) : (
        <div className="placeholder" /> // 占位元素
      )}
    </div>
  );
};

四、部署到线上(Vercel实战)

  1. 安装Vercel CLI

    npm install -g vercel
  2. 项目根目录登录

    vercel login
  3. 一键部署

    vercel

五、性能优化建议

  1. CDN加速:将图片上传至云存储(如AWS S3+CloudFront)

  2. API缓存:设置更长的Cache-Control头部

  3. 代码分割:React.lazy动态导入组件

  4. 图片格式:API支持返回WebP格式(需修改后端)

遇到问题怎么办?

Q:图片加载出现跨域错误?
A:在API端设置响应头:

resp.headers['Access-Control-Allow-Origin'] = '*'  # 生产环境应指定域名

Q:布局出现错位?
A:确保所有图片比例统一,或使用CSS的aspect-ratio属性


下一篇预告
《给随机图片API加上AI鉴黄功能:保护你的网站安全》
我们将使用TensorFlow.js在前端实时检测图片内容安全!