React 작업의 순서

리린·2021년 8월 17일
0

React

목록 보기
12/47

설계

-api 설계: 어떤 주소로 들어가면 어떤 값이 반환되는지 미리 알아두자.
주소 / RESTAPI/ 서버>클라이언트 정보 /클라이언트>서버 정보

리액트 초기세팅

  • 설치: yarn create react-app frontend
  • .prettierrc 파일 추가
  • .eslintrc.json 파일 추가

src/하위 폴더에 components 추가

  1. 스타일링 하기
  • 설치: yarn add styled-components
  • 다음과 같이 불러오기
    import styled from 'styled-components';
  • (선택) 공통 css 가 있을 경우
    import styled {css} from 'styled-components';
  • index.css 스타일링하기
  • App.css 스타일링하기
  • 자세한 방법
    -- 파일 이름은 대문자여야 한다
    -- template일 경우 {children}을 props로 받아와야 한다.
  1. (선택사항) Link 필요할 경우 Link 걸기
  • 설치: yarn add react-router-dom
  • 다음과 같이 불러오기
    import { Link } from "react-router-dom";
  • 팁: Link api디자인을 먼저 해두면 좋다.
  1. app.js에 추가

미디어 쿼리

  1. 참고 사이트
    미디어 쿼리
    리액트 미디어 쿼리

(나는 리액트 미디어 쿼리가 편한 듯하다)

  1. 모듈 설치 &
yarn add react-responsive 
  1. 가져오기
import 
  1. 컴포넌트에서 다음과 같이 사용한다
    (컴포넌트 내부 )
import MediaQuery from 'react-responsive'

const Example = () => (
  <div>
    <h1>Device Test!</h1>
    <MediaQuery minWidth={1224}>
      <p>You are a desktop or laptop</p>
      <MediaQuery minWidth={1824}>
        <p>You also have a huge screen</p>
      </MediaQuery>
    </MediaQuery>
    <MediaQuery minResolution="2dppx">
      {/* You can also use a function (render prop) as a child */}
      {(matches) =>
        matches
          ? <p>You are retina</p>
          : <p>You are not retina</p>
      }
    </MediaQuery>
  </div>
)
  1. 훅으로는 다음과 같이 사용한다

(컴포넌트 내부2)

import { useMediaQuery } from 'react-responsive'

const Example = () => {
  const isDesktopOrLaptop = useMediaQuery(
     { minDeviceWidth: 1224 },
     { deviceWidth: 1600 } // `device` prop
  )

return (
    <div>
      {isDesktopOrLaptop &&
        <p>
          this will always get rendered even if device is shorter than 1224px,
          that's because we overrode device settings with 'deviceWidth: 1600'.
        </p>
      }
    </div>
  )
  • 작은 숫자로 감싼 것은, 모바일 퍼스트 원칙에 따른 것이다.
  1. 조건도 다양하게 붙일 수 있다.
import React from 'react'
import { useMediaQuery } from 'react-responsive'

const Example = () => {
  const isDesktopOrLaptop = useMediaQuery({ minWidth: 1224 })
  const isBigScreen = useMediaQuery({ minWidth: 1824 })
  const isTabletOrMobile = useMediaQuery({ maxWidth: 1224 })
  const isPortrait = useMediaQuery({ orientation: 'portrait' })
  const isRetina = useMediaQuery({ minResolution: '2dppx' })

  return (
    <div>
      ...
    </div>
  )
}
  1. 여러 조건을 한꺼번에 쓰고 싶다면 다음과 같이 쓴다
const Example = () => {
  const isDesktopOrLaptop = useMediaQuery(
     { minDeviceWidth: 1224 },
     { deviceWidth: 1600 } // `device` prop
  )

  return (
    <div>
      {isDesktopOrLaptop &&
        <p>
          this will always get rendered even if device is shorter than 1224px,
          that's because we overrode device settings with 'deviceWidth: 1600'.
        </p>
      }
    </div>
  )
}
  1. 서버사이드 렌더링은 다음과 같이 구현한다( 자세한 건 공식사이트 참고)
import { Context as ResponsiveContext } from 'react-responsive'
import { renderToString } from 'react-dom/server'
import App from './App'

...
  // Context is just a regular React Context component, it accepts a `value` prop to be passed to consuming components
  const mobileApp = renderToString(
    <ResponsiveContext.Provider value={{ width: 500 }}>
      <App />
    </ResponsiveContext.Provider>
  )
...

page 컴포넌트

  1. 페이지 폴더(예시: pages )/ 페이지 파일(예시: LoginPage.js) 만들기
    2.페이지 파일 내용
  • 컴포넌트, 혹은 컨테이너(리덕스를 사용할 경우) 불러오기
import React from 'react';
import AuthTemplate from '../components/auth/AuthTemplate';
import LoginForm from '../containers/auth/LoginForm';

const LoginPage = () => {
  return (
    <AuthTemplate>
      <LoginForm />
    </AuthTemplate>
  );
};

export default LoginPage;

라우터 적용하기

  1. 라우터 적용하기
    -설치: yarn add react-router-dom
  • 다음과 같이 모듈 불러오기
    import { Route } from 'react-router-dom';
    -다음과 같이 페이지 불러오기
import LoginPage from './pages/LoginPage';
  1. app.js에 다음과 같이 쓰기
    -component는 page를 불러오고, path는 하나 혹은 여러개(객체 안 배열) 설정 가능하다.
<Route component={PostListPage} path={['/@:username', '/']} exact />
  1. index.js에 다음과 같이 작성
    -설치: yarn add react-dom react-router-dom
    -다음과 같이 모듈 불러오기
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(

    <BrowserRouter>
      <App />
    </BrowserRouter>,

  document.getElementById('root'),
);
export default rootReducer;

Link 모듈 사용하기

  • 상황: a 태그가 필요할 때.
  1. Link 모듈 가져오기
import { Route, Link } from 'react-router-dom';
  1. 사용하고자 하는 파일 부분에 다음과 같이 적용하기
<Link to="/"></Link>

연산 최적화 (useMemo)

  • 함수형 컴포넌트 내부에서 발생하는 연산 최적화
  1. useMemo 모듈 가져오기
import {useMemo} from 'react
  1. 컴포넌트 내부에서 특정 값이 바뀔 때만 연산이 이뤄지도록 설정
  const avg = useMemo(() => getAverage(list), [list]);

(Average.js)

import React, { useState, useMemo } from 'react';
 
const getAverage = numbers => {
  console.log('평균값 계산 중..');
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');
 
  const onChange = e => {
    setNumber(e.target.value);
  };
  const onInsert = () => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
  };
 
  const avg = useMemo(() => getAverage(list), [list]);
 
  return (
    <div>
      <input value={number} onChange={onChange} />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값:</b> {avg}
      </div>
    </div>
  );
};
 
export default Average;

렌더링 성능 최적화(useCallback)

  • 상황: 리렌더링될 때마다 함수가 새로 만들어지는데, 이를 방지하기 위함.
  1. 모듈설치
import {useCallback} from 'react'
  1. useCallback 인자 정하기
    -고정적으로 사용해도 상관없는 함수
const onChange = useCallback(e => {
    setNumber(e.target.value);
}, []); 

-바뀌면 변해야 하는 함수

const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
  }, [number, list]);

(Average.js)

import React, { useState, useMemo, useCallback } from 'react';
 
const getAverage = numbers => {
  console.log('평균값 계산 중..');
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
};
 
const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');
 
  const onChange = useCallback(e => {
    setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
  }, [number, list]); // number 혹은 list가 바뀌었을 때만 함수 생성
 
  const avg = useMemo(() => getAverage(list), [list]);
 
  return (
    <div>
      <input value={number} onChange={onChange}  />
      <button onClick={onInsert}>등록</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      <div>
        <b>평균값:</b> {avg}
        </div>
    </div>
  );
};
 
export default Average;

컴포넌트 성능 최적화

  • 매우 쉬움
  • props가 바뀌지 않으면 리렌더링하지 않는다.
  1. 모듈 설치 필요x(react에 내장되어 있음)

  2. 마지막 모듈 export default할 때, React.memo로 감싸주기

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
 
const TodoListItem = ({ todo, onRemove, onToggle }) => {
  (...)
};
 
export default React.memo(TodoListItem);
  

onToggle, onRemove 등 함수 최적화

2가지 방법: useState의 함수형 업데이트 기능 vs useReducer 사용
1번방법:
1. useState 불러오기

import {useState} from 'react'
  1. setNumber 에 화살표 함수를 넣어준다.
const [number, setNumber] = useState(0);
// prevNumbers는 현재 number 값을 가리킵니다.
const onIncrease = useCallback(
  () => setNumber(prevNumber => prevNumber + 1),
  [],
);
  1. 이 경우, useCallback을 사용할 경우 2번째 인자에 number를 넣지 않아도 됨.

파라미터로 내부에서 데이터 전달하기

  • 내부에서 정보를 전달하고자 할 때
  • 파라미터: /profile/velopert 에서 'velopert'를 의미하며 :username으로 key를 설정할 수 있다.
  1. (미리 라우터는 app에 적용해 놔야 한다.)
  2. 내부 주소를 app.js에 담기
<Route path="/profile/:username" component={Profile} />
  1. 해당 컴포넌트로 가서 {match} props 를 받아오기
  2. 원하는 정보(key)를 match.params에서 꺼내기
const Profile = ({ match }) => {
  const { username } = match.params;
  const profile = data[username];
  if (!profile) {
    return <div>존재하지 않는 사용자입니다.</div>;
  }
  return (
    <div>
      <h3>
        {username}({profile.name})
      </h3>
      <p>{profile.description}</p>
    </div>
  );

쿼리로 내부에서 데이터 전달하기

  • 내부에서 정보를 전달하고자 할 때
  • 쿼리: /about?details=true
    /about?details=true 에서 '?details=true'를 의미하며 details 가 key, true가 value 이다.
  1. (미리 라우터는 app에 적용해 놔야 한다.)
  2. 모듈 설치
yarn add qs
  1. 내부 주소를 app.js에 담기
<Route path="/about?details=true" component={Profile} />
  1. {location}을 props로 받아온다.
  2. qs.parse로 location.search에 담긴 쿼리를 객체로 변경한다.
import React from 'react';
import qs from 'qs';
 
const About = ({ location }) => {
  const query = qs.parse(location.search, {
    ignoreQueryPrefix: true // 이 설정을 통해 문자열 맨 앞의 ?를 생략합니다.
    }));

뒤로가기 /홈으로 가기/이탈 방지

  1. app.js에서 라우터를 적용한 컴포넌트를 불러오기
<Route path="/about?details=true" component={Profile} />
  1. 해당 컴포넌트에서 {history}를 props로 받아오기
  2. 다음과 같은 메서드를 사용 가능함
  • history.goBack(): 뒤로가기
  • history.push('/'): 메인화면 가기
  • history.block('정말 떠나실 겁니까?!) ;

라우트를 사용하지 않고 데이터 전달받기(파라미터, 쿼리) / 뒤로가기 /홈으로 가기/ 이탈방지 적용하기

  1. 모듈 설치 및 받아오기
import {withRouter} from 'react-router-dom';
  1. {location, match, history} 를 props로 받아오기
import React from 'react';
import { withRouter } from 'react-router-dom';
const WithRouterSample = ({ location, match, history }) => {
  return (
    <div>
      <h4>location</h4>
      <textarea
        value={JSON.stringify(location, null, 2)}
        rows={7}
        readOnly={true}
      />
      <h4>match</h4>
      <textarea
        value={JSON.stringify(match, null, 2)}
        rows={7}
        readOnly={true}
      />
      <button onClick={() => history.push('/')}>홈으로</button>
    </div>
  );
};
 
export default withRouter(WithRouterSample);

팁: JSON.stringyfy (match, null, 2) : 첫 번째 인자는 json, 두 번째 인자는 undenfined를 대체할 것, 세 번째 인자는 띄어쓰기

주의사항
현재 라우트 컴포넌트를 기준으로 라우트를 받아온다.

여러 규칙 중 하나의 라우트만 렌더링하기

  1. 모듈 설치 및 받아오기
import {Switch} from 'react-router-dom';
  1. Route들을 감싸기
  2. 다음과 같이 Route 사용하면, 아무것도 일치하지 않았을 경우 마지막에 뜬다.
<Route render={(props)=> <div> 등} ~/>

NavLink

  • 역할: 현재 경로와 NavLink의 경로가 일치할 경우 activeStyle로 지정한 스타일이 표시된다.
  1. 모듈설치
import {NavLink} from 'react-router-dom';
  1. activeStyle 정의
  2. NavLink 에 activeStyle 과 to 정의
const Profiles = () => {
  const activeStyle = {
    background: 'black',
    color: 'white'
  };
  return (
    <div>
      <h3>사용자 목록:</h3>
      <ul>
        <li>
          <NavLink activeStyle={activeStyle} to="/profiles/velopert">
            velopert
          </NavLink>
        </li>
        <li>
          <NavLink activeStyle={activeStyle} to="/profiles/gildong">
            gildong
          </NavLink>
        </li>
      </ul>
 
      (...)
    </div>
  );
};

아이콘 사용하기

axios 사용하기

  • 상황: 외부 api를 사용해야 할 때
  1. 모듈 설치하기
yarn add axios
  1. 모듈 받아오기
import React, { useState } from 'react';
import axios from 'axios';
  1. props에 들어갈 함수 작성하기
const [data, setData] = useState(null);
  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/todos/1',
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };
  1. 컴포넌트에서 props로 함수 받아오기. (주로 button 컴포넌트에서 onClick={onClick} 형태로 받아온다.) (js는 프로토타입 랭귀지므로 컴포넌트에서 import할 필요는 없다)
(return 컴포넌트 ) 
<button onClick={onClick}>불러오기</button>

(App.js)

import React, { useState } from 'react';
import axios from 'axios';
 
const App = () => {
  const [data, setData] = useState(null);
  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/todos/1',
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };
  return (
    <div>
      <div>
        <button onClick={onClick}>불러오기</button>
      </div>
      {data && <textarea rows={7} value={JSON.stringify(data, null, 2)} readOnly={true} />}
    </div>
  );
};
 
export default App;
     

값에 따라 다른 렌더링을 하고 싶을 때

  1. 해당 값을 useState로 관리하고
  2. if 문으로 각각 다른 컴포넌트를 렌더링한다.

useEffect 와 loading 함수

  • 상황: loading이 필요할 때
  • 뒷정리 함수이므로 async/await는 사용하면 안 된다.
  • 단, 내부에서 async/await 함수를 만들어 사용하는 것은 가능하다.

1.모듈 설치 & 관리할 블록 가져오기

  • 모듈설치 및 가져오기
    import axios from 'axios';
  • (필요하다면) 하위 컴포넌트 가져오기
    import NewsItem from './NewsItem';
  1. useEffect 함수 작성
useEffect(() => {
    // async를 사용하는 함수 따로 선언
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await axios.get(
          'https://newsapi.org/v2/top-headlines?country=kr&apiKey=0a8c4202385d4ec1bb93b7e277b3c51f',
        );
        setArticles(response.data.articles);
      } catch (e) {
        console.log(e);
              }
      setLoading(false);
    };
    fetchData();
  }, []);
  1. loading과 article 값에 따라 다른 컴포넌트 렌더링하기
// 대기 중일 때
  if (loading) {
    return <NewsListBlock>대기 중...</NewsListBlock>;
  }
  1. articles 정보 컴포넌트에 props로 받아와 나타내기
return (
    <NewsListBlock>
      {articles.map(article => (
        <NewsItem key={article.url} article={article} />
      ))}
    </NewsListBlock>
  );

(components/NewList.js)

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
import axios from 'axios';
 
const NewsListBlock = styled.div`
  box-sizing: border-box;
  padding-bottom: 3rem;
  width: 768px;
  margin: 0 auto;
  margin-top: 2rem;
  @media screen and (max-width: 768px) {
    width: 100%;
    padding-left: 1rem;
    padding-right: 1rem;
  }
`;
 
const NewsList = () => {
  const [articles, setArticles] = useState(null);
  const [loading, setLoading] = useState(false);
 
  useEffect(() => {
    // async를 사용하는 함수 따로 선언
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await axios.get(
          'https://newsapi.org/v2/top-headlines?country=kr&apiKey=0a8c4202385d4ec1bb93b7e277b3c51f',
        );
        setArticles(response.data.articles);
      } catch (e) {
        console.log(e);
              }
      setLoading(false);
    };
    fetchData();
  }, []);
 
  // 대기 중일 때
  if (loading) {
    return <NewsListBlock>대기 중...</NewsListBlock>;
  }
  // 아직 articles 값이 설정되지 않았을 때
  if (!articles) {
    return null;
  }
 
  // articles 값이 유효할 때
  return (
    <NewsListBlock>
      {articles.map(article => (
        <NewsItem key={article.url} article={article} />
      ))}
    </NewsListBlock>
  );
};
 
export default NewsList;
  
profile
개발자지망생

0개의 댓글