{"id":335413,"date":"2022-07-07T15:00:47","date_gmt":"2022-07-07T15:00:47","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=335413"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=335413","title":{"rendered":"<span>\u0420\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0443\u044e \u0444\u043e\u0440\u043c\u0443 \u043d\u0430 React \u0438 XState \u0438\u043b\u0438 \u043d\u043e\u0432\u044b\u0435 \u043f\u0440\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0433\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0430<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h3>\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435<\/h3>\n<p>\u0412\u043e\u043f\u0440\u0435\u043a\u0438 \u043c\u043d\u0435\u043d\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f \u0432\u0438\u0434\u0435\u043b \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0445 \u043a \u0441\u0442\u0430\u0442\u044c\u044f\u043c \u043e \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0445 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0430\u0445, \u044f \u043d\u0435 \u0441\u0447\u0438\u0442\u0430\u044e, \u0447\u0442\u043e \u0438\u0445 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u044d\u0442\u043e \u043a\u0430\u043a\u043e\u0439-\u0442\u043e &#171;\u0441\u043f\u0430\u0433\u0435\u0442\u0442\u0438&#187;. \u041d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u043e\u043d\u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u0443\u044e state transition \u043b\u043e\u0433\u0438\u043a\u0443.<\/p>\n<p>\u0425\u043e\u0442\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442 \u0438\u0437 100500 \u0441\u0442\u0435\u0439\u0442\u043e\u0432 \u0441 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u043c\u0438 \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u043c \u0441\u0442\u0435\u0439\u0442\u0430\u043c\u0438, \u043f\u043e\u0436\u0430\u043b\u0443\u0439, \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0430\u0431\u043e \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u043c. \u041d\u043e \u044d\u0442\u043e \u0443\u0436\u0435 \u0442\u0435\u043c\u0430 \u0434\u043b\u044f Separation of Concerns &#8212; \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 <a href=\"https:\/\/xstate.js.org\/\" rel=\"noopener noreferrer nofollow\">XState<\/a>.<\/p>\n<p>\u041a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043a\u0430\u043a \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u044d\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u044b, \u0442\u0430\u043a \u0438 \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0432 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0435 \u0438 \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<h3>XState<\/h3>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437 \u0441 XState \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <a href=\"https:\/\/www.npmjs.com\/package\/vue-kanban\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u0443 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443<\/a>.<\/p>\n<p>\u041d\u043e \u0432 \u0442\u043e\u0442 \u0440\u0430\u0437 \u0437\u043d\u0430\u043a\u043e\u043c\u0441\u0442\u0432\u043e \u043a\u0430\u043a \u0442\u043e \u043d\u0435 \u0437\u0430\u0434\u0430\u043b\u043e\u0441\u044c, \u043a\u0430\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0441\u044f, \u043d\u0435 \u0437\u0430\u0448\u043b\u043e<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/13d\/45f\/5cf\/13d45f5cfd8eea29ba86a9cb26e2958c.jpg\" alt=\"\" title=\"\" width=\"1280\" height=\"720\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/13d\/45f\/5cf\/13d45f5cfd8eea29ba86a9cb26e2958c.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0437 \u2014 \u0432<a href=\"https:\/\/khalilstemmler.com\/articles\/client-side-architecture\/introduction\/\" rel=\"noopener noreferrer nofollow\"> \u0446\u0438\u043a\u043b\u0435 \u0441\u0442\u0430\u0442\u0435\u0439 Khalil Stemmler<\/a>, \u0435\u0433\u043e \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u043b\u043e\u0441\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0441\u043b\u043e\u0435 <strong>Interaction logic.<\/strong><\/p>\n<p>\u0418 \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u044f \u0440\u0435\u0448\u0438\u043b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0432 \u0442\u0435\u043c\u0435, \u0442\u0435\u043c \u0431\u043e\u043b\u0435\u0435 \u0438\u0441\u043a\u0430\u043b \u0442\u0435\u043c\u0443 \u0434\u043b\u044f \u0432\u0435\u0431\u0438\u043d\u0430\u0440\u0430.<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0442\u0435\u043e\u0440\u0438\u0438 \u0438\u0437 \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0438 (\u043f\u0440\u0438\u0437\u043d\u0430\u044e\u0441\u044c, \u043f\u0440\u043e\u0433\u0443\u043b\u0438\u0432\u0430\u043b \u043b\u0435\u043a\u0446\u0438\u0438 \u0432 \u0443\u043d\u0438\u0432\u0435\u0440\u0435, \u0445\u0437 \u043a\u0430\u043a \u0441\u0434\u0430\u043b&#8230;)<\/p>\n<blockquote>\n<p>\u041a\u043e\u043d\u0435\u0301\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0301\u0442 (\u041a\u0410) \u0432 \u0442\u0435\u043e\u0440\u0438\u0438 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u0432 \u2014 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f, \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0438\u043c\u0435\u044e\u0449\u0435\u0433\u043e \u043e\u0434\u0438\u043d \u0432\u0445\u043e\u0434, \u043e\u0434\u0438\u043d \u0432\u044b\u0445\u043e\u0434 \u0438 \u0432 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e\u0441\u044f \u0432 \u043e\u0434\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u0438\u0437 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445. \u042f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u0430\u0441\u0442\u043d\u044b\u043c \u0441\u043b\u0443\u0447\u0430\u0435\u043c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0430, \u0447\u0438\u0441\u043b\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043a\u043e\u043d\u0435\u0447\u043d\u043e.<\/p>\n<\/blockquote>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e, XState \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u044d\u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u041a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442 \u0438\u043b\u0438, \u0435\u0441\u043b\u0438 \u043d\u0430 \u043b\u0430\u0442\u044b\u043d\u0438 &#8212; Finite State Machine<\/p>\n<p>\u042d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043c\u043d\u0435 \u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u0442\u0435\u043c\u043e\u0439 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043b\u043e\u0433\u0438\u043a\u0438 \u0444\u043e\u0440\u043c \u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0443\u0439\u0442\u0438 \u043e\u0442 \u043a\u0443\u0447\u0438 useState \u0438 useEffect&#8230;<\/p>\n<p>\u041d\u043e \u043b\u0443\u0447\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0435\u043e\u0440\u0438\u044e \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435.<\/p>\n<h3>\u041f\u0440\u0430\u043a\u0442\u0438\u043a\u0430<\/h3>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u2014 \u0444\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>\u041a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u044b, \u0447\u0442\u043e \u0442\u0430\u043c \u0434\u0435\u043b\u0430\u0442\u044c? 2 \u0438\u043d\u043f\u0443\u0442\u0430, \u043a\u043d\u043e\u043f\u043a\u0430, \u0434\u0435\u0440\u043d\u0443\u043b\u0438 API, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043b\u0438 &#8212; PROFIT!<\/p>\n<p>\u041d\u043e \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u0444\u0438\u0447\u0438:<\/p>\n<ol>\n<li>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0432\u043e\u0434\u0430<\/p>\n<\/li>\n<li>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0432\u0445\u043e\u0434\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u043f\u043e\u043a\u0430 \u043f\u043e\u043b\u0435 \u043d\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043e<\/p>\n<\/li>\n<li>\n<p>\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u0440\u043e\u043f\u0430\u0434\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f\u00a0<strong>\u044f\u0432\u043d\u043e<\/strong>\u00a0(\u0441 \u043f\u043e\u043a\u0430\u0437\u043e\u043c \u043e\u0448\u0438\u0431\u043a\u0438) \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 blur \u0441\u043e\u0431\u044b\u0442\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0443, \u043f\u043e\u043a\u0430 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f\u00a0<strong>\u043d\u0435 \u044f\u0432\u043d\u043e<\/strong>, \u0434\u043b\u044f \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f 5<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b, \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0438\u0441\u0447\u0435\u0437\u0430\u0442\u044c \u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0444\u043e\u0440\u043c\u044b<\/p>\n<\/li>\n<li>\n<p>\u041e\u0448\u0438\u0431\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u043c \u0432\u0438\u0434\u0435 \u0438 \u043d\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0444\u043e\u0440\u043c\u044b<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u0436\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a. 9 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0437 2 \u043f\u043e\u043b\u0435\u0439 \u0431\u0435\u0437 \u0432\u0441\u044f\u043a\u0438\u0445 \u0442\u0430\u043c 2FA. \u0418 \u0435\u0441\u043b\u0438 \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u0442\u044c\u0441\u044f, \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0438 \u0435\u0449\u0435.<\/p>\n<p>\u041d\u0443, \u0432\u0440\u0435\u043c\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434.<\/p>\n<h4>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430<\/h4>\n<p>\u041d\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434 \u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f Create React App \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438.<\/p>\n<pre><code>    yarn create react-app xstate-demo --template typescript     cd xstate-demo     yarn install     yarn add xstate @xstate\/react mobx mobx-react <\/code><\/pre>\n<h4>\u041f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u043c \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443<\/h4>\n<p>\u0412\u043e\u0437\u044c\u043c\u0435\u043c \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u043d\u0443\u044e feature sliced \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9b1\/9a9\/1bc\/9b19a91bccb430129546a9307af35035.png\" width=\"513\" height=\"331\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9b1\/9a9\/1bc\/9b19a91bccb430129546a9307af35035.png\"\/><figcaption><\/figcaption><\/figure>\n<ul>\n<li>\n<p>\u0412 shared \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u043e\u0431\u0449\u0438\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b: \u0444\u043e\u0440\u043c \u0438\u043d\u043f\u0443\u0442\u044b, \u0444\u043e\u0440\u043c \u043c\u0430\u0448\u0438\u043d\u0443, \u0441\u0442\u043e\u0440\u044b, \u0438 \u0442\u0438\u043f\u044b<\/p>\n<\/li>\n<li>\n<p>\u0412 features \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043e\u0434\u043d\u0430 \u0444\u0438\u0447\u0430 \u2014 login. \u0421\u0443\u0442\u044c \u0444\u0438\u0447\u0435\u0439 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0444\u0438\u0447\u0438 \u043d\u0435 \u0442\u044f\u043d\u0443\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u0437 \u043d\u0435\u0435. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0444\u0438\u0447\u0443, \u0432\u044b\u0437\u043e\u0432 \u0435\u0435 root \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u0444\u0438\u0447\u0443, \u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u043f\u043e\u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442.<\/p>\n<\/li>\n<\/ul>\n<h4>\u041f\u0438\u0448\u0435\u043c \u043a\u043e\u0434<\/h4>\n<h4>1. \u041f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u0430\u0448\u0438\u043d\u0443<\/h4>\n<p>\u041c\u044b \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0443\u044e \u043c\u0430\u0448\u0438\u043d\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438 \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0444\u043e\u0440\u043c\u0430\u0445.<\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 actions, \u0430 \u0442\u0430\u043a \u0436\u0435 \u0442\u0438\u043f\u044b<\/p>\n<pre><code class=\"typescript\">export enum States {     dataEntry = 'dataEntry',     awaitingResponse = 'awaitingResponse',     dataEntryError = 'dataEntryError',     serviceError = 'serviceError',     success = 'success', }  export enum Events {     ENTER_DATA = 'ENTER_DATA',     BLUR_DATA = 'BLUR_DATA',     SUBMIT = 'SUBMIT', }  export enum Actions {     setField = 'setField', }<\/code><\/pre>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u043c\u0430\u0448\u0438\u043d\u044b<\/p>\n<pre><code class=\"typescript\">export interface FormFieldConfigValidationResult {     result: boolean,     errorMessage?: string }  export interface FormFieldConfig {     required: boolean,     field: string,     validator?: (value: any) => FormFieldConfigValidationResult }  export interface FormFieldErrors {     [field: string]: {         message: string     } }   export type FormMachineContext = {     data: any,     dataEntryErrors: FormFieldErrors,     serviceErrors: any,     fields: FormFieldConfig[],     canSubmit: boolean, }<\/code><\/pre>\n<h4>2. \u041a\u043e\u043d\u0444\u0438\u0433 \u043c\u0430\u0448\u0438\u043d\u044b<\/h4>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c factory \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u0430\u0448\u0438\u043d\u044b \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u043c<\/p>\n<pre><code class=\"typescript\">export interface FormMachineFactoryParams {     fields: FormFieldConfig[],     onSubmit: (context: FormMachineContext) => Promise&lt;any>,     onDone: (data: any) => any }  export const formMachineFactory = ({    fields, onSubmit, onDone, }: FormMachineFactoryParams) => {     return createMachine({         id: 'login',         initial: States.dataEntry,         context: {             data: {},             dataEntryErrors: {} as FormErrors,             serviceErrors: {} as FormErrors,             fields,             canSubmit: false,         } as FormMachineContext,         states: {},         actions: {},         guards: {}     }) }<\/code><\/pre>\n<p>\u041f\u0440\u043e\u043f\u0438\u0448\u0435\u043c \u043f\u0435\u0440\u0432\u044b\u0435 <strong>transitions<\/strong> \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u0421\u0443\u0442\u044c \u0442\u0430\u043a\u0430\u044f \u2014 \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u043c\u0441\u044f \u0432 \u0441\u0442\u0430\u0434\u0438\u0438 dataEntry, \u0442\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432\u0432\u043e\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u043e\u0442\u0435\u0440\u044e \u0444\u043e\u043a\u0443\u0441\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b.<\/p>\n<pre><code class=\"typescript\">... states: {     [States.dataEntry]: {         on: {             [Events.ENTER_DATA]: {                 actions: Actions.setField,             },             \/\/ \u043f\u0440\u0438 \u043f\u043e\u0442\u0435\u0440\u0435 \u0444\u043e\u043a\u0443\u0441\u0430 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043b\u0435 \u0438 \u0435\u0441\u043b\u0438 isInvalid \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c              \/\/ \u043c\u0430\u0448\u0438\u043d\u0443 \u0432 dataEntryError             [Events.BLUR_DATA]: [                 {cond: 'isInvalid', target: States.dataEntryError},             ],             [Events.SUBMIT]: {                 cond: 'canSubmitGuard',                 target: States.awaitingResponse             },         },     }, }, ... <\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043f\u0438\u0448\u0435\u043c <strong>actions<\/strong> &#8212; \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0432 \u0441\u0442\u0438\u043b\u0435 &#171;\u0432\u044b\u0441\u0442\u0440\u0435\u043b\u0438\u043b \u0438 \u0437\u0430\u0431\u044b\u043b&#187; <a href=\"https:\/\/xstate.js.org\/docs\/guides\/actions.html\" rel=\"noopener noreferrer nofollow\">https:\/\/xstate.js.org\/docs\/guides\/actions.html<\/a><\/p>\n<p>\u0412 \u044d\u0444\u0444\u0435\u043a\u0442\u0435 setField \u043c\u044b \u0434\u0430\u043d\u043d\u044b\u0435 \u0432\u0432\u043e\u0434\u0430 \u0438\u0437 \u0444\u043e\u0440\u043c \u0438\u043d\u043f\u0443\u0442\u043e\u0432 \u0438 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0432\u0441\u044e \u0444\u043e\u0440\u043c\u0443, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u044f\u0442\u044c, \u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438. \u0410 \u0442\u0430\u043a \u0436\u0435 \u043e\u0447\u0438\u0449\u0430\u0435\u043c context.dataEntryErrors \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0440\u0430\u0442\u044c \u0432\u044b\u0432\u043e\u0434 \u043e\u0448\u0438\u0431\u043a\u0438.<\/p>\n<pre><code class=\"typescript\">function canSubmit(context: FormMachineContext) {     if (Object.keys(context.fields).length === 0) {         return true;     }     return context.fields.reduce((acc, val) => {         const fieldValue = context.data[val.field];         const reqPredicate = val.required ? !!fieldValue : true;         return acc &amp;&amp;             (typeof val.validator === 'function' ?                 val.validator(context.data[val.field]).result : true)        &amp;&amp; reqPredicate     }, true); } ... actions: {     [Actions.setField]: (context, event) => {         context.data[event.data.field] = event.data.value;         delete context.dataEntryErrors[event.data.field];         context.canSubmit = canSubmit(context);     }, }, ... <\/code><\/pre>\n<p><strong>Guards<\/strong> \u2014 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u043e\u0432 \u0432 \u0441\u0442\u0430\u0434\u0438\u0438. \u0418\u0445 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c, \u0447\u0442\u043e\u0431\u044b \u044f\u0432\u043d\u043e \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0435 \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432 \u0441\u0442\u0435\u0439\u0442 dataEntryError. <a href=\"https:\/\/xstate.js.org\/docs\/guides\/guards.html#guards-condition-functions\" rel=\"noopener noreferrer nofollow\">https:\/\/xstate.js.org\/docs\/guides\/guards.html#guards-condition-functions<\/a><\/p>\n<pre><code class=\"typescript\">... guards: {     isInvalid: (context, event) => {         if (!event.data.value) {             return false;         }          const field = context.fields.find(f => f.field === event.data.field);          if (!field) {             return false;         }          const res: FormFieldConfigValidationResult = field.validator ? field.validator(event.data.value) : {             result: true         };          if (!res.result) {             context.dataEntryErrors[event.data.field] = {                 message: res.errorMessage,             };         }         return !res.result     },     canSubmitGuard: (context) => {         return context.canSubmit;     } } ...<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043e\u043f\u0438\u0448\u0435\u043c \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0430\u0434\u0438\u0438. \u0412 \u0441\u0442\u0435\u0439\u0442\u0435 awaitingResponse \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c <strong>service<\/strong> &#8212; \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u044d\u0444\u0444\u0435\u043a\u0442. \u041e\u043d \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c Promise, Callback, Observables \u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u043c\u0430\u0448\u0438\u043d\u043e\u0439. <a href=\"https:\/\/xstate.js.org\/docs\/guides\/communication.html#the-invoke-property\" rel=\"noopener noreferrer nofollow\">https:\/\/xstate.js.org\/docs\/guides\/communication.html#the-invoke-property<\/a><\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 \u044d\u0442\u043e Promise. <\/p>\n<pre><code class=\"typescript\">states: {     ...     [States.awaitingResponse]: {         id: 'submit',         \/\/\u044d\u0442\u043e \u0441\u0435\u0440\u0432\u0438\u0441 - \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u044d\u0444\u0444\u0435\u043a\u0442         invoke: {             src: (context) => {                 return onSubmit(context);             },             onDone: {                 target: States.success             },             onError: [                 {                     actions: (context, event) => {                         context.serviceErrors[event.type] = event.data;                     },                     target: States.serviceError                 }             ]         }     },     [States.dataEntryError]: {         on: {             [Events.ENTER_DATA]: {                 \/\/ \u041f\u0440\u0438 \u0432\u0432\u043e\u0434\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043a\u0430\u043a \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0442\u0430\u043a \u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c state                 actions: Actions.setField,                 target: States.dataEntry             },         }     },     [States.serviceError]: {         on: {             [Events.SUBMIT]: {                 target: States.awaitingResponse             },             [Events.ENTER_DATA]: {                 \/\/ \u041f\u0440\u0438 \u0432\u0432\u043e\u0434\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043a\u0430\u043a \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0442\u0430\u043a \u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c state                 actions: Actions.setField,                 target: States.dataEntry             },         }     },     [States.success]: {         type: 'final',         onDone: {             actions: onDone         },     }, ... <\/code><\/pre>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0447\u0438\u0442\u0430\u0435\u043c\u0443\u044e, \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u0443\u044e, \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u0443\u044e form logic. \u041a\u043e\u0442\u043e\u0440\u0443\u044e \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432 Vue \u0438\u043b\u0438 Svelte, \u0438\u043b\u0438 \u0432\u043e\u043e\u0431\u0449\u0435 \u0432 \u0432\u0430\u043d\u0438\u043b\u0435.<\/p>\n<p><a href=\"https:\/\/github.com\/BEARlogin\/xstate-demo-app\/blob\/master\/src\/shared\/forms\/form-machine.ts\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u043c\u0430\u0448\u0438\u043d\u044b<\/a><\/p>\n<p>\u041a\u0440\u0443\u0442\u044b\u043c \u0431\u043e\u043d\u0443\u0441\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u0442\u0435\u0439\u0442\u0447\u0430\u0440\u0442\u044b \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 <a href=\"https:\/\/stately.ai\/viz\" rel=\"noopener noreferrer nofollow\">Visualizer<\/a> \u0438\u043b\u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0430\u0448\u0438\u043d\u044b \u0447\u0435\u0440\u0435\u0437 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0439\u00a0<a href=\"https:\/\/stately.ai\/registry\/new\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440<\/a><\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ff7\/04b\/bb8\/ff704bbb8aec0784b6b759d2d032e225.png\" width=\"849\" height=\"222\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ff7\/04b\/bb8\/ff704bbb8aec0784b6b759d2d032e225.png\"\/><figcaption><\/figcaption><\/figure>\n<h4>3. \u041f\u0438\u0448\u0435\u043c store \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u0442\u043e\u0440 \u0438 \u044d\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a Api. \u041f\u0435\u0440\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u0448\u0438\u0431\u043e\u0447\u043d\u044b\u043c.<\/p>\n<pre><code class=\"typescript\">import {makeAutoObservable} from \"mobx\"; import {User} from \"..\/types\/User\";   class AuthStore {     user?: User     error: boolean = true;      constructor() {         makeAutoObservable(this);     }      async auth(login, password) {         this.user = await (new Promise&lt;User>((resolve, reject) => {             if (this.error) {                 this.error = false;                 reject('Hello error!');             }             setTimeout(() => {                 resolve({                     role: 'admin',                     login: 'admin'                 })             }, 2000);         }))     } }  export const authStore = new AuthStore();<\/code><\/pre>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u044e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438<\/p>\n<pre><code class=\"typescript\">import React, {InputHTMLAttributes} from \"react\"; import '.\/FormInput.css'  export interface FormInputParams extends InputHTMLAttributes&lt;HTMLInputElement> {     error?: string }  export default function FormInput({error, ...rest}: FormInputParams) {     return (         &lt;div className={\"form-input\"}>             &lt;input                 {...rest} \/>             {error &amp;&amp; &lt;div style={{color: 'red'}}>{error}&lt;\/div>}         &lt;\/div>     ) }<\/code><\/pre>\n<p>\u041e\u0431\u0449\u0438\u0439 \u0445\u0443\u043a \u0434\u043b\u044f \u0432\u0441\u0435\u0445 form machine. \u0417\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0443\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u0432\u0435\u0448\u0430\u0442\u044c \u043d\u0430 \u043f\u043e\u043b\u044f \u0432\u0432\u043e\u0434\u0430, \u0441\u0430\u043c\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0438 \u0441\u0442\u0435\u0439\u0442 \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043e\u043a<\/p>\n<pre><code class=\"typescript\">import {useMemo} from \"react\"; import {Events, formMachineFactory, FormMachineFactoryParams} from \".\/form-machine\"; import {useMachine} from \"@xstate\/react\";  export default function useFormMachine(formConfig: FormMachineFactoryParams) {     \/\/ \u041f\u0440\u0438 \u043f\u0435\u0440\u0435\u0440\u0435\u043d\u0434\u0435\u0440\u0435 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f     const machine = useMemo(() => formMachineFactory(formConfig), []);     const [state, send] = useMachine(machine);      function getError(field: string) {         const error = state.context.dataEntryErrors[field];         return error &amp;&amp; (error.message || `${field} error`);     }      function sendEvent(config: { field: string, type: Events, event: any }) {         const {type, event, field} = config;         send({             type,             data: {                 value: event.target.value,                 field,             }         });     }      function onBlur(event:any, field) {         sendEvent({             type: Events.BLUR_DATA,             event,             field,         });     }      function onChange(event:any, field:string) {         sendEvent({             type: Events.ENTER_DATA,             event,             field,         });     }      return {         machine,         state,         send,         sendEvent,         onBlur,         onChange,         getError,     } } <\/code><\/pre>\n<p>\u0425\u0443\u043a \u0434\u043b\u044f login machine. \u0422\u0443\u0442 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433 \u043f\u043e\u043b\u0435\u0439 \u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043c\u0430\u0448\u0438\u043d\u0443. <\/p>\n<pre><code class=\"typescript\">import {     FormFieldConfigValidationResult,     FormMachineFactoryParams } from \"..\/..\/shared\/forms\/form-machine\"; import {authStore} from \"..\/..\/shared\/stores\/Auth\"; import useFormMachine from \"..\/..\/shared\/forms\/useFormMachine\";  export default function useLoginMachine() {     const formConfig: FormMachineFactoryParams = {         fields: [             {                 field: 'email',                 required: true,                 validator: (value: string): FormFieldConfigValidationResult => {                     if (!value) {                         return {                             result: false,                             errorMessage: 'Please Enter Email'                         }                     }                     let regexEmail = \/^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(\\.\\w{2,3})+$\/;                      return {                         result: !!value.match(regexEmail),                         errorMessage: 'Email wrong format'                     }                 }             },             {                 field: 'password',                 required: true,                 validator: (value: string) => {                     return {                         result: value?.length >= 4,                         errorMessage: 'Password length is less then 4'                     }                 }             },         ],         onSubmit: (context) => authStore.auth(context.data.email, context.data.password),         onDone: () => {             console.log('Auth!');         }     }      return {         ...useFormMachine(formConfig)     } }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 LoginForm. \u0424\u043e\u0440\u043c\u0443 \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d. \u0422\u0430\u043a \u0436\u0435 \u0434\u043b\u044f \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e\u0441\u0442\u0438 \u0432\u044b\u0432\u0435\u0434\u0435\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043c\u0430\u0448\u0438\u043d\u044b. <\/p>\n<pre><code class=\"typescript\">import React, {useMemo} from 'react' import {useMachine} from '@xstate\/react'; import {     Events,     FormFieldConfigValidationResult,     formMachineFactory,     FormMachineFactoryParams,     States } from \"..\/..\/shared\/forms\/form-machine\"; import FormInput from \"..\/..\/shared\/forms\/FormInput\"; import {authStore} from \"..\/..\/shared\/stores\/Auth\"; import {observer} from \"mobx-react\"; import useLoginMachine from \".\/useLoginMachine\";  export default observer(() => {      const {state, send, onBlur, onChange, getError} = useLoginMachine();      return (&lt;div>             {state.matches(States.success) ?                 &lt;>                     User logged in                     &lt;pre>{JSON.stringify(authStore.user, null, \"\\t\")}&lt;\/pre>                 &lt;\/>                 : &lt;>                     &lt;div style={{textAlign: \"center\"}}>                         &lt;div style={{marginBottom: \"1rem\"}}>                             Current State is &lt;b>{state.value.toString()}&lt;\/b>                         &lt;\/div>                         &lt;FormInput                             onBlur={(e) => onBlur(e, 'email')}                             onChange={(e) => onChange(e, 'email')}                             error={getError('email')}                         \/>                         &lt;FormInput                             onBlur={(e) => onBlur(e, 'password')}                             onChange={(e) => onChange(e, 'password')}                             error={getError('password')}                         \/>                          {state.matches(States.awaitingResponse) ? 'Loading...' :                             &lt;button disabled={!state.context.canSubmit &amp;&amp; !state.matches(States.serviceError)}                                     onClick={() => send(Events.SUBMIT)}>Login&lt;\/button>}                          {state.matches(States.serviceError) &amp;&amp; &lt;div style={{color: 'red'}}>                             Service error: {state.context.serviceErrors['error.platform']}. Try again.&lt;\/div>}                      &lt;\/div>                     &lt;pre>{JSON.stringify(state.context, null, 4)}&lt;\/pre>                 &lt;\/>             }         &lt;\/div>     ) })<\/code><\/pre>\n<h4>4. \u0422\u0443\u0442 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0449\u0443\u043f\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/h4>\n<p><a href=\"https:\/\/codesandbox.io\/s\/sparkling-cloud-zzpxhp?from-embed\" rel=\"noopener noreferrer nofollow\">https:\/\/codesandbox.io\/s\/sparkling-cloud-zzpxhp?from-embed<\/a> \u0438\u043b\u0438 \u0441\u0442\u044f\u043d\u0443\u0442\u044c \u0441\u00a0<a href=\"https:\/\/github.com\/BEARlogin\/xstate-demo-app\" rel=\"noopener noreferrer nofollow\">\u0433\u0438\u0442\u0445\u0430\u0431\u0447\u0438\u043a\u0430<\/a><\/p>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0432\u044b\u0434\u0430\u0441\u0442 \u043e\u0448\u0438\u0431\u043a\u0443, \u0432\u0442\u043e\u0440\u0430\u044f success<\/p>\n<h4>5. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439<\/h4>\n<ul>\n<li>\n<p>[x]  \u0424\u043e\u0440\u043c\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0432\u043e\u0434\u0430<\/p>\n<\/li>\n<li>\n<p>[x]  \u0424\u043e\u0440\u043c\u0430 \u0432\u0445\u043e\u0434\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438, \u043f\u043e\u043a\u0430 \u043f\u043e\u043b\u0435 \u043d\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043e<\/p>\n<\/li>\n<li>\n<p>[x]  \u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u0440\u043e\u043f\u0430\u0434\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>[x]  \u041f\u043e\u043b\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u044f\u0432\u043d\u043e (\u0441 \u043f\u043e\u043a\u0430\u0437\u043e\u043c \u043e\u0448\u0438\u0431\u043a\u0438) \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 blur \u0441\u043e\u0431\u044b\u0442\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>[x]  \u041d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0443, \u043f\u043e\u043a\u0430 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438<\/p>\n<\/li>\n<li>\n<p>[x]  \u041f\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u044f\u0432\u043d\u043e \u043f\u0440\u0438 \u0432\u0432\u043e\u0434\u0435, \u0434\u043b\u044f \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f 5<\/p>\n<\/li>\n<li>\n<p>[x]  \u041f\u043e\u0441\u043b\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b, \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0438\u0441\u0447\u0435\u0437\u0430\u0442\u044c \u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0444\u043e\u0440\u043c\u044b<\/p>\n<\/li>\n<li>\n<p>[x]  \u041e\u0448\u0438\u0431\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u043c \u0432\u0438\u0434\u0435 \u0438 \u043d\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0444\u043e\u0440\u043c\u044b<\/p>\n<\/li>\n<li>\n<p>[x]  \u041f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u0436\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c, \u0432\u0441\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u2014 PROFIT!<\/strong><\/p>\n<h3>\u0412\u044b\u0432\u043e\u0434<\/h3>\n<p>\u041c\u044b \u0432\u0438\u0434\u0438\u043c, \u0447\u0442\u043e XState \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u043d\u0430\u0434 interaction logic, \u0443\u043b\u0443\u0447\u0448\u0430\u0435\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430.<\/p>\n<p>\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0443 \u0441 XState \u0438 \u043f\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0441 delayed events and transitions, parallel \u0438 hierarchical state nodes, \u0430 \u0442\u0430\u043a \u0436\u0435 \u043a\u043e\u0441\u043d\u0435\u043c\u0441\u044f \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u0432 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<p>\u041e \u0441\u0435\u0431\u0435<\/p>\n<ul>\n<li>\n<p>CTO \u0432 HR-tech \u0441\u0442\u0430\u0440\u0442\u0430\u043f\u0435 Huntica <sup><sub>[\u0441\u0441\u044b\u043b\u043a\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0430 \u043c\u043e\u0434\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u043c]<\/sub><\/sup><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/t.me\/bearlogin_dev\" rel=\"noopener noreferrer nofollow\">\u0412\u0435\u0434\u0443 \u0430\u0432\u0442\u043e\u0440\u0441\u043a\u0438\u0439 \u043a\u0430\u043d\u0430\u043b \u0432 \u0442\u0435\u043b\u0435\u0433\u0435<\/a> <\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/solvery.io\/ru\/mentor\/bearlogin\" rel=\"noopener noreferrer nofollow\">\u041c\u0435\u043d\u0442\u043e\u0440\u044e \u0437\u0434\u0435\u0441\u044c<\/a><\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/675672\/\"> https:\/\/habr.com\/ru\/post\/675672\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h3>\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435<\/h3>\n<p>\u0412\u043e\u043f\u0440\u0435\u043a\u0438 \u043c\u043d\u0435\u043d\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f \u0432\u0438\u0434\u0435\u043b \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0445 \u043a \u0441\u0442\u0430\u0442\u044c\u044f\u043c \u043e \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0445 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0430\u0445, \u044f \u043d\u0435 \u0441\u0447\u0438\u0442\u0430\u044e, \u0447\u0442\u043e \u0438\u0445 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u044d\u0442\u043e \u043a\u0430\u043a\u043e\u0439-\u0442\u043e &#171;\u0441\u043f\u0430\u0433\u0435\u0442\u0442\u0438&#187;. \u041d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u043e\u043d\u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u0443\u044e state transition \u043b\u043e\u0433\u0438\u043a\u0443.<\/p>\n<p>\u0425\u043e\u0442\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442 \u0438\u0437 100500 \u0441\u0442\u0435\u0439\u0442\u043e\u0432 \u0441 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u043c\u0438 \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u043c \u0441\u0442\u0435\u0439\u0442\u0430\u043c\u0438, \u043f\u043e\u0436\u0430\u043b\u0443\u0439, \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0430\u0431\u043e \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u043c. \u041d\u043e \u044d\u0442\u043e \u0443\u0436\u0435 \u0442\u0435\u043c\u0430 \u0434\u043b\u044f Separation of Concerns &#8212; \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 <a href=\"https:\/\/xstate.js.org\/\" rel=\"noopener noreferrer nofollow\">XState<\/a>.<\/p>\n<p>\u041a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043a\u0430\u043a \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u044d\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u044b, \u0442\u0430\u043a \u0438 \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0432 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0435 \u0438 \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<h3>XState<\/h3>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437 \u0441 XState \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <a href=\"https:\/\/www.npmjs.com\/package\/vue-kanban\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u0443 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443<\/a>.<\/p>\n<p>\u041d\u043e \u0432 \u0442\u043e\u0442 \u0440\u0430\u0437 \u0437\u043d\u0430\u043a\u043e\u043c\u0441\u0442\u0432\u043e \u043a\u0430\u043a \u0442\u043e \u043d\u0435 \u0437\u0430\u0434\u0430\u043b\u043e\u0441\u044c, \u043a\u0430\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0441\u044f, \u043d\u0435 \u0437\u0430\u0448\u043b\u043e<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0437 \u2014 \u0432<a href=\"https:\/\/khalilstemmler.com\/articles\/client-side-architecture\/introduction\/\" rel=\"noopener noreferrer nofollow\"> \u0446\u0438\u043a\u043b\u0435 \u0441\u0442\u0430\u0442\u0435\u0439 Khalil Stemmler<\/a>, \u0435\u0433\u043e \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u043b\u043e\u0441\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0441\u043b\u043e\u0435 <strong>Interaction logic.<\/strong><\/p>\n<p>\u0418 \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u044f \u0440\u0435\u0448\u0438\u043b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0432 \u0442\u0435\u043c\u0435, \u0442\u0435\u043c \u0431\u043e\u043b\u0435\u0435 \u0438\u0441\u043a\u0430\u043b \u0442\u0435\u043c\u0443 \u0434\u043b\u044f \u0432\u0435\u0431\u0438\u043d\u0430\u0440\u0430.<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0442\u0435\u043e\u0440\u0438\u0438 \u0438\u0437 \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0438 (\u043f\u0440\u0438\u0437\u043d\u0430\u044e\u0441\u044c, \u043f\u0440\u043e\u0433\u0443\u043b\u0438\u0432\u0430\u043b \u043b\u0435\u043a\u0446\u0438\u0438 \u0432 \u0443\u043d\u0438\u0432\u0435\u0440\u0435, \u0445\u0437 \u043a\u0430\u043a \u0441\u0434\u0430\u043b&#8230;)<\/p>\n<blockquote>\n<p>\u041a\u043e\u043d\u0435\u0301\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0301\u0442 (\u041a\u0410) \u0432 \u0442\u0435\u043e\u0440\u0438\u0438 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u0432 \u2014 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f, \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0438\u043c\u0435\u044e\u0449\u0435\u0433\u043e \u043e\u0434\u0438\u043d \u0432\u0445\u043e\u0434, \u043e\u0434\u0438\u043d \u0432\u044b\u0445\u043e\u0434 \u0438 \u0432 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e\u0441\u044f \u0432 \u043e\u0434\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u0438\u0437 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445. \u042f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u0430\u0441\u0442\u043d\u044b\u043c \u0441\u043b\u0443\u0447\u0430\u0435\u043c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0430, \u0447\u0438\u0441\u043b\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043a\u043e\u043d\u0435\u0447\u043d\u043e.<\/p>\n<\/blockquote>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e, XState \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u044d\u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u041a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442 \u0438\u043b\u0438, \u0435\u0441\u043b\u0438 \u043d\u0430 \u043b\u0430\u0442\u044b\u043d\u0438 &#8212; Finite State Machine<\/p>\n<p>\u042d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043c\u043d\u0435 \u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u0442\u0435\u043c\u043e\u0439 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043b\u043e\u0433\u0438\u043a\u0438 \u0444\u043e\u0440\u043c \u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0443\u0439\u0442\u0438 \u043e\u0442 \u043a\u0443\u0447\u0438 useState \u0438 useEffect&#8230;<\/p>\n<p>\u041d\u043e \u043b\u0443\u0447\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0435\u043e\u0440\u0438\u044e \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435.<\/p>\n<h3>\u041f\u0440\u0430\u043a\u0442\u0438\u043a\u0430<\/h3>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u2014 \u0444\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>\u041a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u044b, \u0447\u0442\u043e \u0442\u0430\u043c \u0434\u0435\u043b\u0430\u0442\u044c? 2 \u0438\u043d\u043f\u0443\u0442\u0430, \u043a\u043d\u043e\u043f\u043a\u0430, \u0434\u0435\u0440\u043d\u0443\u043b\u0438 API, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043b\u0438 &#8212; PROFIT!<\/p>\n<p>\u041d\u043e \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u0444\u0438\u0447\u0438:<\/p>\n<ol>\n<li>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0432\u043e\u0434\u0430<\/p>\n<\/li>\n<li>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0432\u0445\u043e\u0434\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u043f\u043e\u043a\u0430 \u043f\u043e\u043b\u0435 \u043d\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043e<\/p>\n<\/li>\n<li>\n<p>\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u0440\u043e\u043f\u0430\u0434\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f\u00a0<strong>\u044f\u0432\u043d\u043e<\/strong>\u00a0(\u0441 \u043f\u043e\u043a\u0430\u0437\u043e\u043c \u043e\u0448\u0438\u0431\u043a\u0438) \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 blur \u0441\u043e\u0431\u044b\u0442\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0443, \u043f\u043e\u043a\u0430 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f\u00a0<strong>\u043d\u0435 \u044f\u0432\u043d\u043e<\/strong>, \u0434\u043b\u044f \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f 5<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b, \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0438\u0441\u0447\u0435\u0437\u0430\u0442\u044c \u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0444\u043e\u0440\u043c\u044b<\/p>\n<\/li>\n<li>\n<p>\u041e\u0448\u0438\u0431\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u043c \u0432\u0438\u0434\u0435 \u0438 \u043d\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0444\u043e\u0440\u043c\u044b<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u0436\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a. 9 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0437 2 \u043f\u043e\u043b\u0435\u0439 \u0431\u0435\u0437 \u0432\u0441\u044f\u043a\u0438\u0445 \u0442\u0430\u043c 2FA. \u0418 \u0435\u0441\u043b\u0438 \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u0442\u044c\u0441\u044f, \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0438 \u0435\u0449\u0435.<\/p>\n<p>\u041d\u0443, \u0432\u0440\u0435\u043c\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434.<\/p>\n<h4>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430<\/h4>\n<p>\u041d\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434 \u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f Create React App \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438.<\/p>\n<pre><code>    yarn create react-app xstate-demo --template typescript     cd xstate-demo     yarn install     yarn add xstate @xstate\/react mobx mobx-react <\/code><\/pre>\n<h4>\u041f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u043c \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443<\/h4>\n<p>\u0412\u043e\u0437\u044c\u043c\u0435\u043c \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u043d\u0443\u044e feature sliced \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<ul>\n<li>\n<p>\u0412 shared \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u043e\u0431\u0449\u0438\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b: \u0444\u043e\u0440\u043c \u0438\u043d\u043f\u0443\u0442\u044b, \u0444\u043e\u0440\u043c \u043c\u0430\u0448\u0438\u043d\u0443, \u0441\u0442\u043e\u0440\u044b, \u0438 \u0442\u0438\u043f\u044b<\/p>\n<\/li>\n<li>\n<p>\u0412 features \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043e\u0434\u043d\u0430 \u0444\u0438\u0447\u0430 \u2014 login. \u0421\u0443\u0442\u044c \u0444\u0438\u0447\u0435\u0439 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0444\u0438\u0447\u0438 \u043d\u0435 \u0442\u044f\u043d\u0443\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u0437 \u043d\u0435\u0435. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0444\u0438\u0447\u0443, \u0432\u044b\u0437\u043e\u0432 \u0435\u0435 root \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u0444\u0438\u0447\u0443, \u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u043f\u043e\u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442.<\/p>\n<\/li>\n<\/ul>\n<h4>\u041f\u0438\u0448\u0435\u043c \u043a\u043e\u0434<\/h4>\n<h4>1. \u041f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u0430\u0448\u0438\u043d\u0443<\/h4>\n<p>\u041c\u044b \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0443\u044e \u043c\u0430\u0448\u0438\u043d\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438 \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0444\u043e\u0440\u043c\u0430\u0445.<\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 actions, \u0430 \u0442\u0430\u043a \u0436\u0435 \u0442\u0438\u043f\u044b<\/p>\n<pre><code class=\"typescript\">export enum States {     dataEntry = 'dataEntry',     awaitingResponse = 'awaitingResponse',     dataEntryError = 'dataEntryError',     serviceError = 'serviceError',     success = 'success', }  export enum Events {     ENTER_DATA = 'ENTER_DATA',     BLUR_DATA = 'BLUR_DATA',     SUBMIT = 'SUBMIT', }  export enum Actions {     setField = 'setField', }<\/code><\/pre>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u043c\u0430\u0448\u0438\u043d\u044b<\/p>\n<pre><code class=\"typescript\">export interface FormFieldConfigValidationResult {     result: boolean,     errorMessage?: string }  export interface FormFieldConfig {     required: boolean,     field: string,     validator?: (value: any) => FormFieldConfigValidationResult }  export interface FormFieldErrors {     [field: string]: {         message: string     } }   export type FormMachineContext = {     data: any,     dataEntryErrors: FormFieldErrors,     serviceErrors: any,     fields: FormFieldConfig[],     canSubmit: boolean, }<\/code><\/pre>\n<h4>2. \u041a\u043e\u043d\u0444\u0438\u0433 \u043c\u0430\u0448\u0438\u043d\u044b<\/h4>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c factory \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u0430\u0448\u0438\u043d\u044b \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u043c<\/p>\n<pre><code class=\"typescript\">export interface FormMachineFactoryParams {     fields: FormFieldConfig[],     onSubmit: (context: FormMachineContext) => Promise&lt;any>,     onDone: (data: any) => any }  export const formMachineFactory = ({    fields, onSubmit, onDone, }: FormMachineFactoryParams) => {     return createMachine({         id: 'login',         initial: States.dataEntry,         context: {             data: {},             dataEntryErrors: {} as FormErrors,             serviceErrors: {} as FormErrors,             fields,             canSubmit: false,         } as FormMachineContext,         states: {},         actions: {},         guards: {}     }) }<\/code><\/pre>\n<p>\u041f\u0440\u043e\u043f\u0438\u0448\u0435\u043c \u043f\u0435\u0440\u0432\u044b\u0435 <strong>transitions<\/strong> \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u0421\u0443\u0442\u044c \u0442\u0430\u043a\u0430\u044f \u2014 \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u043c\u0441\u044f \u0432 \u0441\u0442\u0430\u0434\u0438\u0438 dataEntry, \u0442\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432\u0432\u043e\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u043e\u0442\u0435\u0440\u044e \u0444\u043e\u043a\u0443\u0441\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b.<\/p>\n<pre><code class=\"typescript\">... states: {     [States.dataEntry]: {         on: {             [Events.ENTER_DATA]: {                 actions: Actions.setField,             },             \/\/ \u043f\u0440\u0438 \u043f\u043e\u0442\u0435\u0440\u0435 \u0444\u043e\u043a\u0443\u0441\u0430 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043b\u0435 \u0438 \u0435\u0441\u043b\u0438 isInvalid \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c              \/\/ \u043c\u0430\u0448\u0438\u043d\u0443 \u0432 dataEntryError             [Events.BLUR_DATA]: [                 {cond: 'isInvalid', target: States.dataEntryError},             ],             [Events.SUBMIT]: {                 cond: 'canSubmitGuard',                 target: States.awaitingResponse             },         },     }, }, ... <\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043f\u0438\u0448\u0435\u043c <strong>actions<\/strong> &#8212; \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0432 \u0441\u0442\u0438\u043b\u0435 &#171;\u0432\u044b\u0441\u0442\u0440\u0435\u043b\u0438\u043b \u0438 \u0437\u0430\u0431\u044b\u043b&#187; <a href=\"https:\/\/xstate.js.org\/docs\/guides\/actions.html\" rel=\"noopener noreferrer nofollow\">https:\/\/xstate.js.org\/docs\/guides\/actions.html<\/a><\/p>\n<p>\u0412 \u044d\u0444\u0444\u0435\u043a\u0442\u0435 setField \u043c\u044b \u0434\u0430\u043d\u043d\u044b\u0435 \u0432\u0432\u043e\u0434\u0430 \u0438\u0437 \u0444\u043e\u0440\u043c \u0438\u043d\u043f\u0443\u0442\u043e\u0432 \u0438 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0432\u0441\u044e \u0444\u043e\u0440\u043c\u0443, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u044f\u0442\u044c, \u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438. \u0410 \u0442\u0430\u043a \u0436\u0435 \u043e\u0447\u0438\u0449\u0430\u0435\u043c context.dataEntryErrors \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0440\u0430\u0442\u044c \u0432\u044b\u0432\u043e\u0434 \u043e\u0448\u0438\u0431\u043a\u0438.<\/p>\n<pre><code class=\"typescript\">function canSubmit(context: FormMachineContext) {     if (Object.keys(context.fields).length === 0) {         return true;     }     return context.fields.reduce((acc, val) => {         const fieldValue = context.data[val.field];         const reqPredicate = val.required ? !!fieldValue : true;         return acc &amp;&amp;             (typeof val.validator === 'function' ?                 val.validator(context.data[val.field]).result : true)        &amp;&amp; reqPredicate     }, true); } ... actions: {     [Actions.setField]: (context, event) => {         context.data[event.data.field] = event.data.value;         delete context.dataEntryErrors[event.data.field];         context.canSubmit = canSubmit(context);     }, }, ... <\/code><\/pre>\n<p><strong>Guards<\/strong> \u2014 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u043e\u0432 \u0432 \u0441\u0442\u0430\u0434\u0438\u0438. \u0418\u0445 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c, \u0447\u0442\u043e\u0431\u044b \u044f\u0432\u043d\u043e \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0435 \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432 \u0441\u0442\u0435\u0439\u0442 dataEntryError. <a href=\"https:\/\/xstate.js.org\/docs\/guides\/guards.html#guards-condition-functions\" rel=\"noopener noreferrer nofollow\">https:\/\/xstate.js.org\/docs\/guides\/guards.html#guards-condition-functions<\/a><\/p>\n<pre><code class=\"typescript\">... guards: {     isInvalid: (context, event) => {         if (!event.data.value) {             return false;         }          const field = context.fields.find(f => f.field === event.data.field);          if (!field) {             return false;         }          const res: FormFieldConfigValidationResult = field.validator ? field.validator(event.data.value) : {             result: true         };          if (!res.result) {             context.dataEntryErrors[event.data.field] = {                 message: res.errorMessage,             };         }         return !res.result     },     canSubmitGuard: (context) => {         return context.canSubmit;     } } ...<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043e\u043f\u0438\u0448\u0435\u043c \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0430\u0434\u0438\u0438. \u0412 \u0441\u0442\u0435\u0439\u0442\u0435 awaitingResponse \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c <strong>service<\/strong> &#8212; \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u044d\u0444\u0444\u0435\u043a\u0442. \u041e\u043d \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c Promise, Callback, Observables \u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u043c\u0430\u0448\u0438\u043d\u043e\u0439. <a href=\"https:\/\/xstate.js.org\/docs\/guides\/communication.html#the-invoke-property\" rel=\"noopener noreferrer nofollow\">https:\/\/xstate.js.org\/docs\/guides\/communication.html#the-invoke-property<\/a><\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 \u044d\u0442\u043e Promise. <\/p>\n<pre><code class=\"typescript\">states: {     ...     [States.awaitingResponse]: {         id: 'submit',         \/\/\u044d\u0442\u043e \u0441\u0435\u0440\u0432\u0438\u0441 - \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u044d\u0444\u0444\u0435\u043a\u0442         invoke: {             src: (context) => {                 return onSubmit(context);             },             onDone: {                 target: States.success             },             onError: [                 {                     actions: (context, event) => {                         context.serviceErrors[event.type] = event.data;                     },                     target: States.serviceError                 }             ]         }     },     [States.dataEntryError]: {         on: {             [Events.ENTER_DATA]: {                 \/\/ \u041f\u0440\u0438 \u0432\u0432\u043e\u0434\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043a\u0430\u043a \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0442\u0430\u043a \u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c state                 actions: Actions.setField,                 target: States.dataEntry             },         }     },     [States.serviceError]: {         on: {             [Events.SUBMIT]: {                 target: States.awaitingResponse             },             [Events.ENTER_DATA]: {                 \/\/ \u041f\u0440\u0438 \u0432\u0432\u043e\u0434\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043a\u0430\u043a \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0442\u0430\u043a \u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c state                 actions: Actions.setField,                 target: States.dataEntry             },         }     },     [States.success]: {         type: 'final',         onDone: {             actions: onDone         },     }, ... <\/code><\/pre>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0447\u0438\u0442\u0430\u0435\u043c\u0443\u044e, \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u0443\u044e, \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u0443\u044e form logic. \u041a\u043e\u0442\u043e\u0440\u0443\u044e \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432 Vue \u0438\u043b\u0438 Svelte, \u0438\u043b\u0438 \u0432\u043e\u043e\u0431\u0449\u0435 \u0432 \u0432\u0430\u043d\u0438\u043b\u0435.<\/p>\n<p><a href=\"https:\/\/github.com\/BEARlogin\/xstate-demo-app\/blob\/master\/src\/shared\/forms\/form-machine.ts\" rel=\"noopener noreferrer nofollow\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u043c\u0430\u0448\u0438\u043d\u044b<\/a><\/p>\n<p>\u041a\u0440\u0443\u0442\u044b\u043c \u0431\u043e\u043d\u0443\u0441\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u0442\u0435\u0439\u0442\u0447\u0430\u0440\u0442\u044b \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 <a href=\"https:\/\/stately.ai\/viz\" rel=\"noopener noreferrer nofollow\">Visualizer<\/a> \u0438\u043b\u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0430\u0448\u0438\u043d\u044b \u0447\u0435\u0440\u0435\u0437 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0439\u00a0<a href=\"https:\/\/stately.ai\/registry\/new\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440<\/a><\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h4>3. \u041f\u0438\u0448\u0435\u043c store \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u0442\u043e\u0440 \u0438 \u044d\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a Api. \u041f\u0435\u0440\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u0448\u0438\u0431\u043e\u0447\u043d\u044b\u043c.<\/p>\n<pre><code class=\"typescript\">import {makeAutoObservable} from \"mobx\"; import {User} from \"..\/types\/User\";   class AuthStore {     user?: User     error: boolean = true;      constructor() {         makeAutoObservable(this);     }      async auth(login, password) {         this.user =<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-335413","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/335413","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=335413"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/335413\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=335413"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=335413"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=335413"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}