Привіт! 👋 Сьогодні я покажу тобі, як створити NewsHub, сучасний агрегатор новин, використовуючи React, TypeScript і Tailwind CSS. Це ідеальний проєкт як для початківців, так і для досвідчених розробників, які хочуть створити готовий до продакшн новинний додаток з такими функціями, як темна тема, поширення статей і додавання до закладок.
Вступ
NewsHub — це адаптивний веб-додаток, який дозволяє користувачам:
- Переглядати новинні статті за категорією.
- Шукати конкретні новини.
- Додавати улюблені статті до закладок для легкого доступу.
- Ділитися статтями в соціальних мережах.
- Перемикатися між світлим і темним режимами.
Повний вихідний код цього проєкту можна знайти на GitHub.
Технології
Ми будемо використовувати наступні технології:
- React 18 для створення інтерфейсу користувача.
- TypeScript для статичної типізації та підвищення якості коду.
- Tailwind CSS для швидкого та гнучкого стилізування.
- Vite як сучасний і швидкий інструмент для збірки.
- Lucide React для іконок.
- GNews API для отримання новинних даних.
Початок роботи
Спочатку налаштуємо структуру нашого проєкту. Ми використаємо Vite з шаблоном React і TypeScript:
npm create vite@latest news-hub -- --template react-ts
cd news-hub
npm install
Структура проєкту
Ось модульна структура нашого проєкту:
src/
├── components/
├── hooks/
├── services/
├── types/
├── App.tsx
└── main.tsx
Реалізація основних функцій
Тут я поясню основні функції додатку. Щоб слідувати за прикладом, клонуй репозиторій Github.
1. Визначення типів статей
Почнемо з визначення інтерфейсів TypeScript, щоб забезпечити типову безпеку (в файлі src/types/index.ts
):
export interface Article {
id: string;
title: string;
description: string;
source: string;
category: string;
url: string;
imageUrl?: string;
publishedAt: string;
}
export type Category =
| 'all'
| 'world'
| 'nation'
| 'business'
| 'technology'
| 'entertainment'
| 'sports'
| 'science'
| 'health';
Цей фрагмент коду служить як шаблон для визначення структури даних, які використовуються в додатку NewsHub. Ось що кожна частина робить:
A. Інтерфейс Article
Цей інтерфейс визначає форму об'єкта новинної статті. Кожна стаття містить:
id
(string): Унікальний ідентифікатор статті.title
(string): Заголовок статті.description
(string): Короткий опис змісту статті.source
(string): Назва джерела новин або видавця.category
(string): Категорія статті, наприклад, спорт або технології.url
(string): Посилання на повний текст статті.imageUrl
(необов'язкове значення string): Посилання на зображення, яке супроводжує статтю.publishedAt
(string): Дата та час публікації статті.
Це гарантує, що всі статті відповідають єдиній структурі, що полегшує їх обробку та відображення в додатку.
B. Тип Category
Це об'єднаний тип, який визначає можливі категорії новинних статей:
- Включає такі категорії, як
'world'
,'business'
,'entertainment'
тощо. 'all'
— це спеціальна категорія для отримання статей з усіх тем.
Це дозволяє додатку фільтрувати або організовувати статті відповідно до цих заздалегідь визначених категорій, покращуючи досвід перегляду.
Чому це важливо
- Типова безпека: Використовуючи TypeScript, ви можете виявити помилки під час розробки, якщо дані не відповідають визначеній структурі.
- Підтримуваність: Чітке визначення типів робить код зрозумілішим, легшим для читання та підтримки.
- Гнучкість: З добре визначеною структурою додавати нові функції, такі як фільтрація за категорією або валідація вхідних даних, стає простіше.
Ця основа є критично важливою для створення надійного та зручного новинного додатку!
Отримання новин з GNews API
Сервіс newsApi.ts
обробляє API-запити для отримання новинних статей:
const API_KEY = 'your_api_key';
const BASE_URL = 'https://gnews.io/api/v4';
export async function fetchNews(category: string = 'all', query: string = '') {
const endpoint = query ? 'search' : 'top-headlines';
const params = new URLSearchParams({
apikey: API_KEY,
lang: 'en',
country: 'us',
max: '10',
...(query ? { q: query } : {}),
...(category !== 'all' ? { topic: category } : {}),
});
const response = await fetch(`${BASE_URL}/${endpoint}?${params}`);
const data = await response.json();
return {
articles: data.articles.map((article: any, index: number) => ({
id: String(index + 1),
title: article.title || 'Untitled',
description: article.description || 'No description available',
source: article.source?.name || 'Unknown Source',
category: category === 'all' ? 'general' : category,
url: article.url || '#',
imageUrl: article.image,
publishedAt: article.publishedAt || new Date().toISOString(),
})),
};
}
Цей фрагмент коду визначає функцію fetchNews
, яка отримує новинні статті з GNews API на основі категорій або пошукових запитів, заданих користувачем. Ось пояснення, що робить цей код:
A. Налаштування API:
- Функція підключається до GNews API, використовуючи API-ключ (
API_KEY
) та базову URL-адресу (BASE_URL
).
B. Вхідні параметри:
category
: Визначає тип новин (наприклад, 'business', 'technology'). За замовчуванням встановлено 'all' для загальних новин.query
: Пошуковий запит для фільтрації статей. Якщо він заданий, то будуть отримані статті, що відповідають цьому запиту.
C. Вибір ендпоінту:
- Якщо заданий
query
, використовується ендпоінтsearch
для конкретного пошуку. Інакше за замовчуванням використовуєтьсяtop-headlines
для загальних новин.
D. Параметри запиту:
- Мова (
lang
) встановлена на англійську. - Країна (
country
) встановлена на США. - Максимальна кількість статей — 10.
- Додаткові фільтри для запиту та категорії додаються, якщо це необхідно.
E. Отримання та парсинг даних:
- Функція відправляє запит до API, чекає на відповідь і парсить отримані дані у форматі JSON.
F. Форматований вихід:
- Отримані статті перетворюються в єдиний формат, що включає:
- Унікальний ID.
- Заголовок, опис та назву джерела (за замовчуванням використовується заповнювач, якщо вони відсутні).
- Категорія та дата публікації.
- URL та зображення для статті.
G. Повертається:
- Структурований об'єкт, що містить відформатовані статті, що дозволяє легко відображати їх у додатку.
Ця функція є важливою частиною вашого додатку, забезпечуючи динамічний доступ до нових статей, керованих користувачем!
Створення компонента картки статті
Компонент ArticleCard
відображає окремі новинні статті:
import React from 'react';
import { Bookmark, Share2 } from 'lucide-react';
import type { Article } from '../types';
interface ArticleCardProps {
article: Article;
isBookmarked: boolean;
onBookmark: (id: string) => void;
onShare: (article: Article) => void;
}
export function ArticleCard({ article, isBookmarked, onBookmark, onShare }: ArticleCardProps) {
return (
{article.imageUrl && ( )}
{article.title}
{article.description}
Read more
onBookmark(article.id)}> onShare(article)}>
); } ```
Цей компонент `ArticleCard` використовується для відображення інформації про статтю у вигляді картки. Він приймає такі властивості:
- **article**: Об'єкт, що містить деталі статті, такі як заголовок, опис, URL та зображення.
- **isBookmarked**: Логічне значення, яке вказує, чи є стаття в закладках.
- **onBookmark**: Функція, яка обробляє додавання статті до закладок, коли користувач натискає на іконку закладки.
- **onShare**: Функція, яка обробляє поширення статті, коли користувач натискає на іконку спільного доступу.
## Як це працює:
- **Зображення статті**: Якщо у статті є зображення, воно буде відображене в верхній частині картки.
- **Заголовок та опис**: Заголовок статті та короткий опис відображаються в картці. Опис обрізається, щоб вмістити лише два рядки.
- **Посилання на повну статтю**: Надається посилання на повну статтю, яке відкривається в новій вкладці.
- **Кнопки закладки та поширення**:
- Кнопка закладки дозволяє користувачу додавати або видаляти статтю з закладок. Якщо стаття додана в закладки, кнопка підсвічується.
- Кнопка поширення дозволяє користувачу поділитися статтею, використовуючи надану функцію.
Картка має чистий та адаптивний дизайн з ефектами при наведенні та підтримує світлий/темний режим, який адаптується відповідно до уподобань користувача щодо теми.
## 4. Темний режим з Tailwind CSS
Ми використовуємо кастомний хук для збереження налаштувань темного режиму в `localStorage` (в `src/hooks/useLocalStorage.ts`):
import { useState, useEffect } from 'react';
export function useLocalStorage(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(storedValue));
} catch (error) {
console.error(error);
}
}, [key, storedValue]);
return [storedValue, setStoredValue] as const;
}
```
Хук useLocalStorage
є кастомним хуком для React, який допомагає вам керувати станом, одночасно зберігаючи та отримуючи дані з localStorage браузера. Він працює подібно до хука useState
, але з додатковою функціональністю збереження даних у localStorage, щоб дані залишались доступними навіть після оновлення сторінки або повторного відкриття браузера.
Як це працює:
A.
Початковий стан:
- Коли хук використовується вперше, він перевіряє, чи існує вказаний
key
в localStorage. Якщо існує, збережене значення витягується та використовується як початковий стан. Якщо ж у localStorage немає значення, використовується переданеinitialValue
. - Якщо виникає помилка при зчитуванні з localStorage, хук виводить помилку в консоль і повертає
initialValue
.
B. Оновлення стану:
- Коли стан (
storedValue
) оновлюється за допомогою функціїsetStoredValue
, це значення також зберігається в localStorage, забезпечуючи збереження даних навіть після перезавантаження сторінки чи сесії. - Якщо виникає помилка при записі в localStorage, помилка виводиться в консоль.
C. Повернуте значення:
- Хук повертає масив, перший елемент якого — це поточне значення, збережене в localStorage, а другий — функція (
setStoredValue
), яка може бути використана для оновлення збереженого значення.
Розширені функції
1. Пошук з дебаунсом
Для оптимізації API запитів ми використовуємо дебаунс для вводу пошуку:
import { useState, useEffect } from 'react';
export function useDebounce(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
2. Модальне вікно для поширення в соціальних мережах
Модальне вікно для поширення дозволяє користувачам ділитися статтями в різних соціальних мережах:
export function ShareModal({ article, onClose }: { article: Article; onClose: () => void; }) {
const shareUrl = article.url;
const shareText = `Check out this article: ${article.title}`;
const shareLinks = {
x: `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareText)}`,
linkedin: `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(shareUrl)}`,
};
// Render the modal content...
}
Висновок
NewsHub демонструє, як побудувати сучасний новинний додаток за допомогою React і TypeScript. Основні висновки:
- Використання TypeScript для забезпечення типобезпеки.
- Реалізація темного режиму за допомогою Tailwind CSS.
- Ефективне управління станом за допомогою React hooks.
- Створення багаторазових компонентів для масштабованого коду.
- Безперешкодна інтеграція з API.
- Покращення досвіду користувача через соціальний обмін та закладки.
Щоб почати:
- Отримайте свій API-ключ від GNews.
- Замініть API-ключ в
newsApi.ts
. - Встановіть залежності за допомогою
npm install
. - Запустіть сервер розробки за допомогою
npm run dev
.
Якщо ви знайшли цей посібник корисним, поділіться ним з іншими, хто вивчає React і TypeScript, та поставте кілька лайків. Якщо у вас є питання або відгуки, залишайте їх у коментарях нижче. Щасливого кодування!
Перекладено з: Building a Modern News Hub with React and TypeScript: A Comprehensive Guide