React 4.

AWESOMee·2022년 7월 4일
0

React

목록 보기
4/6
post-thumbnail

immutability(불변성)

  • 불변성을 지킨다는 것은 기존의 값을 수정하는 것이 아니고 새로운 값을 만들어 내는 것

nextArrayBad는 array와 같은 주소값을 참조하는 것
nextArrayBad의 값을 변경하면 원본 배열인 array의 값도 함께 변경됨

nextArrayGood은 array를 복제하여 만든 것
nextArrayGood의 값을 변경해도 원본 배열인 array의 값은 변경되지 않는다.

  • 만약 복제해야될 객체나 배열이 복잡해지면 불변성을 유지하기가 어려워짐


immer library

AppWithoutImmer.js

import { useRef, useState, useCallback } from "react";

function AppWithoutImmer() {
  const nextId = useRef(1);
  const [form, setForm] = useState({ name: "", username: "" });
  const [data, setData] = useState({
    array: [],
    uselessValue: null,
  });

  const handleChange = useCallback(
    (e) => {
      const { name, value } = e.target;
      setForm({
        ...form,
        [name]: value,
      });
    },
    [form]
  );

  const handleSubmit = useCallback(
    (e) => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username,
      };
      setData({
        ...data,
        array: data.array.concat(info),
      });

      setForm({
        name: "",
        username: "",
      });

      nextId.current += 1;
    },
    [data, form.name, form.username]
  );

  const handleRemove = useCallback(
    (id) => {
      setData({
        ...data,
        array: data.array.filter((info) => info.id !== id),
      });
    },
    [data]
  );

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        type="text"
        placeholder="아이디"
        value={form.username}
        onChange={handleChange}
      />
      <input
        name="name"
        type="text"
        placeholder="이름"
        value={form.name}
        onChange={handleChange}
      />
      <button type="submit">등록</button>
      <ul>
        {data.array.map((info) => (
          <li key={info.id} onClick={() => handleRemove(info.id)}>
            {info.username} {info.name}
          </li>
        ))}
      </ul>
    </form>
  );
}

export default AppWithoutImmer;

with Immer

import { useRef, useState, useCallback } from "react";
import produce from "immer";

function App() {
  const nextId = useRef(1);
  const [form, setForm] = useState({ name: "", username: "" });
  const [data, setData] = useState({
    array: [],
    uselessValue: null,
  });

  const handleChange = useCallback((e) => {
    const { name, value } = e.target;
    setForm(
      produce((draft) => {
        draft[name] = value;
      })
    );
  }, []);

  const handleSubmit = useCallback(
    (e) => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username,
      };
      setData(
        produce((draft) => {
          draft.array.push(info);
        })
      );

      setForm({
        name: "",
        username: "",
      });

      nextId.current += 1;
    },
    [form.name, form.username]
  );

  const handleRemove = useCallback(
    (id) => {
      setData(
        // 소스코드가 복잡해질 때는 그냥 immer 사용하지 않고 작성
        {
          ...data,
          array: data.array.filter((info) => info.id !== id),
        }
        // produce(data, (draft) => {
        //   draft.array.splice(
        //     draft.array.findIndex((info) => info.id !== id),
        //     1
        //   );
        //   // pop을 쓰면 제일 마지막의 요소부터 순서대로 pop되기 때문에 적합하지 않음
        //   // draft.array.pop((info) => info.id === id);
        // })
      );
    },
    [data]
  );

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        type="text"
        placeholder="아이디"
        value={form.username}
        onChange={handleChange}
      />
      <input
        name="name"
        type="text"
        placeholder="이름"
        value={form.name}
        onChange={handleChange}
      />
      <button type="submit">등록</button>
      <ul>
        {data.array.map((info) => (
          <li key={info.id} onClick={() => handleRemove(info.id)}>
            {info.username} {info.name}
          </li>
        ))}
      </ul>
    </form>
  );
}

export default App;

React Router

import {BrowserRouter} from 'react-router-dom'

Index.js

...
import {BrowserRouter} from 'react-router-dom'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

...

App.js

import "./App.css";
import { Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />}></Route>
      <Route path="/about" element={<About />}></Route>
    </Routes>
  );
}

export default App;

리액트 라우터 사용시에는 Link 태그 이용
(a 태그 이용하면 브라우저에서 페이지를 새로 불러오기 때문)

Home.js

import React from "react";
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <div>
      <h1>Home</h1>
      <p>The First Page</p>
      <Link to="/about">Move to About Page</Link>
    </div>
  );
};

export default Home;

URL Parameter

About.js

import React from "react";
import { useLocation } from "react-router-dom";

const About = () => {

    const location = useLocation();

  return (
    <div>
      <h1>Intro</h1>
      <p>Project Using Router</p>
      {/* search : query string included "?" */}
      <p>{location.search}</p>
    </div>
  );
};

export default About;

Home.js

import React from "react";
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <div>
      <h1>Home</h1>
      <p>The First Page</p>
      <ul>
        <li>
          <Link to="/about">Move to About Page</Link>
        </li>
        <li>
            <Link to="/profile/yeji">yeji's profile</Link>
        </li>
        <li>
            <Link to="/profile/hong">hong's profile</Link>
        </li>
        <li>
            <Link to="/profile/void">Not Existing profile</Link>
        </li>
      </ul>
    </div>
  );
};

export default Home;

App.js

import "./App.css";
import { Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";
import Profile from "./components/Profile";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />}></Route>
      <Route path="/about" element={<About />}></Route>
      <Route path="/profile/:name" element={<Profile />}></Route>
    </Routes>
  );
}

export default App;

Query String

About.js

import React from "react";
import { useSearchParams } from "react-router-dom";

const About = () => {
  const [searchParam, setSearchParam] = useSearchParams();
  const detail = searchParam.get("detail");
  const mode = searchParam.get("mode");

  const onToggleDetail = () => {
    setSearchParam({
      mode,
      detail: detail === "true" ? false : true,
    });
  };

  const inIncreaseMode = () => {
    const nextMode = mode === "null" ? 1 : parseInt(mode) + 1;
    setSearchParam({
      mode: nextMode,
      detail,
    });
  };

  return (
    <div>
      <h1>Intro</h1>
      <p>Project Using Router</p>
      {/* search : query string included "?" */}
      {/* <p>{location.search}</p> */}
      <p>detail: {detail}</p>
      <p>mode: {mode}</p>
      <button type="button" onClick={onToggleDetail}>
        Toggle detail
      </button>
      <button type="button" onClick={inIncreaseMode}>
        mode + 1
      </button>
    </div>
  );
};

export default About;


import { Outlet } from "react-router-dom"

Articles.js

import React from "react";
import { Link, Outlet } from "react-router-dom";

const Articles = () => {
  return (
    <ul>
        {/* Route의 children으로 들어가는 JSX element를 보여줌 */}
        <Outlet />
      <li>
        <Link to="/articles/1">post 1</Link>
      </li>
      <li>
        <Link to="/articles/2">post 2</Link>
      </li>
      <li>
        <Link to="/articles/3">post 3</Link>
      </li>
    </ul>
  );
};

export default Articles;

Article.js

import React from "react";
import { useParams } from "react-router-dom";

const Article = () => {
  const { id } = useParams();
  return (
    <div>
      <h2>post {id}</h2>
    </div>
  );
};

export default Article;

App.js

import "./App.css";
import { Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";
import Profile from "./components/Profile";
import Articles from "./components/Articles";
import Article from "./components/Article";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />}></Route>
      <Route path="/about" element={<About />}></Route>
      <Route path="/profile/:name" element={<Profile />}></Route>
      <Route path="/articles" element={<Articles />}>
        <Route path=":id" element={<Article />}></Route>
      </Route>
    </Routes>
  );
}

export default App;

<Outlet />을 이용하면 리스트가 밑에 그대로 보이고 상단에 라우팅한 페이지 표출됨


useNavigate

Layout.js

import React from "react";
import { Outlet, useNavigate } from "react-router-dom";

const Layout = () => {
    const navigate = useNavigate();

    const goBack = () => {
        navigate(-1);
    }

    const goArticles = () => {
        navigate('/articles');
    }
  return (
    <div>
      <header style={{ background: "lightgray", padding: 16, fontSize: 24 }}>
        Header
        <button type="button" onClick={goBack}>Back</button>
        <button type="button" onClick={goArticles}>go to post list</button>
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
};

export default Layout;

App.js

import "./App.css";
import { Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";
import Profile from "./components/Profile";
import Articles from "./components/Articles";
import Article from "./components/Article";
import Layout from "./components/Layout";

function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<Home />}></Route>
        <Route path="/about" element={<About />}></Route>
        <Route path="/profile/:name" element={<Profile />}></Route>
        <Route path="/articles" element={<Articles />}>
          <Route path=":id" element={<Article />}></Route>
        </Route>
      </Route>
    </Routes>
  );
}

export default App;


Articles.js

import React from "react";
import { NavLink, Outlet } from "react-router-dom";

const Articles = () => {
  const activeStyle = {
    color: "green",
    fontSize: 21,
  };
  return (
    <ul>
      {/* Route의 children으로 들어가는 JSX element를 보여줌 */}
      <Outlet />
      <li>
        <NavLink
          to="/articles/1"
          style={({ isActive }) => (isActive ? activeStyle : undefined)}
        >
          post 1
        </NavLink>
      </li>
      <li>
        <NavLink
          to="/articles/2"
          style={({ isActive }) => (isActive ? activeStyle : undefined)}
        >
          post 2
        </NavLink>
      </li>
      <li>
        <NavLink
          to="/articles/3"
          style={({ isActive }) => (isActive ? activeStyle : undefined)}
        >
          post 3
        </NavLink>
      </li>
    </ul>
  );
};

export default Articles;

ver2

Articles.js

import React from "react";
import { NavLink, Outlet } from "react-router-dom";

const ArticleItem = ({ id }) => {
  const activeStyle = {
    color: "green",
    fontSize: 21,
  };

  return (
    <li>
      <NavLink
        to={`/articles/${id}`}
        style={({ isActive }) => (isActive ? activeStyle : undefined)}
      >
        post {id}
      </NavLink>
    </li>
  );
};

const Articles = () => {
  return (
    <ul>
      {/* Route의 children으로 들어가는 JSX element를 보여줌 */}
      <Outlet />
      <ArticleItem id={1} />
      <ArticleItem id={2} />
      <ArticleItem id={3} />
    </ul>
  );
};

export default Articles;

isLogged == true ? mypage : login page

Login.js

import React from 'react';

const Login = () => {
    return (
        <div>
            Login Page
        </div>
    );
};

export default Login;

Mypage.js

import React from 'react';
import { Navigate } from 'react-router-dom';

const Mypage = () => {

    const isLogged = false;

    if(!isLogged) {
        return <Navigate to='/login' />
    }
    return (
        <div>
            My Page
        </div>
    );
};

export default Mypage;

App.js

import "./App.css";
import { Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";
import Profile from "./components/Profile";
import Articles from "./components/Articles";
import Article from "./components/Article";
import Layout from "./components/Layout";
import NotFound from "./components/NotFound";
import Login from "./components/Login";
import Mypage from "./components/Mypage";

function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<Home />}></Route>
        <Route path="/about" element={<About />}></Route>
        <Route path="/profile/:name" element={<Profile />}></Route>
        <Route path="/articles" element={<Articles />}>
          <Route path=":id" element={<Article />}></Route>
        </Route>
        <Route path="/login" element={<Login />}></Route>
        <Route path="/mypage" element={<Mypage />}></Route>
      </Route>
      <Route path="*" element={<NotFound />}></Route>
    </Routes>
  );
}

export default App;
profile
개발을 배우는 듯 하면서도

0개의 댓글