Статьи

Создание системы отслеживания привычек с помощью Prisma 2, пользовательского интерфейса Chakra и React

В июне 2019 года был выпущен Prisma 2 Preview . Prisma 1 изменил способ взаимодействия с базами данных. Мы могли получить доступ к базам данных через простые методы и объекты JavaScript без необходимости писать запрос на самом языке баз данных. Prisma 1 действовала как абстракция перед базой данных, поэтому было проще создавать CRUD-приложения (создавать, читать, обновлять и удалять) .

Архитектура Prisma 1 выглядела так:

Призма 1 архитектура

Обратите внимание, что для доступа к базе данных необходим дополнительный сервер Prisma. Последняя версия не требует дополнительного сервера. Он называется Prisma Framework (ранее известный как Prisma 2), который полностью переписан для Prisma. Оригинальная Prisma была написана на Scala, поэтому ее нужно было запускать через JVM, и для ее работы требовался дополнительный сервер. У него также были проблемы с памятью.

Prisma Framework написан на Rust, поэтому объем памяти невелик. Кроме того, дополнительный сервер, необходимый для использования Prisma 1, теперь связан с серверной частью, поэтому вы можете использовать его как библиотеку.

Prisma Framework состоит из трех автономных инструментов:

  1. Photon : типобезопасный и автоматически сгенерированный клиент базы данных («замена ORM»)
  2. Lift : декларативная система миграции с настраиваемыми рабочими процессами
  3. Studio : IDE базы данных, которая предоставляет интерфейс администратора для поддержки различных рабочих процессов базы данных.

Призма 2 архитектура

Photon — это типизированный клиент базы данных, который заменяет традиционные ORM, а Lift позволяет нам декларативно создавать модели данных и выполнять миграцию базы данных. Studio позволяет нам выполнять операции с базой данных через красивый интерфейс администратора.

Зачем использовать Prisma?

Prisma устраняет сложность написания сложных запросов к базе данных и упрощает доступ к базе данных в приложении. Используя Prisma, вы можете изменять базовые базы данных без необходимости изменять каждый запрос. Это просто работает. В настоящее время он поддерживает только MySQL, SQLite и PostgreSQL.

Prisma обеспечивает безопасный тип доступа к базе данных, предоставляемый автоматически сгенерированным клиентом Prisma. Он имеет простой и мощный API для работы с реляционными данными и транзакциями. Это позволяет визуальное управление данными с Prisma Studio .

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

Prisma объединяет доступ к нескольким базам данных одновременно (в ближайшее время) и, следовательно, значительно снижает сложность в рабочих процессах между базами данных (в ближайшее время).

Он обеспечивает автоматическую миграцию базы данных (необязательно) через Lift на основе декларативной модели данных, выраженной с использованием языка определения схем GraphQL (SDL).

Предпосылки

Для этого урока вам необходимы базовые знания React . Вы также должны понимать React Hooks .

Поскольку этот учебник в основном ориентирован на Prisma, предполагается, что у вас уже есть практические знания о React и его основных понятиях.

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

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

Чтобы убедиться, что мы находимся на одной странице, вот версии, используемые в этом руководстве:

  • Узел v12.11.1
  • npm v6.11.3
  • NPX v6.11.3
  • пряжа v1.19.1
  • prisma2 v2.0.0-preview016.2
  • реагировать v16.11.0

Структура папок

Наша структура папок будет следующей:

 streaks-app/ client/ server/ 

client/ папка будет загружен из create-реагировать-приложение, а server/ папка будет загружен из prisma2 CLI.

Таким образом, вам просто нужно создать корневую папку с именем streaks-app/ и подпапки будут сгенерированы во время ее создания с соответствующими CLI. Создайте папку streaks-app/ и cd в нее следующим образом:

 $ mkdir streaks-app && cd $_ 

Серверная часть (сторона сервера)

Bootstrap новый проект Prisma 2

Вы можете загрузить новый проект Prisma 2 с помощью команды npx следующим образом:

 $ npx prisma2 init server 

Кроме того, вы можете установить prisma2 интерфейс prisma2 строки prisma2 и запустить команду init . Сделайте следующее:

 $ yarn global add prisma2 // or npm install --global prisma2 $ prisma2 init server 

Запустите интерактивный prisma2 init и выберите шаблон

Выберите следующее в интерактивных подсказках:

  1. Выберите стартовый комплект
  2. Выберите JavaScript
  3. Выберите GraphQL API
  4. Выберите SQLite

После завершения команда init создаст начальную настройку проекта на server/ папке.

Теперь откройте файл schema.prisma и замените его следующим:

 generator photon { provider = "photonjs" } datasource db { provider = "sqlite" url = "file:dev.db" } model Habit { id String @default(cuid()) @id name String @unique streak Int } 

schema.prisma содержит модель данных, а также параметры конфигурации.

Здесь мы указываем, что мы хотим подключиться к источнику данных SQLite с именем dev.db а также к целевым генераторам кода, таким как генератор photonjs .

Затем мы определяем модель данных Habit , которая состоит из id , name и streak .

id — это первичный ключ типа String со значением по умолчанию cuid () .

name имеет тип String , но с ограничением, что оно должно быть уникальным.

streak имеет тип Int .

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

 const { Photon } = require('@generated/photon') const photon = new Photon() async function main() { const workout = await photon.habits.create({ data: { name: 'Workout', streak: 49, }, }) const running = await photon.habits.create({ data: { name: 'Running', streak: 245, }, }) const cycling = await photon.habits.create({ data: { name: 'Cycling', streak: 77, }, }) const meditation = await photon.habits.create({ data: { name: 'Meditation', streak: 60, }, }) console.log({ workout, running, cycling, meditation, }) } main() .catch(e => console.error(e)) .finally(async () => { await photon.disconnect() }) 

Этот файл создает все виды новых привычек и добавляет его в базу данных SQLite.

Теперь зайдите в файл src/index.js и удалите его содержимое. Мы начнем добавлять контент с нуля.

Сначала выполните импорт необходимых пакетов и объявите некоторые константы:

 const { GraphQLServer } = require('graphql-yoga') const { makeSchema, objectType, queryType, mutationType, idArg, stringArg, } = require('nexus') const { Photon } = require('@generated/photon') const { nexusPrismaPlugin } = require('nexus-prisma') 

Теперь давайте объявим нашу модель Habit чуть ниже:

 const Habit = objectType({ name: 'Habit', definition(t) { t.model.id() t.model.name() t.model.streak() }, }) 

Мы используем objectType из пакета nexus для объявления Habit .

Параметр name должен быть таким же, как определено в файле schema.prisma .

Функция definition позволяет вам предоставлять определенный набор полей, где бы ни Habit . Здесь мы выставляем id , name и streak field.

Если мы представим только поля id и name , только те два будут отображаться везде, где есть ссылки на Habit .

Ниже вставьте константу Query :

 const Query = queryType({ definition(t) { t.crud.habit() t.crud.habits() // t.list.field('habits', { // type: 'Habit', // resolve: (_, _args, ctx) => { // return ctx.photon.habits.findMany() // }, // }) }, }) 

Мы используем queryType из пакета nexus для объявления Query .

Генератор фотонов генерирует API, который предоставляет функции CRUD в модели Habit . Это то, что позволяет нам предоставлять t.crud.habit() и t.crud.habits() .

t.crud.habit() позволяет нам запрашивать любую индивидуальную привычку по ее id или по name . t.crud.habits() просто возвращает все привычки.

Кроме того, t.crud.habits() также можно записать как:

 t.list.field('habits', { type: 'Habit', resolve: (_, _args, ctx) => { return ctx.photon.habits.findMany() }, }) 

И приведенный выше код, и t.crud.habits() дадут одинаковые результаты.

В приведенном выше коде мы создаем поле с именем habits . Тип возврата — Habit . Затем мы вызываем ctx.photon.habits.findMany() чтобы получить все привычки из нашей базы данных SQLite.

Обратите внимание, что имя свойства habits генерируется автоматически с использованием пакета множественного числа . Поэтому рекомендуется называть наши модели единичными, то есть Habit а не Habits .

Мы используем метод findMany по habits , который возвращает список объектов. Мы находим все habits как не findMany никаких условий внутри findMany . Вы можете узнать больше о том, как добавить условия внутри findMany здесь .

Ниже Query вставьте Mutation следующим образом:

 const Mutation = mutationType({ definition(t) { t.crud.createOneHabit({ alias: 'createHabit' }) t.crud.deleteOneHabit({ alias: 'deleteHabit' }) t.field('incrementStreak', { type: 'Habit', args: { name: stringArg(), }, resolve: async (_, { name }, ctx) => { const habit = await ctx.photon.habits.findOne({ where: { name, }, }) return ctx.photon.habits.update({ data: { streak: habit.streak + 1, }, where: { name, }, }) }, }) }, }) 

Mutation использует mutationType из пакета nexus .

CRUD API здесь предоставляет createOneHabit и deleteOneHabit .

createOneHabit , как следует из названия, создает привычку, а deleteOneHabit удаляет привычку.

createOneHabit называется псевдонимом createHabit , поэтому при вызове мутации мы вызываем createHabit а не createOneHabit .

Точно так же мы вызываем deleteHabit вместо deleteOneHabit .

Наконец, мы создаем поле с именем incrementStreak , которое увеличивает полосу привычки. Тип возврата — Habit . Он принимает name аргумента, указанное в поле args типа String . Этот аргумент получен в функции resolve в качестве второго аргумента. Мы находим эту habit , вызывая ctx.photon.habits.findOne() при передаче параметра name в предложении where . Нам нужно это, чтобы получить нашу текущую streak . Затем, наконец, мы обновляем habit , увеличивая streak на 1.

Под Mutation вставьте следующее:

 const photon = new Photon() new GraphQLServer({ schema: makeSchema({ types: [Query, Mutation, Habit], plugins: [nexusPrismaPlugin()], }), context: { photon }, }).start(() => console.log( ` Server ready at: http://localhost:4000\n See sample queries: http://pris.ly/e/js/graphql#5-using-the-graphql-api`, ), ) module.exports = { Habit } 

Мы используем метод makeSchema из пакета nexus , чтобы объединить нашу модель Habit и добавить Query and Mutation в массив types . Мы также добавляем nexusPrismaPlugin в наш массив plugins . Наконец, мы запускаем наш сервер по адресу localhost: 4000 . Порт 4000 является портом по умолчанию для graphql-yoga . Вы можете изменить порт, как предложено здесь .

Давайте запустим сервер сейчас. Но сначала нам нужно убедиться, что наши последние изменения схемы записаны в node_modules/@generated/photon . Это происходит при запуске prisma2 generate .

Если вы не установили prisma2 глобально, вам придется заменить prisma2 generate на ./node_modules/.bin/prisma2 generate . Затем нам нужно перенести нашу базу данных для создания таблиц.

Перенос базы данных с помощью Lift

Миграция вашей базы данных с помощью Lift выполняется в два этапа:

  1. Сохраните новую миграцию (миграции представлены в виде каталогов в файловой системе)
  2. Запустите миграцию (чтобы перенести схему базовой базы данных)

В командах CLI эти шаги могут быть выполнены следующим образом (шаги CLI находятся в процессе обновления, чтобы соответствовать):

 $ prisma2 lift save --name 'init' $ prisma2 lift up 

Опять же, вам придется заменить prisma2 на ./node_modules/.bin/prisma2 если вы не установили его глобально.

Теперь процесс миграции завершен. Мы успешно создали таблицу. Теперь мы можем заполнить нашу базу данных начальными значениями.

Продолжайте и выполните следующую команду в терминале:

 $ yarn seed 

Это seed.js нашу базу данных восемью привычками, как указано в нашем файле seed.js

Теперь вы можете запустить сервер, набрав:

 $ yarn dev 

Это запустит ваш сервер по адресу localhost: 4000 , который вы можете открыть и запросить все API, которые вы сделали.

Перечислите все привычки

 query habits { habits { id name streak } } 

Streaks App - список привычек

Найти привычку по имени

 query findHabitByName { habit(where: { name: "Workout" }) { id name streak } } 

Streaks App - Найти привычку по имени

Создать привычку

 mutation createHabit { createHabit(data: { name: "Swimming", streak: 10 }) { id name streak } } 

Streaks App - Создать привычку

Удалить привычку

 mutation deleteHabit { deleteHabit(where: { id: "ck2kinq2j0001xqv5ski2byvs" }) { id name streak } } 

Streaks App - Удалить привычку

Полоса приращения

 mutation incrementStreak { incrementStreak(name: "Workout") { streak } } 

Приложение Полосы - Полосы Полосы

Это все, что нам нужно для серверной части. Давайте работать над интерфейсом сейчас.

Front End (Клиентская сторона)

Bootstrap новый проект React

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

 $ npx create-react-app client 

В качестве альтернативы, вы можете установить create-react-app глобально и загрузить новый React Project, а затем сделать это:

 $ yarn global add create-react-app // or npm install --global create-react-app $ create-react-app client 

Это запускает новый проект React с использованием create-react-app .

Теперь перейдите в каталог client/ , запустите проект и введите:

 $ cd client $ yarn start 

Это запустит клиентскую часть на localhost: 3000 .

Теперь это должно выглядеть так:

Создать приложение React - Init

Теперь перейдите в каталог src/ и удалите ненужные файлы, такие как App.css , App.test.js , index.css и logo.svg :

 $ cd src $ rm App.css App.test.js index.css logo.svg 

Удалите ссылки на удаленные файлы из index.js и App.js

index.js теперь должен выглядеть так:

 import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; ReactDOM.render(<App />, document.getElementById("root")); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister(); 

И убедитесь, что ваш App.js выглядит так:

 import React from 'react' function App() { return <div>Streaks App</div> } export default App 

urql: универсальный язык запросов React

Идите вперед и сначала установите urql , который является альтернативой Apollo Client . Нам также нужно установить graphql , так как это равноправная зависимость от urql . Вы можете сделать это, набрав в терминале следующую команду:

 $ cd .. // come out of the 'src/' directory and into the 'client/' directory $ yarn add urql graphql 

Теперь подключите urql к urql части Prisma GraphQL, изменив App.js следующим образом:

 import React from 'react' import { createClient, Provider } from 'urql' const client = createClient({ url: 'http://localhost:4000/' }) const App = () => ( <Provider value={client}> <div>Streaks App</div> </Provider> ) export default App 

Здесь мы используем urql createClient , передавая наш внутренний urlurl а затем передавая его в качестве value prop компоненту Provider . Это позволяет нам запрашивать, изменять или подписываться на любой компонент, который является дочерним компонентом компонента Provider .

Теперь это должно выглядеть так:

Streaks App - Init

Чакра UI

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

 $ yarn add @chakra-ui/core @emotion/core @emotion/styled emotion-theming 

Чакра использует Emotion под капотом, поэтому нам нужно установить его и его зависимости от сверстников.

В этом уроке нам также понадобится graphql-tag для синтаксического анализа наших запросов GraphQL, response react-icons для отображения красивых значков, @seznam/compose-react-refs для составления нескольких @seznam/compose-react-refs -hook-form для создания форм.

Обязательно установите их также, набрав в терминале следующее:

 $ yarn add graphql-tag react-icons @seznam/compose-react-refs react-hook-form 

Теперь перейдите и измените App.js на следующее:

 import { Text, ThemeProvider } from '@chakra-ui/core' import React from 'react' import { createClient, Provider } from 'urql' const client = createClient({ url: 'http://localhost:4000/' }) const App = () => ( <Provider value={client}> <ThemeProvider> <> <Text fontSize='5xl' textAlign='center'> Streaks App </Text> </> </ThemeProvider> </Provider> ) export default App 

Мы импортировали Text и ThemeProvider из @chakra-ui/core .

Text компонент используется для визуализации текста и абзацев в интерфейсе. По умолчанию он отображает <p> .

Мы делаем наши Text компоненты fontSize как 5xl и выравниваем его по центру.

Мы также обертываем все это в ThemeProvider . ThemeProvider позволяет нам добавлять тему в наше приложение, передавая объект theme в качестве реквизита. Пользовательский интерфейс Chakra поставляется с темой по умолчанию, которую мы видим, если мы ThemeProvider поверх наших компонентов. Макет теперь выглядит так:

Streaks App - чакра пользовательского интерфейса Init

Попробуйте удалить ThemeProvider чтобы увидеть, как он влияет на макет. Это выглядит так:

Streaks App - Чакра UI Init Удалить ThemeProvider

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

Теперь graphql создадим components и папку graphql :

 $ mkdir components graphql 

Зайдите в папку graphql и создайте файлы с именами createHabit.js , deleteHabit.js , incrementStreak.js , listAllHabits.js и index.js .

 $ cd graphql $ touch createHabit.js deleteHabit.js incrementStreak.js listAllHabits.js index.js 

Список всех привычек запроса

Откройте listAllHabits.js и вставьте следующее:

 import gql from 'graphql-tag' export const LIST_ALL_HABITS_QUERY = gql` query listAllHabits { habits { id name streak } } ` 

Обратите внимание, что приведенный выше query аналогичен тому, что мы ввели в редакторе GraphiQL. Вот как используется GraphQL. Сначала мы набираем query или mutation в редакторе GraphiQL и видим, дает ли он данные, которые нам нужны, а затем просто копируем и вставляем их в приложение.

Создать привычку мутации

Внутри createHabit.js вставьте следующее:

 import gql from 'graphql-tag' export const CREATE_HABIT_MUTATION = gql` mutation createHabit($name: String!, $streak: Int!) { createHabit(data: { name: $name, streak: $streak }) { id name streak } } ` 

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

Удалить привычку мутации

Вставьте следующее в deleteHabit.js :

 import gql from 'graphql-tag' export const DELETE_HABIT_MUTATION = gql` mutation deleteHabit($id: ID!) { deleteHabit(where: { id: $id }) { id name streak } } ` 

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

Инкрементная мутация

Вставьте следующее в incrementStreak.js :

 import gql from 'graphql-tag' export const INCREMENT_STREAK_MUTATION = gql` mutation incrementStreak($name: String) { incrementStreak(name: $name) { streak } } ` 

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

Наконец, чтобы упростить import всего из одного файла, вставьте в index.js следующее:

 export * from './createHabit' export * from './deleteHabit' export * from './incrementStreak' export * from './listAllHabits' 

Это позволяет нам import из одного файла вместо четырех разных файлов. Это полезно, когда у нас есть десятки queries и mutations .

Теперь перейдите в каталог components/ и создайте файлы с именами CreateHabit.js , DeleteHabit.js , Habit.js , ListAllHabits.js и index.js .

 $ cd ../components/ $ touch CreateHabit.js DeleteHabit.js Habit.js ListAllHabits.js index.js 

Мы коснемся остальных файлов позже в этом руководстве, но сейчас откройте index.js и вставьте следующее:

 export * from './Common/Error' export * from './Common/Loading' export * from './CreateHabit' export * from './DeleteHabit' export * from './Habit' export * from './ListAllHabits' 

Теперь создайте папку Common/ и внутри Error.js создайте Error.js и Error.js :

 $ mkdir Common && cd $_ $ touch Loading.js Error.js 

cd $_ позволяет нам войти в Common каталог сразу после его создания. Затем мы создаем Error.js и Error.js внутри него.

Теперь создайте папку utils/ внутри каталога src/ с двумя файлами внутри нее — getIcon.js и index.js :

 $ cd ../../ $ mkdir utils/ && cd $_ $ touch getIcon.js index.js 

Создавайте иконки для привычек

Теперь откройте getIcon.js и вставьте следующее:

 import { AiOutlineQuestion } from 'react-icons/ai' import { FaCode, FaRunning, FaSwimmer } from 'react-icons/fa' import { FiPhoneCall } from 'react-icons/fi' import { GiCycling, GiMeditation, GiMuscleUp, GiTennisRacket, } from 'react-icons/gi' import { MdSmokeFree } from 'react-icons/md' const icons = [ { keywords: ['call', 'phone'], pic: FiPhoneCall, }, { keywords: ['workout', 'muscle', 'body-building', 'body building'], pic: GiMuscleUp, }, { keywords: ['cycling', 'cycle'], pic: GiCycling, }, { keywords: ['running', 'run'], pic: FaRunning, }, { keywords: ['swimming', 'swim'], pic: FaSwimmer, }, { keywords: ['racket', 'tennis', 'badminton'], pic: GiTennisRacket, }, { keywords: [ 'smoke', 'smoking', 'no smoking', 'no-smoking', 'smoke free', 'no smoke', ], pic: MdSmokeFree, }, { keywords: ['code', 'code everyday', 'program', 'programming'], pic: FaCode, }, { keywords: ['meditate', 'meditation'], pic: GiMeditation, }, ] export const getIcon = name => { let icon = AiOutlineQuestion for (let i = 0; i < icons.length; i++) { const { keywords, pic } = icons[i] const lowerCaseName = name.toLowerCase() const doesKeywordExistInName = keywords.some(keyword => lowerCaseName.includes(keyword), ) if (doesKeywordExistInName) { icon = pic break } } return icon } 

Это вспомогательный файл, который содержит одну функцию с именем getIcon . Он принимает имя привычки и возвращает соответствующий значок. Чтобы добавить больше значков, вам нужно добавить объект в массив icons с соответствующими keywords и pic , которые можно импортировать из реагирующих значков .

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

Откройте index.js и вставьте следующую index.js :

 export * from './getIcon' 

Идите вперед, откройте Loading.js и вставьте следующее:

 import { Flex, Spinner } from '@chakra-ui/core' import React from 'react' export const Loading = () => ( <Flex justify='center' flexWrap='wrap'> <Spinner thickness='4px' speed='0.65s' emptyColor='gray.200' color='blue.800' size='xl' /> </Flex> ) 

Мы показываем хороший Spinner который мы импортировали из библиотеки чакр UI. Мы заключаем его в компонент Flex который позволяет легко применять Flexbox без необходимости написания CSS. На мой взгляд, Chakra позволяет легко создавать красивые приложения без необходимости написания собственного CSS.

Теперь откройте Error.js и вставьте следующее:

 import { Alert, AlertDescription, AlertIcon, AlertTitle, Flex, } from '@chakra-ui/core' import React from 'react' export const Error = () => ( <Flex justify='center' flexWrap='wrap'> <Alert status='error'> <AlertIcon /> <AlertTitle mr={2}>Whoops,</AlertTitle> <AlertDescription> there has been an error. Please try again later! </AlertDescription> </Alert> </Flex> ) 

Здесь мы показываем окно с ошибкой. Вы можете легко найти приведенный выше код в документации по Chakra UI. Здесь нет ракетостроения. Просто старая копия-паста.

Показать одну привычку

Откройте Habit.js и вставьте следующее:

 import { Badge, Box, Flex, Text } from '@chakra-ui/core' import React from 'react' import { useMutation } from 'urql' import { INCREMENT_STREAK_MUTATION } from '../graphql/index' import { getIcon } from '../utils/index' const colors = [ 'tomato', 'green.400', 'yellow.300', 'cornflowerblue', 'antiquewhite', 'aquamarine', 'lightpink', 'navajowhite', 'red.500', 'lightcoral' ] export const Habit = ({ index, habit }) => { const { id, name, streak } = habit const bgColor = colors[index % colors.length] const [res, executeMutation] = useMutation(INCREMENT_STREAK_MUTATION) // eslint-disable-line no-unused-vars return ( <Flex align='center' justify='flex-end' direction='column' bg={bgColor} width='300px' height='300px' borderRadius='40px' margin='16px' padding='16px' > <Box as={getIcon(name)} size='144px' /> <Text fontWeight='hairline' fontSize='3xl' textAlign='center'> {name} <Badge as='span' fontWeight='hairline' fontSize='xl' rounded='full' mx='2' px='3' textTransform='lowercase' cursor='pointer' onClick={() => executeMutation({ name })} > {streak} </Badge> </Text> </Flex> ) } 

Компонент Habit отображает одну habit со значком streak . Требуется index и habit . Мы используем index чтобы вращать фоновые цвета habit из массива colors . После отображения последнего цвета он вернется к первому.

Внутри компонента Flex мы отображаем icon , вызывая компонент Box с пропуском as . Пропорка as используется для замены элемента div по умолчанию компонента Box на все, что указано в пропе as . Таким образом, в этом случае мы заменим его на возвращаемое значение getIcon , которое является icon getIcon react-icons .

Далее мы отображаем name внутри компонента Text и оборачиваем streak компонентом Badge . При нажатии на streak вызывается INCREMENT_STREAK_MUTATION , который мы определили выше с useMutation функции useMutation . Мы передаем соответствующее name привычки функции, чтобы мы могли увеличить эту конкретную привычку.

Показать список привычек

Откройте ListAllHabits.js и вставьте следующее:

 import { Flex, Text } from '@chakra-ui/core' import React from 'react' import { useQuery } from 'urql' import { LIST_ALL_HABITS_QUERY } from '../graphql/index' import { Error, Habit, Loading } from './index' export const ListAllHabits = () => { const [{ fetching, error, data }] = useQuery({ query: LIST_ALL_HABITS_QUERY }) if (fetching) return <Loading /> if (error) return <Error /> const noHabits = !data.habits.length return ( <Flex justify='center' align='center' flexWrap='wrap' flexDirection={noHabits ? 'column' : 'row'} > {noHabits && ( <Text fontWeight='bold' fontSize='3xl' color='tomato'> You currently track 0 habits. Add one. </Text> )} {data.habits.map((habit, i) => ( <Habit key={habit.id} index={i} habit={habit} /> ))} </Flex> ) } 

Здесь мы выбираем все привычки, вызывая urql useQuery , передавая LIST_ALL_HABITS_QUERY . Это возвращает fetching , error и data .

Когда fetching true , мы показываем компонент Loading , который показывает Spinner .

Когда error равен true , мы отображаем компонент Error , который отображает Alert .

Позже мы проверяем, есть ли какие-либо habits , и если нет никаких habits то мы показываем, что You currently track 0 habits. Add one. You currently track 0 habits. Add one.

Streaks App - Нет привычек

Если у нас есть какие-либо habits , мы показываем их так:

Streaks App - список привычек

Попробуйте нажать на значок streak чтобы увидеть его увеличение.

Удалить привычку

Теперь откройте DeleteHabit.js и вставьте следующее:

 import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, IconButton, } from '@chakra-ui/core' import React from 'react' import { useMutation } from 'urql' import { DELETE_HABIT_MUTATION } from '../graphql/index' export const DeleteHabit = ({ id, name }) => { const [isOpen, setIsOpen] = React.useState() const onClose = () => setIsOpen(false) const cancelRef = React.useRef() const [res, executeMutation] = useMutation(DELETE_HABIT_MUTATION) // eslint-disable-line no-unused-vars const deleteHabit = () => { executeMutation({ id }) onClose() } return ( <> <IconButton variantColor='red' border='1px solid white' aria-label='Delete Habit' size='md' icon='delete' cursor='pointer' onClick={() => setIsOpen(true)} /> <AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose} > <AlertDialogOverlay /> <AlertDialogContent> <AlertDialogHeader fontSize='lg' fontWeight='bold'> Delete “{name}” Habit </AlertDialogHeader> <AlertDialogBody> Are you sure? You can't undo this action afterwards. </AlertDialogBody> <AlertDialogFooter> <Button ref={cancelRef} onClick={onClose}> Cancel </Button> <Button variantColor='red' onClick={deleteHabit} ml={3}> Delete </Button> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </> ) } 

Большая часть этого кода взята из AlertDialog пользовательского интерфейса Chakra . Основная цель этого компонента — показать значок trash при щелчке, оповещает модал двумя кнопками « Cancel и « Delete . При нажатии кнопки « Cancel вызывается функция onClose , которая приводит к исчезновению модальной линии, а при нажатии кнопки « Delete вызывается функция deleteHabit .

Функция deleteHabit вызывает DELETE_HABIT_MUTATION , передавая ей id она получает от родительского компонента, и закрывает модальное состояние, вызывая onClose .

Теперь снова откройте Habit.js и добавьте следующий импорт в начало:

 import { DeleteHabit } from './index' 

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

 <DeleteHabit id={id} name={name} /> 

Весь файл Habit.js теперь должен выглядеть так:

 import { Badge, Box, Flex, Text } from '@chakra-ui/core' import React from 'react' import { useMutation } from 'urql' import { INCREMENT_STREAK_MUTATION } from '../graphql/index' import { getIcon } from '../utils/index' import { DeleteHabit } from './index' const colors = [ 'tomato', 'green.400', 'yellow.300', 'cornflowerblue', 'antiquewhite', 'aquamarine', 'lightpink', 'navajowhite', 'red.500', 'lightcoral' ] export const Habit = ({ index, habit }) => { const { id, name, streak } = habit const bgColor = colors[index % colors.length] const [res, executeMutation] = useMutation(INCREMENT_STREAK_MUTATION) // eslint-disable-line no-unused-vars return ( <Flex align='center' justify='flex-end' direction='column' bg={bgColor} width='300px' height='300px' borderRadius='40px' margin='16px' padding='16px' > <Box as={getIcon(name)} size='144px' /> <Text fontWeight='hairline' fontSize='3xl' textAlign='center'> {name} <Badge as='span' fontWeight='hairline' fontSize='xl' rounded='full' mx='2' px='3' textTransform='lowercase' cursor='pointer' onClick={() => executeMutation({ name })} > {streak} </Badge> <DeleteHabit id={id} name={name} /> </Text> </Flex> ) } 

Теперь это должно выглядеть так:

Streaks App - Удалить привычку

Теперь попробуйте щелкнуть значок trash на любой из привычек. Это должно открыть модал следующим образом:

Streaks App - Удалить модальное шоу Привычка

Если вы нажмете « Отмена» , он просто закроет модальное окно. Если вы нажмете « Удалить» , привычка будет удалена из пользовательского интерфейса и самой базы данных Prisma следующим образом:

Streaks App - удаление привычки тренировки

Создать привычку

Теперь давайте откроем CreateHabit.js и вставим следующее:

 import { Button, Flex, FormControl, FormLabel, Icon, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure, } from '@chakra-ui/core' import composeRefs from '@seznam/compose-react-refs' import React, { useRef } from 'react' import useForm from 'react-hook-form' import { useMutation } from 'urql' import { CREATE_HABIT_MUTATION } from '../graphql/index' export const CreateHabit = () => { const { handleSubmit, register } = useForm() const { isOpen, onOpen, onClose } = useDisclosure() const [res, executeMutation] = useMutation(CREATE_HABIT_MUTATION) // eslint-disable-line no-unused-vars const initialRef = useRef() const finalRef = useRef() const onSubmit = (values, e) => { const { name, streak } = values executeMutation({ name, streak: +streak, }) e.target.reset() onClose() } return ( <Flex width='300px' height='300px' borderRadius='40px' margin='16px' padding='16px' justify='center' flexWrap='wrap' > <Icon name='small-add' onClick={onOpen} fontSize='300px' cursor='pointer' /> <Modal initialFocusRef={initialRef} finalFocusRef={finalRef} isOpen={isOpen} onClose={onClose} > <ModalOverlay /> <ModalContent> <ModalHeader>Create Habit</ModalHeader> <ModalCloseButton /> <form onSubmit={handleSubmit(onSubmit)}> <ModalBody pb={6}> <FormControl> <FormLabel htmlFor='name'>Habit name</FormLabel> <Input name='name' ref={composeRefs(initialRef, register)} placeholder='Enter your habit' width='90%' /> </FormControl> <FormControl mt={4}> <FormLabel htmlFor='streak'>Streak</FormLabel> <Input name='streak' type='number' placeholder='Enter your streak' width='90%' ref={register} /> </FormControl> </ModalBody> <ModalFooter> <Button type='submit' rounded='md' bg='green.500' color='white' mr={3} > Save </Button> <Button onClick={onClose}>Cancel</Button> </ModalFooter> </form> </ModalContent> </Modal> </Flex> ) } 

Опять же, большая часть этого контента скопирована из FormControl пользовательского интерфейса Chakra . Здесь мы показываем значок + пользователю, который мы вводим из собственного компонента Icon чакры.

Когда щелкает значок + , мы открываем модальную модель, которая использует реагирующую форму .

React Hook Form — это самый простой способ создания форм с помощью Hooks. Нам просто нужно передать в register ref на input мы хотим отслеживать. Мы получаем register когда вызываем хук useForm из useForm react-hook-form . Мы также получаем handleSubmit , который нам нужно передать компоненту form . Нам нужно передать handleSubmit функцию. В нашем случае мы передаем onSubmit и первыми values параметров этой функции являются те значения, которые мы получаем, которые onSubmit пользователь.

Здесь следует отметить одну важную вещь: мы используем composeRefs из @seznam/compose-react-refs для составления нескольких ссылок. Это необходимо, потому что нам нужно предоставить ссылку на регистрацию, чтобы зарегистрировать нашу форму React Hook Form и отслеживать значение. И второй ref initialRef необходим, потому что нам нужно сосредоточиться на первом вводе, как только появится всплывающее окно. Это необходимо для доступности, а также для тех, кто использует программы чтения с экрана.

Наконец, когда мы вызываем onSubmit мы проверяем, не является ли он пустым, а затем вызываем мутацию с двумя параметрами name и streak . +streak String означает, что String приведена в Number . По сути, все значения, возвращаемые из React Hook Form, являются strings , но в нашем бэкэнде мы ожидаем number .

Наконец, мы reset форму, чтобы очистить все значения и состояния ввода. И тогда мы закрываем модальные.

Теперь идем дальше и импортируем CreateHabit в ListAllHabits.js вверху:

 import { CreateHabit, Error, Habit, Loading } from './index' 

Кроме того, обязательно Array.map() его чуть выше, где вы перечислите все привычки, используя Array.map() следующим образом:

 <CreateHabit /> 

Файл ListAllHabits.js должен теперь выглядеть так:

 import { Flex, Text } from '@chakra-ui/core' import React from 'react' import { useQuery } from 'urql' import { LIST_ALL_HABITS_QUERY } from '../graphql/index' import { CreateHabit, Error, Habit, Loading } from './index' export const ListAllHabits = () => { const [{ fetching, error, data }] = useQuery({ query: LIST_ALL_HABITS_QUERY }) if (fetching) return <Loading /> if (error) return <Error /> const noHabits = !data.habits.length return ( <Flex justify='center' align='center' flexWrap='wrap' flexDirection={noHabits ? 'column' : 'row'} > {noHabits && ( <Text fontWeight='bold' fontSize='3xl' color='tomato'> You currently track 0 habits. Add one. </Text> )} <CreateHabit /> {data.habits.map((habit, i) => ( <Habit key={habit.id} index={i} habit={habit} /> ))} </Flex> ) } 

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

Streaks App - знак плюс

Теперь нажмите знак + и добавьте нашу привычку Workout с 50 полосами, которые мы удалили.

Streaks App - Добавить привычку тренировки

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

Streaks App - Workout Habit 50 Streak

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

Приложение Treaks - Complete

Вывод

В этом уроке мы с нуля создали полное приложение для отслеживания привычек «Streaks». Мы использовали Chakra UI в качестве нашей библиотеки компонентов React для быстрого создания красивого и доступного приложения. Пользовательский интерфейс Chakra помог нам создавать оповещения, модалы и спиннеры, просто добавив встроенные строительные блоки, чтобы мы могли сосредоточиться на написании логики, а не на написании CSS.

Мы использовали React Hooks Form для создания простых и легких форм с помощью React Hooks. Это позволило нам сохранять наши формы СУХИМ без написания большого количества кода.

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

Пока Prisma Framework находится в бета-версии , вы можете развлечься в своих сторонних проектах. Это скоро выйдет, так что следите за обновлениями.

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