diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/.eslintrc.cjs" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/.eslintrc.cjs" new file mode 100644 index 0000000..3e212e1 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/.eslintrc.cjs" @@ -0,0 +1,21 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/.gitignore" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/.gitignore" new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/.gitignore" @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/README.md" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/README.md" new file mode 100644 index 0000000..f768e33 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/README.md" @@ -0,0 +1,8 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/index.html" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/index.html" new file mode 100644 index 0000000..0c589ec --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/index.html" @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/package.json" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/package.json" new file mode 100644 index 0000000..60e6908 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/package.json" @@ -0,0 +1,24 @@ +{ + "name": "react-19-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0-canary-fd0da3eef-20240404", + "react-dom": "^19.0.0-canary-fd0da3eef-20240404" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "vite": "^5.2.0" + } +} diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/App.jsx" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/App.jsx" new file mode 100644 index 0000000..9cdf27e --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/App.jsx" @@ -0,0 +1,25 @@ +import UseStatusForm from "./components/UseStatusForm"; +import UseStateForm from "./components/UseStateForm"; +import Optimistic from "./components/Optimistic/Optimistic"; + +function App() { + return ( +
+
+

use form status

+ +
+
+
+

use form state

+ +
+
+

use optimistic

+ +
+
+ ); +} + +export default App; diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/Optimistic/Optimistic.jsx" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/Optimistic/Optimistic.jsx" new file mode 100644 index 0000000..a31f8eb --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/Optimistic/Optimistic.jsx" @@ -0,0 +1,48 @@ +import { useOptimistic, useState } from "react"; + +const DEFAULT_MESSAGES = [{ text: "Hey, I am initail", sending: false, key: 1 }]; + +const Optimistic = () => { + const [messages, setMessages] = useState(DEFAULT_MESSAGES); + const [optimisticMessages, addOptimisticMessage] = useOptimistic(messages, (state, newMessage) => [ + ...state, + { text: newMessage, sending: true }, + ]); + + async function handleSendFormData(formData) { + const sentMessage = await fakeDelayAction(formData.get("message")); + setMessages((messages) => [...messages, { text: sentMessage }]); + } + + const handleSubmit = async (userData) => { + addOptimisticMessage(userData.get("username")); + + await handleSendFormData(userData); + }; + + return ( + <> + {optimisticMessages.map((message, index) => ( +
+ {message.text} + {!!message.sending && (Sending...)} +
+ ))} +
+

OptimisticState Hook

+
+ + +
+ +
+ + ); +}; + +export default Optimistic; + +async function fakeDelayAction(message) { + await new Promise((res) => setTimeout(res, 3000)); + return message; +} diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/Optimistic/index.js" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/Optimistic/index.js" new file mode 100644 index 0000000..d2b81f6 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/Optimistic/index.js" @@ -0,0 +1 @@ +export { default } from "./Optimistic"; diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStateForm/UseStateForm.jsx" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStateForm/UseStateForm.jsx" new file mode 100644 index 0000000..0fb2830 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStateForm/UseStateForm.jsx" @@ -0,0 +1,26 @@ +import { useFormState } from "react-dom"; + +const UseStateForm = () => { + const [message, formAction] = useFormState(handleSubmit, null); + + return ( +
+ + + + {message &&

{message.text}

} +
+ ); +}; + +const handleSubmit = (prevState, queryData) => { + const name = queryData.get("username"); + console.log(prevState); + + if (name === "Chanyeong") { + return { success: true, text: "Welcome" }; + } + return { success: false, text: "Error" }; +}; + +export default UseStateForm; diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStateForm/index.js" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStateForm/index.js" new file mode 100644 index 0000000..0a75218 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStateForm/index.js" @@ -0,0 +1 @@ +export { default } from "./UseStateForm"; diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/Submit.jsx" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/Submit.jsx" new file mode 100644 index 0000000..1d7711b --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/Submit.jsx" @@ -0,0 +1,9 @@ +import { useFormStatus } from "react-dom"; + +const Submit = () => { + const { pending } = useFormStatus(); + + return ; +}; + +export default Submit; diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/UseStatusForm.jsx" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/UseStatusForm.jsx" new file mode 100644 index 0000000..bc871e3 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/UseStatusForm.jsx" @@ -0,0 +1,13 @@ +import Submit from "./Submit"; + +const UseStatusForm = () => { + return ( +
+ + + ); +}; + +const handleSubmit = () => new Promise((res) => setTimeout(res, 3000)); + +export default UseStatusForm; diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/index.js" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/index.js" new file mode 100644 index 0000000..922e1e3 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/components/UseStatusForm/index.js" @@ -0,0 +1 @@ +export { default } from "./UseStatusForm"; diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/main.jsx" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/main.jsx" new file mode 100644 index 0000000..0d1758a --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/src/main.jsx" @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); diff --git "a/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/vite.config.js" "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/vite.config.js" new file mode 100644 index 0000000..5a33944 --- /dev/null +++ "b/99. \355\220\201\353\213\271\355\220\201\353\213\271/7\355\232\214\354\260\250/\354\260\254\354\230\201/react-19-example/vite.config.js" @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/React19.md b/React19.md new file mode 100644 index 0000000..9277574 --- /dev/null +++ b/React19.md @@ -0,0 +1,49 @@ +# react 19 정리 + +## 주 변화 + +### concurrent rendering + +- react18이 synchronous rendering에 의존했던것과 달리, react19는 동시성을 활용해, UI 업데이트를 더 작은 비동기 작업으로 세분화함 +- 우선순위가 높은 업데이트를 먼저 처리해 더 매끄럽고 유연한 사용자 경험을 제공함 + +### suspense for data fetching reinvented + +- 데이터를 불러올때 사용하는 suspense에 대한 성능 개선 +- suspense가 concurrent rendering과 원활하게 통합되기 때문에 최적의 성능을 위해 데이터를 가져오는 작업과 렌더링을 동시에 진행할 수도 있음 + +### server side rendering 개선 + +- streaming과 concurrent가 발전했다는 점을 활용해 더 빠른 TTFB와 향상된 SEO 성능을 제공함 + +> TTFB: Time to First Byte로 브라우저가 페이지를 요청한 시점과 서버로부터 첫 번째 정보 byte를 수신한 시점 사이의 시간 + +### 점진적 도입 가능 + +- 기존 react 생태계가 다양하다는 사실을 인지하고, 개발자가 react 프로젝트를 점진적으로 upgrade할 수 있음 + +### performance optimization + +- react19는 runtime 효율은 높이고 번들 사이즈는 줄이기 위해 다양한 성능 최적화 기술을 사용함 +- 최적화된 조정 알고리즘부터 지연 컴포넌트 로딩에 이르기까지, 더 빠른 렌더링과 효율적인 메모리 관리 기능을 제공하기 위해 노력 +- 새로운 scheduler architecture를 도입해, 렌더링 우선순위와 resource활용도를 세밀하게 제어하고 성능 튜닝을 강화할 수 있도록 도움 + +### debugging developer tool + +- react19를 사용하는 개발자는 component life cycle, state management, performance bottleneck 등에 대한 상세한 insight를 확보하고, 앱을 보다 효과적으로 최적화 할 수 있음 +- time-slicing profiler와 flame charts와 같은 기능을 통해 이전보다 향상된 정밀도로 성능 관련 문제를 진단하고 해결하도록 지원 + +#### time-slicing profiler + +#### flame chart + +### 핵심 기능 + +#### react compiler (experimental) + +- react를 더 작고 최적화된 javascript로 compile함으로써, TTFB 및 UX 개선할 수 있음 +- 아직 experimental 단계로, 널리 사용할 준비는 되어있지 않음 + +#### reference + +- https://kmong.com/article/1852--React-19-%EC%B5%9C%EC%8B%A0-%EA%B8%B0%EB%8A%A5-%ED%95%9C%EB%88%88%EC%97%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0