1. https://joshua-dev-story.blogspot.com/2020/01/react-spring.html
  2. https://joshua-dev-story.blogspot.com/2020/01/react-spring.html
  3. https://joshua-dev-story.blogspot.com/2020/01/react-spring-3.html

아파치 서버 추가 방법 :

STS 아파치 톰캣 서버 추가

import logo from './logo.svg';
import './App.css';
import React, { useState } from 'react'; // useState 훅 이용 (react에서 제공하는 기본적인 함수)
import customAxios from './customAxios';
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Link
} from 'react-router-dom';

function Header(props){
  console.log("props : " , props, props.title)
  return <header>
    <h1><a href='/' onClick={(event)=>{
      event.preventDefault(); // 리로드 방지
      props.onChangeMode(); // Header 태그에 작성한 이벤트 호출
    }}>{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));  // Nav 태그에 작성한 이벤트 호출, event.target.id는 문자열
        // event.target : 이벤트를 유발시킨 태그 = a 태그
      }}>{t.title}</a>
      </li>)
  }
  return <nav>
    <ol>
      {lis}
    </ol>
  </nav>
}

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

function Create(props){
  return <article>
    <h2>Create</h2>
    <form onSubmit={event=>{
      event.preventDefault(); // submit 됐을 때 reload 방지
      const title = event.target.title.value;  // event.target == form 태그
      const body = event.target.body.value;  // event.target == form 태그
      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"/></p>
    </form>
  </article>
}

function Update(props){
  const [title, setTitle] = useState(props.title);  // props.title을 state로 환승
  const [body, setBody] = useState(props.body); // props.body를 state로 환승
  return <article>
    <h2>Update</h2>
    <form onSubmit={event=>{
      event.preventDefault(); // submit 됐을 때 reload 방지
      const title = event.target.title.value;  // event.target == form 태그
      const body = event.target.body.value;  // event.target == form 태그
      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"/></p>
    </form>
  </article>
}

function App() {

// IP주소 변수 선언
 const [ip, setIp] = useState('');

// IP주소 값을 설정합니다.
 function callback(data) {
   setIp(data);
 }

// 첫번째 렌더링을 다 마친 후 실행합니다.
  useState(
  () => {
    // 클라이언트의 IP주소를 알아내는 백엔드의 함수를 호출합니다.
    customAxios('/ip', callback);
  }, []
);

  // const _mode = useState('WELCOME'); // 지역변수를 state로 업그레이드. 초기값: WELCOME 
  // const mode = _mode[0];  // state의 초기 값
  // const setMode = _mode[1]; // state를 바꿀 때 사용
  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] // 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)=>{
      const newTopics = [...topics] // topics 배열 복제
      const updatedTopic = {id:id ,title:title, body:body}  // mode === "READ" 가 실행된 상태이므로 id값은 id state를 사용해주면됨

      for(let i=0; i<newTopics.length; i++){
        if(newTopics[i].id === id){
          newTopics[i] = updatedTopic;
          break;
        }
      }
      setTopics(newTopics);
      setMode('READ');
    }}></Update>
  }
  return (
    <div>
      <Router>
      <div className="App">
        <nav>
          <ul>
            <li>
              <Link to="/">홈</Link>
            </li>
            <li>
              <Link to="/about">소개</Link>
            </li>
            <li>
              <Link to="/users">사용자</Link>
            </li>
          </ul>
        </nav>

        {/* <Routes>는 하위 <Route>들을 살펴보고 현재 URL과 일치하는 첫 번째 경로를 렌더링합니다. */}
        <Routes>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/users">
            <Users />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Routes>
      </div>
    </Router>

      <Header title="WEB" onChangeMode={()=>{
        setMode('WELCOME'); // state인 mode 값 변경
      }}></Header>
      <Nav topics = {topics} onChangeMode={(_id)=>{
        setMode('READ'); // state인 mode 값 변경
        setId(_id); // state인 id 값 변경
      }}></Nav>
      {content}
      <ul>
        <li><a href='/create' onClick={event=>{
          event.preventDefault();
          setMode('CREATE');
        }}>Create</a></li>
        {contextControl}
      </ul>
    </div>
  );
}

function Home() {
  // IP주소 변수 선언
  const [ip, setIp] = useState('');

  // IP주소 값을 설정합니다.
  function callback(data) {
    setIp(data);
  }

  // 첫번째 렌더링을 다 마친 후 실행합니다.
  useState(
    () => {
      // 클라이언트의 IP주소를 알아내는 백엔드의 함수를 호출합니다.
      customAxios('/ip', callback);
    }, []
  );

  return (
    <header className="App-header">
      이 기기의 IP주소는 {ip}입니다.
    </header>
  );
}

function About() {
  return (
    <div>
      <hr />
      <h2>소개 페이지</h2>
    </div>
  );
}

function Users() {
  return (
    <div>
      <hr />
      <h2>사용자 페이지</h2>
    </div>
  );
}

export default App;

최종 코드

import logo from './logo.svg';
import './App.css';
import React, { useState } from 'react'; // useState 훅 이용 (react에서 제공하는 기본적인 함수)
import customAxios from './customAxios';
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Link
} from 'react-router-dom';

function Header(props){
  console.log("props : " , props, props.title)
  return <header>
    <h1><a href='/' onClick={(event)=>{
      event.preventDefault(); // 리로드 방지
      props.onChangeMode(); // Header 태그에 작성한 이벤트 호출
    }}>{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));  // Nav 태그에 작성한 이벤트 호출, event.target.id는 문자열
        // event.target : 이벤트를 유발시킨 태그 = a 태그
      }}>{t.title}</a>
      </li>)
  }
  return <nav>
    <ol>
      {lis}
    </ol>
  </nav>
}

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

function Create(props){
  return <article>
    <h2>Create</h2>
    <form onSubmit={event=>{
      event.preventDefault(); // submit 됐을 때 reload 방지
      const title = event.target.title.value;  // event.target == form 태그
      const body = event.target.body.value;  // event.target == form 태그
      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"/></p>
    </form>
  </article>
}

function Update(props){
  const [title, setTitle] = useState(props.title);  // props.title을 state로 환승
  const [body, setBody] = useState(props.body); // props.body를 state로 환승
  return <article>
    <h2>Update</h2>
    <form onSubmit={event=>{
      event.preventDefault(); // submit 됐을 때 reload 방지
      const title = event.target.title.value;  // event.target == form 태그
      const body = event.target.body.value;  // event.target == form 태그
      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"/></p>
    </form>
  </article>
}

function App() {

// IP주소 변수 선언
 const [ip, setIp] = useState('');

// IP주소 값을 설정합니다.
 function callback(data) {
   setIp(data);
 }

// 첫번째 렌더링을 다 마친 후 실행합니다.
  useState(
  () => {
    // 클라이언트의 IP주소를 알아내는 백엔드의 함수를 호출합니다.
    customAxios('/ip', callback);
  }, []
);

  // const _mode = useState('WELCOME'); // 지역변수를 state로 업그레이드. 초기값: WELCOME 
  // const mode = _mode[0];  // state의 초기 값
  // const setMode = _mode[1]; // state를 바꿀 때 사용
  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] // 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)=>{
      const newTopics = [...topics] // topics 배열 복제
      const updatedTopic = {id:id ,title:title, body:body}  // mode === "READ" 가 실행된 상태이므로 id값은 id state를 사용해주면됨

      for(let i=0; i<newTopics.length; i++){
        if(newTopics[i].id === id){
          newTopics[i] = updatedTopic;
          break;
        }
      }
      setTopics(newTopics);
      setMode('READ');
    }}></Update>
  }
  return (
    <div>
      <Router>
      <div className="App">
        <nav>
          <ul>
            <li>
              <Link to="/">홈</Link>
            </li>
            <li>
              <Link to="/about">소개</Link>
            </li>
            <li>
              <Link to="/users">사용자</Link>
            </li>
          </ul>
        </nav>

        {/* <Routes>는 하위 <Route>들을 살펴보고 현재 URL과 일치하는 첫 번째 경로를 렌더링합니다. */}
        <Routes>
          <Route path="/about" element={ <About /> }>
          </Route>
          <Route path="/users" element={ <Users /> }>
          </Route>
          <Route path="/"  element={ <Home /> }>
          </Route>
        </Routes>
      </div>
    </Router>

      <Header title="WEB" onChangeMode={()=>{
        setMode('WELCOME'); // state인 mode 값 변경
      }}></Header>
      <Nav topics = {topics} onChangeMode={(_id)=>{
        setMode('READ'); // state인 mode 값 변경
        setId(_id); // state인 id 값 변경
      }}></Nav>
      {content}
      <ul>
        <li><a href='/create' onClick={event=>{
          event.preventDefault();
          setMode('CREATE');
        }}>Create</a></li>
        {contextControl}
      </ul>
    </div>
  );
}

function Home() {
  // IP주소 변수 선언
  const [ip, setIp] = useState('');

  // IP주소 값을 설정합니다.
  function callback(data) {
    setIp(data);
  }

  // 첫번째 렌더링을 다 마친 후 실행합니다.
  useState(
    () => {
      // 클라이언트의 IP주소를 알아내는 백엔드의 함수를 호출합니다.
      customAxios('/ip', callback);
    }, []
  );

  return (
    <header className="App-header">
      이 기기의 IP주소는 {ip}입니다.
    </header>
  );
}

function About() {
  return (
    <div>
      <hr />
      <h2>소개 페이지</h2>
    </div>
  );
}

function Users() {
  return (
    <div>
      <hr />
      <h2>사용자 페이지</h2>
    </div>
  );
}

export default App;