러닝 리액트

q6hillz·2022년 5월 8일
0

react

목록 보기
16/16
    var peaks = ["대청봉", "중청봉", "소청봉"]
    var canyons = ["천불동계곡", "가야동계곡"]
    var seoraksan = [...peaks, ...canyons]

    console.log(tahoe.join(', '))

   // 스프레드 연산자와 구조분해를 함께 사용
    var lakes = ["경포호", "화진포", "송지호", "청초호"]
    var [first, ...rest] = lakes

    console.log(rest.join(", "))

	//객체 반환하기
	const person = (firstName, lastName) => ({
    	first: firstName,
      	last: lastName
    });

	console.log(person("현석","오"));	

자바스크립트 컴파일링은 코드를 더 많은 브라우저가 이해할 수 있는 다른 버전의 자바스크립트 구문으로 변환한다.

	    // 객체 구조분해

    var sandwich =  {
      bread: "더치 크런치",
      meat: "참치",
      cheese: "스위스",
      toppings: ["상추", "토마토", "머스타드"]
    }

    var {bread, meat} = sandwich //객체의 2개의 프로퍼티만 가져옴

    console.log(bread, meat)

    bread = "마늘"
    meat = "칠면조"

    console.log(bread,meat)
    console.log(sandwich.bread, sandwich.meat)

	const [,,thirdAnimal] = ["캥거루","웜뱃","코알라"];
	console.log(thirdAnimal);

    var name = "Julia Mancuso"
    var sound = "go fast"

    const skier = {
      name,
      sound,
      powderYell() {
        let yell = this.sound.toUpperCase()
        console.log(`${yell} ${yell} ${yell}!!!`)
      },
      speed(mph) {
        this.speed = mph
        console.log('속력(mph):', mph)
      }
    }

    skier.powderYell()
    skier.speed(350)
    console.log(JSON.stringify(skier))

    // .reverse()가 peaks 배열을 변경함

    var peaks = ["대청봉", "중청봉", "소청봉"]
    var [last] = peaks.reverse()

    console.log(last)
    console.log(peaks.join(', '))

//프로미스 기본
    const getFakeMembers = count => new Promise((resolves, rejects) => {
      const api = `https://api.randomuser.me/?nat=US&results=${count}`
      const request = new XMLHttpRequest()
      request.open('GET', api)
      request.onload = () =>
           (request.status === 200) ?
            resolves(JSON.parse(request.response).results) :
            reject(Error(request.statusText))
      request.onerror = (err) => rejects(err)
      request.send()
    })

    getFakeMembers(5).then(
      members => console.log(members),
      err => console.error(
          new Error("randomuser.me에서 멤버를 가져올 수 없습니다."))
    )

경우에 따라 모듈에서 단 하나의 이름만을 외부에 익스포트하고 싶을 때가 있다. 기본 타입, 객체, 배열, 함수 등 모든 타입의 자바스크립트 이름을 외부로 노출시킬 수 있음.

	const free = new Expedition("프릴 산", 2, ["식수","간식"]);
	export default free;

	import {print as p, log as l} from './text-helpers';
	p('메세지를 print');
	l('메세지를 log');

2개 이상의 화살표가 있다면 고차 함수를 사용하고 있다는 뜻이다.

원본 배열을 훼손시키지 않고 복사 및 새롭게 창출하는 방법


let list = [ {title: "과격한 빨강"} ];
const addColor = (title,array) => array.concat({title});

addColor("화려한 녹색", list);

//or
const addColor = (title,list) => [...list, {title}];

	//객체를 배열화하는 방법
	const schools = {
     "Yorktown": 10,
     "Washington & Lee": 2,
     "Wakerfield": 5
    };

	const schoolArray = Object.keys(schools).map(key => ({
      name: key,
      wins: schools[key]
    })
    );

//array.reduce 사용법
    const colors = [
        {
            id: '-xekare',
            title: "rad red",
            rating: 3
        },
        {
            id: '-jbwsof',
            title: "big blue",
            rating: 2
        },
        {
            id: '-prigbj',
            title: "grizzly grey",
            rating: 5
        },
        {
            id: '-ryhbhsl',
            title: "banana",
            rating: 1
        }
    ]

    const hashColors = colors.reduce(
        (hash, {id, title, rating}) => {
            hash[id] = {title, rating}
            return hash
        },
        {}
    )

    console.log(hashColors)

리액트는 브라우저 DOM을 갱신해주기 위해 만들어진 라이브러리이다. 가상 DOM은 리액트 엘리먼트로 이뤄진다. 리액트 엘리먼트는 개념상 HTML 엘리먼트와 비슷하지만 실제로는 자바스크립트 객체이다. 가상 DOM을 변경하면 리액트는 DOM API를 통해 그 변경 사항을 효율적으로 렌더링 해준다.

	$$typeof: Symbol(React.element),
      "type": "h1",
      "key" : null,
      "ref" : null,
      "props": {id: "recipe-0", children: "구운 연어"},
      "_owner": null,
      "_store": {}

리액트 엘리먼트(컴포넌트를 이루는 작은 단위)의 type 프로퍼티는 만들려는 HTML이나 SVG 엘리먼트의 타입을 지정한다. props 프로퍼티는 DOM 엘리먼트를 만들기 위해 필요한 데이터나 자식 엘리먼트들을 표현한다. children 프로퍼티는 텍스트 형태로 표시할 다른 내부 엘리먼트이다.

JSX는 자바스크립트의 JS와 XML의 X를 합친 말이다.

어떤 브라우저도 JSX를 지원하지 않는다. => JSX 해석 인터프리터 바벨 존재.


//참고할만한 문법구조 
const Data = [
    {
        "name": "Baked Salmon",
        "ingredients": [
            { "name": "Salmon", "amount": 1, "measurement": "lb" },
            { "name": "Pine Nuts", "amount": 1, "measurement": "cup" },
            { "name": "Butter Lettuce", "amount": 2, "measurement": "cups" },
            { "name": "Yellow Squash", "amount": 1, "measurement": "med" },
            { "name": "Olive Oil", "amount": 0.5, "measurement": "cup" },
            { "name": "Garlic", "amount": 3, "measurement": "cloves" }
        ],
        "steps": [
            "Preheat the oven to 350 degrees.",
            "Spread the olive oil around a glass baking dish.",
            "Add the salmon, garlic, and pine nuts to the dish.",
            "Bake for 15 minutes.",
            "Add the yellow squash and put back in the oven for 30 mins.",
            "Remove from oven and let cool for 15 minutes. Add the lettuce and serve."
        ]
    },
    {
        "name": "Fish Tacos",
        "ingredients": [
            { "name": "Whitefish", "amount": 1, "measurement": "lb" },
            { "name": "Cheese", "amount": 1, "measurement": "cup" },
            { "name": "Iceberg Lettuce", "amount": 2, "measurement": "cups" },
            { "name": "Tomatoes", "amount": 2, "measurement": "large"},
            { "name": "Tortillas", "amount": 3, "measurement": "med" }
        ],
        "steps": [
            "Cook the fish on the grill until hot.",
            "Place the fish on the 3 tortillas.",
            "Top them with lettuce, tomatoes, and cheese."
        ]
    }
]

import Recipe from './Recipe'
import '../../stylesheets/Menu.css'

const Menu = ({ recipes }) =>
    <article>
        <header>
            <h1>Delicious Recipes</h1>
        </header>
        <div className="recipes">
            { recipes.map((recipe, i) =>
                <Recipe key={i} {...recipe} />)
            }
        </div>
    </article>

Menu.displayName = 'Menu'

export default Menu


<Menu recipes={Data} />

props를 구조 분해 한 뒤, 전체 배열을 Data로 전달하고 모듈을 통해 recipes 객체를 분해하여 각각 해당하는 레시피의 component를 생성한다.

Webpack == bundler

웹팩은 import문을 발견할 때마다 파일시스템에서 해당 모듈을 찾아서 번들에 포함시켜준다. 웹팩은 이런 임포트 트리를 쫓아가면서 필요한 모듈을 모두 번들에 넣어준다. 이것이 곧 의존 관계 그래프가 형성된다.

create-react app

위 명령은 리액트 프로젝트에서 React, ReactDOM, react-scripts에 대한 의존 관계를 설정해준다. 여기서 react-scripts는 바벨, ESLint, 웹팩 등을 설치해서 개발자가 직접 도구를 설정할 시간을 줄인다.

컴포넌트 트리란?

컴포넌트 트리는 프로퍼티를 통해 데이터가 흘러갈 수 있는 컴포넌트 계층 구조이다.

훅스에서 기억할 가장 중요한 내용은 훅이 걸린 컴포넌트를 렌더러와 연동시킨다는 점이다. 훅스에서 set 메소드를 사용하게 되면 해당 훅스가 걸린 컴포넌트는 다시 랜더링 된다는 특징이 존재한다.

순수 컴포넌트란?

상태가 없기 때문에 항상 같은 프롭에 대해 같은 사용자 인터페이스를 렌더링해주는 컴포넌트를 말한다.

Custom Hook

	import { useState } from "react";

	export const useInput = initialValue => {
    	const [value, setValue] = useState(initialValue);
      	return [
          { value, onChange: e => setValue(e.target.value) },
          () => setValue(initialValue)
        ];
    };

	const [titleProps, resetTitle] = useInput("");
	const [colorProps, resetColor] = useInput("#0000");

	<input {...titleProps} type="text" placeholder="color title..." required />

uuid 패키지

	import { v4 } from "uuid";

uuidv4 creates v4 UUIDs.

리액트 콘텍스트

콘텍스트를 사용하면 데이터를 한 위치에 저장할 수 있지만, 데이터를 사용하지 않을 여러 컴포넌트를 거쳐서 최종 컴포넌트에 전달할 필요가 없어진다.

import React, { createContext } from "react";
import colors from "./color-data";
import { render } from "react-dom";
import App from "./App";

export const ColorContext = createContext();

render(
  <ColorContext.Provider value={{ colors }}>
    <App />
  </ColorContext.Provider>,
  document.getElementById("root")
);

리액트에서 콘텍스트를 사용하려면 먼저 콘텍스트 프로바이더에게 데이터를 넣고, 프로바이더를 컴포넌트 트리에 추가해야 한다. 리액트에는 새로운 콘텍스트 객체를 만들 때 쓰는 createContext라는 함수가 있다. 만들어진 콘텍스트 객체에는 콘텍스트 Provider와 콘텍스트 Consumer라는 2가지 컴포넌트가 들어있다.

전체 App 컴포넌트를 프로바이더로 감쌌기 때문에 컴포넌트 트리 안의 모든 컴포넌트에서 colors 배열을 볼 수 있다. 여기서 ColorContext를 익스포트 해야 한다는 점에 유의하라. 익스포트를 해야 콘텍스트로부터 colors 데이터가 필요한 컴포넌트가 ColorContext.Consumer를 사용할 수 있다.

콘텍스트 Provider가 꼭 전체 애플리케이션을 감쌀 필요는 없다. 콘텍스트 Provider로 컴포넌트 트리 중 일부분만 감싸도 좋다. Provider는 자신이 감싸는 컴포넌트의 자식들에게만 콘텍스트를 제공한다.

useContext를 통해 색 얻기

useContext 훅을 사용해 콘텍스트에서 값을 얻을 수 있다.

import React, { useContext } from "react";
import { ColorContext } from "./";
import Color from "./Color";

export default function ColorList(){
  const { colors } = useContext(ColorContext);
  if(!colors.length) return <div>No Colors Listed. (Add a Color)</div>;
  return (
   	<div className="color-list">
    {
    	colors.map(color => <Color key={color.id} {...color} />)
    }
    </div>
  )

createContext()를 통해서 만들어진 컴포넌트의 provider를 전달해주고 싶은 컴포넌트를 감싸면 내부 컴포넌트는 useContext를 통해서 value를 가져올 수 있게 된다.

useEffect 함수 안에 코드를 적는다는 것은 렌더러가 렌더링을 한 직후에 부수효과로 실행한다는 뜻이다. useEffect로 감싸지지 않은 부분은 리랜더시 재선언되는 구조.

useMemo

useMemo는 메모화된 값을 계산하는 함수를 호출한다. 리액트에서 useMemo를 사용하면 캐시된 값과 계산한 값을 비교해서 실제 값이 변경됬는지 검사해준다.

	import React, { useEffect, useMemo } from "react";
	
	const words = useMemo(() => {
      const words = children.split(" ");
      return words;
    }, []);

	useEffect(()=> {
      console.log("fresh render");
    }, [words]);

useMemo에 의존 관계 배열을 전달하지 않으면 렌더링이 일어날 때마다 값을 재계산한다. "[]" 미선언시를 뜻함.

useCallback도 캐시화를 통해 렌더링시 재선언을 피하는 Memorization 함수인데 값 대신 함수를 메모화한다.

	const fn = useCallback(() => {
     	console.log("hello");
      	console.log("world");
    }, []);

	useEffect(()=> {
    	console.log("fresh render");
      	fn();
    }, [fn]);

useLayoutEffect

useLayoutEffect는 렌더링 사이클의 특정 순간에 호출된다.
1. 렌더링
2. useLayoutEffect가 호출됨
3. 브라우저의 화면 그리기: 이 시점에 컴포넌트에 해당하는 엘리먼트가 실제로 DOM에 추가됨
4. useEffect가 호출됨

<사용내역>
창의 크기가 바뀐 경우 엘리먼트의 너비와 높이를 얻고 싶을 때.
마우스 위치를 추적하는 경우.

useEffect 비동기 함수 실행

	useEffect(() => {
   	(async () => {
      await SomePromise();
    })();
});

useReducer 코드 개선

useReducer의 사용 목적은?
현재 상태를 받아서 새 상태를 반환하는 함수라 할 수 있다.

	function Checkbox() {
     const [checked, setChecked] = useState(false);
      
     function toggle(){
     	setChecked(checked => !checked);
     }
     
     return (
       <>
       	.............
       </>
     );
    }

위 코드를 useReducer로 재치환하여 좀 더 가독성 있게 만든다.

	function Checkbox() {
      const [checked, toggle] = useReducer(checked => !checked, false);

      ...

여기서 useReducer는 리듀서 함수와 초기 상태 false를 받는다.
useReducer는 기본적으로 Array.reduce 함수와 결을 같이 한다. 예시를 하나 들어보자.

	const numbers = [28, 34, 67, 68]'
	numbers.reduce((number, nextNumber) => number + nextNumber, 0);

위의 코드는 초기값을 0으로 설정하고 numbers 배열의 값을 순차적으로 더한 결과값을 반환한다.

	function Numbers() {
     const [number, setNumber] = useReducer(
		(number, newNumber) => number + newNumber,0);
      )
      
     return <h1 onClick={() => setNumber(30)}>{number}</h1>;
    }

리듀서를 써서 초기 상태와 새 정보를 쉽게 한데 모을 수 있다.

	function User(){
     const [user, setUser] = useReducer(
       (user, newDetails) => ({ ...user, ...newDetails }),
      	firstUser
      );
      ...
    }

기존 객체는 firstUser이고, setUser(리듀서)에 들어온 인자를 스프레드 기법으로 현재(user)객체와 합친다.

memo 훅

memo 함수에 전달되는 두 번째 인자는 술어. 술어는 항상 true나 false를 반환하게되고 이 결과 값이 렌더링을 결정하게 된다.

	const PureCat = memo(
      Cat,
      (prevProps, nextProps) => prevProps.name === nextProps.name
    );

전달된 이전 프롭의 name과 이후의 name을 비교하여,
1.true => 리렌더링 되지 않음
2.false => 렌더링 됨

fetch 예시

import React, { useState, useEffect } from "react";

function GitHubUser({ login }) {
  const [data, setData] = useState();

  useEffect(() => {
    if (!login) return;
    fetch(`https://api.github.com/users/${login}`)
      .then(response => response.json())
      .then(setData)
      .catch(console.error);
  }, [login]);

  if (data) return <pre>{JSON.stringify(data, null, 2)}</pre>;

  return null;
}

export default function App() {
  return <GitHubUser login="moonhighway" />;
}

참고
리액트에서 컴포넌트가 null을 반환하면 아무 것도 렌더링하지 말라는 뜻이다.

로컬 스토리지 저장

import React, { useState, useEffect } from "react";

const loadJSON = key => key && JSON.parse(localStorage.getItem(key));
const saveJSON = (key, data) => localStorage.setItem(key, JSON.stringify(data));

function GitHubUser({ login }) {
  const [data, setData] = useState(loadJSON(`user:${login}`));

  useEffect(() => {
    if (!data) return;
    if (data.login === login) return;
    const { name, avatar_url, location } = data;
    saveJSON(`user:${login}`, {
      name,
      login,
      avatar_url,
      location
    });
  }, [data]);

  useEffect(() => {
    if (!login) return;
    if (data && data.login === login) return;
    fetch(`https://api.github.com/users/${login}`)
      .then(response => response.json())
      .then(setData)
      .catch(console.error);
  }, [login]);

  if (data) return <pre>{JSON.stringify(data, null, 2)}</pre>;

  return null;
}

export default function App() {
  return <GitHubUser login="moonhighway" />;
}

...........
export default function App() {
 return <GitHubUser login="moonhighway" />; 
}

컴포넌트 props에서 login값을 받아와 loadJSON 실행. localStorage에서 key값에 해당하는 value값을 추출해와서 data에 초기화시킨다.
여기서 2가지 분기.

1) data가 null.
2) data localStorage에 존재.

1번의 경우.
첫번째 useEffect 넘어감. 두번째 useEffect에서 fetch를 통해 비동기적 요청을 수행. response를 json으로 받아와서 data에 저장해줌.
data가 변경되었으니 첫번째 useEffect 재실행됨. (내가 착각한게 있는데 useMediaQuery와 같은 hook이 state처럼 변경되면 rerender를 실행하게 해주는거지 useEffect는 리렌더링 시켜주지 않는다.)

data는 서버에서 받아온 JSON임. data.login과 현재 props로 입력한 login이 같기때문에 return됨.

2번의 경우.
data.login과 현재 props로 입력한 login이 같기때문에 return됨.

웹 스토리지 API를 사용하면 브라우저에 데이터를 저장 할 수 있음. 이때 window.localStorage나 window.sessionStorage 객체를 사용한다.

sessionStorage => 탭을 닫거나 브라우저를 재시작하면 데이터는 사라짐.
localStorage => 도구 설정가서 제거하기 전까지는 무기한 보관.

JavaScript Object Notation (JSON)은 Javascript 객체 문법으로 구조화된 데이터를 표현하기 위한 문자 기반의 표준 포맷입니다.

Parse the data with JSON.parse(), and the data becomes a JavaScript object.

loadJSON, saveJSON, JSON 파싱과 같은 경우들은 모두 동기적 작업이다. 일반적으로 이런 함수에서는 성능 유지를 위해 데이터의 크기를 제한하는 편이 좋다.

useEffect는 Array dependency가 존재할 경우 Re render될 경우에만 실행된다.


라우터

import { useColors } from "./";

export function ColorDetails(){
 	let { id } = useParams();
 	let { colors } = useColors();
 	let foundColor = colors.find(
      color => color.id === id
    );
  
  	console.log(foundColor);
  	
  	return (
      <div>
      	<h1>Details</h1>
      </div>
    );
}

URL의 파라미터를 가져와서 hook으로 활용하는 방법. URL 파라미터가 변경되면은 Component도 리렌더링된다.

import { useNavigate } from "react-router-dom";
	
let navigate = useNavigate();

return (
	<section
  		className="color"
  		onClick={()=> navigate(`/${id}`)}
  	>
    </section>
);

useNavigate를 통해서 다른 페이지로 이동 가능.

// Router.js

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Web from "../Pages/Web";
import WebPost from "../Pages/WebPost";

const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="web/*" element={<Web />}>
          <Route path=":id" element={<WebPost />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
};

export default Router;
// Web.js

import React from "react";
import { Link, Routes, Route, Outlet } from "react-router-dom";
import WebPost from "./WebPost";

const Web = () => {
  return (
    <div>
      <h1>This is Web</h1>
      <ul>
        <li>
          <Link to="1">Post #1</Link>
        </li>
        <li>
          <Link to="2">Post #2</Link>
        </li>
        <li>
          <Link to="3">Post #3</Link>
        </li>
        <li>
          <Link to="4">Post #4</Link>
        </li>
      </ul>

      <Outlet />
    </div>
  );
};

export default Web;

//출처 : https://velog.io/@soryeongk/ReactRouterDomV6

라우트가 감싸고 있는 prop.children을 outlet으로 렌더링한다.

0개의 댓글