React Blog: Проект фазы 2

Этот проект предназначен для фазы 2 программы Flatiron School Software Engineering. Требования заключались в создании приложения react с 5 компонентами, 3 маршрутами и db.json для хранения данных для операций CRUD.

Начало работы

Начало проекта было упрощено с помощью Create React App. Create React App — это инструмент, поддерживаемый командой React Team, который позволяет выполнить базовую настройку для создания одностраничного приложения React:

npx create-react-app my-app
cd my-app
npm start
Войти в полноэкранный режим Выйти из полноэкранного режима

Это создаст файлы, папки и зависимости, чтобы начать кодирование без тяжелой конфигурации. Две включенные зависимости, которые немного облегчают нам жизнь, — это babel и webpack.

  • Babel: является транспондером, который преобразует React JSX и современный Javascript (ECMAScript 6) в более старый ECMAScript 5. Это было полезно, когда ES5 был более широко распространен, и до сих пор используется для компиляции функций и синтаксиса, которые не были реализованы в ES. Он используется в Create React App для преобразования специфического синтаксиса React, нашего JSX, в правильный Javascript.

  • Webpack: автоматизирует проверку файлов на наличие зависимостей для импорта и включения в приложение. Он объединяет наши активы, код и модули npm в один файл.

Список компонентов

Руководство требовало наличия не менее 5 компонентов, и я знал, что мне понадобится NavBar, коллекция постов, индивидуальный контент и возможность создавать посты. Я распределил компоненты следующим образом:

  • NavBar: Название проекта и 3 кнопки, которые направляют к Home, About и Create Post.
  • SinglePost: Этот компонент будет отображать подробную информацию о каждом посте.
  • PostList: Форматирует список заголовков постов с возможностью клика.
  • CreatePost: Содержит форму для отправки нового поста.
  • About: Информация о проекте и о себе.
  • HomePage (Домашняя страница): Это домашняя страница приложения. В настоящее время она является родительской только для Post List.

NavBar содержит ссылки на HomePage, About и CreatePost.

<div className="navbar">

            <nav className="nav-links">
            <h1>Project Blog </h1>
                <Button component={Link} to="/" startIcon={<HomeIcon />} color="info" variant="outlined" size="medium">Home</Button>
                <Button component={Link} to="/about" startIcon={<InfoIcon />} color="info" variant="outlined" size="medium">About</Button>
                <Button component={Link} to="/create-post" startIcon={<FiberNewIcon />} color="info" variant="outlined" size="medium">New Post</Button>
            </nav>
            <hr />
        </div>
Вход в полноэкранный режим Выйти из полноэкранного режима

Hooks, HomePage и SinglePost

Информация для блога хранится в файле db.json, доступ к которому в приложении осуществляется с помощью пользовательского хука useFetch.

import { useState, useEffect } from "react";

function useFetch(url) {
      const [data, setData] = useState([]);
      useEffect(() => {
          fetch(url)
          .then((res) => res.json())
          .then((data) => setData(data));
      }, [url]);
      return [data];
};
export default useFetch;
Вход в полноэкранный режим Выход из полноэкранного режима

useState состоит из: переменной состояния data, функции, которая обновляет состояние setData, и начального значения, передаваемого в useState, которое представляет собой пустой массив.

useEffect принимает два аргумента, функцию обратного вызова и зависимости. Функция использует fetch() для получения информации из предоставленного url, разбирает ее с помощью json() и использует setData для хранения данных из fetch в состоянии data. Массив зависимостей получает url, что означает, что useEffect будет запущен снова только при изменении значения url.

Этот хук useFetch используется в двух местах, HomePage и SinglePost. HomePage делает запрос на выборку и передает посты в PostList.

function HomePage(){
    const [posts] = useFetch('http://localhost:3004/posts');
    console.log(posts);
    return (
        <div className="homepage">
            <PostList
                title="All Posts"
                posts={posts}
            />
        </div>
    );
}
Вход в полноэкранный режим Выход из полноэкранного режима

PostList принимает данные {posts}, сортирует по id и использует map() для создания списка постов для отображения на HomePage.

const PostList = ({posts}) => {
    let sortedPosts = posts.slice().sort((a, b) => b.id - a.id);

    return ( 
        <div className="post-list">
            <div className="title">
                <h2>All Posts</h2>
            </div>
            {sortedPosts.map(post=> (
                <div className="list-items" key={post.id}>
                    <Link to={`/posts/${post.id}`}>
                        <h3>{post.title}</h3>
                    </Link>
                    <h6>Created {post.created}</h6>
                    <p>Written by {post.author}</p>
                </div>
            ))}
        </div>
     );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это создает <div> для каждого поста, который содержит ссылку на пост, дату его создания и автора. Переменная sortedPosts переставляет посты с id по убыванию, что позволяет отображать недавно созданные посты в верхней части списка.

Будущие соображения для HomePage и PostList

Этот дизайн имеет смысл с точки зрения функциональности, так как вы обычно хотите видеть более новое содержимое первым, но это не полное решение. Наличие кнопки-тумблера для переключения между возрастанием и убыванием является еще одним решением, но все же не решает проблему масштаба. Идеально было бы иметь фильтр сообщений по теме или поиск сообщений, так как количество сообщений растет и найти конкретные сообщения становится все сложнее.

SinglePost использует хук useFetch для получения всех деталей поста. При нажатии на заголовок поста из списка PostList будет показан этот пост.

function SinglePost() {
    const { id } = useParams();
    const [post] = useFetch(`http://localhost:3004/posts/${id}`);
    const navigate = useNavigate();
Вход в полноэкранный режим Выход из полноэкранного режима

useParams использует id для сопоставления маршрута из App.js в SinglePost.

function App() {

  return (
    <div className="App">
      <NavBar />
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/posts/:id" element={<SinglePost />} />
        <Route path="/create-post" element={<CreatePost />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </div>
  );
}
Войти в полноэкранный режим Выход из полноэкранного режима

useNavigate заменил useHistory в React Router v6 и используется здесь для перенаправления на домашний url. Это используется с функцией удаления, включенной в SinglePost:

const handleDelete = () => {
        fetch(`http://localhost:3004/posts/${id}`, {
            method: 'DELETE'
        }).then(() => {
            navigate('/'); 
        });
    }
Войти в полноэкранный режим Выход из полноэкранного режима

При нажатии кнопки delete в нижней части страницы выполняется запрос на удаление поста, и пользователь перенаправляется на home, которая является домашней страницей, с удалением поста из PostList.
SinglePost возвращает подробную информацию о блоге, стилизованную с использованием Material UI:

return (
        <div className="single-post">
            <div className="post-title">
                <Typography variant="h3">{post.title}</Typography>
                <Typography variant="caption">Created on {post.created}</Typography>
            </div>
            <div className="post-body">
                <Typography variant="body1">{post.body}</Typography>
                <br />
                <br />
                <Typography variant="body2">By {post.author}</Typography>
            </div>
            <Button variant="contained" onClick={handleDelete}>Delete Post</Button>
        </div>
    );
Вход в полноэкранный режим Выход из полноэкранного режима

Будущие соображения по поводу SinglePost

В будущем я хотел бы добавить к кнопке удаления диалог подтверждения для предотвращения случайного удаления. Компонент в целом мог бы использовать улучшенную стилизацию. В небольших масштабах отсутствие стилизации не представляет особой проблемы, но для больших масштабов или производственной среды привлекательный пользовательский интерфейс всегда полезен.

CreatePost

Страница CreatePost оформлена с использованием Material UI и имеет область ввода текста для заголовка, тела и имени автора.

function CreatePost() {
    const [title, setTitle] = useState('');
    const [body, setBody] = useState('');
    const [author, setAuthor] = useState('');
    const navigate = useNavigate();
    const today = new Date().toJSON().slice(0, 10).replace(/-/g, '/');
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь вызывается useState, и переменные состояния обновляются значениями title, body и author из формы.
Переменные navigate и today используются в функции handleSubmit CreatePost. Navigate снова используется для перенаправления пользователя обратно в home после отправки формы. Today используется для получения даты в момент отправки в формате mm/dd/yyy и присваивается значению «created» поста.

const handleSubmit = (e) => {
        e.preventDefault();
        const newPost = { title, body, author, created: today};
        fetch('http://localhost:3004/posts', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(newPost)
        })
            .then(() => { navigate('/'); })
    }
Вход в полноэкранный режим Выход из полноэкранного режима

Возвращается CreatePost:

<Box
            component="form"
            sx={{
            '& .MuiTextField-root': { m: 1, width: '50ch' },
            }}
            noValidate
            autoComplete="off"
        >
            <div className="create-post-form">
                <TextField
                    id="outlined-basic"
                    label="Title"
                    variant="outlined"
                    value={title}
                    onChange={(e) => setTitle(e.target.value)} required
                />
                <br />
                <TextField
                    id="outlined-multiline-flexible"
                    label="Body"
                    variant="outlined"
                    multiline
                    rows={10}
                    value={body}
                    onChange={(e) => setBody(e.target.value)} required
                />
                <br />
                <TextField
                    id="outlined-basic"
                    label="Author"
                    variant="outlined"
                    value={author}
                    onChange={(e) => setAuthor(e.target.value)} required
                />
                <br />
                <Button variant="contained" type="submit" onClick={handleSubmit}>Submit</Button>
            </div>
        </Box>
Войти в полноэкранный режим Выход из полноэкранного режима

со стилизацией, пользователь видит следующее:

App и About

Это код для App:

function App() {

  return (
    <div className="App">
      <NavBar />
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/posts/:id" element={<SinglePost />} />
        <Route path="/create-post" element={<CreatePost />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </div>
  );
}
Войти в полноэкранный режим Выход из полноэкранного режима

Здесь есть компонент NavBar и маршруты к различным страницам. <Routes> и <Route> используются React Router для рендеринга на основе текущего местоположения. Когда местоположение меняется, <Routes> смотрит на каждый <Route>, который является его дочерним элементом, и определяет новые элементы для рендеринга. <BrowserRouter> вызывается вокруг App в корневом элементе:

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
 <BrowserRouter>
   <App />
 </BrowserRouter>
);
Войти в полноэкранный режим Выйти из полноэкранного режима

<BrowserRouter> сохраняет текущее местоположение в адресной строке и осуществляет навигацию, используя стек истории браузера.
About — очень простой компонент, который просто предоставляет справочную информацию о себе и требованиях к приложению.

Заключительные размышления

Я бы хотел вернуться к этому и обновить стиль и функциональность. Добавить фильтр для сообщений по мере роста списка и диалог подтверждения для удаления сообщения.

Оцените статью
devanswers.ru
Добавить комментарий