reflex는 프론트엔드와 백엔드의 구분이 다소 모호(?)한 pure-python fullstack webframework이다.
reflex로 만드는 웹앱은 기본적으로 State라는 클래스 하나, 그리고 index라는 함수 하나로 구성된다.
이를 이해하기 위해서는 가장 간단한 앱 하나를 만들어보는 것이 좋다.
우선 State 없이 간단한 페이지함수인 index를 만들어보자.
import reflex as rx
def index():
return rx.heading("Hello world!")
app = rx.App(state=State)
app.add_page(index)
app.compile()
python 코드로 구성된 간단한 소스이지만,
이는 리플렉스를 구성하고 있는
node.js, React, Next.js, Chakra-ui, FastAPI 등 다양한 프론트/백엔드 프레임워크를 통해
구체적인 html과 자바스크립트 코드로 컴파일되어 배포된다.
(화면은 아래와 같다)
(.web 폴더를 통해 컴파일 결과를 확인할 수 있지만, 추천하지는 않는다.)
위 소스코드는 어떻게 컴파일되는지 궁금하신 분들을 위해.. 아래 첨부해놓는다.
<!DOCTYPE html>
<html>
<head>
<style data-next-hide-fouc="true">body {
display: none
}</style>
<noscript data-next-hide-fouc="true">
<style>body {
display: block
}</style>
</noscript>
<meta charSet="utf-8"/>
<meta name="viewport" content="width=device-width"/>
<title>Reflex App</title>
<meta content="A Reflex app." name="description"/>
<meta content="favicon.ico" property="og:image"/>
<meta name="next-head-count" content="5"/>
<noscript data-n-css=""></noscript>
<script defer="" nomodule="" src="/_next/static/chunks/polyfills.js?ts=1696314172501"></script>
<script src="/_next/static/chunks/webpack.js?ts=1696314172501" defer=""></script>
<script src="/_next/static/chunks/main.js?ts=1696314172501" defer=""></script>
<script src="/_next/static/chunks/pages/_app.js?ts=1696314172501" defer=""></script>
<script src="/_next/static/chunks/pages/index.js?ts=1696314172501" defer=""></script>
<script src="/_next/static/development/_buildManifest.js?ts=1696314172501" defer=""></script>
<script src="/_next/static/development/_ssgManifest.js?ts=1696314172501" defer=""></script>
<noscript id="__next_css__DO_NOT_USE__"></noscript>
</head>
<body>
<script id="chakra-script">!(function () {
try {
var a = function (c) {
var v = "(prefers-color-scheme: dark)", h = window.matchMedia(v).matches ? "dark" : "light",
r = c === "system" ? h : c, o = document.documentElement, s = document.body, l = "chakra-ui-light",
d = "chakra-ui-dark", i = r === "dark";
return s.classList.add(i ? d : l), s.classList.remove(i ? l : d), o.style.colorScheme = r, o.dataset.theme = r, r
}, n = a, m = "light", e = "chakra-ui-color-mode", t = localStorage.getItem(e);
t ? a(t) : localStorage.setItem(e, a(m))
} catch (a) {
}
})();</script>
<div id="__next">
<style data-emotion="css-global 1b7scut">:host, :root, [data-theme] {
--chakra-ring-inset: var(--chakra-empty, /*!*/ /*!*/);
--chakra-ring-offset-width: 0px;
--chakra-ring-offset-color: #fff;
--chakra-ring-color: rgba(66, 153, 225, 0.6);
--chakra-ring-offset-shadow: 0 0 #0000;
--chakra-ring-shadow: 0 0 #0000;
--chakra-space-x-reverse: 0;
--chakra-space-y-reverse: 0;
--chakra-colors-transparent: transparent;
--chakra-colors-current: currentColor;
--chakra-colors-black: #000000;
--chakra-colors-white: #FFFFFF;
--chakra-colors-whiteAlpha-50: rgba(255, 255, 255, 0.04);
--chakra-colors-whiteAlpha-100: rgba(255, 255, 255, 0.06);
--chakra-colors-whiteAlpha-200: rgba(255, 255, 255, 0.08);
--chakra-colors-whiteAlpha-300: rgba(255, 255, 255, 0.16);
--chakra-colors-whiteAlpha-400: rgba(255, 255, 255, 0.24);
--chakra-colors-whiteAlpha-500: rgba(255, 255, 255, 0.36);
--chakra-colors-whiteAlpha-600: rgba(255, 255, 255, 0.48);
--chakra-colors-whiteAlpha-700: rgba(255, 255, 255, 0.64);
--chakra-colors-whiteAlpha-800: rgba(255, 255, 255, 0.80);
--chakra-colors-whiteAlpha-900: rgba(255, 255, 255, 0.92);
--chakra-colors-blackAlpha-50: rgba(0, 0, 0, 0.04);
--chakra-colors-blackAlpha-100: rgba(0, 0, 0, 0.06);
--chakra-colors-blackAlpha-200: rgba(0, 0, 0, 0.08);
--chakra-colors-blackAlpha-300: rgba(0, 0, 0, 0.16);
--chakra-colors-blackAlpha-400: rgba(0, 0, 0, 0.24);
--chakra-colors-blackAlpha-500: rgba(0, 0, 0, 0.36);
--chakra-colors-blackAlpha-600: rgba(0, 0, 0, 0.48);
--chakra-colors-blackAlpha-700: rgba(0, 0, 0, 0.64);
--chakra-colors-blackAlpha-800: rgba(0, 0, 0, 0.80);
--chakra-colors-blackAlpha-900: rgba(0, 0, 0, 0.92);
--chakra-colors-gray-50: #F7FAFC;
--chakra-colors-gray-100: #EDF2F7;
--chakra-colors-gray-200: #E2E8F0;
--chakra-colors-gray-300: #CBD5E0;
--chakra-colors-gray-400: #A0AEC0;
--chakra-colors-gray-500: #718096;
--chakra-colors-gray-600: #4A5568;
--chakra-colors-gray-700: #2D3748;
--chakra-colors-gray-800: #1A202C;
--chakra-colors-gray-900: #171923;
--chakra-colors-red-50: #FFF5F5;
--chakra-colors-red-100: #FED7D7;
--chakra-colors-red-200: #FEB2B2;
--chakra-colors-red-300: #FC8181;
--chakra-colors-red-400: #F56565;
--chakra-colors-red-500: #E53E3E;
--chakra-colors-red-600: #C53030;
--chakra-colors-red-700: #9B2C2C;
--chakra-colors-red-800: #822727;
--chakra-colors-red-900: #63171B;
--chakra-colors-orange-50: #FFFAF0;
--chakra-colors-orange-100: #FEEBC8;
--chakra-colors-orange-200: #FBD38D;
--chakra-colors-orange-300: #F6AD55;
--chakra-colors-orange-400: #ED8936;
--chakra-colors-orange-500: #DD6B20;
--chakra-colors-orange-600: #C05621;
--chakra-colors-orange-700: #9C4221;
--chakra-colors-orange-800: #7B341E;
--chakra-colors-orange-900: #652B19;
--chakra-colors-yellow-50: #FFFFF0;
--chakra-colors-yellow-100: #FEFCBF;
--chakra-colors-yellow-200: #FAF089;
--chakra-colors-yellow-300: #F6E05E;
--chakra-colors-yellow-400: #ECC94B;
--chakra-colors-yellow-500: #D69E2E;
--chakra-colors-yellow-600: #B7791F;
--chakra-colors-yellow-700: #975A16;
--chakra-colors-yellow-800: #744210;
--chakra-colors-yellow-900: #5F370E;
--chakra-colors-green-50: #F0FFF4;
--chakra-colors-green-100: #C6F6D5;
--chakra-colors-green-200: #9AE6B4;
--chakra-colors-green-300: #68D391;
--chakra-colors-green-400: #48BB78;
--chakra-colors-green-500: #38A169;
--chakra-colors-green-600: #2F855A;
--chakra-colors-green-700: #276749;
--chakra-colors-green-800: #22543D;
--chakra-colors-green-900: #1C4532;
--chakra-colors-teal-50: #E6FFFA;
--chakra-colors-teal-100: #B2F5EA;
--chakra-colors-teal-200: #81E6D9;
--chakra-colors-teal-300: #4FD1C5;
--chakra-colors-teal-400: #38B2AC;
--chakra-colors-teal-500: #319795;
--chakra-colors-teal-600: #2C7A7B;
--chakra-colors-teal-700: #285E61;
--chakra-colors-teal-800: #234E52;
--chakra-colors-teal-900: #1D4044;
--chakra-colors-blue-50: #ebf8ff;
--chakra-colors-blue-100: #bee3f8;
--chakra-colors-blue-200: #90cdf4;
--chakra-colors-blue-300: #63b3ed;
--chakra-colors-blue-400: #4299e1;
--chakra-colors-blue-500: #3182ce;
--chakra-colors-blue-600: #2b6cb0;
--chakra-colors-blue-700: #2c5282;
--chakra-colors-blue-800: #2a4365;
--chakra-colors-blue-900: #1A365D;
--chakra-colors-cyan-50: #EDFDFD;
--chakra-colors-cyan-100: #C4F1F9;
--chakra-colors-cyan-200: #9DECF9;
--chakra-colors-cyan-300: #76E4F7;
--chakra-colors-cyan-400: #0BC5EA;
--chakra-colors-cyan-500: #00B5D8;
--chakra-colors-cyan-600: #00A3C4;
--chakra-colors-cyan-700: #0987A0;
--chakra-colors-cyan-800: #086F83;
--chakra-colors-cyan-900: #065666;
--chakra-colors-purple-50: #FAF5FF;
--chakra-colors-purple-100: #E9D8FD;
--chakra-colors-purple-200: #D6BCFA;
--chakra-colors-purple-300: #B794F4;
--chakra-colors-purple-400: #9F7AEA;
--chakra-colors-purple-500: #805AD5;
--chakra-colors-purple-600: #6B46C1;
--chakra-colors-purple-700: #553C9A;
--chakra-colors-purple-800: #44337A;
--chakra-colors-purple-900: #322659;
--chakra-colors-pink-50: #FFF5F7;
--chakra-colors-pink-100: #FED7E2;
--chakra-colors-pink-200: #FBB6CE;
--chakra-colors-pink-300: #F687B3;
--chakra-colors-pink-400: #ED64A6;
--chakra-colors-pink-500: #D53F8C;
--chakra-colors-pink-600: #B83280;
--chakra-colors-pink-700: #97266D;
--chakra-colors-pink-800: #702459;
--chakra-colors-pink-900: #521B41;
--chakra-colors-linkedin-50: #E8F4F9;
--chakra-colors-linkedin-100: #CFEDFB;
--chakra-colors-linkedin-200: #9BDAF3;
--chakra-colors-linkedin-300: #68C7EC;
--chakra-colors-linkedin-400: #34B3E4;
--chakra-colors-linkedin-500: #00A0DC;
--chakra-colors-linkedin-600: #008CC9;
--chakra-colors-linkedin-700: #0077B5;
--chakra-colors-linkedin-800: #005E93;
--chakra-colors-linkedin-900: #004471;
--chakra-colors-facebook-50: #E8F4F9;
--chakra-colors-facebook-100: #D9DEE9;
--chakra-colors-facebook-200: #B7C2DA;
--chakra-colors-facebook-300: #6482C0;
--chakra-colors-facebook-400: #4267B2;
--chakra-colors-facebook-500: #385898;
--chakra-colors-facebook-600: #314E89;
--chakra-colors-facebook-700: #29487D;
--chakra-colors-facebook-800: #223B67;
--chakra-colors-facebook-900: #1E355B;
--chakra-colors-messenger-50: #D0E6FF;
--chakra-colors-messenger-100: #B9DAFF;
--chakra-colors-messenger-200: #A2CDFF;
--chakra-colors-messenger-300: #7AB8FF;
--chakra-colors-messenger-400: #2E90FF;
--chakra-colors-messenger-500: #0078FF;
--chakra-colors-messenger-600: #0063D1;
--chakra-colors-messenger-700: #0052AC;
--chakra-colors-messenger-800: #003C7E;
--chakra-colors-messenger-900: #002C5C;
--chakra-colors-whatsapp-50: #dffeec;
--chakra-colors-whatsapp-100: #b9f5d0;
--chakra-colors-whatsapp-200: #90edb3;
--chakra-colors-whatsapp-300: #65e495;
--chakra-colors-whatsapp-400: #3cdd78;
--chakra-colors-whatsapp-500: #22c35e;
--chakra-colors-whatsapp-600: #179848;
--chakra-colors-whatsapp-700: #0c6c33;
--chakra-colors-whatsapp-800: #01421c;
--chakra-colors-whatsapp-900: #001803;
--chakra-colors-twitter-50: #E5F4FD;
--chakra-colors-twitter-100: #C8E9FB;
--chakra-colors-twitter-200: #A8DCFA;
--chakra-colors-twitter-300: #83CDF7;
--chakra-colors-twitter-400: #57BBF5;
--chakra-colors-twitter-500: #1DA1F2;
--chakra-colors-twitter-600: #1A94DA;
--chakra-colors-twitter-700: #1681BF;
--chakra-colors-twitter-800: #136B9E;
--chakra-colors-twitter-900: #0D4D71;
--chakra-colors-telegram-50: #E3F2F9;
--chakra-colors-telegram-100: #C5E4F3;
--chakra-colors-telegram-200: #A2D4EC;
--chakra-colors-telegram-300: #7AC1E4;
--chakra-colors-telegram-400: #47A9DA;
--chakra-colors-telegram-500: #0088CC;
--chakra-colors-telegram-600: #007AB8;
--chakra-colors-telegram-700: #006BA1;
--chakra-colors-telegram-800: #005885;
--chakra-colors-telegram-900: #003F5E;
--chakra-borders-none: 0;
--chakra-borders-1px: 1px solid;
--chakra-borders-2px: 2px solid;
--chakra-borders-4px: 4px solid;
--chakra-borders-8px: 8px solid;
--chakra-fonts-heading: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--chakra-fonts-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--chakra-fonts-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--chakra-fontSizes-3xs: 0.45rem;
--chakra-fontSizes-2xs: 0.625rem;
--chakra-fontSizes-xs: 0.75rem;
--chakra-fontSizes-sm: 0.875rem;
--chakra-fontSizes-md: 1rem;
--chakra-fontSizes-lg: 1.125rem;
--chakra-fontSizes-xl: 1.25rem;
--chakra-fontSizes-2xl: 1.5rem;
--chakra-fontSizes-3xl: 1.875rem;
--chakra-fontSizes-4xl: 2.25rem;
--chakra-fontSizes-5xl: 3rem;
--chakra-fontSizes-6xl: 3.75rem;
--chakra-fontSizes-7xl: 4.5rem;
--chakra-fontSizes-8xl: 6rem;
--chakra-fontSizes-9xl: 8rem;
--chakra-fontWeights-hairline: 100;
--chakra-fontWeights-thin: 200;
--chakra-fontWeights-light: 300;
--chakra-fontWeights-normal: 400;
--chakra-fontWeights-medium: 500;
--chakra-fontWeights-semibold: 600;
--chakra-fontWeights-bold: 700;
--chakra-fontWeights-extrabold: 800;
--chakra-fontWeights-black: 900;
--chakra-letterSpacings-tighter: -0.05em;
--chakra-letterSpacings-tight: -0.025em;
--chakra-letterSpacings-normal: 0;
--chakra-letterSpacings-wide: 0.025em;
--chakra-letterSpacings-wider: 0.05em;
--chakra-letterSpacings-widest: 0.1em;
--chakra-lineHeights-3: .75rem;
--chakra-lineHeights-4: 1rem;
--chakra-lineHeights-5: 1.25rem;
--chakra-lineHeights-6: 1.5rem;
--chakra-lineHeights-7: 1.75rem;
--chakra-lineHeights-8: 2rem;
--chakra-lineHeights-9: 2.25rem;
--chakra-lineHeights-10: 2.5rem;
--chakra-lineHeights-normal: normal;
--chakra-lineHeights-none: 1;
--chakra-lineHeights-shorter: 1.25;
--chakra-lineHeights-short: 1.375;
--chakra-lineHeights-base: 1.5;
--chakra-lineHeights-tall: 1.625;
--chakra-lineHeights-taller: 2;
--chakra-radii-none: 0;
--chakra-radii-sm: 0.125rem;
--chakra-radii-base: 0.25rem;
--chakra-radii-md: 0.375rem;
--chakra-radii-lg: 0.5rem;
--chakra-radii-xl: 0.75rem;
--chakra-radii-2xl: 1rem;
--chakra-radii-3xl: 1.5rem;
--chakra-radii-full: 9999px;
--chakra-space-1: 0.25rem;
--chakra-space-2: 0.5rem;
--chakra-space-3: 0.75rem;
--chakra-space-4: 1rem;
--chakra-space-5: 1.25rem;
--chakra-space-6: 1.5rem;
--chakra-space-7: 1.75rem;
--chakra-space-8: 2rem;
--chakra-space-9: 2.25rem;
--chakra-space-10: 2.5rem;
--chakra-space-12: 3rem;
--chakra-space-14: 3.5rem;
--chakra-space-16: 4rem;
--chakra-space-20: 5rem;
--chakra-space-24: 6rem;
--chakra-space-28: 7rem;
--chakra-space-32: 8rem;
--chakra-space-36: 9rem;
--chakra-space-40: 10rem;
--chakra-space-44: 11rem;
--chakra-space-48: 12rem;
--chakra-space-52: 13rem;
--chakra-space-56: 14rem;
--chakra-space-60: 15rem;
--chakra-space-64: 16rem;
--chakra-space-72: 18rem;
--chakra-space-80: 20rem;
--chakra-space-96: 24rem;
--chakra-space-px: 1px;
--chakra-space-0-5: 0.125rem;
--chakra-space-1-5: 0.375rem;
--chakra-space-2-5: 0.625rem;
--chakra-space-3-5: 0.875rem;
--chakra-shadows-xs: 0 0 0 1px rgba(0, 0, 0, 0.05);
--chakra-shadows-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--chakra-shadows-base: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--chakra-shadows-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--chakra-shadows-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--chakra-shadows-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--chakra-shadows-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
--chakra-shadows-outline: 0 0 0 3px rgba(66, 153, 225, 0.6);
--chakra-shadows-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
--chakra-shadows-none: none;
--chakra-shadows-dark-lg: rgba(0, 0, 0, 0.1) 0px 0px 0px 1px, rgba(0, 0, 0, 0.2) 0px 5px 10px, rgba(0, 0, 0, 0.4) 0px 15px 40px;
--chakra-sizes-1: 0.25rem;
--chakra-sizes-2: 0.5rem;
--chakra-sizes-3: 0.75rem;
--chakra-sizes-4: 1rem;
--chakra-sizes-5: 1.25rem;
--chakra-sizes-6: 1.5rem;
--chakra-sizes-7: 1.75rem;
--chakra-sizes-8: 2rem;
--chakra-sizes-9: 2.25rem;
--chakra-sizes-10: 2.5rem;
--chakra-sizes-12: 3rem;
--chakra-sizes-14: 3.5rem;
--chakra-sizes-16: 4rem;
--chakra-sizes-20: 5rem;
--chakra-sizes-24: 6rem;
--chakra-sizes-28: 7rem;
--chakra-sizes-32: 8rem;
--chakra-sizes-36: 9rem;
--chakra-sizes-40: 10rem;
--chakra-sizes-44: 11rem;
--chakra-sizes-48: 12rem;
--chakra-sizes-52: 13rem;
--chakra-sizes-56: 14rem;
--chakra-sizes-60: 15rem;
--chakra-sizes-64: 16rem;
--chakra-sizes-72: 18rem;
--chakra-sizes-80: 20rem;
--chakra-sizes-96: 24rem;
--chakra-sizes-px: 1px;
--chakra-sizes-0-5: 0.125rem;
--chakra-sizes-1-5: 0.375rem;
--chakra-sizes-2-5: 0.625rem;
--chakra-sizes-3-5: 0.875rem;
--chakra-sizes-max: max-content;
--chakra-sizes-min: min-content;
--chakra-sizes-full: 100%;
--chakra-sizes-3xs: 14rem;
--chakra-sizes-2xs: 16rem;
--chakra-sizes-xs: 20rem;
--chakra-sizes-sm: 24rem;
--chakra-sizes-md: 28rem;
--chakra-sizes-lg: 32rem;
--chakra-sizes-xl: 36rem;
--chakra-sizes-2xl: 42rem;
--chakra-sizes-3xl: 48rem;
--chakra-sizes-4xl: 56rem;
--chakra-sizes-5xl: 64rem;
--chakra-sizes-6xl: 72rem;
--chakra-sizes-7xl: 80rem;
--chakra-sizes-8xl: 90rem;
--chakra-sizes-prose: 60ch;
--chakra-sizes-container-sm: 640px;
--chakra-sizes-container-md: 768px;
--chakra-sizes-container-lg: 1024px;
--chakra-sizes-container-xl: 1280px;
--chakra-zIndices-hide: -1;
--chakra-zIndices-auto: auto;
--chakra-zIndices-base: 0;
--chakra-zIndices-docked: 10;
--chakra-zIndices-dropdown: 1000;
--chakra-zIndices-sticky: 1100;
--chakra-zIndices-banner: 1200;
--chakra-zIndices-overlay: 1300;
--chakra-zIndices-modal: 1400;
--chakra-zIndices-popover: 1500;
--chakra-zIndices-skipLink: 1600;
--chakra-zIndices-toast: 1700;
--chakra-zIndices-tooltip: 1800;
--chakra-transition-property-common: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
--chakra-transition-property-colors: background-color, border-color, color, fill, stroke;
--chakra-transition-property-dimensions: width, height;
--chakra-transition-property-position: left, right, top, bottom;
--chakra-transition-property-background: background-color, background-image, background-position;
--chakra-transition-easing-ease-in: cubic-bezier(0.4, 0, 1, 1);
--chakra-transition-easing-ease-out: cubic-bezier(0, 0, 0.2, 1);
--chakra-transition-easing-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--chakra-transition-duration-ultra-fast: 50ms;
--chakra-transition-duration-faster: 100ms;
--chakra-transition-duration-fast: 150ms;
--chakra-transition-duration-normal: 200ms;
--chakra-transition-duration-slow: 300ms;
--chakra-transition-duration-slower: 400ms;
--chakra-transition-duration-ultra-slow: 500ms;
--chakra-blur-none: 0;
--chakra-blur-sm: 4px;
--chakra-blur-base: 8px;
--chakra-blur-md: 12px;
--chakra-blur-lg: 16px;
--chakra-blur-xl: 24px;
--chakra-blur-2xl: 40px;
--chakra-blur-3xl: 64px;
--chakra-breakpoints-base: 0em;
--chakra-breakpoints-sm: 30em;
--chakra-breakpoints-md: 48em;
--chakra-breakpoints-lg: 62em;
--chakra-breakpoints-xl: 80em;
--chakra-breakpoints-2xl: 96em;
}
.chakra-ui-light :host:not([data-theme]), .chakra-ui-light :root:not([data-theme]), .chakra-ui-light [data-theme]:not([data-theme]), [data-theme=light] :host:not([data-theme]), [data-theme=light] :root:not([data-theme]), [data-theme=light] [data-theme]:not([data-theme]), :host[data-theme=light], :root[data-theme=light], [data-theme][data-theme=light] {
--chakra-colors-chakra-body-text: var(--chakra-colors-gray-800);
--chakra-colors-chakra-body-bg: var(--chakra-colors-white);
--chakra-colors-chakra-border-color: var(--chakra-colors-gray-200);
--chakra-colors-chakra-inverse-text: var(--chakra-colors-white);
--chakra-colors-chakra-subtle-bg: var(--chakra-colors-gray-100);
--chakra-colors-chakra-subtle-text: var(--chakra-colors-gray-600);
--chakra-colors-chakra-placeholder-color: var(--chakra-colors-gray-500);
}
.chakra-ui-dark :host:not([data-theme]), .chakra-ui-dark :root:not([data-theme]), .chakra-ui-dark [data-theme]:not([data-theme]), [data-theme=dark] :host:not([data-theme]), [data-theme=dark] :root:not([data-theme]), [data-theme=dark] [data-theme]:not([data-theme]), :host[data-theme=dark], :root[data-theme=dark], [data-theme][data-theme=dark] {
--chakra-colors-chakra-body-text: var(--chakra-colors-whiteAlpha-900);
--chakra-colors-chakra-body-bg: var(--chakra-colors-gray-800);
--chakra-colors-chakra-border-color: var(--chakra-colors-whiteAlpha-300);
--chakra-colors-chakra-inverse-text: var(--chakra-colors-gray-800);
--chakra-colors-chakra-subtle-bg: var(--chakra-colors-gray-700);
--chakra-colors-chakra-subtle-text: var(--chakra-colors-gray-400);
--chakra-colors-chakra-placeholder-color: var(--chakra-colors-whiteAlpha-400);
}</style>
<style data-emotion="css-global fubdgu">html {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
font-family: system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
touch-action: manipulation;
}
body {
position: relative;
min-height: 100%;
margin: 0;
font-feature-settings: "kern";
}
:where(*, *::before, *::after) {
border-width: 0;
border-style: solid;
box-sizing: border-box;
word-wrap: break-word;
}
main {
display: block;
}
hr {
border-top-width: 1px;
box-sizing: content-box;
height: 0;
overflow: visible;
}
:where(pre, code, kbd,samp) {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 1em;
}
a {
background-color: transparent;
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
abbr[title] {
border-bottom: none;
-webkit-text-decoration: underline;
text-decoration: underline;
-webkit-text-decoration: underline dotted;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
:where(b, strong) {
font-weight: bold;
}
small {
font-size: 80%;
}
:where(sub,sup) {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
img {
border-style: none;
}
:where(button, input, optgroup, select, textarea) {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
:where(button, input) {
overflow: visible;
}
:where(button, select) {
text-transform: none;
}
:where(
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner
) {
border-style: none;
padding: 0;
}
fieldset {
padding: 0.35em 0.75em 0.625em;
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
progress {
vertical-align: baseline;
}
textarea {
overflow: auto;
}
:where([type="checkbox"], [type="radio"]) {
box-sizing: border-box;
padding: 0;
}
input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none !important;
}
input[type="number"] {
-moz-appearance: textfield;
}
input[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none !important;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
details {
display: block;
}
summary {
display: -webkit-box;
display: -webkit-list-item;
display: -ms-list-itembox;
display: list-item;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
:where(
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre
) {
margin: 0;
}
button {
background: transparent;
padding: 0;
}
fieldset {
margin: 0;
padding: 0;
}
:where(ol, ul) {
margin: 0;
padding: 0;
}
textarea {
resize: vertical;
}
:where(button, [role="button"]) {
cursor: pointer;
}
button::-moz-focus-inner {
border: 0 !important;
}
table {
border-collapse: collapse;
}
:where(h1, h2, h3, h4, h5, h6) {
font-size: inherit;
font-weight: inherit;
}
:where(button, input, optgroup, select, textarea) {
padding: 0;
line-height: inherit;
color: inherit;
}
:where(img, svg, video, canvas, audio, iframe, embed, object) {
display: block;
}
:where(img, video) {
max-width: 100%;
height: auto;
}
[data-js-focus-visible] :focus:not([data-focus-visible-added]):not(
[data-focus-visible-disabled]
) {
outline: none;
box-shadow: none;
}
select::-ms-expand {
display: none;
}
:root, :host {
--chakra-vh: 100vh;
}
@supports (height: -webkit-fill-available) {
:root, :host {
--chakra-vh: -webkit-fill-available;
}
}
@supports (height: -moz-fill-available) {
:root, :host {
--chakra-vh: -moz-fill-available;
}
}
@supports (height: 100dvh) {
:root, :host {
--chakra-vh: 100dvh;
}
}</style>
<style data-emotion="css-global 1cgn62j">body {
font-family: var(--chakra-fonts-body);
color: var(--chakra-colors-chakra-body-text);
background: var(--chakra-colors-chakra-body-bg);
transition-property: background-color;
transition-duration: var(--chakra-transition-duration-normal);
line-height: var(--chakra-lineHeights-base);
}
*::-webkit-input-placeholder {
color: var(--chakra-colors-chakra-placeholder-color);
}
*::-moz-placeholder {
color: var(--chakra-colors-chakra-placeholder-color);
}
*:-ms-input-placeholder {
color: var(--chakra-colors-chakra-placeholder-color);
}
*::placeholder {
color: var(--chakra-colors-chakra-placeholder-color);
}
*, *::before, ::after {
border-color: var(--chakra-colors-chakra-border-color);
}</style>
<style data-emotion="css-global 8gzb3n">.js-focus-visible :focus:not([data-focus-visible-added]) {
outline: none;
box-shadow: none;
}</style>
<style data-emotion="css 1dklj6k">.css-1dklj6k {
font-family: var(--chakra-fonts-heading);
font-weight: var(--chakra-fontWeights-bold);
font-size: var(--chakra-fontSizes-3xl);
line-height: 1.33;
}
@media screen and (min-width: 48em) {
.css-1dklj6k {
font-size: var(--chakra-fontSizes-4xl);
line-height: 1.2;
}
}</style>
<h2 class="chakra-heading css-1dklj6k">Hello world!</h2><span></span><span id="__chakra_env" hidden=""></span></div>
<script src="/_next/static/chunks/react-refresh.js?ts=1696314172501"></script>
<script id="__NEXT_DATA__" type="application/json">{
"props": {
"pageProps": {}
},
"page": "/",
"query": {},
"buildId": "development",
"nextExport": true,
"autoExport": true,
"isFallback": false,
"scriptLoader": []
}</script>
</body>
</html>
하여간 굉장히 길다.
밑에서 16번째 줄만 읽어보면
<h2 class="chakra-heading css-1dklj6k">Hello world!</h2>
라는 라인과 그 안에 "Hello world!"가 보인다.
이 긴 코드들을 이해하려고 시도하는 것은
(개인적으로는) 리플렉스 제작자의 의도와 벗어난 것이라고 생각한다.
그저, 리플렉스 사용자가 페이지와 로직만 구현하면 되게끔
모든 부수적인 코드를 미리 구현해놓았다고 받아들이면 좋겠다.
다시 본론으로 돌아와서,
위에서 파이썬으로 구현한 라인은 "Hello world!"라는 제목을 브라우저에 표시하는 코드이다.
index 함수를 보면 특별한 내용 없이 rx.heading이라는 컴포넌트 하나를 리턴했는데
이 리턴한 컴포넌트가 곧 html 페이지가 된다.
이번엔 heading 컴포넌트 아래에 일반 text 컴포넌트를 하나만 더 추가해보자.
리플렉스 원칙상 하나의 페이지 함수(예:index)는 하나의 컴포넌트만 리턴해야 한다.
그래서 두 개 이상의 컴포넌트를 한 페이지에 조합하기 위해서는
여러 개의 컴포넌트를 담을 수 있는 레이아웃 컴포넌트란 게 필요하다.
이번에는 간단히 rx.container라는 컨테이너 컴포넌트를 사용해서
rx.heading과 rx.text를 담아보자.
import reflex as rx
def index():
return rx.container(
rx.heading("Hello world!"),
rx.text("This is reflex.")
)
app = rx.App()
app.add_page(index)
app.compile()
코드를 위와 같이 작성하고
터미널에서 `reflex run`을 실행하면
브라우저의 localhost:3000으로 아래와 같은 화면을 확인할 수 있다.
이렇게 간단한 페이지를 만들어보았다.
그런데 리플렉스는 이렇게 간단한 정적 페이지를 만드는 용도로 사용하기엔
내재된 기능이 참 다양하고,
특히 다소 복잡한 백엔드 로직을 구현하는 방법도
대부분의 경우 굉장히 간단히 파이썬 코드로 구현이 가능하다.
이론적으로는, React와 FastAPI로 구현할 수 있는 모든 프론트/백엔드 로직 구현이 가능하다.
다음 포스팅에서는 본격적으로 State 클래스와 몇 가지 prop 속성을 추가하면서
간단한 동적 웹앱을 하나 만들어보자.
+버튼과 -버튼으로 특정 숫자를 올리고 내리는
일명 Counter 앱을 만들어보고자 한다.
'REFLEX 튜토리얼 > reflex 공식문서 요약' 카테고리의 다른 글
투두리스트에 할 일을 추가해보자. #Create (0) | 2023.10.03 |
---|---|
파이썬의 리스트도 표현할 수 있나? # 투두리스트 앱 만들기 (0) | 2023.10.03 |
간단한 동적 웹앱 만들어보기 : Counter (0) | 2023.10.03 |
댓글