생활코딩 react
react는 복잡한 코드는 숨기고 사용자 정의 태그를 만드는 것
class vs function : function으로 하는게 많아졌다
필요할때 배워서 그때그때 사용할 수 있도록
시작은 react document
온라인 플레이 그라운드 stackblitz
싱글 페이지 애플리케이션 create react app
nodejs 환경 만들기
npx create-react-app my-app
react 서버 시작
npm start
src/index.js
입구가 되는 파일, 전역 설정
import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; const root = ReactDOM.createRoot(document.getElementById("root")); // 요 루트는 public/index.html 의 id="root" root.render( <React.StrictMode> <App /> </React.StrictMode> ); reportWebVitals();```
src/index.css
전체적인 스타일
src/App.js
요기가 내용
import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import App from "./App"; // 2. App 태그는 여기서 왔고 ./App은 뒤에 .js가 생략됨 import reportWebVitals from "./reportWebVitals"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> // 1. 요게 내용 </React.StrictMode> ); reportWebVitals();
src/App.css
요거가 App의 스타일
public/index.html
바탕이 되는 html
npm run build
build 폴더 생성
스테틱 서버 가동 - 실 서비스 버전
nodejs 로 돌아가는 서버 serve
npx serve -s build
"리액트는 사용자 태그<tag attr=value></tag>
를 만드는 기술이다"
복잡한 코드의 정리 정돈 - 한 묶음을 이름을 지어주고 정리하자 - 사용자 정의 태그는 첫글자 대문자
리액트에서는 사용자정의 태그라고 안하고 컴포넌트component 라고 한다
//컴포넌트 정의 (여기에 컴포넌트 태그 안 내용이 들어간다) function Header() { return ( <header> <h1> <a href="/">React</a> </h1> </header> ); } function App() { return ( <div> <Header></Header> //컴포넌트 사용, 대문자로 시작 (여기에는 컴포넌트 태그와 속성만 들어간다) </div> ); }
react에서 속성을 props 라고 한다
//컴포넌트 정의 function Header(props) { // 다들 props라고 쓴다 return ( <header> <h1> <a href="/">{props.title}</a> //데이터 들어갈 영역 잡기 </h1> </header> ); } function App() { return ( <div> <Header title="REACT"></Header> // 해당이름의 속성으로 추가 </div> ); }
여러 태그를 한번에 처리할때는 for문을 돌리거나 앱을 통해 값을 전달한다
한 영역에 여러 태그는 각각 key 값을 가져야 한다
function Navi(props) { const list = []; //리스트를 담기 위한 배열 만들기 for (let i = 0; i < props.topics.length; i++) { // for문을 돌려서 태그를 생성하고 props 값을 추가한다 let t = props.topics[i]; list.push( <li key={t.id}> // 한 영역에서 다중 생성된 것은 React에서 구분하기 위해 key값이 필요하다 <a href={"/read/" + t.id}>{t.title}</a> </li> ); } return ( <nav> <ol>{list}</ol> //처리된 배열을 { }로 감싸서 전달 </nav> ); } function App() { //데이터값 배열 선언 const topics = [ { id: 1, title: "html", body: "html is..." }, { id: 2, title: "css", body: "css is..." }, { id: 3, title: "js", body: "js is..." } ]; return ( <div> <Navi topics={topics}></Navi> //prop 값으로 내용 받기 </div> ); }
onChangeMode
이벤트를 가진 컴포넌트 만들기
function Header(props) { return ( <header> <h1> <a href="/" onClick={(event) => { //javascript의 onclick과는 사용방법이 조금 다르다 event.preventDefault(); props.onChangeMode(); }}> {props.title} </a> </h1> </header> ); } function Navi(props) { const list = []; for (let i = 0; i < props.topics.length; i++) { let t = props.topics[i]; list.push( <li key={t.id}> <a id={t.id} href={"/read/" + t.id} onClick={(event) => { event.preventDefault(); props.onChangeMode(event.target.id); }}> {t.title} </a> </li> ); } return ( <nav> <ol>{list}</ol> </nav> ); } function App() { const topics = [ { id: 1, title: "html", body: "html is..." }, { id: 2, title: "css", body: "css is..." }, { id: 3, title: "js", body: "js is..." }, ]; return ( <div> <Header title="REACT" onChangeMode={() => { // 적용되는 곳 alert("This is Header"); }} </Header> <Navi topics={topics} onChangeMode={(id) => { alert(id); }} </Navi> </div> ); }
컴포넌트의 구조에서
컴포넌트-> prop을 통해 -> [ ] -> return
[ 내부에서 컴포넌트를 사용하는 state(다시실행) ] -> return
props, states 공통점 : 작동하면 리턴값을 제공
props는 (컴포넌트) function을 사용하는 외부자를 위한 것 - 자바스크립트의 동작
state 설명 : state는 컴포넌트를 만드는 내부자를 위한 - 내부가 바뀌어도 App() 함수는 다시 실행되지 않기 때문에 return 값에는 변화가 없다. 그래서, state를 사용한다
state 이해 : 원래대로라면 한번 실행된 (컴포넌트) function의 리턴값은 바뀌지 않지만, state를 이용하면 그 내부의 값이 바뀌었을때 return 값을 다시 처리해서 보여준다
state 요약 : 컴포넌트 내부 피드백 처리
맨 위에 이렇게 선언해서 useState 가져옴
import { useState } from "react";
배열을 리턴, [0]은 상태값, [1]은 함수
변수를 두개로 나눠서 기본값두는 곳에[0]을 두고 바뀌는 곳에[1]에 두면 [1]이 변했을때 [0]이 변한다
풀어쓰면 이렇게 쓸 수 있는데,
const _mode = useState(defaultValue); // 기본값 변수 할당 const mode = _mode[0]; // [0]번은 기본값 const setMode = _mode[1]; // [1]번은 그 값을 바꿀수 있게 하는 함수
줄여쓰면 이렇게 쓸 수 있다
const [mode, setMode] = useState(defaultValue);
App()
에서 새로운 항목 생성을 위한 모양 생성
//return : App()의 화면 출력 영역 <a href="/create" onClick={event=>{ event.preventDefault(); setMode("create"); //클릭하면 mode 값을 create로 변경 }}>Create</a>
mode가 create 일 때,
//App()의 변수 선언 부분 const [topics, setTopics] = useState([ //기존값들을 state로 변경, 유동적인 리스트가 되니까 { id: 1, title: "html", body: "html is..." }, { id: 2, title: "css", body: "css is..." }, { id: 3, title: "js", body: "js is..." }, ]); const [nextId, setNextId] = useState(topics.length + 1); //신규 생성되는 id값의 초기값은 마지막 원소의 다음 번호 (여기서는 4가 된다), setNextId는 언제 사용?? let content = null; // 조건에 따라서 내용이 나오도록 영역 변수 선언 //만약에 mode가 create로 바뀌었다면 else if (mode === "create") { content = ( <Create onCreate={(_title, _body) => { //onCreate로 생성 const newTopic = { id: nextId, title: _title, body: _body }; //다음 신규 아이디값이 필요한데.... 그래서 nextId state 만듬 const newTopics = [...topics]; //기존 topics를 복제해서 newTopics에... newTopics.push(newTopic); // 새로 복제한 newTopics에 새로운 항목 데이터 newTopic을 넣는다 setTopics(newTopics); // 복제하고 새 데이터를 넣은 newTopics를 원래 topics에 추가setTopics한다 setMode("read"); // (추가 작업) create mode를 read mode로 state 변경하고 setId(nextId); // id를 다음 id(nextId)로 변경 setNextId(nextId + 1); // (다음 글 준비) nextId에 +1 해서 다음 create 대비 }} </Create> ); } ... return ( ... <Navi></Navi> ... {content} // 조건에 따라서 내용이 나오는 영역 배정
state를 변경해서 다양한 경우에 세련되게 대응할 수 있다
참고로 ...
객체 밸류 데이터를 복제는 이렇게 한다고 한다 - 데이터 객체 변경을 위해
newValue = {...value} //범객체(객체) 오리지널을 안건드리고 객체밸류를 복제, 변경 newValue 변경 setValue(newValue) newValue = [...value] //범객체(배열) 오리지널을 안건드리고 배열값을 복제, 변경 setValue(newValue)
// Create() 를 바탕으로 function Update(props) { // prop으로 받은 값을 컴포넌트 안의 state로 변경해서 변수 state를 바꿀 수 있게 해야 한다 const [title, setTitle] = useState(props.title); const [body, setBody] = useState(props.body); return ( <article> <h2>Update</h2> <form onSubmit={(event) => { event.preventDefault(); const title = event.target.title.value; const body = event.target.body.value; props.onUpdate(title, body); }}> <div> <input type="text" name="title" placeholder="title" value={title} onChange={(event) => { //자바스크립트의 change와 다르게 값을 입력할때마다 바꾼다 setTitle(event.target.value); // state를 새로 set해서 value를 바꿔준다 }} /> </div> <div> <textarea name="body" placeholder="body" value={body} onChange={(event) => { //자바스크립트의 change와 다르게 값을 입력할때마다 바꾼다 setBody(event.target.value); // state를 새로 set해서 value를 바꿔준다 }} ></textarea> </div> <div> <input type="submit" value="Update" /> </div> </form> </article> ); }
//App() 에서 보이는 모양은... function App() { const [mode, setMode] = useState("welcome"); // App 전체에 영향을 주는 mode state 설정 const [id, setId] = useState(null); // App 전체에 영향을 주는 id state 설정 const [topics, setTopics] = useState([ // App 전체에 영향을 주는 컨텐츠topics state 설정 { id: 1, title: "html", body: "html is..." }, { id: 2, title: "css", body: "css is..." }, { id: 3, title: "js", body: "js is..." }, ]); let contextControl = null; // 필요할 때만 나오도록 따로 변수로 선언 ... // update 링크는 welcome (첫화면에서는 x), read(상세보기)에서만 보이게 else if (mode === "read") { contextControl = ( <li> <a href={"/update/" + id} onClick={(event) => { event.preventDefault(); setMode("update"); }}> Update </a> </li> ); } // update 상세 화면 else if (mode === "update") { let title, body = null; // 내용을 받기 위해 변수(메모리 공간 확보) 준비 for (let i = 0; i < topics.length; i++) { if (topics[i].id === id) { // 현재 id와 일치하는 것을 찾아 데이터를 title, body 변수에 넣기 title = topics[i].title; body = topics[i].body; } } content = ( <Update title={title} body={body} onUpdate={(title, body) => { const newTopics = [...topics]; // ... 수식으로 객체(배열) 내용을 새로운 변수에 복제 const updatedTopic = { id: id, title: title, body: body }; //mode가 read 일때만 나오므로 id는 그대로 사용해도 됨 for (let i = 0; i < newTopics.length; i++) { if (newTopics[i].id === id) { newTopics[i] = updatedTopic; break; } } setTopics(newTopics); // state로 내용 업데이트 setMode("read"); // state로 read 상태로 변경 (섬세한? 처리) }}> </Update> ); } ... return ( ... <Navi></Navi> ... {content} ... <a href="/create" ... > ... </a> ... {contextControl} // 필요할 때만 나오도록 조건문에 넣기
react에서 여러개의 태그를 묶을때 빈태그 사용, 노출되지 않고 감싸기만 함 - 리액트에서는 하나의 변수에 담는 컴포넌트는 하나의 태그로 묶어야 하기 때문에 사용
<> // 빈태그 (여는 태그) ... <li></li> <li></li> ... </> // 빈태그 (닫는 태그)
delete는 read 모드에 추가
function App() { ... else if (mode === "read") { ... contextControl = ( <> // react 빈태그로 감싸기 <li> <a href={"/update/" + id} onClick={(event) => { event.preventDefault(); setMode("update"); }}> Update </a> </li> <li> <input type="button" value="Delete" onClick={(event) => { const newTopics = []; // 새로운 객체를 만들고 for (let i=0; i < topics.length; i++) { // 데이터 전체를 살펴서 if (topics[i].id !== id) { // 현재 read의 id를 제외하고 - 삭제 처리 newTopics.push(topics[i]); // 새로운 객체에 추가 } } setTopics(newTopics); // 새로운 객체를 데이터에 넣고 setMode("welcome"); // 첫화면welcome 으로 mode 전환 }} </li> </> ); }