생활 코딩 React

신승준·2022년 6월 26일
0

실습 환경 구축

  • Node.js 설치
  • vscode에서 터미널에 npx create-react-app . 입력
    • .은 현재 폴더를 의미한다. 즉 현재 폴더 내에 react 실습 환경이 설치된다.
  • 터미널에 npm start 입력



소스코드 수정 방법

수정

  • npm start 입력 시, src/index.js를 찾고 이 파일을 동작하면서 react-app이 실행된다.
    • 즉 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'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
  • 위 코드에서 <App />은 ./App로부터 비롯된다.
    • 즉 .이니 현재 폴더의 App이라는 파일로부터 위 태그가 왔음을 의미한다.
  • src 폴더 안의 index.js가 입구이고, 이 index.js에는 여러가지 전역적인 설정들이 들어가게 된다.



  • 즉, 실질적으로 아래의 코드가 위의 화면을 구성하게 된다.
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;



import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      Helle, React!
    </div>
  );
}

export default App;
  • 위의 코드와 같이 Hello, React!만 입력했을 때 이 글자가 화면 좌우 기준으로 중앙에 오는 것을 알 수 있다. 이는 어디 선가 CSS가 개입했다는 뜻이다.




  • App 태그에 대한 것은 App.css가 관여한다.(index.js에는 index.css가 관여한다)
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

배포

  • 터미널에 npm run bulid 입력
    • build라는 폴더가 생긴다.
    • build는 배포판을 만드는 과정이라고 보면 된다
    • index.html의 공백이 없어진 것을 볼 수가 있다.
    • 실제로 배포할 때는 공백조차 줄여서 불필요한 용량를 줄이기 위함이다.
    • build라는 결과를 서비스할 때 serve라는 웹 서버 앱을 쓰는 것을 추천한다.
      • 이 때 -s라는 옵션을 주면, 사용자가 어떤 경로로 들어오든 index.html을 서비스, 즉 마주하게 해준다.
      • serve -s build
      • build라는 폴더 안에 있는 index.html을 서비스해주겠다라는 의미이다.
      • serve는 node.js로 만들어진 앱이다.
      • 이를 간편히 실행시킬 때는 터미널에 npx serve -s build라고 입력하면 된다.



컴포넌트 만들기

  • React는 사용자 정의 태그를 만드는 기술이다.
  • 컴포넌트, Component



props

  • React에서 속성은 prop이라고 부른다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
  • 위와 같이 StrictMode로 감싸져 있으면, 개발 모드에서 오류를 잘 잡기 위해 두 번씩 렌더링된다.
    • 따라서 console.log 코드를 1번 작성했더라도 2번 찍힐 수 있다.

  • function App()에서 Nav 태그에 topics를 넘겨주고 있다. 이 topics를 function Nav()에서 props로 받게 되고, 이를 간단히 for문으로 li 태그를 작성하였다.



이벤트

  • Header 컴포넌트에 event를 추가하겠다.
  • 여기에서 HTML처럼 보이는 것들은 HTML이 아니다. 유사 HTML이다. react가 최종적으로 컨버팅해서 HTML로 만들어준다. 따라서 a 태그의 온클릭 속성 또한, HTML에서는 onclick이지만, 여기는 react이기 때문에 문법이 조금 다르다. onClick으로 작성해줘야 한다.
  • event.preventDefault()
    • 위에서는 a 태그의 기본 동작을 방지한다.
    • 즉 a 태그를 클릭해도 reload가 일어나지 않는다.
  • props.onChangeMode()
    • function App()에서 Header 넘겨준 onChangeMode라는 function을 실행하게 된다.
<h1><a href="/" onClick={function(event) {
    event.preventDefault();
    props.onChangeMode();
  }}>{props.title}</a></h1>
  • Header 태그를 누르면 a 태그로 인해 reload가 된다. 하지만 onClick으로, Header를 누르면 function(event)가 소환되는데, 이 때 a의 기본적인 동작인 reload가 방지된다.
    • 또한 클릭하면 이전에 받아둔 props의 onChangeMode라는 함수가 실행되게 된다. 이 때 이 함수는 alert("Header")로, Header 태그를 누르면 알림창이 뜨게 된다.

  • function App()에서 Nav 태그에 onChangeMode라는 것을 넘겨주고 있다.
    • 그러면 Nav에서는 topic의 id를 바탕으로 alert(id)를 띄우게 된다.
    • 이때 event.target은, 이 event를 발생시킨 주체를 말한다. 여기서는 1. html, 2. css, 3. javascript와 같은 list 태그이다. 그 a 태그의 id를 받아와서 alert창을 띄우게 된다.



State

  • 컴포넌트에서 입력은 props 였다. 이를 처리해서 어떤 값을 만드면 그것이 return 값이다.

  • 이 때 props과 함께 컴포넌트 함수를 다시 실행해서 새로운 return 값을 만들어주는 것이 state이다.

  • useState의 인자는, 그 state의 초기값이다.

    • 이 state의 값은 0번째 index로 읽는다. -> _mode[0]
    • 그 state를 바꿀 떄는 1번째 index의 함수로 바꾼다. -> _mode[1]
  • 컴포넌트를 클릭하게 되면 setMode로 인해 function App이 다시 실행되고, mode 값, 즉 state가 바뀌게 된다.

    • 이 바뀐 mode 값에 따라 content 또한 if문에서 바뀌게 된다.
  • props.onChangeMode(Number(event.target.id));

    • 태그로부터 받은 값은 문자열이다.(태그로 들어간 것들은 숫자라도 문자열로 바뀐다) 따라서 Number로 변환해줘야, setId(_id)에서 _id로 문자열이 아닌 숫자가 들어가게 된다.
      • 그러면 function App()에서 if (topics[i].id == id)에서 제대로 숫자끼리 비교가 가능하여, 조건문 만족 시 우리가 원하는 코드를 동작하게 만들 수 있다.



Create

  • newValue = {...value} : value을 복제한 새로운 데이터가 newValue가 된다.
  • 이 복제한 newValue를 바꾼다.
  • setValue(newValue)하면 컴포넌트가 다시 실행된다.



Update

  • Update = Create + Read

  • props는 외부자가 내부자로 전달하는 값이다.

  • state는 내부자가 사용하는 값이다.

  • 수정일 경우, function Update로 전달된 props의 title, body를 사용하는 것이 아니라, state로 변환 후 event가 발생할 때마다 setTitle, setBody로 title과 body가 변경되도록 해야 한다.

    • props는 그저 계속 전달해주고 있는 값이기 때문에 고정되어 진다.
    • event가 발생할 때마다 이것이 바뀔 수 있도록 state를 이용하는 것이다.
    • 그 후 Update를 누르면 onUpdate가 실행되어 변경된 title과 body를 바탕으로 글의 내용이 수정된다.



Delete

  • react에서 빈 태그인 <>, </>는 복수의 태그를 묶어놓기 위함이다. react에서는 복수의 태그가 생기면 이렇게 빈 태그이든 상위 태그이든 묶어줘야 한다.
  • button 타입은 기본 behavior가 없기 때문에 event.preventDefault()를 해줄 필요가 없다.



최종 코드

import logo from './logo.svg';
import './App.css';
import {useState} from 'react';

function Create(props) {
  return <article>
    <h2>Create</h2>
    <form onSubmit={event=> {
      event.preventDefault();
      const title = event.target.title.value;
      const body = event.target.body.value;
      props.onCreate(title, body);
    }}>
      <p><input type="text" name="title" placeholder="title"/></p>
      <p><textarea name="body" placeholder="body"></textarea></p>
      <p><input type="submit" value="Create"></input></p>
    </form>
  </article>
}

function Update(props) {
  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);
    }}>
      <p><input type="text" name="title" placeholder="title" value={title} onChange={event=> {
        // console.log(event.target.value);
        setTitle(event.target.value);
      }}/></p>
      <p><textarea name="body" placeholder="body" value={body} onChange={event=> {
        // console.log(event.target.value);
        setBody(event.target.value);
      }}></textarea></p>
      <p><input type="submit" value="Update"></input></p>
    </form>
  </article>
}

function Header(props) {
  
  return <header>
  <h1><a href="/" onClick={(event) => {
    event.preventDefault();
    props.onChangeMode();
  }}>{props.title}</a></h1>
</header>
}

function Nav(props) {
  const list = []
  
  for (let i = 0; i < props.topics.length; i++) {
    let topic = props.topics[i];
    list.push(<li key={topic.id}>
      <a id={topic.id} href={'/read/' + topic.id} onClick={event=>{
        event.preventDefault();
        props.onChangeMode(Number(event.target.id));
      }}>{topic.title}</a>
    </li>)
  }
  
  return <nav>
  <ol>
    {list}
  </ol>
</nav>
}

function Article(props) {
  // console.log(props);
  
  return <article>
  <h2>{props.title}</h2>
  {props.body}
</article>
}

function App() {
  // const _mode = useState("WELCOME");  // 상태를 만드는 것이다. 그러면 상태가 리턴되어 _mode를 받을 것이다.
                                      // useState는 배열을 반환한다. 그 배열의 0번째 원소는 '상태의 값'을 읽을 때 쓰는 데이터이고, 1번째 원소는 그 상태의 값을 변경할 때 쓰는 함수이다.
  // const mode = _mode[0];
  // const setMode = _mode[1];     
  
  // 위 3줄을 간단하게 아래처럼 1줄로 표현하기도 한다. 더 축약되어 있기 때문에 밑의 코드를 더 많이 쓴다.
  const [mode, setMode] = useState("WELCOME");
  const [id, setId] = useState(null);
  const [nextId, setNextId] = useState(4);
  const [topics, setTopics] = useState([
    {id: 1, title: 'html', body: 'html is ...'},
    {id: 2, title: 'css', body: 'css is ...'},
    {id: 3, title: 'javascript', body: 'javascript is ...'}
  ]);
  
  let content = null;
  let contextControl = null;
  
  if (mode === "WELCOME") {
    content = <Article title="WELCOME" body="Welcome to React!"></Article>;
    
  } else if (mode === "READ") {
    let title = null;
    let body = null;
    
    for (let i = 0; i < topics.length; i++) {
      if (topics[i].id == id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    
    content = <Article title={title} body={body}></Article>;
    contextControl = <>
      <li><a href={"/update/" + id} onClick={event=> {
        event.preventDefault();
        setMode("UPDATE");
      }}>Update</a></li>
      <li><input type="button" value="Delete" onClick={(event)=> {
        // event.preventDefault();   // button 타입의 input 태그는 기본 behavior가 없기 때문에 preventDefault()를 해주지 않아도 된다.
        const newTopics = [];
        
        for (let i = 0; i < topics.length; i++) {
          if (topics[i].id != id) {
            newTopics.push(topics[i]);
          }
        }
        
        setTopics(newTopics);
        setMode("WELCOME");
      }}/></li>
    </>
    
    
  } else if (mode === "CREATE") {
    content = <Create onCreate={(_title, _body)=> {
      const newTopic = {id: nextId, title: _title, body: _body};
      const newTopics = [...topics];
      newTopics.push(newTopic);
      setTopics(newTopics);
      setMode("READ");
      setId(nextId);
      setNextId(nextId + 1);
    }}></Create>
    
  } else if (mode === "UPDATE") {
    let title = null;
    let body = null;
    
    for (let i = 0; i < topics.length; i++) {
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    
    content = <Update title={title} body={body} onUpdate={(title, body)=>{
      // console.log(title, body);
      const newTopics = [...topics];
      const updatedTopic = {id: id, title: title, body: body};
      for (let i = 0; i < newTopics.length; i++) {
        if (newTopics[i].id === id) {
          newTopics[i] = updatedTopic;
        }
      }
      
      setTopics(newTopics);
      setMode("READ");
    }}></Update>;
  }
  
  return (
    <div>
      <Header title="WEB" onChangeMode={() => {
        setMode("WELCOME");
      }}></Header>
      <Nav topics={topics} onChangeMode={(_id) => {
        setMode("READ");
        setId(_id);
      }}></Nav>
      {content}
      <ul>
        <li><a href="/create" onClick={event=> {
          event.preventDefault();
          setMode("CREATE");
        }}>Create</a></li>
        {contextControl}
        
      </ul>
    </div>
  );
}

export default App;
profile
메타몽 닮음 :) email: alohajune22@gmail.com

0개의 댓글