реклама
разместить

Оптимизация подключения REST API для React приложений

stepanstepan4@gmail.com
stepanstepan4@gmail.com

Основная идея

Инструкция направлена на оптимизацию способа обмена данными между клиентом и сервером.

При написании компонентов, большинство разработчиков нагружают свой код излишней логикой в процессе подключения REST API. В итоге компонент в большей степени выполняет роль прокси объекта, и становится тяжело читаемым, трудно тестируемым, что особо пагубно влияет на разработку в команде. Основная задача инструкции направлена на обеспечение более декларативного подхода. Ниже представлены два варианта: код оптимального подключения REST API и классический способ для одного и того же компонента.

//OPTIMAL METHOD const CartList = () => { const {cart} = useSelector((state) => state) const clear = () => { API.clear() .catch((e)=>alert(e)) } return <View> {cart.map(({productName}, key) => { return <Text>{productName}</Text> })} <Button title={'clear'} onPress={clear}/> </View> } //CLASSIC OLD const CartList = () => { const dispatch = useDispatch() const {cart} = useSelector((state) => state) const clear = () => { //Request -> response -> logic -> dispatch fetch('http://server.com' + '/clear') .then(res => res.json()) .then(({result, cart}) => { result ? dispatch(setCart(cart)) : alert('FAIL') }) } return <View> {cart.map(({productName}, key) => { return <Text>{productName}</Text> })} <Button title={'clear'} onPress={clear}/> </View> }

В первом случае мы используем уникальный объект «API», который сам выполняет все за нас, обеспечивает более полиморфный подход и переносит логику в общий модуль. Во втором примере присутствует большая логическая нагрузка на компонент.

Далее будет рассмотрена реализация данного объекта с использованием redux и нативных возможностей javascript.
Информация актуальна для React и React-Native приложений.

stepanturchenko@mail.ru
stepanturchenko@mail.ru

Однако существует множество других шаблонов обмена бизнес данными между клиентом и сервером с использованием сторонних библиотек. Некоторые из них:

Оглавление

Интеграция клиентского API

1. Создание класса API.

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

//API create class API{ // ... methods ... } export default new API()

Далее требуется наполнить класс асинхронными методами для взаимодействия с сервером.

//API method add class API{ location = 'http://server.com/' //GET /list async getCartList(){ return await fetch(this.location + 'list').then(res=>res.json()) } //POST /:id async addProduct({product_id}){ return await fetch(this.location + product_id, {method:'post'}).then(res=>res.json()) } } export default new API()

В данном примере добавлены два метода «getCartList» (получение списка данных) и «addProduct» (добавление данных на сервер). В результате вызова оба метода возвращают Promise, которые при выполнении возвращает ответ сервера в JSON формате.

2. Интеграция Redux.

Далее требуется добавить взаимодействие с состоянием приложения через redux store. Предполагается, что уже подключен redux \ modx (В примере redux). Добавим dispatch метод, и будем его вызывать внутри наших методов (можно вызвать dispatch до запроса для мгновенного обновления состояния, либо после запроса, когда требуется использовать данные из ответа сервера.)

//dispatching in API import store from './stroe' import {setCartAction, addProductById} from './actions' class API{ location = 'http://server.com/' dispatch(action){ store.dispatch(action) } //GET /list async getCartList(){ const result = await fetch(this.location+'list').then(res=>res.json()) //dispatch after request this.dispatch(setCartAction(result)) return result } //POST /:id async addProduct({product_id}){ //dispatch before request this.dispatch(addProductById(product_id)) return await fetch(this.location + product_id, {method:'post'}).then(res=>res.json()) } } export default new API()
В результате получается довольно читабельный и последовательный алгоритм (на схеме справа) stepanstepan4@gmail.com
В результате получается довольно читабельный и последовательный алгоритм (на схеме справа) stepanstepan4@gmail.com

3. Использование внутри компонентов.

В результате мы можем использовать наш класс внутри компонентов множества компонентов. При модификации api запроса потребуется изменить лишь логику внутри одного из методов класса внутри нашего модуля.
Пример использования:

const ProductAdd = () => { const {cart} = useSelector((state) => state) const addProduct = (product_id) => { API.addProduct(product_id) .then(({data})=>{ // *** success *** }) .catch((e)=>alert(e)) } return <View> {cart.map(({product_name, product_id}, key) => { return <Text onPress={()=>addProduct(product_id)} > {product_name} </Text> })} </View> } export default ProductAdd;
Благодаря такому подходу контекст тела компонента становится чище и лучше, подобно тому, как мы создаем собственные хуки для переноса логики.  stepanstepan4@gmail.com
Благодаря такому подходу контекст тела компонента становится чище и лучше, подобно тому, как мы создаем собственные хуки для переноса логики.  stepanstepan4@gmail.com

При этом мы можем вызывать методы класса с использованием своих хуков, например:

// file myHooks.js import {useSelector} from 'react-redux'; import API from './API' export const useUserDataRequest = ()=>{ const {user} = useSelector() useEffect(()=>{ API.getAndSetUserData() },[]) return {user} } export default useUserDataRequest; ////////////////////// //View component file import {useUserDataRequest} from "./myHooks" export default ()=>{ const {user} = useUserDataRequest() if(!user){ return <p>Загрузка ...</p> } return <h1>{user.name}</h1> }

В момент отрисовки компонента, происходит вызов метода API и происходи загрузка данных и смена состояния. Данный подход очень напоминает хуки graphql-apollo.

Идеология

Благодаря данному подходу мы избавляемся от большой логической нагрузки и уменьшаем зону ответственности при подключении и модификации api.
Сплошными стрелками на схеме отмечена основная логическая нагрузка при подключении.

Без использования API класса stepanstepan4@gmail.com
Без использования API класса stepanstepan4@gmail.com
С использованием API класса становится намного меньше логики которую нужно брать во внимание при написании компонента. stepanstepan4@gmail.com
С использованием API класса становится намного меньше логики которую нужно брать во внимание при написании компонента. stepanstepan4@gmail.com

Axios

Данный паттерн можно модифицировать благодаря использованию библиотеки axios .
Основная польза от использования библиотеки:

  • Доступ к Interceptors (перехват запросов и ответов через обработчик)
  • create метод для создания универсального интерфейса запросов.
  • Оптимизация обработки ответа сервера для text и json формата.
  • Удобная типизация.

Пример класса API с использованием основных кейсов библиотеки

//axios API pattern const server = axios.create({ baseURL: 'http://server.com', }) class API { constructor() { //INITIAL REQ AND RES OBSERVE server.interceptors.request.use((req) => { this.dispatch(setLoadingAction(true)) return req }) server.interceptors.response.use((res) => { this.dispatch(setLoadingAction(false)) return res }) } dispatch(action){ store.dispatch(action) } getHeaderWithToken(){ return {headers: {token: store.getState().token}} } setError(e){ console.warn(e) dispatch(setErrorAction(e)) } async getUserDataAndSet(){ const result = await server.get( '/user/info', this.getHeaderWithToken()) .catch((e) => this.setError(e)) this.dispatch(setUserAction(result.data.user)) return result } }
axios предоставляет удобный интерфейс для перехвата события запроса и\или ответа. При этом можно модифицировать объекты данных. Это удобно, когда требуется изменить состояние загрузки приложения внутри redux при каждом запросе. stepanstepan4@gmail.com
axios предоставляет удобный интерфейс для перехвата события запроса и\или ответа. При этом можно модифицировать объекты данных. Это удобно, когда требуется изменить состояние загрузки приложения внутри redux при каждом запросе. stepanstepan4@gmail.com

Typescript

Для защиты типов и поддержки кода добавим типизацию с применением typescript. Такой подход позволит получить доступ к результатам ответа сервера без документации к API сервера спустя длительное время работы с кодом.

import {setLoadAction, setProductCartAction, setTokenAction, setUserAction} from "../store/actions"; import {ActionType, ProductType, StateType, TokenType, UserType} from "../types/types"; import axios, {AxiosRequestConfig, AxiosResponse} from 'axios' import store from "../store/store"; import LOCATION from "../vars/LOCATION"; import Message from "../config/Message"; import formatPhone from "../config/formatPhone"; const server = axios.create({ baseURL: LOCATION, }) type IResult<T = {}> = { data: { result: boolean } & T } interface IStatus { status?: number } type SuccessResponse<T = {}> = IResult<T> & IStatus type FailRespone = IResult //API AUTOMATICLY SET STORE APPLICATION STATE class API { constructor() { //INITIAL REQ AND RES SUBSCRIBE OBSERVE server.interceptors.request.use((req: AxiosRequestConfig) => { this.setLoader(true) if (req?.data?.phone) { const {phone} = req.data req.data.phone = formatPhone(phone) } return req }) server.interceptors.response.use((res: AxiosResponse) => { this.setLoader(false) return res }) } private dispatch(action: ActionType) { store.dispatch<ActionType>(action) } private FailResult: FailRespone = {data: {result: false}} private getState(): StateType { return store.getState() } private getUserToken(): TokenType { return this.getState().token || '' } private setLoader(load: boolean) { this.dispatch(setLoadAction(load)) return load } private setUserTokenToStore({token}: { token: TokenType }): void { this.dispatch(setTokenAction(token)) } private setError(e: Error): IResult { this.setLoader(false) Message('Ошибка соединения') console.warn(e, 'ERR') return this.FailResult } private getHeaderWithToken(): { headers: { token: TokenType } } { return {headers: {token: this.getUserToken()}} } async getSmsCodeByPhone({phone}: { phone: string }): Promise<SuccessResponse> { return (await server.post<{ phone: string }, any>('/user/code', {phone}) .catch((e) => this.setError(e))) } async verifyUserLoginByCodeAndSetToken({phone, code}: { phone: string, code: string }): Promise<SuccessResponse<{ is_registered?: boolean, token?: string }>> { const result: SuccessResponse<{ is_registered?: boolean, token?: string }> = (await server.post<{ phone: string, code: string }, any>('/user/verify', {phone, code}) .catch((e) => this.setError(e))) if (result?.data?.token) { this.dispatch(setTokenAction(result.data.token)) } return result } async registrationUserIfToken({username, email, phone}: { username: string, email: string, phone: string }): Promise<SuccessResponse> { if (!this.getUserToken()) { console.warn('TOKEN NOT EXIST IN STORE') return this.FailResult } return (await server.post<{ username: string, email: string, phone: string }, any>( '/user/registration', {username, email, phone}, this.getHeaderWithToken()) .catch((e) => this.setError(e))) } async scanProductByScanner({code}: { code: string }): Promise<SuccessResponse<{ exists?: boolean, product?: ProductType, cart?: ProductType[], total?: string }>> { const result: SuccessResponse<{ exists?: boolean, product?: ProductType, cart?: ProductType[], total?: string }> = (await server.post<{ code: string }, any>( '/product/add', {code}, this.getHeaderWithToken()) .catch((e) => this.setError(e))) if (result?.data?.result && result?.data?.cart && result?.data?.total) { this.dispatch(setProductCartAction(result.data.cart, result.data.total)) return result } console.warn('CART NONE OR RESULT FALSE') return this.FailResult } } export default new API()

Контакты

Если у вас возникли вопросы, буду рад ответить и обсудить

Турченко Степан
Fullstack middle developer
  • stepanturchenko@mail.ru
  • stepanstepan4@gmail.com
Как вы общаетесь с сервером в своих компонентах
current API class pattern
graphql-apollo
redux-saga / thunk
другое
1313
реклама
разместить
6 комментариев

Степ, пости такое лучше на хабр, тутошний народ гораздо больше любит когда кого-то Яндекс еда обманула на гамбургер

5

Я извиняюсь как человек, пишущий в ооп стиле и пользующийся мобиксом, вот этот трэш, подписанный как "CLASSIC OLD", его реально пишут? Его там тимлиды смотрят на код ревью, говорят что все норм и аппрувят? Просто мне казалось, в редаксе запросы к сервисам принято хотя бы в экшены прятать.

1

привет,
Пример с использованием экшенов я особо не упоминал, тк это в большей степени напоминает сагу, где происходит практически то же самое , но через прослойку эффектов.
Если посмотреть ≈90% гайдов с ютуба, все они рекомендуют именно такой способ "classic".
Сам я рекомендую использовать данный способ из статьи только при не большом количестве эндпоинтов, когда выгоднее отказаться от включения библиотеки

Привет, дай наводку, где мы с тобой виделись)
Возможно, это мой первый опыт, 
появился повод попробовать для конкурса

А есть пример проекта на GitHub? Хотелось бы посмотреть это в действии.

Невыдуманные истории, когда владельцы бизнеса решили пренебречь информационной безопасностью

Мечта собственника — сильная команда и высокая производительность. Но риски для компании мало кто осознает. Рассказываем, как предпринимателю понять мотивы сотрудников, есть ли угроза мошеннических схем, подделок документов, утечек информации и финансов.

Невыдуманные истории, когда владельцы бизнеса решили пренебречь информационной безопасностью
1515
44
33
11
11
реклама
разместить
Батюшка Бизнесменский, или как автор vc из топ-5 ворует чужую статью, выдавая её за своё интервью, а неудобные комментарии удаляет

Незапланированное воскресное расследование о том, как топовый автор обманывает читателей vc.ru

Батюшка Бизнесменский, или как автор vc из топ-5 ворует чужую статью, выдавая её за своё интервью, а неудобные комментарии удаляет
2323
66
33
11
11
Стратегию на 5 лет может написать любой дурак! А ты попробуй спланировать шаг на завтра?
Стратегию на 5 лет может написать любой дурак! А ты попробуй спланировать шаг на завтра?

Я очень долго думал, что Стратег подразумевает аццкие компетенции в построении Стратегий. Типа как Император Бонапарт, стоишь на холме, под тобой поле сражения. Конница справа, артиллерия слева, посредине тоже кто-то есть.

11
Космический корабль Crew Dragon с российским космонавтом Кириллом Песковым пристыковался к МКС

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

Фото NASA 
66
Недвижка подорожала до максимума, рубль сильно укрепился. Ипотека, дивиденды, облигации, крипта и другие новости. Воскресный инвестдайджест

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

Недвижка подорожала до максимума, рубль сильно укрепился. Ипотека, дивиденды, облигации, крипта и другие новости. Воскресный инвестдайджест
2020
33
33
11
11
Меня выгнали с работы, и я открыла глянцевый журнал. Разборки, воровство, слёзы...

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

Меня выгнали с работы, и я открыла глянцевый журнал. Разборки, воровство, слёзы...
1717
33
22
11
31 вариант международных площадок где можно опубликовать свою статью. В одной табличке с названиями, сайтами, описаниями и посещаемостью. Делюсь с вами
31 вариант международных площадок где можно опубликовать свою статью. В одной табличке с названиями, сайтами, описаниями и посещаемостью. Делюсь с вами
2222
55
Блогер, а ты отслеживаешь источники трафика в свой ТГ канал?
Блогер, а ты отслеживаешь источники трафика в свой ТГ канал?

Основная цель этой статьи — собрать реальный опыт авторов и блогеров. Если вы нашли удобные инструменты или придумали собственные лайфхаки, поделитесь ими в комментариях. Расскажите, что сработало в вашем случае, а что оказалось неэффективным.

22
11
«Чем ты гордишься?»: интервью со специалистом по визуальным эффектам клипа Abracadabra Lady Gaga

Что заставляет специалиста по-настоящему гордиться своей работой?Я попросил лучших в своём деле рассказать свои истории.
Читайте коллекцию отличных историй замечательных специалистов!
Сегодня с нами Игорь Эйт — VFX Supervisor (Lady Gaga, Damiano David, Kaytranada, Korn, Little Big* и др.) с рассказом о своей работе.

Рубрика «Чем ты гордишься?» | Изображение создано в сервисе <a href="https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fgerwin.io&postId=1043253" rel="nofollow noreferrer noopener" target="_blank">Gerwin.io</a>
1111
33
реклама
разместить
«Вам бы делать хорроры»: в Голливуде появились первые ИИ-киностудии

Издание Forbes рассказало, что за фильмы они снимают и какие инструменты используют.

1717
77
33
Наш актеры и так играют как ИИ
Делится ли имущество в "гражданском браке"?

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

Делится ли имущество в "гражданском браке"?
55
[]