Давайте подробнее рассмотрим, как мы отделяем презентацию от логики.
Наши презентационные компоненты представляют собой библиотеку компонентов, которая разработана в сборнике рассказов и совершенно не знает логики приложения.
Каждый компонент презентации состоит из трех файлов. Файл компонента, файл сборника рассказов и файл стилей. Компоненты презентации — это единственные компоненты, которым разрешены html и css. Они могут содержать базовое локальное состояние, но никогда не будут выполнять какие-либо операции CRUD с любым API.
Ниже приведен пример компонента Avatar.
src/presentational/navigation/Avatar/Avatar.tsx
import React from 'react' import { useAvatarStyles } from './Avatar.styles' import { Avatar as MuiAvatar } from '@material-ui/core' export interface IAvatarProps { title: string avatar: string testid?: string } export const Avatar: React.FunctionComponent<IAvatarProps> = ({ title, testid, avatar }) => { const classes = useAvatarStyles() return ( <div data-testid={testid} className={classes.root}> <MuiAvatar avatar={avatar} className={classes.avatar}>{title}</MuiAvatar> </div> ) }
src/presentational/navigation/Avatar/Avatar.styles.tsx
import { makeStyles } from '@material-ui/core/styles' import { fluidHeight } from '../../../utils/fluidHeight' export const useAvatarStyles = makeStyles(() => ({ root: { ...fluidHeight(165, 2), paddingTop: 5, display: 'flex', justifyContent: 'center', }, avatar: { width: '122px', height: '122px', }, }), { name: 'avatar' } )
src/presentational/navigation/Avatar/Avatar.stories.tsx
import React from 'react' import { Story, Meta } from '@storybook/react/types-6-0' import { Avatar, IAvatarProps } from './Avatar' export default { title: 'navigation/Avatar', component: Avatar, } as Meta const Template: Story<IAvatarProps> = (args) => <Avatar {...args} /> export const Default = Template.bind({}) Default.args = { title: 'Some Title', }
Наше изображение аватара исходит из CMS, и, поскольку мы хотим, чтобы наши презентационные компоненты были чистыми, мы извлекаем данные из их компонентов-контейнеров, которые мы называем компонентами «Просмотр».
Компоненты просмотра никогда не имеют html или css, и вся логика находится в файле ловушки, чтобы упростить тестирование. Потому что тогда мы можем протестировать хук с помощью react-hooks-testing-library, а не тестировать весь компонент.
src/views/navigation/AvatarView/AvatarView.tsx
import React from 'react' import { Avatar } from '../../../presentational/navigation/Avatar/Avatar' import { useAvatar } from './useAvatarView' export const AvatarView: React.FunctionComponent = () => { const { ...props } = useAvatar() return <Avatar testid={'user-avatar'} {...props} /> }
src/views/navigation/AvatarView/useAvatarView.tsx
import { useUser } from '../../../context/UserContextProvider' export const useAvatar = () => { const { avatar, isLoading, error } = getAvatar() const { user } = useUser() return { isLoading, error, avatar, title: user.name } }
Все операции чтения выполняются в файлах с префиксом «get» и находятся в папке запросов.
источник/запросы/getAvatar.ts
import useSWR from 'swr' import { swrKeys } from '../constants/swrKeys' import { IAvatarDTO } from '../pages/api/avatar' export function getAvatar() { const swr = useSWR<IAlertsDTO>(swrKeys.avatar) return { ...swr, avatar: swr.data?.avatar, isLoading: !swr.data && !swr.error, } }
В веб-разработке мы обычно читаем гораздо больше, чем выполняем операции записи, поэтому мы разделяем наши запросы в отдельную папку.
Каждое чтение получает свой собственный файл, он никогда не загромождается.