В июне 2019 года был выпущен Prisma 2 Preview . Prisma 1 изменил способ взаимодействия с базами данных. Мы могли получить доступ к базам данных через простые методы и объекты JavaScript без необходимости писать запрос на самом языке баз данных. Prisma 1 действовала как абстракция перед базой данных, поэтому было проще создавать CRUD-приложения (создавать, читать, обновлять и удалять) .
Архитектура Prisma 1 выглядела так:
Обратите внимание, что для доступа к базе данных необходим дополнительный сервер Prisma. Последняя версия не требует дополнительного сервера. Он называется Prisma Framework (ранее известный как Prisma 2), который полностью переписан для Prisma. Оригинальная Prisma была написана на Scala, поэтому ее нужно было запускать через JVM, и для ее работы требовался дополнительный сервер. У него также были проблемы с памятью.
Prisma Framework написан на Rust, поэтому объем памяти невелик. Кроме того, дополнительный сервер, необходимый для использования Prisma 1, теперь связан с серверной частью, поэтому вы можете использовать его как библиотеку.
Prisma Framework состоит из трех автономных инструментов:
- Photon : типобезопасный и автоматически сгенерированный клиент базы данных («замена ORM»)
- Lift : декларативная система миграции с настраиваемыми рабочими процессами
- Studio : IDE базы данных, которая предоставляет интерфейс администратора для поддержки различных рабочих процессов базы данных.
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
и выберите шаблон
Выберите следующее в интерактивных подсказках:
- Выберите стартовый комплект
- Выберите JavaScript
- Выберите GraphQL API
- Выберите 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 выполняется в два этапа:
- Сохраните новую миграцию (миграции представлены в виде каталогов в файловой системе)
- Запустите миграцию (чтобы перенести схему базовой базы данных)
В командах 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 } }
Найти привычку по имени
query findHabitByName { habit(where: { name: "Workout" }) { id name streak } }
Создать привычку
mutation createHabit { createHabit(data: { name: "Swimming", streak: 10 }) { id name streak } }
Удалить привычку
mutation deleteHabit { deleteHabit(where: { id: "ck2kinq2j0001xqv5ski2byvs" }) { id name streak } }
Полоса приращения
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 .
Теперь это должно выглядеть так:
Теперь перейдите в каталог 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
, передавая наш внутренний url
— url
а затем передавая его в качестве value
prop компоненту Provider
. Это позволяет нам запрашивать, изменять или подписываться на любой компонент, который является дочерним компонентом компонента Provider
.
Теперь это должно выглядеть так:
Чакра 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
поверх наших компонентов. Макет теперь выглядит так:
Попробуйте удалить 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.
Если у нас есть какие-либо habits
, мы показываем их так:
Попробуйте нажать на значок 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> ) }
Теперь это должно выглядеть так:
Теперь попробуйте щелкнуть значок trash
на любой из привычек. Это должно открыть модал следующим образом:
Если вы нажмете « Отмена» , он просто закроет модальное окно. Если вы нажмете « Удалить» , привычка будет удалена из пользовательского интерфейса и самой базы данных Prisma следующим образом:
Создать привычку
Теперь давайте откроем 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> ) }
Теперь он должен показывать знак +
следующим образом:
Теперь нажмите знак +
и добавьте нашу привычку Workout
с 50
полосами, которые мы удалили.
Как только вы нажмете Save
, обратите внимание, что он сразу же добавляется
Вы можете добавить кучу других привычек, которые вы хотите отслеживать. После добавления нескольких привычек, теперь это выглядит так:
Вывод
В этом уроке мы с нуля создали полное приложение для отслеживания привычек «Streaks». Мы использовали Chakra UI в качестве нашей библиотеки компонентов React для быстрого создания красивого и доступного приложения. Пользовательский интерфейс Chakra помог нам создавать оповещения, модалы и спиннеры, просто добавив встроенные строительные блоки, чтобы мы могли сосредоточиться на написании логики, а не на написании CSS.
Мы использовали React Hooks Form для создания простых и легких форм с помощью React Hooks. Это позволило нам сохранять наши формы СУХИМ без написания большого количества кода.
В нашем бэкэнде мы использовали Prisma Framework . Мы использовали собственный Photon от Prisma для декларативного создания моделей данных и Lift для выполнения миграции баз данных. Prisma упрощает запрос к базе данных, используя статическую типизацию, что позволяет нам с уверенностью кодировать. Встроенное автозаполнение позволяет нам писать приложения с молниеносной скоростью.
Пока Prisma Framework находится в бета-версии , вы можете развлечься в своих сторонних проектах. Это скоро выйдет, так что следите за обновлениями.
Теперь продолжайте уверенно создавать свои собственные приложения с полным стеком.