Файл: Знакомство и выбор технологии для разработки.pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 25.04.2023

Просмотров: 241

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

ПРАКТИЧЕСКАЯ ЧАСТЬ

ПРОЕКТИРОВАНИЕ ДИЗАЙНА

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

После авторизации или регистрации, заменим кнопки входа и регистрации на создать статью, имя пользователя с его аватаркой и кнопкой выхода:

Страница регистрации будет иметь следующий вид:

Страница входа:

Страница создания новой записи приобретет следующий вид:

Так же нужно продумать как будет выглядить отдельно страница записи, и если запись конкретного пользователя, который ее оставил еще вывести кнопки удаления и редактирования, данная страница будет выглядеть следующим образом:

Нельзя забыть про редактирование профиля пользователя:

СОЗДАНИЕ САЙТА

Теперь когда макет готов можно перейти к непосредственно самой разработке интернет-блога.

Установим Node.js с оффициального сайта, для возможности устанавливать зависимости к проекту.

После перейдем в терминале в нужную директорию и пропишем create-react-app для создания базового проекта. После нужно продумать основные компоненты из которых будет состоять приложение, в итоге оно будет иметь следующую древовидную структуру:

Во вкладке components находятся основные компоненты, которые будут отрисовываться на странице, redux – в ней находятся файлы для управления состоянием, containers выступает в роли связующего, компоненты в ней связывают redux с react, в fonts лежат шрифты, в icons картинки, в services лежат файлы с отправкой запросов, в shared переиспользуемые компоненты или стили.

После переходим к “верстке” сайта, тут ничего необычного, переносим дизайн макета на сайт, добавляем стили.


После этого переходим к хранению состояния. В крации редакс работает следующим образом:

При срабатывании события вызваном в пользовательском интерфейсе, это же событие вызывает редьюсер, которое в свою очередь обновляет стейт, который в свою очередь обновляет контент в пользовательском интерфейсе. Звучит немного запутанно, для наглядности рассмотрим на одном из примеров в моем проекте.

У нас есть создание записи, за это отвечает файл create-article.jsx, код которого:

import React, { useEffect } from 'react';
import 'antd/dist/antd.css';
import './create-article.scss';
import { message } from 'antd';
import {Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import ArticleForm from '../../shared/article-form';
import { LoadingOutlined } from '@ant-design/icons';

const CreateArticle = ({ article, user, successCreating, error, asyncCreateArticle, reset, successfullDownload }) => {
useEffect(() => {
return reset;
}, [reset]);

if (!Object.keys(user).length) {
return <Redirect to="/sign-in"/>;
}

if(error){
message.error('Failed');
}
if(!successfullDownload) {
if (successCreating) {
message.success('Success');
return <Redirect to="/"/>;
}
return <LoadingOutlined className="spinner" spin />;
}

return (
<ArticleForm user={user}
error={error}
article={article}
mission="create"
successCreating={successCreating}
reset={reset}
asyncCreateArticle={asyncCreateArticle}
/>
);
};

CreateArticle.propTypes = {
user: PropTypes.shape({
id: PropTypes.number,
email: PropTypes.string,
createdAt: PropTypes.string,
updatedAt: PropTypes.string,
username: PropTypes.string,
bio: PropTypes.string,
image: PropTypes.string,
token: PropTypes.string,
}).isRequired,
article: PropTypes.shape({
slug: PropTypes.string,
title: PropTypes.string,
description: PropTypes.string,
body: PropTypes.string,
tagList: PropTypes.arrayOf(PropTypes.string),
createdAt: PropTypes.string,
updatedAt: PropTypes.string,

}).isRequired,
successCreating: PropTypes.bool.isRequired,
error: PropTypes.bool.isRequired,
asyncCreateArticle:PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
};

export default CreateArticle;

Функции asyncCreateArticle, reset и состояния: article, user, succesCreating, error, successfullDownload берутся из связющего файла create-article-container.jsx, код которого:

import { connect } from 'react-redux';
import CreateArticle from '../../components/create-article';
import { asyncCreateArticle, reset } from '../../redux/action-creators';
import {
getError,
getLastOpenedArticle,
getSuccessCreatingArticle,
getSuccessfullDownload,
getUser,
} from '../../redux/users-selectors';

const mapStateToProps = (state) => ({
article: getLastOpenedArticle(state),
user: getUser(state),
successCreating: getSuccessCreatingArticle(state),
error: getError(state),
successfullDownload: getSuccessfullDownload(state),
});

const mapDispatchToProps = {
asyncCreateArticle,
reset,
};

export default connect(mapStateToProps, mapDispatchToProps)(CreateArticle);

Функция connect упрощает процесс связки react’a и redux, первым параметром указывается функция или объект хранящее в себя поля состояния, вторым параметром объект хранящий в себе функции action’ов.


В create-article.jsx все упирается в компоненту ArticleForm, это компонента предназначена как для создания записи, так и для ее редактирования, ее код:

import React, { useEffect } from 'react';
import 'antd/dist/antd.css';
import { Form, Input, Button, message } from 'antd';
import PropTypes from 'prop-types';

const formItemLayout = {
labelCol: {
xs: {
span: 24,
},
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
},
};

const formItemLayoutWithOutLabel = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 20, offset: 4 },
},
};

const ArticleForm = ({ article, user, error, asyncCreateArticle, reset, mission, asyncEditArticle }) => {
useEffect(() => {
return reset;
}, [reset]);


const {slug} = article;
const [form] = Form.useForm();

const onFinish = ({ title, shortDescription, text, tagList}) => {
if(error) message.error('Failed');

if(mission === 'edit') {
return asyncEditArticle(user.token, title, shortDescription, text, tagList, slug)
}
return asyncCreateArticle(user.token, title, shortDescription, text, tagList);
};

const head = mission === "edit" ? 'Edit Article' : 'Create new article';

const initialValues = mission === 'edit' ? {
title: article.title,
shortDescription: article.description,
text: article.body,
tagList:article.tagList,
} : null;

return (
<Form
{...formItemLayout}
className="form form__create"
form={form}
name="register"
onFinish={onFinish}
scrollToFirstError
initialValues
={initialValues}
>
<h2>{head}</h2>
<Form.Item
name="title"
label="Title"
className="form__item"
rules={[
{
required: true,
message: 'Please input title',
whitespace: true,
},
() =>({
validator(rule, value){
if(value.length > 16){
return Promise.reject(
"Title must be less then 16 characters"
);
}
return Promise.resolve();
}
})
]}
>
<Input placeholder="Title" className="form__input"/>
</Form.Item>

<Form.Item
name="shortDescription"
label="Short description"
className="form__item"
rules={[
{
required: true,
message: 'Please input short description',
},
() =>({
validator(rule, value){
if(value.length > 32){
return Promise.reject(
"Short description must be less then 32 characters"
);
}
return Promise.resolve();
}
})
]}
>
<Input placeholder="Description" className="form__input"/>
</Form.Item>

<Form.Item
name="text"
label="Text"
className="form__item"
rules={[
{
required: true,
message: 'Please enter text article',
},
]}
>
<Input.TextArea placeholder="Text" className="form__input" style={{ height: 168 }}/>
</Form.Item>

<Form.List name="tagList">
{(fields, { add, remove }, { errors }) => (
<>
{fields.map((field, index) => (

<Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
required={false}
key={field.key}
className="form__row"
>
<Form.Item
style={{ marginLeft: 0}}
{...field}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
whitespace: true,
message: 'Please input tag or delete this field.',
},
]}
noStyle
>
<Input placeholder="Tag" className="input__tag"/>
</Form.Item>

<Button
className="btn__tag btn__tag-del"
type="danger" ghost
onClick
={() => remove(field.name)}
>Delete</Button>
<Button
className="btn__tag btn__tag-add"
type="primary" ghost
onClick
={() => add()}
>Add Tag</Button>

</Form.Item>
))}

{(fields.length === 0) ? <Form.Item style={{textAlign: 'left'}}>
<Button
type="primary" ghost
className
="btn__tag btn__tag-add"
onClick={() => add()}
>
Add tag
</Button>
<Form.ErrorList errors={errors}/>
</Form.Item> : null}
</>
)}
</Form.List>

<Form.Item {...tailFormItemLayout} style={{textAlign:'left'}}>
<Button type="primary"
className="btn__save"
block
htmlType
="submit">
Save
</Button>
</Form.Item>
</Form>
);
};

ArticleForm.defaultProps = {
asyncEditArticle: () => {},
asyncCreateArticle: () => {},
reset: () => {},
successEditing: false,
mission: '',
}

ArticleForm.propTypes = {
user: PropTypes.shape({
id: PropTypes.number,
email: PropTypes.string,
createdAt: PropTypes.string,
updatedAt: PropTypes.string,
username: PropTypes.string,
bio: PropTypes.string,
image: PropTypes.string,
token: PropTypes.string,
}).isRequired,
article: PropTypes.shape({
slug: PropTypes.string,
title: PropTypes.string,
description: PropTypes.string,
body: PropTypes.string,
tagList: PropTypes.arrayOf(PropTypes.string),
createdAt: PropTypes.string,
updatedAt: PropTypes.string,

}).isRequired,
error: PropTypes.bool.isRequired,
asyncEditArticle:PropTypes.func,
asyncCreateArticle:PropTypes.func,
reset: PropTypes.func.isRequired,
mission: PropTypes.string,
};

export default ArticleForm;


Т.к я взял за пример создание записи, будем рассматривать только этот случай, при нажатии кнопки создать и при прохождении проверок, что все поля не пустые вызовится функция onFinish, которая в свою очередь вызовет asyncCreateArticle(это наш action) и передаст в него те данные, которые пользоветь хочет отобразить в записи. Код asyncCreateArticle находится в файле action-creators.js и его код следующий:

export const asyncCreateArticle = (token, title, description, body, tagList) => {
return async function inside(dispatch) {
try {
dispatch(reset());
await createArticle(token, title, description, body, tagList);
dispatch(articleCreated());
} catch (error) {
dispatch(articleNotCreated());
}
};
};

В этой функции dispatch приходится связующим между action’ом и reducer’ом, вызывается функция reset и articleCreated, в случае ошибки articleNotCreated, для примера рассмотрим код и логику работы функции articleCreated(с остальными по аналогии):

const articleCreated = () => ({
type: ARTICLE_CREATED,
});

в ней указан тип ‘ARTICLE_CREATED’ при срабатывании которого вызывается функция с данным типом в reducer’e:

const successCreatingArticle = (state = false, action) => {
switch (action.type) {
case ARTICLE_CREATED:
return true;
case RESET:
return false;
default:
return state;
}
}

У каждой такой функции есть собственное состояние(state), которое в данном случае равно false, и при успешном создании возвращается true, на этом завязан еще и некий интерактив, ведь когда пользователь нажал на создание записи, то процесс отправки запроса на сервер и обратно занимает некоторое время и пользователь должен сразу понимать, что идет загрузка, в коде выше есть проверка, повторю его:

if(!successfullDownload) {
if (successCreating) {
message.success('Success');
return <Redirect to="/"/>;
}
return <LoadingOutlined className="spinner" spin />;
}

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

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