React 2022 개정판(기초_복습)

코드 실행

npm start에서 해당 port에 이미 다른게 존재한다는 내용 있어도 y 입력해서 넘어가주면 다른 포트로 개발 서버 열어준다.

react 의 큰 특징이 사용자 정의 컴포넌트 이다. 즉, 이걸 활용해 코드 수정한다.

  • 이전 코드
// App.js
import ReactDOM from 'react-dom'

ReactDom.render(
	<React.StrictMode><App/></React.StrictMode>,
    document.getElementById('root')
)
  • 현재 코드
// App.js
import ReactDOM from 'react-dom/client'; // 다름

const root = ReactDOM.createRoot(document.getElementById('root')); // 다름
root.render(
  // <React.StrictMode> 참고로 StrictMode때문에 2번 렌더링 되는경우 있으니 꼭 인지할것.
  <div>
    <App />
  </div>
  // </React.StrictMode>
);

리액트는 npx로 생성한 파일이 너무 크기 때문에 배포할 땐 꼭 build 를 한다.
npm run build 는 배포판 버전으로 build 폴더로 생성된다.

  • 부가 옵션으로 npx serve -s build 입력시 build 폴더에 index.html이 젤 먼저 실행되게 한다.


PROP(=속성)

src, width, height가 다 PROP이다. 만약 사용자 컴포넌트에서도 이런 속성을 다루고 싶다면?!

<img src="image.jpg" width="100" height="100"/>

이름은 사람들도 다 props로 보통 명칭함. {}로 감싸면 표현식

  • 참고로 이것과 더불어 spread, 구조분해를 정리한 포스팅을 볼 것. 관련문서
function Header(props) {
    return <header>
        <h1><a href="/">{props.title}</a></h1>
        </header>
}
// 속성을 넣어서 만들었음
// 아래 실행
<Header title="REACT"></Header>

List같이 배열같은걸 props로 넘기는것도 간단하게 가능.

  • 다만, key반복문 내에서 꼭 넘겨줘야 warning이 안뜬다.
function Nav(props) {
    const lis = []
    for(let i=0; i<props.list.length; i++){
        let t = props.list[i];
        lis.push(<li key={t.id}><a>{t.title}</a></li>)
    }
}
// 반복문을 이용해 만들었음
// 실행
const list = [{id:1, title:'html'}, {id:2, title:'css'}, ...]
<Nav list = {list}></Nav>


이벤트

onclick부분 처럼 이벤트사용자 컴포넌트에서도 만들고 싶다면?!

<input type="button" onclick="alert('hi')">

마찬가지로 props로 접근하면 된다.

  • a태그 클릭시 해당 list값의 id값이 alert 되는 구조이다.
  • event.preventDefault(); 기본 이벤트 Default값 방지(실행 안하는거임)
function Nav(props) {
    const lis = []
    for(let i=0; i<props.list.length; i++){
        let t = props.list[i];
        lis.push(<li key={t.id}><a id={t.id} onClick={event=>{
			event.preventDefault();
    		props.onChangeMode(event.target.id);
		}}>{t.title}</a></li>)
    }
}
// event.target은 event를 유발시킨 a태그를 의미
// 실행
const list = [{id:1, title:'html'}, {id:2, title:'css'}, ...]
<Nav list = {list} onChangeMode={(id)=>{
    alert(id);
}}></Nav>


state

prop와, state가 변경시 React재렌더를 해서 컴포넌트를 다시 생성

  • prop는 컴포넌트를 사용하는 외부자를 위한 데이터
  • state는 컴포넌트를 만드는 내부자를 위한 데이터

image-20220711025149073

아래의 코드는 Header, Nav 태그를 누를때 마다 mode가 바껴서 해당하는 content가 출력되는 코드이다. 그러나 정상 작동하지 않는다.

  • prop나 state가 변경될시 함수가 재렌더 된다고 하였다. 그런데 현재 코드는 재렌더가 일어나지를 않으니까 처음 App()함수 return한 값을 그대로 사용하고 있을 뿐이다.
function App() {
    const mode = 'WELCOME';
	const list = [{id:1, title:'html'}, {id:2, title:'css'}, ...];
	let content = null;
    if(mode === 'WELCOME') {
		content = <Header title="WELCOME"/>
    } else if(mode === 'READ') {
		content = <Header title="READ"/>
    }
    return (
    	<div>
        	<Header~~~~~~~ onChangeMode={() =>{
        		mode = 'WELCOME';
		    }}</Header>
        	<Nav ~~~~~~~ onChangeMode={(id) => {
                mode = 'READ';
            }}</Nav>
    		{content}
    	</div>
    );
};

어떻게 해결을 하나? useState 라는 리액트에서 제공하는 상태관리 훅을 사용하면 된다. 관련문서

image-20220711030852359 useState('WELCOME') 은 2개의 원소를 가지는 객체이다.

// 수정코드
function App() {
    const [mode, setMode] = useState('WELCOME');
	const list = [{id:1, title:'html'}, {id:2, title:'css'}, ...];
	let content = null;
    if(mode === 'WELCOME') {
		content = <Header title="WELCOME"/>
    } else if(mode === 'READ') {
		content = <Header title="READ"/>
    }
    return (
    	<div>
        	<Header~~~~~~~ onChangeMode={() =>{
        		setMode('WELCOME');
		    }}</Header>
        	<Nav ~~~~~~~ onChangeMode={(id) => {
                setMode('READ');
            }}</Nav>
    		{content}
    	</div>
    );
};

Header, Nav클릭시 잘 변경되는거 확인하였고, Nav각 a태그들 클릭시 변경되는 부분을 코드로.

  • _id는 state의 id구분하기 위함.
  • _id는 클릭한 a태그의 id값을 가짐.
  • list 요소의 id숫자이다.
function Nav(props){
    // ... 생략
    props.onChangeMode(Number(event.target.id)); // number로 바꿔줘야 함.
    // ... 생략
}
// ... 생략
function App() {
    const [mode, setMode] = useState('WELCOME');
    const [id, setId] = useState(null);
	const list = [{id:1, title:'html'}, {id:2, title:'css'}, ...];
	let content = null;
    if(mode === 'WELCOME') {
		content = <Header title="WELCOME"/>
    } else if(mode === 'READ') { 
        // 이부분 for문으로 구성
        let title, body = null;
        for(let i = 0 ; i<list.length; i++){
            if(list[i].id === id) {
                title = list[i].title;
                body = list[i].body;
            }
        }
		content = <Header title={title} body={body}/>
    }
    return (
    	<div>
        	<Header~~~~~~~ onChangeMode={() =>{
        		setMode('WELCOME');
		    }}</Header>
        	<Nav ~~~~~~~ onChangeMode={(_id) => {
                setMode('READ');
                setId(_id);
            }}</Nav>
    		{content}
    	</div>
    );
};


CRUD

Create

  • Create를 클릭하면 mode를 CREATE로 바꿔서 화면에 해당 content 출력하려고 함.
// a태그를 이렇게 추가해서 위에서 else if(mode === 'CREATE')... 이런식으로 해결하면 된다.
<a onClick={(event) => {
    event.preventDefault();
    setMode('CREATE');
}}>Create/<a>

image-20220711033526145 그림처럼 구성이 될거고, 내부적으로 form으로 해결하면 된다.

form과 더불어 중요한 개념이 여기서 나타난다.

  • 2번째 그림이 복제를 하는 이유인데, value가 원시 데이터이며 배열인데 거기에 push를 한다는것은원시 데이터인 value 배열이 변경된거다. 여기서 setValue(value);를 해봤자 원시데이터의 value와 setValue의 매개변수로 넣은 value가 값이 동일하단건 당연한 얘기이다. 따라서 재렌더를 하지않는다.

image-20220711034229888

image-20220711035324866

이처럼 기존 숫자나 이런것 말고, 범객체(객체, 배열 등)들은 state 처리때 복제본을 써야한다.

  • event.target은 당연히 form태그
// Create 태그에 form태그 이용 + prevenDefault활용
function Create() {
    return <article>
    	<h2>Create</h2>
    	<form onSubmit={event=>{
                event.prevetDefault();
                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>
}

// App함수
// ... 생략
	const [nextId, setNextId] = useState(4) // 임시로 추가해서 이용
	const [list, setList]~~ state로 감싸기.
// ... 생략
} else if(mode === 'CREATE'){
    content = <Create onCreate={(_title, _body) => {
        const newList = {id:nextId, title:_title, body:_body}
        const newLists = [...list] // 복제
        newLists.push(newList)
        setList(newLists); // 데이터 변경시 렌더링
        
        setMode('READ');
        setId(nextId);
        setNextId(nestId+1);
    }}></Create>
}

Update

Update = Create + Read

Create와 유사하게 작성하면 되고, 자세한건 코드를 참고. Update는 난이도가 조금 있는 부분이다.

  • 주의할점은 기존의 값(props)이 form태그 내의 input태그의 value값으로 value={props.title} 이런식으로 가면 일종의 이것은 명령어이기 때문에 input태그에 아무것도 입력을 할 수가 없다.
  • 따라서 state로 환승하는것이 매우 중요하다
  • 또한 onChange이벤트와 같이 이를 활용해 setState를 통해 state값을 input태그의 사용자가 입력한 값으로 바꿔서 input태그에 value값에 다시 나타내는것 또한 중요하다.
  • 참고로 WELCOM 모드는 Update, Delete를 보여주지 않고 READ 모드가 보여준다.

Delete

input에 type을 button으로 추가하였다. 코드도 매우 간단하게 추가해서 끝낸다.

// 최종 코드
import logo from './logo.svg';
import './App.css';
import {useState} from 'react';

function Article(props){
  return <article>
    <h2>{props.title}</h2>
    {props.body}
  </article>
}
function Header(props){
  return <header>
    <h1><a href="/" onClick={(event)=>{
      event.preventDefault();
      props.onChangeMode();
    }}>{props.title}</a></h1>
  </header>
}
function Nav(props){
  const lis = []
  for(let i=0; i<props.topics.length; i++){
    let t = props.topics[i];
    lis.push(<li key={t.id}>
      <a id={t.id} href={'/read/'+t.id} onClick={event=>{
        event.preventDefault();
        props.onChangeMode(Number(event.target.id));
      }}>{t.title}</a>
    </li>)
  }
  return <nav>
    <ol>
      {lis}
    </ol>
  </nav>
}
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=>{
        setTitle(event.target.value);
      }}/></p>
      <p><textarea name="body" placeholder="body" value={body} onChange={event=>{
        setBody(event.target.value);
      }}></textarea></p>
      <p><input type="submit" value="Update"></input></p>
    </form>
  </article>
}
function App() {
  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="Hello, WEB"></Article>
  } else if(mode === 'READ'){
    let title, 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={()=>{
        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, 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;
          break;
        }
      }
      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;


참고문헌

생활코딩

댓글남기기