• Как связать элементы формы, что бы список основывался на предыдущих значениях формы?

    IgorPI
    @IgorPI Автор вопроса
    Всё получилось.

    Хотя, не понятно, сделал всё то же самое что и раньше.
    Правда, изменил HTPP method from GET to POST

    Полный код формы.
    spoiler

    <?php
    
    namespace App\Form;
    
    use App\Entity\Brand;
    use App\Entity\Model;
    use App\Entity\Modification;
    use App\Entity\Series;
    use Doctrine\ORM\EntityRepository;
    use Symfony\Bridge\Doctrine\Form\Type\EntityType;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\Extension\Core\Type\NumberType;
    use Symfony\Component\Form\Extension\Core\Type\TextType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\Form\FormEvent;
    use Symfony\Component\Form\FormEvents;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class ModificationType extends AbstractType
    {
    	public function buildForm(FormBuilderInterface $builder, array $options)
    	{
    		$builder
    			->add("name", TextType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Наименование",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 255]),
    				],
    				"required" => true,
    			])
    			->add("alt_name", TextType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Альтернативное наименование",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 255]),
    				],
    				"required" => true,
    			])
    			->add("seo_name", TextType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "SEO Name",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 100]),
    				],
    				"required" => true,
    			])
    			->add("power_kwt", NumberType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Мощность кВт",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Positive(),
    				],
    				"empty_data" => 0,
    				"required" => true,
    			])
    			->add("power_horse", NumberType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Мощность л.с.",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Positive()
    				],
    				"empty_data" => 0,
    				"required" => true,
    			])
    			->add("year_from", ChoiceType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Год начала производства",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => $this->getYears(),
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 4]),
    				],
    				"required" => true,
    			])
    			->add("year_to", ChoiceType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Год завершения производства",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => $this->getYears(),
    				"empty_data" => "н.в.",
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 4]),
    				],
    				"required" => false,
    			])
    			->add("brand", EntityType::class, [
    				"label" => "Бренд",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"class" => Brand::class,
    				"choice_label" => function ($brand) {
    					return $brand->getName();
    				},
    				"attr" => [
    					"class" => "form-control select"
    				],
    				"constraints" => [
    					new Assert\NotBlank()
    				]
    			])
                ->add("model", ChoiceType::class, [
    				"label" => "Модель",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => [],
    				"attr" => [
    					"class" => "form-control select"
    				],
    				"constraints" => [
    					new Assert\NotBlank()
    				],
    				"disabled" => true
    			])
                ->add("series", ChoiceType::class, [
    				"label" => "Серия",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => [],
    				"attr" => [
    					"class" => "form-control select"
    				],
    				"constraints" => [
    					new Assert\NotBlank()
    				],
    				"disabled" => true
    			]);
    
    		$builder->get("brand")->addEventListener(
    			FormEvents::POST_SUBMIT,
    			function (FormEvent $event) {
    				$form = $event->getForm();
    
    				$brand_id = $event->getData();
    
    				$form->getParent()->add("model", EntityType::class, [
    					"label_attr" => [
    						"class" => "col-sm-2 form-control-label"
    					],
    					"attr" => [
    						"class" => "form-control select"
    					],
    					"class" => Model::class,
    					"query_builder" => function (EntityRepository $er) use ($brand_id) {
    						return $er->createQueryBuilder("m")
    							->andWhere("m.brand = :brand_id")
    							->setParameter("brand_id", $brand_id)
    							->orderBy("m.name", "ASC");
    					},
    					"choice_label" => function ($entity)
    					{
    						return $entity->getName();
    					},
    					"constraints" => [
    						new Assert\NotBlank()
    					],
    					"required" => true
    				]);
    
    				$form->getParent()->add("series", EntityType::class, [
    					"label_attr" => [
    						"class" => "col-sm-2 form-control-label"
    					],
    					"attr" => [
    						"class" => "form-control select"
    					],
    					"class" => Series::class,
    					"query_builder" => function (EntityRepository $er) use ($brand_id) {
    						return $er->createQueryBuilder("s")
    							->andWhere("s.brand = :brand_id")
    							->setParameter("brand_id", $brand_id)
    							->orderBy("s.name", "ASC");
    					},
    					"choice_label" => function ($entity)
    					{
    						return $entity->getName();
    					},
    					"constraints" => [
    						new Assert\NotBlank()
    					],
    					"required" => true
    				]);
    			}
    		);
    	}
    
    	public function configureOptions(OptionsResolver $resolver)
    	{
    		$resolver->setDefaults([
    			"data_class" => Modification::class,
    		]);
    	}
    
    	/**
    	 * @return array
    	 */
    	private function getYears(): array
    	{
    		$years = [];
    		for ($i = date("Y"); $i >= 1955; $i--) {
    			$years[$i] = $i;
    		}
    		return $years;
    	}
    }

    Ответ написан
    Комментировать
  • Как сделать запросы к api синхронными?

    IgorPI
    @IgorPI
    Недавно такая же проблема была.
    Всё тот же await, но только конкретная реализация.

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

    Выдернул непосредственно из проекта

    spoiler

    import { app } from '@/main'
    import { Cookie } from '@/plugins/cookie'
    import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
    import Vue from 'vue'
    import { sleep } from '@/Utils'
    
    // Full config:  https://github.com/axios/axios#request-config
    // axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
    // axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
    // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    
    /* eslint-disable */
    const config = {
      baseURL: process.env.VUE_APP_API,
      timeout: 30000,
      validateStatus (status: number) {
        return status < 500 // Resolve only if the status code is less than 500
      }
    }
    /* eslint-enable */
    
    const _axios: AxiosInstance = axios.create(config)
    const cookie: Cookie = new Cookie()
    let isRefreshTokenProcess = false
    const promises: any[] = []
    /* eslint-disable */
    // @ts-ignore
    _axios.interceptors.request.use(async (config: AxiosRequestConfig): AxiosRequestConfig | Promise<AxiosRequestConfig> => {
    
      // todo: set locale optional
      config.headers.Language = 'ru'
    
      if (isRefreshTokenProcess) {
        console.log('%c%s', 'color: red;', `Запрос ${config.url} ожидает обновление токена...`)
        promises.push(new Promise(async (resolve) => {
          while (isRefreshTokenProcess) {
            await sleep(500)
          }
          console.log('%c%s', 'color: green;', `Запрос ${config.url} разрешён!`)
          resolve()
        }))
        // This is process update token
        await Promise.all(promises)
      }
    
      if (cookie.has('access_token')) {
          config.headers.Authorization = `Bearer ${cookie.get('access_token')}`
          return config
        } else {
          if (cookie.has('refresh_token')) {
            console.log('%c%s', 'color: blue;', 'Обновление токена...')
            isRefreshTokenProcess = true
            await axios.post(`${process.env.VUE_APP_API}/account/authorization/refresh-token`, {
              refresh_token: cookie.get('refresh_token')
            }).then((response: AxiosResponse) => {
              if (response.status === 200) {
                cookie.set('access_token', response.data.access_token, { 'max-age': 600, 'path': '/' })
                cookie.set('refresh_token', response.data.refresh_token, { path: '/' })
                config.headers.Authorization = `Bearer ${cookie.get('access_token')}`
              }
            }).catch(() => {
              app.$router.replace({ name: 'login' }).then()
            })
          }
          isRefreshTokenProcess = false
          return Promise.resolve(config)
        }
      },
      function (error) {
        // Do something with request error
        return Promise.reject(error)
      }
    )
    /* eslint-disable */
    /*
      Array of patterns to ignore status checks.
     */
    
    // Add a response interceptor
    _axios.interceptors.response.use(
      (response): Promise<AxiosResponse> | any => {
        if (response.status === 401) {
          return app.$router.replace({ name: 'login' }).finally(() => Promise.reject(response))
        } else {
          return response
        }
      },
      function (error) {
        // Do something with response error
        return Promise.reject(error)
      }
    )
    
    class AxiosPlugin {
      public install () {
        Object.defineProperties(Vue.prototype, {
          axios: {
            get () {
              return _axios
            }
          },
          $axios: {
            get () {
              return _axios
            }
          }
        })
      }
    }
    
    const axiosPlugin: AxiosPlugin = new AxiosPlugin()
    
    Vue.use(axiosPlugin)
    
    export default axiosPlugin
    export const $axios: AxiosInstance = _axios



    Работает следующим образом.
    Токен хранится в куках, с меньшим временем хранения, то есть с куков будет удалён раньше чем протухнет на сервере.

    Перед выполнением запроса, мы проверяем, есть ли токен в куках, если нет то обновляем с помощью refresh_token.
    Так же выставляется флаг, мол в процессе обновления токена и все последующие запросы помещаются в массив промисов.
    Далее в цикле проверяем, можно ли разрешить промис если да то выдыхаем и запросы ушли на сервер.
    Ответ написан
    4 комментария
  • Регистрация и логин юзера по API в symfony. Как делать?

    IgorPI
    @IgorPI
    Вы как то всё смешали в кучу, капчу, API, Google, Формы.

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

    Авторизация, ну тут стандартные механизмы Symfony, которые можно модернизировать и юзать JWT.

    Про капчу нечего сказать.
    Ответ написан
    Комментировать
  • Как в Vue-приложении сначала загрузить данные в $store, а уже потом рендерить шаблоны?

    IgorPI
    @IgorPI
    const Foo = {
      template: `...`,
      beforeRouteEnter (to, from, next) {
        // вызывается до подтверждения пути, соответствующего этому компоненту.
        // НЕ ИМЕЕТ доступа к контексту экземпляра компонента `this`,
        // так как к моменту вызова экземпляр ещё не создан!
      },
      beforeRouteUpdate (to, from, next) {
        // вызывается когда маршрут, что рендерит этот компонент изменился,
        // но этот компонент будет повторно использован в новом маршруте.
        // Например, для маршрута с динамическими параметрами `/foo/:id`, когда мы
        // перемещаемся между `/foo/1` и `/foo/2`, экземпляр того же компонента `Foo`
        // будет использован повторно, и этот хук будет вызван когда это случится.
        // Также имеется доступ в `this` к экземпляру компонента.
      },
      beforeRouteLeave (to, from, next) {
        // вызывается перед переходом от пути, соответствующего текущему компоненту;
        // имеет доступ к контексту экземпляра компонента `this`.
      }
    }


    beforeRouteEnter (to, from, next) {
      next(vm => {
        // экземпляр компонента доступен как `vm`
      })
    }


    Источник
    Ответ написан
    2 комментария
  • Реакция в 00:00, как сделать?

    IgorPI
    @IgorPI
    Очевидно, что каждую секунду следует проверять некоторое условие.
    И скорее всего в setInterval.

    Если говорить о простом.

    Но можно юзать специальную библиотеку, честно признаюсь не юзал планировщик на ноде.
    Но принципы планировщиков похожи.
    Ответ написан
  • Общий обработчик ошибок для запросов во vue?

    IgorPI
    @IgorPI
    Используйте интерцептор.

    Подкину свой пример.
    Оформил в виде плагина для VUE.
    Обратите внимание на эту строчку
    export const $axios: AxiosInstance = _axios

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

    import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
    import Vue from 'vue'
    
    // Full config:  https://github.com/axios/axios#request-config
    // axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
    // axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
    // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    
    /* eslint-disable */
    const config = {
      baseURL: process.env.VUE_APP_API,
      timeout: 30000,
      validateStatus (status: number) {
        return status < 500 // Resolve only if the status code is less than 500
      }
    }
    /* eslint-enable */
    
    const _axios: AxiosInstance = axios.create(config)
    
    /* eslint-disable */
    // @ts-ignore
    _axios.interceptors.request.use(async (config: AxiosRequestConfig): AxiosRequestConfig | Promise<AxiosRequestConfig> => {
      return Promise.resolve(config)
      },  (error) => {
        // Do something with request error
        return Promise.reject(error)
      }
    )
    /* eslint-disable */
    
    // Add a response interceptor
    _axios.interceptors.response.use((response): Promise<AxiosResponse> | any => {
      return response
      },  (error) => {
        // Do something with response error
        return Promise.reject(error)
      }
    )
    
    class AxiosPlugin {
      public install () {
        Object.defineProperties(Vue.prototype, {
          axios: {
            get () {
              return _axios
            }
          },
          $axios: {
            get () {
              return _axios
            }
          }
        })
      }
    }
    
    const axiosPlugin: AxiosPlugin = new AxiosPlugin()
    
    Vue.use(axiosPlugin)
    
    export default axiosPlugin
    export const $axios: AxiosInstance = _axios
    Ответ написан
    Комментировать
  • Как связать ключ в таблицах?

    IgorPI
    @IgorPI
    В таблице orders должен быть внешний ключ person_id который ссылается на таблицу person на первичный ключ.

    Фактически это выглядит так
    CREATE TABLE `persons` (
    	`id` INT(10,0) NOT NULL AUTO_INCREMENT,
    	`name` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
    	`phone` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_0900_ai_ci',
    	PRIMARY KEY (`id`) USING BTREE
    )
    COLLATE='utf8mb4_0900_ai_ci'
    ENGINE=InnoDB;
    
    
    CREATE TABLE `orders` (
    	`id` INT(10,0) NOT NULL AUTO_INCREMENT,
    	`date` DATETIME NULL DEFAULT NULL,
    	`person_id` INT(10,0) NOT NULL DEFAULT '0',
    	`street` VARCHAR(255) NOT NULL DEFAULT '0' COLLATE 'utf8mb4_0900_ai_ci',
    	`name` VARCHAR(255) NOT NULL DEFAULT '0' COLLATE 'utf8mb4_0900_ai_ci',
    	`phone` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8mb4_0900_ai_ci',
    	PRIMARY KEY (`id`) USING BTREE,
    	INDEX `person_id` (`person_id`) USING BTREE,
    	CONSTRAINT `FK1_OP` FOREIGN KEY (`person_id`) REFERENCES `rmok`.`persons` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
    )
    COLLATE='utf8mb4_0900_ai_ci'
    ENGINE=InnoDB;
    Ответ написан
  • Можно ли получить данные из дочернего компонента?

    IgorPI
    @IgorPI
    Привожу пример рабочего компонента.
    Обратите внимание на

    model: {
        prop: 'selected',
        event: 'change'
      },


    <template>
      <v-autocomplete
        v-model="selected"
        :items="users"
        :search-input.sync="usersSearchQuery"
        persistent-hint
        :label="label"
        :rules="rules"
        :loading="loading"
      >
        <template
          v-slot:prepend
          v-if="visibleIcon && ['lg', 'md'].includes($vuetify.breakpoint.name)"
        >
          <v-icon class="pl-5 pr-9">mdi-account-tie</v-icon>
        </template>
        <template v-slot:selection="{ attr, on, item }">
          <span>{{ item.first_name }} {{ item.last_name }}</span>
        </template>
        <template v-slot:item="{ item }">
          <v-list-item-avatar
            color="indigo"
            class="headline font-weight-light white--text"
          >
            {{ item.first_name.charAt(0) }}
          </v-list-item-avatar>
          <v-list-item-content>
            <v-list-item-title>{{ item.first_name }} {{ item.last_name }}</v-list-item-title>
            <v-list-item-subtitle
              v-if="item.role"
              v-text="item.role.name"
            ></v-list-item-subtitle>
          </v-list-item-content>
        </template>
      </v-autocomplete>
    </template>
    
    <script lang="ts">
    import Vue from 'vue'
    import { debounce } from 'vuetify/src/util/helpers'
    import { Users } from '@/api/Users'
    
    export default Vue.extend({
      name: 'UserAutocomplete',
      model: {
        prop: 'selected',
        event: 'change'
      },
      props: {
        selectedId: {
          type: Number,
          default: 0
        },
        search: {
          type: String,
          default: ''
        },
        rules: {
          type: Array,
          default: undefined
        },
        visibleIcon: {
          type: Boolean,
          default: false
        },
        label: {
          type: String,
          default: ''
        }
      },
    
      data () {
        return {
          loading: false,
          selectOnce: false,
          selected: null,
          usersSearchQuery: null,
          usersProcessLoading: false,
          usersSearchDebounce: debounce((q: string) => {
            this.loading = true
            new Users().findUsers(q)
              .then(({ count, items }) => {
                this.users = items
    
                if (this.selectOnce === false) {
                  this.selectOnce = true
                  this.selected = this.users.find((e) => e.id === this.selectedId)
                  console.log(this.selectedId, this.users)
                  console.log(this.selected)
                }
              }).finally(() => {
                this.loading = false
              })
          }, 400),
          users: []
        }
      },
    
      watch: {
        selected (value) {
          this.$emit('change', value)
        },
    
        usersSearchQuery (val: string) {
          this.usersSearchDebounce(val)
        }
      },
    
      created () {
        this.usersSearchQuery = this.search
      }
    })
    </script>
    Ответ написан
    1 комментарий
  • Docker не является внутренней или внешней?

    IgorPI
    @IgorPI
    5f8f1a2397bbc452460266.png
    Вручную указать путь до исполняемых файлов.
    Ответ написан
    6 комментариев
  • Как установить докер?

    IgorPI
    @IgorPI
    Установите Docker Desktop for Windows Edge Release notes
    Там если я не ошибаюсь уже не нужен Hyper-V

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

    Самое классное, что последние версии докера для винды загружается за секунды.
    Ответ написан
    6 комментариев
  • Как проще всего отслеживать изменение объекта или переменной?

    IgorPI
    @IgorPI
    Не буду умничать, сам не делал, хотя уже давно программирую.
    Вам нужно это
    Ответ написан
    Комментировать
  • Как сделать плагин так, что бы он стал реактивным в шаблоне?

    IgorPI
    @IgorPI Автор вопроса
    В общем дело обстоит следующим образом.

    В Vue есть метод observable

    Нужно указать объект за которым планируем следить
    Vue.observable({
          count: 0
        })


    В моём случае, так
    class JsSIPlugin {
      public install () {
        Object.defineProperties(Vue.prototype, {
          $jsSIP: {
            get (): JsSIP {
              return $jssip
            }
          }
        })
    
        Vue.observable($jssip)
      }
    }


    Теперь в шаблоне происходит магия.
    Ответ написан
    1 комментарий
  • Как избежать явного первичного ключа?

    IgorPI
    @IgorPI Автор вопроса
    Сделал по данной статье

    Пока так, исключительно для того что бы доктрина успокоилась
    Ответ написан
    Комментировать
  • Как написать интерпретатор для простых команд боту?

    IgorPI
    @IgorPI Автор вопроса
    Мне посоветовали смотреть в сторону state machine.

    Действительно, это то что мне требовалось для решения данной задачи.
    Нашёл библиотеку

    Там уже был интерпретатор, причём с достаточным потенциалом.

    Какие преимущества я для себя выделил:
    1. Изменение диалога без изменения кода бота.
    2. Горячая перезагрузка сценария
    3. Ветвление диалога (самое важное)

    И самое главное, у меня получилось на основе данной библиотеки реализовать мощного бота с интерпретатором действий.

    Теперь просто пишу сценарии и радуюсь.
    Ответ написан
    Комментировать
  • Какой посоветуете набор vue компонентов для создания личного кабинета?

    IgorPI
    @IgorPI
    Мне нравится Quasar
    Работал с Vuetify, слишком много багов, по сравнению с Quasar
    Ответ написан
  • Не подключаются колонки и наушники. Что делать?

    IgorPI
    @IgorPI
    На худой конец
    Ищите драйвера производителей железа.
    Ответ написан
    Комментировать
  • Как в symfony secure проверить авторизованность пользователя?

    IgorPI
    @IgorPI
    В Symfony есть так называемый фаервол.

    В моём проекте это выглядит так:
    security:
      encoders:
        App\Entity\User:
          algorithm: auto
    
      # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
      providers:
        app_security_user_provider:
          id: App\Security\UserProvider
    
      #    registration:
      #        pattern:  ^/secure.registration
      #        stateless: true
      #        anonymous: true
    
    
      firewalls:
        dev:
          pattern: ^/(_(profiler|wdt)|css|images|js)/
          security: false
    
        authorization:
          pattern: ^/authorization
          security: false
    
        main:
          anonymous: ~
          stateless: true
          provider: app_security_user_provider
          guard:
            authenticators:
              - App\Security\Authenticator
    
            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication
    
            # https://symfony.com/doc/current/security/impersonating_user.html
          # switch_user: true
    
      # Easy way to control access for large sections of your site
      # Note: Only the *first* access control that matches will be used
      access_control:
      # - { path: ^/profile, roles: ROLE_USER }


    Это глобальная защита всех маршрутов.
    Так же существует так называемая локальная защита чего-то, например каких то действий в экшене.
    // throw exception
      $this->denyAccessUnlessGranted(UserVoter::READ["read"], User::class, "Вам запрещено просматривать пользователей.");
    // or
    // return boolean
    $this->isGranted(UserVoter::READ["read"], User::class)
    Ответ написан
  • Как вы запоминаете термины языка?

    IgorPI
    @IgorPI
    Чем чаще всего используешь какой-нибудь паттерн / фреймворк / и т. д начинаешь не только помнить его термины, но и то как оно работает. Стоит тебе перестать активно их использовать остаются только термины, а в будущем забудешь и их.

    Именно по этому, узкоспециализированные специалисты очень хорошо разбираются в своём деле.

    © Игорь
    Ответ написан
    Комментировать