Часть II JWT Auth с помощью Elixir на Phoenix 1.4 API и React Native

Щелкните здесь, чтобы перейти к части I, где мы создаем Elixir на основе API JWT Phoenix 1.4, упомянутого в этом руководстве.

Хотя это часть II руководства по веб-токенам Elixir / Phoenix - React Native JSON, клиент React Native JWT, созданный с помощью этого руководства, будет работать с любым подходящим API.

React Native - это мощный кроссплатформенный фреймворк для разработки мобильных приложений с открытым исходным кодом и лицензией MIT, который можно использовать для разработки и доставки полностью нативных Android, Windows и iOS с единой унифицированной кодовой базой.

Давайте создадим с его помощью клиент API веб-токенов JSON!

Создание клиента JWT на React Native

Примечание. Поскольку в этом руководстве основное внимание уделяется функциям JWT-клиента и AsyncStorage, мы будем обрабатывать навигацию с помощью простой условной визуализации экрана, а не с помощью пакета навигации. и мы будем обрабатывать состояние с помощью vanilla React Native, а не Redux / Flux.

Если вы знакомы с основами React Native, перейдите к разделу Планирование нашего приложения.

Настройка среды разработки React Native

Для этого проекта React Native мы создадим нашу кодовую базу с помощью пакета npm response-native-cli. Чтобы правильно следовать этому руководству, вам потребуются узел, npm и (в идеале) пряжа.

Если у вас еще не настроена среда разработки React Native, вы можете либо следовать инструкциям по установке для установки собственного инструмента разработки (что мы будем использовать в этом руководстве), либо вы можете настроить среду разработки Expo React Native здесь.

Примечание. Если вы работаете в Windows или Linux, Expo идеально подходит для тестирования приложений iPhone, поскольку вы не можете запустить Xcode или его бесчисленное множество симуляторов телефона в Windows или Linux.

Создание приложений React Native

Давайте откроем нашу командную строку и запустим react-native init jwt_client.

Это создает папку с именем jwt_client/, которая содержит наши package.json, разные файлы конфигурации, и каталоги /android + /ios, содержащие код для конкретной платформы.

Вам не нужно будет прикасаться к своим /android или /ios каталогам, если вам не нужно добавлять изображения / аудиоресурсы в свой проект или пока вы не будете готовы настроить свое приложение для развертывания в различных магазинах приложений.

Вы также можете в конечном итоге отредактировать свой /android или /ios источник, если хотите добавить в свое приложение расширенный код, зависящий от платформы, например модули микширования звука C ++, которые затем можно связать с React Native. В этом руководстве (и в большинстве основных случаев использования RN) мы будем полностью кодировать наше приложение на React Native.

В дополнение к вышеупомянутым файлам наша команда init также сгенерировала наши исходные файлы исходного кода приложения React Native, index.js и App.js.

jwt_client/index.js - это точка входа для нашего приложения, где функция AppRegistry.registerComponent React Native регистрирует корневой компонент нашего приложения (содержащийся в App.js) с собственными процессами компиляции приложения.

Нам понадобится нечто большее, чем App.js, для запуска нашего JWT-клиента (по крайней мере, с комфортом), поэтому давайте перейдем к ...

Планирование нашего приложения

Мы создаем приложение, которое регистрирует и аутентифицирует пользователей через наш Elixir / Phoenix JWT API из части I. Оно сможет делать следующее:

  1. POST регистрация новых пользователей на нашей конечной точке / sign_up.
  2. POST существующие пользователи входят в нашу конечную точку / sign_in.
  3. Сохранить JWT, возвращенные после входа / регистрации POSTs, в локальное хранилище устройства.
  4. Загрузить JWT из локального хранилища устройства.
  5. Удалите файлы JWT из локального хранилища устройства, чтобы выйти из приложения.
  6. Сделайте GET запросов с аутентификацией.

Мы будем использовать удобные функции AsyncStorage React Native для взаимодействия с хранилищем устройства.

При запуске наше приложение должно попытаться загрузить JWT из хранилища устройства.

Если в памяти устройства нет JWT, наше приложение должно отобразить экран входа / регистрации.

Если наше приложение обнаруживает JWT в хранилище устройства, оно должно отображать экран с аутентификацией.

Структура папки приложения

Если вы пропустили раздел инициализации, запустите react-native init jwt_client в своем интерфейсе командной строки.

Создайте папку с именем src в нашем jwt_client/ родительском каталоге, как jwt_client/src/, затем переместите App.js в src/.

Отредактируйте index.js, чтобы отразить это изменение:

Создайте три новых каталога в src/:

  1. src/components/ для наших компонентов React
  2. src/screens/ для компонентов экрана нашего приложения
  3. src/services/ для функций локального хранения данных нашего приложения.

Создайте еще один каталог в components/, src/components/common/ для функциональных компонентов без состояния, которые мы будем использовать в наших более сложных компонентах классов с отслеживанием состояния.

Компоненты приложения

Создайте два новых файла компонентов экрана, screens/Auth.js и screens/LoggedIn.js .

На нашем экране Auth будет отображаться либо форма Регистрация, либо компонент формы Вход в зависимости от желаемого состояния нашего пользователя, поэтому давайте создадим соответствующие файлы компонентов в нашем components/ каталог, components/Registration.js и components/Login.js .

Прежде чем мы начнем писать наши основные компоненты, давайте разогреемся, создав общий компонент загрузочного счетчика в components/common/, как common/Loading.js .

Внутри Loading.js импортируйте React из React и { View, ActivityIndicator } из React Native, затем объявите функциональный компонент с именем Загрузка:

Если компонент не требует методов состояния или жизненного цикла, сделайте его функциональным компонентом, объявив его с помощью const ComponentName = (props) => {, а не class ComponentName extends Component.

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

Давайте передадим свойство size в ActivityIndicator и зададим стиль контейнера нашему View:

flex: -1 устанавливает для View нашего компонента естественную фиксированную ширину / высоту на нашем макете, когда для него достаточно места, и сжимает наш вид, если его нет.

Мы передадим свойство size через наш компонент Loading на экранах, которые его используют, как в: <Loading size={'large'} />.

Давайте добавим индексируемый оператор экспорта в конец нашего компонента Загрузка:

Создайте общий индексный файл компонентов, как common/index.js, и экспортируйте из него наш компонент Loading:

Экспортируя компоненты в папку с index.js, мы можем импортировать эти компоненты в одном операторе с другими компонентами, проиндексированными в этом каталоге, например,: import { Loading, Button, ... } from ‘./common/'; vs import Loading from ‘./common/Loading; import Button from ‘./common/Button;’.

Теперь давайте напишем наш корневой компонент!

App.js, корневой компонент

Откройте App.js и замените шаблон, созданный init следующим кодом:

В начале нашего файла мы импортируем React, наш общий компонент загрузки и наши экраны Auth и LoggedIn, которые скоро будут созданы.

Мы храним наш JWT в состоянии нашего корневого компонента, установленном с помощью this.state = { jwt: ‘’ } в constructor() нашего компонента.

В функции render() приложения мы возвращаем наш <Auth /> компонент, если наше состояние не содержит JWT (if (!this.state.jwt)), и наш <LoggedIn /> компонент, если он есть.

Мы используем оператор else if для нашего this.state.jwt == true случая, а не троичный, потому что мы собираемся добавить сюда еще одно условие позже.

Теперь давайте скомпонуем наши компоненты экрана Auth!

Экран аутентификации

В screens/Auth.js напишите следующий код:

В дополнение к нашему типичному импорту React, Component и React Native's View, мы импортируем наши компоненты Login и Registration в начале этого файла как проиндексированные компоненты (мы еще не проиндексировали их, поэтому это не будет скомпилировано).

Мы разрешаем использование реквизита, передавая props через конструктор нашего компонента Auth с constructor(props) и super(props).. Мы устанавливаем единый элемент начального состояния, showLogin, который мы будем использовать для переключения между нашими формами входа и регистрации.

Наш объект стиля styles.container, переданный через родительский тег <View> нашего компонента, выравнивает и выравнивает содержимое представления по центру экрана.

Поскольку это представление является корневым представлением для этих компонентов Auth, присвоение ему стиля макета flex: 1 позволяет нам стилизовать его дочерние представления относительно друг друга. (Дополнительную информацию о Flex см. В документации React Layout Props).

Теперь добавьте функцию под названием whichForm () под конструктором, привяжите ее к нашему компоненту, добавив this.whichForm = this.whichForm.bind(this); внутри конструктора, и передайте ее через наши теги View как {this.whichForm()}:

Как написано выше, whichForm () проверяет наше this.state.showLogin логическое значение и возвращает <Login />, если оно истинно, или <Registration />, если ложно, в инструкции return нашей функции render ().

Связывание whichForm () в нашем конструкторе дает ему доступ к нашему состоянию и дает нам доступ к this.whichForm () в других частях нашего компонента, таких как наш render ( ) функция.

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

Давайте создадим и привяжем функцию authSwitch () внутри нашего компонента, а затем передадим ее через наши компоненты Регистрация и Вход в качестве свойства authSwitch={this.authSwitch}:

При запуске authSwitch () устанавливает для showLogin значение, противоположное его текущему состоянию, or!this.state.showLogin.

Передача authSwitch () через наши дочерние компоненты дает им доступ к нему, что позволяет нам изменять состояние нашего компонента Auth из его дочерних компонентов.

Прежде чем мы сможем написать наши формы регистрации и входа, давайте напишем наши…

Общие компоненты формы

Вернувшись в наш каталог components/common/, создайте файлы common/Button.js , common/Input.js и common/TextLink.js .

Button.js:

Вышеупомянутый функциональный компонент Button отображает тег View flex-direction: row styled, содержащий TouchableOpacity, содержащий компонент Text.

Мы передаем два свойства через наш компонент Button: onPress (функция, которая запускается при нажатии кнопки) и дочерние элементы (текст, который отображается в наших тегах Text).

В действии это будет выглядеть так: <Button onPress={this.someFunction}> Some Button Text </Button>

Мы выравниваем и стилизуем наш текст и нашу кнопку с помощью объекта const стилей, а поскольку у нас есть более одного свойства для нашего объекта стилей, мы отменяем ссылку это внутри нашего компонента как const { button, text } = styles;.

Это разыменование позволяет нам запускать эти стили через теги наших компонентов без styles.style избыточности ссылок, как in<Text style={text}> вместо Text style={styles.text}>.

Мы также можем отменить ссылку на состояние, например: const { jwt, loading } = this.state;, в компонентах с отслеживанием состояния.

TextLink.js:

Код нашего компонента TextLink почти идентичен нашему компоненту Button, но с разными стилями (без backgroundColor, borderRadius, borderWidth, borderColor и т. д.), чтобы он отображался как подчеркнутая текстовая ссылка, а не как кнопка.

Input.js:

Наш компонент Input состоит из родительского View, содержащего метку (стилизованный компонент Text), и компонента TextInput.

Поскольку представление, содержащее наши компоненты Text label и TextInput, имеет стиль flexDirection: 'row', Text и TextInput будут отображаться горизонтально, а не вертикально, в виде «строки», а не «столбца».

Если передать flex: 1 в нашу текстовую метку и flex: 3 в наш TextInput, наша текстовая метка займет 1/4 строки, а наш TextInput займет 3/4 (flex: 1 + flex: 3 = flex: 4).

Этот относительный размер с помощью flex работает только потому, что строка, содержащая Text и TextInput, родительский View, имеет стиль flex: 1. Размер гибкого дочернего элемента относительно его братьев и сестер составляет childFlexValue / X, где X - это сумма гибких значений всех братьев и сестер.

TextInput реквизиты

Этот компонент Input настроен на получение множества свойств, таких как {label} для отображаемого текстового дочернего элемента его компонента Text label, и свойство для каждого из следующих свойств TextInput:

  • secureTextEntry = {boolean} - если true, набранные символы будут скрыты звездочками (пароль: ********), а не простым текстом (пароль: пароль).
  • placeholder = {string} - текст-заполнитель, заменяемый при вводе текста.
  • value = {string} - значение содержимого поля ввода.
  • onChangeText = {string} - событие / функция, привязанные к полю ввода, запускаются при изменении значения TextInput. Это то, что мы используем для setState () в родительских компонентах нашего Input.
  • autoCorrect = {boolean} - если false, автокоррекция для этого поля отключена.
  • multiline = {boolean} - будет ли вход иметь одну или несколько строк.
  • numberOfLines = {integer} - если multiline = {true}, это устанавливает, сколько строк будет во вводе.

Чтобы узнать больше о свойствах TextInput, ознакомьтесь с документацией React Native.

Теперь, когда мы закончили писать наши общие функциональные компоненты, давайте экспортируем их все из common/index.js:

Пора переходить к нашему…

Компонент регистрационной формы

Прежде чем мы запустим наш компонент Регистрация, создайте файл components/index.js и экспортируйте из него все (*) в ./Registration:

Откройте components/Registration.js и введите следующее:

Мы импортируем все основные и общие компоненты нашей формы, а затем устанавливаем компонент Регистрация без экспорта по умолчанию, чтобы мы могли проиндексировать его из нашего export { Registration }; оператора в конце файла.

Мы передаем реквизиты через наш конструктор, а затем устанавливаем следующие части состояния:

  • email, password и password_confirmation для получения ввода соответствующих им полей формы,
  • error для отображения ошибок и
  • loading, чтобы скрыть нашу кнопку и показать компонент Загрузка при отправке формы.

Обратите внимание, как мы отменяем ссылку на наше состояние с помощью const { email, password, ...} = this.state;, поэтому нам не нужно писать this.state несколько раз.

Теперь давайте напишем наш компонент формы и установим несколько стилей:

Обратите внимание, что мы добавляем родительский контейнер View со стилем width: 100% form и несколькими входами View контейнеры со стилем flexDirection: ‘row’ section.

Мы передаем соответствующие реквизиты в каждый из наших компонентов Ввод, например secureTextEntry для полей формы пароля, с соответствующими placeholder и label реквизитами.

Мы также передаем стрелочные функции в каждый из свойств функции onChangeText нашего Input:

<Input ...onChangeText={password_confirmation => this.setState({ password_confirmation })} />

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

Эта стрелочная функция работает аналогично созданию функции passwordConfirmation(){this.setState({...})) в нашем компоненте и привязке ее в нашем конструкторе с this.passwordConfirmation = this.passwordConfirmation.bind(this), но без всего дополнительного кода.

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

Добавьте поле Текст для отображения наших ошибок в нижней части представления формы с соответствующим errorTextStyle в наших стилях const и добавьте этот errorTextStyle в ссылка на наши стили в начале нашей функции render ():

Под нашим <Text . . .> {error} </Text>компонентом давайте добавим тернарный оператор, который показывает наш регистр <Button />, если наше состояние loading равно false, или большой компонент <Loading />, если наше loading состояние равно true:

Теперь, когда мы написали несколько компонентов, давайте посмотрим, что у нас есть, запустив react-native run-ios в нашем интерфейсе командной строки!

Это не самая красивая форма, но она работает!

Ну, по крайней мере, поля… наша кнопка Rebecca Purple, ну, в данный момент не так уж и много…

Прежде чем мы добавим нашу функцию регистрации пользователя POST к этой кнопке и, наконец, получим для этого приложения React Native какое-либо действие JWT, давайте добавим компонент TextLink в родительский элемент View нашей формы.

Помните функцию authSwitch (), которую мы передали в свойства нашего компонента регистрации?

Этот компонент TextLink получит нашу функцию authSwitch () prop в своей опоре функции onPress, поэтому, когда мы нажимаем эту TextLink, он переключит нас на нашу Login компонент:

С нашими компонентами регистрации, давайте посмотрим, как это выглядит! Давайте снова react-native run-ios:

Почему это не сработало? Проверьте ошибку: Adjacent JSX elements must be wrapped in an enclosing tag.., что означает, что у нашего компонента может быть только один корневой компонент.

Поскольку наш компонент Регистрация отображает<View style={form}></View><TextLink...></TextLink> бок о бок, наш транспилятор сбивается с толку.

Вместо того, чтобы создавать еще один тег View вокруг наших компонентов, который потенциально может разрушить наш макет, давайте импортируем и воспользуемся удобной функцией React Fragment! :

Если мы снова запустим react-native run-ios, наша регистрационная форма отобразится!

Но если мы коснемся нашей TextLink, мы получим еще одну ошибку; мы еще не создали компонент Вход!

Компонент формы входа

Наша форма Вход будет почти идентична нашей форме Регистрация, но с другим текстом TextLink и Button.

Напишите в components/Login.js следующее:

Теперь, если мы снова запустим наше приложение, наши TextLink переключат нас между Входом и Регистрацией в нашем Auth экран!

Приложение React Native, которое мы создали до этого момента, можно просмотреть в frontend-only ветке репозитория Github этого руководства: React-Native-JWT-Client.

Это все для этого раздела!

Щелкните здесь, чтобы перейти к части III, Создание клиента JWT на React: запросы API и AsyncStorage