react 개발의 이모저모

419페이지
action.name에서 name은 컴포넌트의 prop key를 의미한다.
예제> 리듀서 함수의 [action.name]:action.value는 각각 name 과 value를 의미

<input name="name" value={name} onChange={onChange}/>

리액트 개발의 원칙

  1. 리액트 컴포넌트 사용하여 프론트엔드 먼저 구현하기
  2. index.js, app.js에 ROUTER, LINK 적용하기
  3. react-router-redux 적용하기

src/index.js

<사용될 기술 >

  1. React
    사용: import React from 'react'
    컴포넌트 렌더링 및 리액트 사용할 수 있게 도와줌

  2. BrowserRouter
    사용: import {BrowserRouter} from 'react-router-dom'
    history정보를 가지고, 새로고침하지 않고도 주소를 변경하고 주소 관련 정보를 props로 조회하거나 사용할 수 있도록 도와준다.

  3. App.js
    사용: import App from './App'
    2번 사이에 App을 렌더링해야 한다.

  4. index.css
    사용: import './index.css'
    css을 적용해야 한다.

App.js

  1. React
    사용: import React from 'react'
    컴포넌트 렌더링 및 리액트 사용할 수 있게 도와줌

  2. Route
    사용: import {Route} from 'react-router-dom'
    , 다양한 컴포넌트 파일 불러오기
    router를 사용할 수 있게 도와줌
    component={컴포넌트 파일}을 컴포넌트의 속성으로 넣어 렌더링한다.
    특이사항: exact라는 props를 true로 설정하면 /about을 접속할 때 /가 렌더링되지 않는다.
    특이사항2: path props에 배열을 넣어 여러 경로에서 같은 컴포넌트를 보여줄 수 있다.

  3. Link
    사용: import {Route, Link} from 'react-router-dom'
    html의 a태그와 같은 역할을 한다.

파라미터로 정보 가져오기

조건: 백엔드에 파라미터가 존재할 경우 사용

  • 팁: 파라미터와 쿼리
    파라미터 예시: /profile/velopert
    -- 특정 아이디 혹은 이름을 조회할 때 사용함
    쿼리 예시: /about?details=true
    -- 키워드 검색 및 페이지에 필요한 옵션 전달시 사용

파라미터 사용 방법:

  1. 설계
    1) 백엔드 API: 파라미터, 혹은 쿼리에 담을 정보 결정.
    2) 컴포넌트: 이 정보를 컴포넌트에서 어떻게 사용할 것인지 결정
    3) (클라이언트 버튼이라면 어디든지 가능): 링크 어떤 요소를 클릭하여 보낼지 결정.
    4) App.js: Route path 추가 및 2)의 컴포넌트 연결하기 .
  1. 컴포넌트단
    1) 파라미터 저장공간인 {match} props를 컴포넌트의 인자로 받아온다.
    2) match.params에 들어있는 정보를 비구조할당으로 빼내 사용한다.
    3) 받아온 값을 컴포넌트 내부에서 사용한다.

(Profile.js)
이때

import React from 'react';
 
const data = {
  velopert: {
    name: '김민준',
    description: '리액트를 좋아하는 개발자'
  },
  gildong: {
    name: '홍길동',
    description: '고전 소설 홍길동전의 주인공'
  }
};
 
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>
  );
  };
  export default Profile;
  1. Link 모듈을 사용하여 링크 만들기
  2. app.js에 path 추가하기
    설치: import { Route, Link } from 'react-router-dom';
    가져오기: 2에서 만든 컴포넌트
    App.js
import React from 'react';
import { Route, Link } from 'react-router-dom';
import About from './About';
import Home from './Home';
import Profile from './Profile';
 
const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/"></Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profile/velopert">velopert 프로필</Link>
        </li>
        <li>
          <Link to="/profile/gildong">gildong 프로필</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} exact={true} />
      <Route path={['/about', '/info']} component={About} />
      <Route path="/profile/:username" component={Profile} />
    </div>
  );
};
 
export default App;

쿼리로 정보 가져오기

조건: 백엔드에 쿼리가 존재할 경우 사용

  • 팁: 파라미터와 쿼리
    파라미터 예시: /profile/velopert
    -- 특정 아이디 혹은 이름을 조회할 때 사용함
    쿼리 예시: /about?details=true
    -- 키워드 검색 및 페이지에 필요한 옵션 전달시 사용

쿼리 사용 방법:

설계
1) 쿼리(예시: ?detail=true&another=1)을 객체 형태로 변환하기 : yarn add qs 설치하기
백엔드 API: 파라미터, 혹은 쿼리에 담을 정보 결정.
2) 컴포넌트: 이 정보를 컴포넌트에서 어떻게 사용할 것인지 결정
3) (클라이언트 버튼이라면 어디든지 가능): 링크 어떤 요소를 클릭하여 보낼지 결정.
4) App.js: Route path 추가 및 2)의 컴포넌트 연결하기 .

  1. 쿼리를 객체 형태로 변환하기
    콘솔 설치: yarn add qs

  2. 컴포넌트 만들기
    1) qs 모듈 사용하기

import qs from 'qs';

2) 쿼리 저장공간인 {location} props를 컴포넌트의 인자로 받아온다.

  • 쿼리는 location.search에 저장되어 있다.
  • location의 형태는 다음과 같다
{
  "pathname": "/about",
  "search": "?detail=true", <<<= 이곳에 쿼리가 들어있다.
  "hash": ""
}

3) qs를 parse하되, 첫 번째 인자는 query인 location.search가, 두 번째 인자는 옵션이 들어간다. qs.parse는 첫번째 인자로 받아온 query형태를 객체 형태로 변환한다.
(About.js)

import React from 'react';
import qs from 'qs';
 
const About = ({ location }) => {
  const query = qs.parse(location.search, {
    ignoreQueryPrefix: true // 이 설정을 통해 문자열 맨 앞의 ?를 생략합니다.
     });
  const showDetail = query.detail === 'true'; // 쿼리의 파싱 결과 값은 문자열입니다.
  return (
    <div>
      <h1>소개</h1>
      <p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트입니다.</p>
      {showDetail && <p>detail 값을 true로 설정하셨군요!</p>}
    </div>
  );
};
 
export default About;
    
  1. Link 모듈을 사용하여 링크 만들기
  2. app.js에 path 추가하기
    설치: import { Route, Link } from 'react-router-dom';
    가져오기: 2에서 만든 컴포넌트
  • posts/:username처럼 파라미터식 path는 필요없고, 쿼리를 제외한 기본 경로만 추가하면 된다.
  • Route에 path를 추가하고 컴포넌트를 렌더링한다.
    App.js
const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/"></Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} exact={true} />
      <Route path={['/about', '/info']} component={About} />
      <Route path="/profiles" component={Profiles} />
    </div>
  );
};
 
export default App;

리액트 라우터 부가 기능

  • 상황: 컴포넌트에서 특정 버튼을 누를 때 뒤로 가기, 로그인 후 화면 전환, 다른 페이지로의 이탈 방지가 필요할 때
  • 조건: 해당 컴포넌트가 라우터의 컴포넌트로 사용되어야 함.
  • 설계
    1) 컴포넌트에서 history 받아오고 적용하기
    2) 컴포넌트를 App의 Route의 컴포넌트로 붙이기
  1. 컴포넌트에 history 인자 props로 받아오기
    1) 컴포넌트에서 {history} props를 컴포넌트의 인자로 받아온다.
    2) history의 다양한 메서드를 사용하여 효과를 준다.
  • history: 라우터로 사용된 컴포넌트에 match.location과 함께 전달되는 props중 하나.
  • 메서드 소개
    history.goback(): 뒤로 가기
    history.push('/'): 홈으로 이동
    history.block('경고메시지') : true 혹은 false를 돌려준다.
import React, { Component } from 'react';
 
class HistorySample extends Component {
  // 뒤로 가기
  handleGoBack = () => {
    this.props.history.goBack();
  };
 
  // 홈으로 이동
  handleGoHome = () => {
    this.props.history.push('/');
  };
 
  componentDidMount() {
    // 이것을 설정하고 나면 페이지에 변화가 생기려고 할 때마다 정말 나갈 것인지를 질문함
    this.unblock = this.props.history.block('정말 떠나실 건가요?');
  }
 
  componentWillUnmount() {
    // 컴포넌트가 언마운트되면 질문을 멈춤
    if (this.unblock) {
      this.unblock();
    }
  }
 
  render() {
    return (
      <div>
        <button onClick={this.handleGoBack}>뒤로</button>
        <button onClick={this.handleGoHome}>홈으로</button>
      </div>
    );
  }
}
  1. 라우터에서 컴포넌트 렌더링하기 (필수, 라우터에서 history 를 사용하게 해줌)
import React from 'react';
import { Route, Link } from 'react-router-dom';
import About from './About';
import Home from './Home';
import Profiles from './Profiles';
import HistorySample from './HistorySample';
 
const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/"></Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필</Link>
        </li>
        <li>
          <Link to="/history">History 예제</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} exact={true} />
      <Route path={['/about', '/info']} component={About} />
      <Route path="/profiles" component={Profiles} />
      <Route path="/history" component={HistorySample} />
    </div>
  );
};
 
export default App;

axios

  • 상황: 외부 api를 사용해야 할 때

  • 설계
    1) api 확보
    2) 필요 모듈 설치
    3) props에 들어갈 함수 작성하기
    4) 컴포넌트에서 props로 함수 받아오기. (주로 button 컴포넌트에서 onClick={onClick} 형태로 받아온다.) (js는 프로토타입 랭귀지므로 컴포넌트에서 import할 필요는 없다)

  1. api 확보한다.
  1. 다음과 같은 모듈 설치하고 받아오기
    import React, { useState } from 'react';
    import axios from 'axios';

  2. props에 들어갈 함수 작성하기
    1) 함수 작성
    2) async~ await 사용
    3) try ~ catch 사용
    4) await 뒤에는 axios.get(api주소) 사용
    4) setData(받아온 내용의 data) 설정하기

  3. 컴포넌트에 함수 적용하기
    1) 같은 파일에 컴포넌트가 있는가, 없는가.
    a. (같은 파일에서 return할 경우) 함수 적용하기
    b. (없다. 다른 파일에서 받아오는 경우) 상위 컴포넌트에서 a를 진행하고, 이를 props로 받아온 다음 함수 적용하기
    2) JSON.stringify(useState로 관리되는 데이터, null, 2) 를 value로 설정하기

  • 두 번째 인자는 replacer로 undefined나 속성이 다른 값을 null로 대체한다는 의미
  • 세 번째 인자는 space 로, 2칸 들여쓴다는 의미
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;
     

useEffect 와 loading 함수

  • 상황: loading이 필요할 때

  • 뒷정리 함수이므로 async/await는 사용하면 안 된다.

  • 단, 내부에서 async/await 함수를 만들어 사용하는 것은 가능하다.

  • 설계
    1) 모듈 설치 & 하위 컴포넌트 가져오기
    2) useEffect 함수 작성
    3) loading과 articles 상태 다르게 나타내기
    4)articles 정보 컴포넌트에 props로 받아와 나타내기

1.모듈 설치 & 관리할 블록 가져오기
1) 모듈설치
import axios from 'axios';
2) (필요하다면) 하위 컴포넌트 가져오기
import NewsItem from './NewsItem';

  1. useEffect 함수 작성
    1)async ~await 함수 작성
    2) async~ await 함수 불러오기
    3) 두 번째 인자에는 어떤 값이 업데이트될 때마다 전체를 리렌더링할지 배열로 가져오기
    (없다면 첫 렌더링 때만 함수가 생성된다)

(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;
  

( 보충하기- pc 기준 325 페이지 )

category 기능 구현하기

  • 상황: 카테고리가 필요하며 select효과를 주고 싶을 때

  • 설계
    1) 카테고리 컴포넌트 디자인과 함께 만들기
    ( components/Categories.js)
    2)

import React from 'react';
import styled from 'styled-components';
 
const categories = [
  {
    name: 'all',
    text: '전체보기'
  },
  {
    name: 'business',
    text: '비즈니스'
  },
  {
    name: 'entertainment',
    text: '엔터테인먼트'
  },
  {
    name: 'health',
    text: '건강'
  },
  {
    name: 'science',
        text: '과학'
  },
  {
    name: 'sports',
    text: '스포츠'
  },
  {
    name: 'technology',
    text: '기술'
  }
];
 
const CategoriesBlock = styled.div`
  display: flex;
  padding: 1rem;
  width: 768px;
  margin: 0 auto;
  @media screen and (max-width: 768px) {
    width: 100%;
    overflow-x: auto;
  }
`;
 
const Category = styled.div`
  font-size: 1.125rem;
  cursor: pointer;
  white-space: pre;
  text-decoration: none;
  color: inherit;
  padding-bottom: 0.25rem;
 
  &:hover {
    color: #495057;
  }
 
  & + & {
    margin-left: 1rem;
  }
`;
const Categories = () => {
  return (
    <CategoriesBlock>
      {categories.map(c => (
        <Category key={c.name}>{c.text}</Category>
      ))}
    </CategoriesBlock>
  );
};
 
export default Categories;
  1. 상위 컴포넌트( 이 경우 App.js) 작성하기
    1) 하위 컴포넌트 2개 받아오기
    2) 모듈 설치하기
    import React, { useState, useCallback } from 'react';
    3) category 값을 useState로 관리하기
    4) onSelect함수 작성하기
    5) categories 컴포넌트에 상태관리중인 category와 onSelect props 넣기
    6) NewList 컴포넌트에서 상태관리중인 category를 props로 넣기

(App.js)

import React, { useState, useCallback } from 'react';
import NewsList from './components/NewsList';
import Categories from './components/Categories';
 
const App = () => {
  const [category, setCategory] = useState('all');
  const onSelect = useCallback(category => setCategory(category), []);
 
  return (
    <>
      <Categories category={category} onSelect={onSelect} />
      <NewsList category={category} />
    </>
  );
};
 
export default App;
  1. 하위 컴포넌트(1과 동일) 작성
    1) 에서 onSelect와 category를 props로 받아오기
    2)이를 하위의 하위 컴포넌트( < Category >) 의 key, active, onClick의 props로 세팅하기
  • key={c.name}
    key는 카테코리의 이름이다.
  • active={category === c.name}
    active는 카테고리와 c.name이 일치하느냐에 따라 다르다.
    (일치하는 이유는 onClick으로 해당 카테고리 이름이 category 상태관리에 업데이트되었기 때문이다. )
  • onClick={() => onSelect(c.name)
    ( onClick으로 해당 카테고리 이름이 category 상태관리에 업데이트된다. )
    (팁: const onSelect = useCallback(category => setCategory(category), []); 으로 상위 컴포넌트에서 정의함)
const Categories = ({ onSelect, category }) => {
  return (
    <CategoriesBlock>
      {categories.map(c => (
        <Category
          key={c.name}
          active={category === c.name}
          onClick={() => onSelect(c.name)}
        >
          {c.text}
        </Category>
      ))}
    </CategoriesBlock>
  );
};

( components/Categories.js)

import React from 'react';
import styled, { css } from 'styled-components';
 
const categories = [
  (...)
];
 
const CategoriesBlock = styled.div`
  (...)
`;
 
const Category = styled.div`
  font-size: 1.125rem;
  cursor: pointer;
  white-space: pre;
  text-decoration: none;
  color: inherit;
  padding-bottom: 0.25rem;
 
  &:hover {
    color: #495057;
  }
 
  ${props =>
    props.active && css`
      font-weight: 600;
      border-bottom: 2px solid #22b8cf;
color: #22b8cf;
      &:hover {
        color: #3bc9db;
      }
  `}
 
  & + & {
    margin-left: 1rem;
  }
`;
const Categories = ({ onSelect, category }) => {
  return (
    <CategoriesBlock>
      {categories.map(c => (
        <Category
          key={c.name}
          active={category === c.name}
          onClick={() => onSelect(c.name)}
        >
          {c.text}
        </Category>
      ))}
    </CategoriesBlock>
  );
};
 
export default Categories;
  1. 카테고리에 따라 다른 내용 api에서 가져오기
    1) 이전 포스팅에서 공부한 api 작업이 선행되어 있어야 함.
    2) category를
    3)category 값에 따라 api가 바뀌므로 useEffect의 두 번째 인자를 [category]로 넣어주기
    (components/NewsList.js)
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
import axios from 'axios';
 
const NewsListBlock = styled.div`
  (...)
`;
const NewsList = ({ category }) => {
  const [articles, setArticles] = useState(null);
  const [loading, setLoading] = useState(false);
 
  useEffect(() => {
    // async를 사용하는 함수 따로 선언
    const fetchData = async () => {
      setLoading(true);
      try {
      //category가 all이면 '', 아니면 &category=${category}`
        const query = category === 'all' ? '' : `&category=${category}`;
        const response = await axios.get(
          `https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=0a8c4202385d4ec1bb93b7e277b3c51f`,
        );
        setArticles(response.data.articles);
      } catch (e) {
        console.log(e);
      }
      setLoading(false);
    };
    fetchData();
  }, [category]);
 
  (...)
};
 
export default NewsList;
  

좋은 웹페이지 즐겨찾기