Рубрики
Без рубрики

Как управлять состоянием в приложении RACT с контекстом и крючками

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

Автор оригинала: FreeCodeCamp Community Member.

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

Основываясь на этой статье, многое (два на самом деле) людей задавали вопросы, связанные с тем, как состояние может управляться в приложении React, используя только контекст и крючки, что привело к мнению мне, выполняя небольшие исследования по теме.

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

Основная причина для страницы входа в систему – показать, как мы можем поделиться состоянием Auth по приложению, которое является общим случаем использования для приложений, которые используют библиотеку, такую как Redux.

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

Для Backend Server я создал простое экспресс-приложение и разместил его на Heroku. У него есть две основные конечные точки:

  • /Вход – для аутентификации. При успешном входе в систему он возвращает токен JWT и детали пользователей.
  • /Песни – Возвращает список песен.

Если вы хотите добавить дополнительную функциональность, можно найти репозиторий для приложения Backend здесь Отказ

Реконструировать

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

  • Уместите – Этот крюк позволяет нам использовать состояние в функциональных компонентах (эквивалент it.state и it.setState в компонентах класса)
  • упреждающий текст – Этот крюк принимает объект контекста и возвращает все, что передается как значение ROP в Mycontext.provider Отказ Если вы не знаете о контексте, это способ пропускания состояния из родительского компонента к любому другому компоненту внутри дерева (независимо от того, насколько глубоко) без необходимости передавать его через другие компоненты, которые не требуют его (проблема удачно названа сверление). Вы можете прочитать больше о контексте здесь Отказ
  • Успеведщик – Это альтернатива Уместите И его можно использовать для сложной логики состояния. Это мой любимый крючок, потому что он работает так же, как библиотека redux. Это принимает редуктор типа:
(state, action) => newState

А также начальный государственный объект, прежде чем вернуть новое состояние.

НАЧИНАЯ

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

  • Узел (≥ 6)
  • Текстовый редактор

В вашем терминале введите команду:

npx create-react-app hooked

Если у вас нет NPX Доступно вы можете установить Create-raction-app в глобальном масштабе в вашей системе:

npm install -g create-react-app
create-react-app hooked

Вы создадите пять компонентов к концу этой статьи:

  • Header.js – этот компонент будет содержать заголовок приложения (очевидно), а также отображать кнопку выхода из системы, которая содержит имя пользователя. Кнопка будет отображаться только, если пользователь аутентифицируется.
  • App.js – это компонент верхнего уровня, в котором мы создадим контекст аутентификации (поговорим об этом позже). Этот компонент также условно делает либо компонент входа в систему, если пользователь не вошел в систему или домашний компонент, если пользователь аутентифицируется.
  • Home.js – Этот компонент приведет к списку песен с сервера и представляет его на странице.
  • Login.js – Этот компонент будет содержать форму входа в систему для пользователя. Он также будет нести ответственность за приготовление запроса на конечную точку входа в систему и обновлять контекст аутентификации с помощью ответа с сервера.
  • CARD.JS – это презентационная компонент (UI), который оказывает, что детали песни, передаваемой в нее.

Теперь давайте создадим пустые компоненты, которые мы позже добавим логику. В SRC Папка, создайте папку и назвать ее Компоненты Затем создайте четыре этих четырех файлов, а именно Header.js , Home.js , Login.js и Card.js :

Header.js.

import React from "react";
export const Header = () => {
  return (
    
  );
};
export default Header;

Home.js.

import React from "react";
export const Home = () => {
return (
    
); }; export default Home;

Login.js.

import React from "react";
import logo from "../logo.svg";
import { AuthContext } from "../App";
export const Login = () => {
return (
    
); }; export default Login;

И App.js Файл должен выглядеть так:

import React from "react";
import "./App.css";
function App() {
return (
      
); } export default App;

В App.js Файл, мы создадим контекст авторизации, который пройдет состояние auth из этого компонента к любому другому компоненту, которое требует его. Создайте контекст аутентификации, как это ниже:

import React from "react";
import "./App.css";
import Login from "./components/Login";
import Home from "./components/Home";
import Header from "./components/Header";
export const AuthContext = React.createContext(); // added this
function App() {
return (
    
      
); } export default App;

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

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

import React from "react";
import "./App.css";
import Login from "./components/Login";
import Home from "./components/Home";
import Header from "./components/Header";
export const AuthContext = React.createContext();
const initialState = {
  isAuthenticated: false,
  user: null,
  token: null,
};
const reducer = (state, action) => {
  switch (action.type) {
    case "LOGIN":
      localStorage.setItem("user", JSON.stringify(action.payload.user));
      localStorage.setItem("token", JSON.stringify(action.payload.token));
      return {
        ...state,
        isAuthenticated: true,
        user: action.payload.user,
        token: action.payload.token
      };
    case "LOGOUT":
      localStorage.clear();
      return {
        ...state,
        isAuthenticated: false,
        user: null
      };
    default:
      return state;
  }
};
function App() {
  const [state, dispatch] = React.useReducer(reducer, initialState);
return (
    
      
{!state.isAuthenticated ? : }
); } export default App;

В фрагменте выше на фрагменте много, но позвольте мне объяснить каждую часть:

const initialState = {
  isAuthenticated: false,
  user: null,
  token: null,
};

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

const reducer = (state, action) => {
  switch (action.type) {
    case "LOGIN":
      localStorage.setItem("user", JSON.stringify(action.payload.user));
      localStorage.setItem("token", JSON.stringify(action.payload.token));
      return {
        ...state,
        isAuthenticated: true,
        user: action.payload.user,
        token: action.payload.token
      };
    case "LOGOUT":
      localStorage.clear();
      return {
        ...state,
        isAuthenticated: false,
        user: null,
        token: null,
      };
    default:
      return state;
  }
};

Функция редуктора содержит оператор Case-Switch, которое основано на определенных действиях, возвращает новое состояние. Действия в редукторе:

  • Вход – Когда этот тип действий отправляется, он также будет отправлен с полезной нагрузкой (содержащей пользователь и токен ). Он сохраняет пользователя и токен к LocalStorage, а затем возвращает новое состояние, настроек Isauthenticated к правда , а также устанавливает Пользователь и токен ключи к их соответствующим значениям на основе полезной нагрузки действия.
  • Выход из системы – Когда это действие отправляется, мы очищаем локальную местность всех данных и установить Пользователь и токен к null Отказ

Если никаких действий не будет отправлена, он возвращает начальное состояние.

const [state, dispatch] = React.useReducer(reducer, initialState);

Успеведщик Крюк возвращает два параметра, Государство и Отправка Отказ Государство Содержит состояние, которое используется в компоненте, и он обновляется на основе отправленных действий. Отправка это функция, которая используется в приложении для вызова/отправки действий, которые преобразуют или изменяют состояние.


      
{!state.isAuthenticated ? : }

Здесь, в Context.Provider Компонент, мы передаем объект в ценность пропры Объект содержит Государство и Отправка Функция, так что ее можно использовать любым другим компонентом, который требует этого контекста. Затем мы условно визуализируем компоненты – если пользователь аутентифицирован, мы визуализируем Главная компонент, иначе мы визуализируем Вход составная часть.

Компонент входа в систему

В компоненте входа в систему, давайте добавим необходимые элементы для формы, как показано ниже:

import React from "react";
export const Login = () => {
return (
    

Login

); }; export default Login;

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

import React from "react";
export const Login = () => {
  const initialState = {
    email: "",
    password: "",
    isSubmitting: false,
    errorMessage: null
  };
const [data, setData] = React.useState(initialState);
const handleInputChange = event => {
    setData({
      ...data,
      [event.target.name]: event.target.value
    });
  };
return (
    

Login

{data.errorMessage && ( {data.errorMessage} )}
); }; export default Login;

В указанном выше коде мы прошли в инициация объект в Уместите крюк. В объекте мы обрабатываем состояние электронной почты, состояние пароля, состояние, которое используется для проверки, если форма отправляется на сервер, а также errormessage Значение, которое обрабатывает ошибки с сервера.

Далее мы добавим функцию, которая обрабатывает представление формы в API Backend. В этой функции мы будем использовать извлекать API для отправки полезной нагрузки на сервер. Если ответ успешен, мы отправим Вход Действие, а также пройти ответ от сервера в качестве полезной нагрузки в диспетчерее действие. Если с сервера есть ошибка (если учетные данные входа не действительны), мы называем Сетдата и пройти errormessage с сервера, который будет отображаться в форме. Для того, чтобы позвонить отправку, нам нужно импортировать Authcontext от Приложение компонент в нашу Вход компонент, а затем используйте Отправка Функция в приложении. Ваш финал Вход Компонент должен выглядеть как ниже:

import React from "react";
import { AuthContext } from "../App";
export const Login = () => {
  const { dispatch } = React.useContext(AuthContext);
  const initialState = {
    email: "",
    password: "",
    isSubmitting: false,
    errorMessage: null
  };
const [data, setData] = React.useState(initialState);
const handleInputChange = event => {
    setData({
      ...data,
      [event.target.name]: event.target.value
    });
  };
const handleFormSubmit = event => {
    event.preventDefault();
    setData({
      ...data,
      isSubmitting: true,
      errorMessage: null
    });
    fetch("https://hookedbe.herokuapp.com/api/login", {
      method: "post",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        username: data.email,
        password: data.password
      })
    })
      .then(res => {
        if (res.ok) {
          return res.json();
        }
        throw res;
      })
      .then(resJson => {
        dispatch({
            type: "LOGIN",
            payload: resJson
        })
      })
      .catch(error => {
        setData({
          ...data,
          isSubmitting: false,
          errorMessage: error.message || error.statusText
        });
      });
  };
return (
    

Login

{data.errorMessage && ( {data.errorMessage} )}
); }; export default Login;

Домашняя компонент

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

Давайте построим разметку для этого компонента. Мы хотим получить песни и карту через список возвращенных песен, а затем рендерируйте Карта Компонент для каждой песни. Карта Компонент представляет собой простой функциональный компонент, который передается некоторым реквизит рендер. Создать Card.js Файл в Компоненты Папка, и в этом файле добавьте следующий код ниже:

import React from "react";
export const Card = ({ song }) => {
    
  return (
    

{song.name}

BY: {song.artist}
); }; export default Card;

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

Вернуться в нашу Главная Компонент при обработке сетевых запросов в большинстве приложений мы пытаемся визуализировать три основных состояния. Во-первых, когда запрос обрабатывается (с помощью погрузчика каких-либо сортировков), то когда запрос успешен (путем предоставления полезной нагрузки или показывая уведомление об успехе), и, наконец, когда запрос не удается (показывая уведомление об ошибках). Чтобы сделать запрос, когда компонент установлен, а также обрабатывает эти три состояния, мы будем использовать Useffect и Успеведщик крючки.

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

const initialState = {
  songs: [],
  isFetching: false,
  hasError: false,
};

Песни Удержит список песен, извлеченных с сервера, и оно изначально пусто. осматривает используется для представления состояния загрузки и первоначально устанавливается на ложь Отказ Haserror используется для представления состояния ошибки и также изначально устанавливается на ложь Отказ

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

const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SONGS_REQUEST":
      return {
        ...state,
        isFetching: true,
        hasError: false
      };
    case "FETCH_SONGS_SUCCESS":
      return {
        ...state,
        isFetching: false,
        songs: action.payload
      };
    case "FETCH_SONGS_FAILURE":
      return {
        ...state,
        hasError: true,
        isFetching: false
      };
    default:
      return state;
  }
};

Давайте сломаемся. Если мы отправляем Fetch_songs_request Действие в нашем приложении мы возвращаем новое состояние со значением осматривает установить правда Отказ Если мы отправляем Fetch_songs_success Действие в нашем приложении мы возвращаем новое состояние со значением осматривает установить ложь , а потом Песни Установите на полезную нагрузку, отправленную обратно с сервера. Наконец, если мы отправляем Fetch_songs_failure Действие в нашем приложении мы возвращаем новое состояние со значением осматривает установить ложь и Haserror установить ложь Отказ

Теперь, когда у нас есть крючок, наш Главная Компонент должен выглядеть так:

import React from "react";
import { AuthContext } from "../App";
import Card from "./Card";
const initialState = {
  songs: [],
  isFetching: false,
  hasError: false,
};
const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SONGS_REQUEST":
      return {
        ...state,
        isFetching: true,
        hasError: false
      };
    case "FETCH_SONGS_SUCCESS":
      return {
        ...state,
        isFetching: false,
        songs: action.payload
      };
    case "FETCH_SONGS_FAILURE":
      return {
        ...state,
        hasError: true,
        isFetching: false
      };
    default:
      return state;
  }
};
export const Home = () => {
  const [state, dispatch] = React.useReducer(reducer, initialState);
return (
    
{state.isFetching ? ( LOADING... ) : state.hasError ? ( AN ERROR HAS OCCURED ) : ( <> {state.songs.length > 0 && state.songs.map(song => ( ))} )}
); }; export default Home;

Чтобы быстро пройти через то, что происходит, внутри Главная Функция мы добавляем Успеведщик крючок и пройти в Редуктор и инициация Что в свою очередь возвращает две переменные, а именно Государство и Отправка Отказ

Тогда в нашей функции рендеринга мы условно визуализируем промежуток с «загрузкой …» текстом, если State.iseching или мы оказываем промежуток с сообщением об ошибке, если State.haserror Отказ В противном случае мы петлю через список песен и визуализировать каждый как Карта Компонент, прохождение в необходимом реквизит Отказ

Связать все, мы добавим Useffect Функция, которая будет обрабатывать сетевые звонки и отправлять необходимые Действие на основе ответа сервера. Добавление крюка должно сделать наши Главная Компонент выглядит как фрагмент ниже:

import React from "react";
import { AuthContext } from "../App";
import Card from "./Card";
const initialState = {
  songs: [],
  isFetching: false,
  hasError: false,
};
const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SONGS_REQUEST":
      return {
        ...state,
        isFetching: true,
        hasError: false
      };
    case "FETCH_SONGS_SUCCESS":
      return {
        ...state,
        isFetching: false,
        songs: action.payload
      };
    case "FETCH_SONGS_FAILURE":
      return {
        ...state,
        hasError: true,
        isFetching: false
      };
    default:
      return state;
  }
};
export const Home = () => {
  const { state: authState } = React.useContext(AuthContext);
  const [state, dispatch] = React.useReducer(reducer, initialState);
React.useEffect(() => {
    dispatch({
      type: "FETCH_SONGS_REQUEST"
    });
    fetch("https://hookedbe.herokuapp.com/api/songs", {
      headers: {
        Authorization: `Bearer ${authState.token}`
      }
    })
      .then(res => {
        if (res.ok) {
          return res.json();
        } else {
          throw res;
        }
      })
      .then(resJson => {
        console.log(resJson);
        dispatch({
          type: "FETCH_SONGS_SUCCESS",
          payload: resJson
        });
      })
      .catch(error => {
        console.log(error);
        dispatch({
          type: "FETCH_SONGS_FAILURE"
        });
      });
  }, [authState.token]);

  return (
    
    
{state.isFetching ? ( LOADING... ) : state.hasError ? ( AN ERROR HAS OCCURED ) : ( <> {state.songs.length > 0 && state.songs.map(song => ( ))} )}
); }; export default Home;

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

Внутри Useffect Функция, мы изначально отправляем Fetch_songs_request Так что промежуток загрузки показывает, что мы производим сетевой запрос, используя извлекать API и прохождение токена мы получили от Authcontext как заголовок. Если ответ успешен, мы отправляем Fetch_songs_success Действие и пройти список песен, полученных с сервера в качестве полезной нагрузки в действии. Если есть ошибка с сервера, мы отправляем Fetch_songs_failure Действие, так что диапазон ошибок отображается на экране.

Последнее следует отметить в нашем Useffect Крючок состоит в том, что мы передаем токен в массиве зависимостей крючка (Подробнее о Useffect здесь ). Это означает, что наш крючок будет вызван только при изменении этого токена, что может произойти только в том случае, если токен истекает, и нам нужно получить новый или войти в систему как новый пользователь. Так что для этого пользователя крючок будет называться только один раз.

Хорошо, мы сделаем с логикой. Все, что осталось, это CSS. С тех пор как в деталях стилизации приложения выходит за рамки этой статьи, вы можете скопировать фрагмент CSS ниже и вставить его в App.csss файл:

/******  LOGIN PAGE  ******/
.login-container{
  display: flex;
  align-items: center;
  background-image: url("./assets/carry-on-colour.svg");
  height: calc(100vh - 70px);
  background-repeat: no-repeat;
  background-position: right;
  padding-left: 5%;
  padding-right: 5%;
  margin-top: 70px;
}
.card {
  /* Add shadows to create the "card" effect */
  box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  transition: 0.3s;
  height: 70%;
  width: 45%;
}
/* On mouse-over, add a deeper shadow */
.card:hover {
  box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
/* Add some padding inside the card container */
.login-container .container {
  padding-left: 7%;
  padding-right: 7%;
  height: 100%;
}
.login-container .container h1{
  font-size: 2.5rem;
}
.login-container .container form{
  display: flex;
  height: 80%;
  flex-direction: column;
  justify-content: space-around;
  align-self: center;
}
input[type="text"], input[type="password"]{
  padding-left: 1px;
  padding-right: 1px;
  height: 40px;
  border-radius: 5px;
  border: .5px solid rgb(143, 143, 143);
  font-size: 15px;
}
label{
  display: flex;
  flex-direction: column;
}
.login-container button{
  height: 40px;
  font-weight: bold;
  font-size: 15px;
  background-color: #F42B4B;
  color: rgb(255, 255, 255);
}
.login-container button:hover{
  background-color: rgb(151, 25, 46);
  cursor: pointer;
}
.login-container button:focus{
  outline: none !important;
}


.spinner {
  animation: spinner infinite .9s linear;
  height: 90%;
}
.spinner:focus{
  border:none;
}
@keyframes spinner {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
.form-error{
  color: #F42B4B;
  text-align: center;
}
@media screen and (max-width: 700px){
  .login-container{
    justify-content: center;
    background-image: none;
  }
  .card {
    width: 80%;
    align-self: center;
  }
  
}
@media screen and (max-width: 350px){
  .card {
    width: 100%;
  }
  
}
/******  LOGIN PAGE  ******/


/******  HEADER  ******/
#navigation{
  width: 100%;
  position: fixed;
  z-index: 10;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  background-color: #F42B4B;
  box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  height: 70px;
  top: 0;
  padding-right: 5px;
  padding-left: 5px;
}
#navigation h1{
  color: white;
}
#navigation button{
  background-color: transparent;
  border: none;
  align-self: center;
}
#navigation button:hover{
  cursor: pointer;
}
#navigation button:focus{
  outline: none !important;
}
/******  HEADER  ******/


/******  HOME PAGE  ******/
.home {
  margin-top: 100px;
  margin-left: 2%;
  margin-right: 2%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
.home .loader{
  align-self: center;
  width: 100%;
  text-align: center;
}
.home .error{
  width: 100%;
  align-self: center;
  color: #F42B4B;
  font-size: 30px;
  font-weight: bold;
  text-align: center;
}
.home>.card {
  /* Add shadows to create the "card" effect */
  box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  transition: 0.3s;
  height: 400px;
  width: 30%;
  position: relative;
  margin-bottom: 2%;
}
/* On mouse-over, add a deeper shadow */
.home .card:hover {
  box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.home .card>img{
  width: 100%;
  height: 100%;
}
.home .content{
  bottom: 0;
  z-index: 9;
  position: absolute;
  background-color: rgba(255, 255, 255, 0.7);
  display: flex;
  flex-direction: column;
  width: 100%;
  align-items: center;
  height: 35%;
  padding-bottom: 5px;
  transition: 0.5s;
}
.home .content:hover{
  background-color: rgba(255, 255, 255, 1);
  height: 50%;
  cursor: pointer;
}
.content>h2{
  text-align: center;
  font-size: 2rem;
}
@media screen and (max-width: 780px){
.home{
    justify-content: space-around;
  }
  .home .card {
    width: 45%;
  }
}
@media screen and (max-width: 500px){
  .home .card {
    width: 90%;
  }
}
@media screen and (min-width: 1400px){
  .home {
    margin: auto;
    width: 1400px;
  }
  .toggle-button{
    margin-bottom: 10px;
  }
}
/******  HOME PAGE  ******/

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

Вы можете получить доступ к репо GitHub, нажав на этот ссылка Отказ Обратите внимание, что REPO имеет некоторые добавленные функции, такие как создание новой песни.