Skip to content
Π“Π»Π°Π²Ρ‹

🏭 Π€Π°Π±Ρ€ΠΈΠΊΠΈ ​

ΠžΡ‡Π΅Π½ΡŒ часто Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°Ρ… трСбуСтся ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ высокоуровнСвыС Π±Π»ΠΎΠΊΠΈ бизнСс-Π»ΠΎΠ³ΠΈΠΊΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡ‚ΡŒΡΡ нСсколько Ρ€Π°Π·.

НапримСр:

  • grid-списки с Ρ‚ΠΎΠ½ΠΊΠΎΠΉ настройкой ΠΏΠΎΠ»Π΅ΠΉ, Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΠ΅ΠΉ, Π»ΠΎΠ³ΠΈΠΊΠΎΠΉ ΠΏΠΎΠ΄Π³Ρ€ΡƒΠ·ΠΊΠΈ Π΄Π°Π½Π½Ρ‹Ρ….
  • ΠΌΠΎΠ΄Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΎΠΊΠ½Π° с Π±ΠΎΠΉΠ»Π΅Ρ€ΠΏΠ»Π΅ΠΉΡ‚-Π»ΠΎΠ³ΠΈΠΊΠΎΠΉ открытия/закрытия.
  • Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΡ ΠΈ Π΅Π΅ настройки, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π² ΠΈΡ‚ΠΎΠ³Π΅ ΡΠΎΠ±ΠΈΡ€Π°ΡŽΡ‚ΡΡ Π² ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ эффСкта получСния Π΄Π°Π½Π½Ρ‹Ρ….

Π€Π°Π±Ρ€ΠΈΠΊΠΈ ΠΏΠΎΠΌΠΎΠ³Π°ΡŽΡ‚ ΠΈΠ·Π±Π°Π²ΠΈΡ‚ΡŒΡΡ ΠΎΡ‚ дублирования Π² ΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ… кСйсах.

Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ​

ΠœΡ‹ Π΄Π΅Π»ΠΈΠΌ Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ Π½Π° Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ / Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ view.

Π€Π°Π±Ρ€ΠΈΠΊΠΈ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ Ρ€Π°ΡΠΏΠΎΠ»Π°Π³Π°ΡŽΡ‚ΡΡ Π² ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Ρ… дирСкториях:

ts
./feature/model/factory/createSmthModel.ts
./feature/view/factory/createSmthView.tsx

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ ​

НиТС ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ-ΠΌΠΎΠ΄Π΅Π»ΠΈ для создания ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π° Π²Π΅Ρ€ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ дСйствия ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π² систСмС Ρ‡Π΅Ρ€Π΅Π· Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ ΠΈ присланный ΠΊΠΎΠ΄:

tsx
// @/features/agreement-compose/model/factory/createAgreementFactoryModel.ts

import { Method, requestFx } from '@/dal'
import { requiredValidator } from '@/lib/form-validators'
import { showNotification } from '@/ui/notifications'
import { attachWrapper } from '@42px/effector-extra'
import { AxiosError, AxiosResponse } from 'axios'
import { Domain, guard, sample } from 'effector'
import { createForm } from 'effector-forms'
import {
  AgreementStep,
  ValidateAgreementCodeFxError,
  ValidateAgreementCodeFxPayload,
} from './types'

type CreateAgreementFactoryModelPayload = {
  d: Domain
}
export const createAgreementFactoryModel = ({
  d,
}: CreateAgreementFactoryModelPayload) => {
  const $agreementStep = d.store(AgreementStep.phone)

  const validateAgreements = d.event()
  const validateAgreementsCode = d.event()
  const validateAgreementFx = attachWrapper({
    effect: requestFx,
    mapParams: (phone: string) => ({
      method: Method.post,
      body: {
        phone,
      },
      url: '/user/send-phone',
    }),
    mapResult: ({ result }: { result: AxiosResponse<void> }) => result.data,
    mapError: ({ error }: { error: AxiosError }) =>
      error.response?.data as ValidateAgreementCodeFxError,
  })

  const validateAgreementCodeFx = attachWrapper({
    effect: requestFx,
    mapParams: (payload: ValidateAgreementCodeFxPayload) => ({
      method: Method.post,
      body: payload,
      url: '/user/accept-code',
    }),
    mapResult: ({ result }: { result: AxiosResponse<void> }) => result.data,
    mapError: ({ error }: { error: AxiosError }) =>
      error.response?.data as ValidateAgreementCodeFxError,
  })

  const agreementForm = createForm({
    domain: d,
    fields: {
      phone: {
        init: '',
        rules: [requiredValidator],
      },
      code: {
        init: '',
        rules: [requiredValidator],
      },
    },
    validateOn: ['submit'],
  })

  $agreementStep.on(validateAgreementFx.done, () => AgreementStep.code)

  sample({
    clock: validateAgreements,
    source: agreementForm.$values,
    fn: ({ phone }) => phone,
    target: validateAgreementFx,
  })

  sample({
    clock: validateAgreementsCode,
    source: agreementForm.$values,
    target: validateAgreementCodeFx,
  })

  sample({
    clock: guard({
      clock: validateAgreementFx.failData,
      filter: (d) => d.message.type === 'errorPhone',
    }),
    fn: ({ message }) => ({
      rule: 'errorPhone',
      errorText: message.text,
    }),
    target: agreementForm.fields.phone.addError,
  })

  sample({
    clock: guard({
      clock: validateAgreementCodeFx.failData,
      filter: (d) => d.message.type === 'errorCode',
    }),
    fn: ({ message }) => ({
      rule: 'errorCode',
      errorText: message.text,
    }),
    target: agreementForm.fields.code.addError,
  })

  sample({
    clock: validateAgreementFx.done,
    fn: () => ({
      message: 'Код Π±Ρ‹Π» ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ ΠΏΠΎ БМБ',
    }),
    target: showNotification,
  })

  /*
        Π—Π΄Π΅ΡΡŒ ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ, ΠΊΠ°ΠΊΠΈΠ΅ ΡŽΠ½ΠΈΡ‚Ρ‹ ΠΌΡ‹ Ρ…ΠΎΡ‚ΠΈΠΌ ΠΏΠΎΡˆΠ°Ρ€ΠΈΡ‚ΡŒ Π²Π½Π΅ΡˆΠ½Π΅ΠΌΡƒ ΠΌΠΈΡ€Ρƒ
    */
  return {
    validateAgreementCodeFx,
    validateAgreementFx,
    validateAgreements,
    validateAgreementsCode,
    $agreementStep,
    agreementForm,
  }
}

НиТС ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½Π° Ρ„Π°Π±Ρ€ΠΈΠΊΠ°, ΡΠΎΠ·Π΄Π°ΡŽΡ‰Π°Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ ΠΏΠΎΠ΄ модСль ΠΈΠ· ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π° Π²Ρ‹ΡˆΠ΅:

tsx
import React from 'react'
import styled from 'styled-components'
import { useForm } from 'effector-forms'
import { useStore } from 'effector-react'

import { Button, FormField, FormLabel, Input, LinkButton } from '@/ui'
import { createAgreementFactoryModel } from '../../model'
import { AgreementStep } from '../../model/types'

export const createAgreementForm = ({
  agreementForm,
  $agreementStep,
  validateAgreementCodeFx,
  validateAgreements,
  validateAgreementsCode,
}: ReturnType<typeof createAgreementFactoryModel>) => {
  const AgreementForm = () => {
    const { fields } = useForm(agreementForm)
    const agreementStep = useStore($agreementStep)
    const loading = useStore(validateAgreementCodeFx.pending)

    return (
      <>
        <FormField>
          <FormLabel>Π’Π΅Π»Π΅Ρ„ΠΎΠ½</FormLabel>
          <Input
            value={fields.phone.value}
            onChange={fields.phone.onChange}
            hasError={fields.phone.hasError()}
            errorText={fields.phone.errorText()}
            placeholder="+7 (999) 000 00 00"
            disabled
          />
        </FormField>

        {agreementStep === AgreementStep.phone && (
          <ButtonWrapper>
            <Button onClick={() => validateAgreements()}>ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠ΄</Button>
          </ButtonWrapper>
        )}
        {agreementStep === AgreementStep.code && (
          <>
            <FormField>
              <FormLabel>Код</FormLabel>
              <Input
                type="code"
                value={fields.code.value}
                onChange={fields.code.onChange}
                hasError={fields.code.hasError()}
                errorText={fields.code.errorText()}
                placeholder=""
              />
            </FormField>
            {!loading && (
              <ButtonWrapper>
                <LinkButton onClick={() => validateAgreements()}>
                  Π—Π°ΠΏΡ€ΠΎΡΠΈΡ‚ΡŒ ΠΊΠΎΠ΄ Π΅Ρ‰Π΅ Ρ€Π°Π·
                </LinkButton>
              </ButtonWrapper>
            )}
            <ButtonWrapper>
              <Button
                onClick={() => validateAgreementsCode()}
                loading={loading}
              >
                ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠ΄
              </Button>
            </ButtonWrapper>
          </>
        )}
      </>
    )
  }
  return AgreementForm
}

ИспользованиС ​

Π‘ΠΎΠ·Π΄Π°Π½Π½ΡƒΡŽ Π²Ρ‹ΡˆΠ΅ Ρ„Π°Π±Ρ€ΠΈΠΊΡƒ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΡΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π²ΠΎ внСшний ΠΌΠΈΡ€ ΠΈ ΠΏΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π² Π΄Ρ€ΡƒΠ³ΠΈΡ… Ρ„ΠΈΡ‡Π°Ρ…:

Model ​

ts
// @/features/buy-product/model/private.ts
import { createAgreementFactoryModel } from '@/features/agreement-compose/model'
import { d } from './domain'

export const {
  validateAgreementCodeFx,
  validateAgreementFx,
  validateAgreements,
  validateAgreementsCode,
  $agreementStep,
  agreementForm,
} = createAgreementFactoryModel({ d })

View ​

ts
// @/features/buy-product/view/entries/Agreement.tsx

import * as AgreementModel from '../../model/private'
import { createAgreementForm } from '@/features/agreement-compose/view'

export const AgreementForm = createAgreementForm(AgreementModel)

42px Company