Container / Presentation 방식

container : 데이터 조작을 다루는 컴포넌트

presentation : 화면을 다루는 컴포넌트

CRA는 NPX로

NPX는 그 순간 최신의  소스를 받아와서 설치 후 삭제함

NPM 전역으로 CRA를 설치후 CRA를 하면, 설치 당시의 버전으로 사용해야하고,

나중에 패키지가 업데이트 되면 새로 전역에 설치해야함

또, CRA의 의존성 패키지들을 로컬에 남겨두지 않음

React + type script

npx create-react-app my-app --template typescript

React에 typescript , scss 추가

yarn add -D typescript @types/node @types/react @types/react-dom @types/jest
yarn add sass

// tsconfig.js
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
  },
  "include": [
    "src"
  ]
}

React + eslint + prettier + redux tool-kit

yarn add -D prettier eslint-config-prettier eslint-plugin-prettier eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add @reduxjs/toolkit react-redux react-router-dom

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    node: true,
  },
  extends: [
    'prettier',
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint'],
  rules: {
    'prettier/prettier': ['error', { endOfLine: 'auto' }],
  },
};

// .prettierrc.js
module.exports = {       
  // String is in single quotes (') 
  // 문자열은 홀따옴표(')로 
  singleQuote: true,
  // With a semicolon at the end of the code. 
  // 코드 마지막에 세미콜른이 있게 
  semi: true,
  // Do not use tabs and replace them with space bars. 
  // 탭의 사용을 금하고 스페이스바 사용으로 대체하게 
  useTabs: false,
  // Indentation width of 2 spaces 
  // 들여쓰기 너비는 2칸
  tabWidth: 2,
  // When you create an object or array, you also put a comma on the element or on the back of the key-value.
  // 객체나 배열을 작성 할 때, 원소 혹은 key-valueㅇ의 맨 뒤에 있는 것에도 쉼표를 붙임
  trailingComma: 'all',
  //One line of code is maximum 80 spaces 
  // 코드 한줄이 maximum 80칸
  printWidth: 80,
};

filter vs find

const item = productList.find((list: productListProps) => {
    return list.id === Number(id);
});
// 조건에 해당하는 요소 반환
const item2 = productList.filter((list: productListProps) => {  //
  return list.id === Number(id);
});
// 배열만들어서 만들어줌

React Query

yarn add react-query

//index.tsx
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement,
);
root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <Provider store={store}>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </Provider>
    </QueryClientProvider>
  </React.StrictMode>,
);


// DashBoard.tsx 대쉬보드 컴포넌트
import axios from 'axios';
import { useQuery } from 'react-query';
import * as React from 'react';

const DashBoard = () => {
   const result = useQuery(
    '작명',
    () =>
      axios.get('https://codingapple1.github.io/userdata.json').then((a) => {
        return a.data;
      }),
    {
      refetchOnWindowFocus: false, // react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행합니다. 그 재실행 여부 옵션 입니다.
      retry: 0, // 실패시 재호출 몇번 할지
      onSuccess: (data) => {
        // 성공시 호출
        console.log(data);
      },
      onError: (e) => {
        // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됩니다.)
        // 강제로 에러 발생시키려면 api단에서 throw Error 날립니다. (참조: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default)
        console.log(e.message);
      },
    },
  );

  return (
    <div>
      {result.isLoading && '로딩중'}
      {result.error && '에러남'}
      {result.data && result.data.name}
    </div>
  );
};

export default DashBoard;

불변성 지키면서 배열 업데이트

let [users, setUsers] = useState([]);

# 배열에 추가
setUsers(users.concat(user));
# 배열에 추가 (함수형)
setUsers((prev) => prev.concat(user));

# 배열에서 삭제 
const onRemove = id => {
  // user.id 가 id 인 것을 제거
  setUsers(users.filter(user => user.id !== id));
};
 
#배열 수정
const onToggle = id => {
  setUsers(
    users.map(user =>
      user.id === id ? { ...user, active: !user.active } : user
    )
  );
};

불변성 지키면서 객체(object) 업데이트

#객체에 추가 #객체 업데이트
setUsers(state => {...state, key: value})

#객체에서 제거 #loadash
setUsers(state => {..._.omit(state, 'deleteKey')})

 

맨 아래로 Scroll 하기

messageListRef.current?.scrollTo(0, messageListRef.current.scrollHeight);

필자가 접한 react 튜토리얼을 보면

리액트의 프톤트에서 백엔드로 요청을 할때

중간에  proxy서버를 두고 요청한다.

"proxy": "http://localhost:7080",   //package.json

처음에는 단순히 리액트는 3000에 실행되어있고

서버의 경우 7080포트(필자의 백엔드 서버)에 실행되어있으니

CORS( cross-origin requests)를 방지하기 위함인줄 알고 개발을 진행했다. 

 cors는 웹개발시 무조건 만나는 기초 내용이니 검색해보자!

 

* 간단하게 추가 설명을 보태면 3000에서 실행된 리액트가 보낸 요청을 프록시 서버인 7080이 가로챈 뒤 백엔드서버인 7080으로 요청을 보내기 때문에 CORS가 발생하지 않는다.

 

 

허나 한가지 이유가 더있다.

axios.get('http://localhost:3001/users?email=${email}')
    .then(res => {
        console.log(res);
    }
         
fetch('http://localhost:3001/auth/login',{
    method:"post",
    body : JSON.stringify(loginInfo),
})

proxy 설정을 하지 않고 ajax를 요청하면 위와 같이 코딩이  될 것이다.

프론트에서 요청하는 부분이 한부분도 아니고 만약 주소가 바뀌거나 

서비스를 실제 디플로이한다면 고객사마다 주소가 다르고 포트가 다르고 등등의 상황이 생길 것이다.

그러면 react 프로젝트 내 모든 ajax 부분의 주소를 다 바꾸어야한다.

유지보수가 똥망이 된다.

 

axios.get('/users?email=${email}')
    .then(res => {
        console.log(res);
    }
         
fetch('/auth/login',{
    method:"post",
    body : JSON.stringify(loginInfo),
 })
"proxy": "http://localhost:7080",   //package.json

이를 위와 같이 수정하고 package.json에 프록시를 추가한다.

처음 코드와 똑같이 작동할 것이다.

 

위와 같이 하면 proxy부분만 수정하면 된다.

 

 

 

 

일반적인 컴포넌트 렌더링으로 변수(props)를 넘길때

<DashboardView param1={parameter}/>

 이런식으로 넘긴다.

<컴포넌트이름 파라미터이름={값}> 과 같은 형태 !!

그런데 Router 를통해 라우팅할때는 

<Route path="/admin/DashboardView" exact component={DashboardView} />

이런 형태로 component를 호출한다.
그럼 변수는 어떻게 넘긴단 말인가?

<Route path="/admin/DashboardView" exact component={() => <DashboardView param1={parameter} />}/>

이런식으로 넘기면 넘길 수 있다.

그리고 해당 컴포넌트에서는 

// DahboardVeiw.js
export default function DashboardView(props) {
	console.log(props.param1);
	render(<div>리액트 배워보자</div>)
}

이와 같이 기존 props를 전달받듯이 받으면 된다.

 

사내 신규 프로젝트를 맡게 되어 React를 사내 서비스에 최초 도입하고자 한다.

 

Front : React 

Back-End : SpringBoot

DB : HSQLDB

 


(node 및 npm 선행 설치는 필수이다.) 

 

설치

먼저 React를 시작하기 위해서는 

npx create-react-app react-todo

을 실행한다.

해당 명령어는 react를 바로 사용할 수 있도록 초기 설정을 모두 진행해준다.

 

명령어를 실행하고 잠시 기다리면

"your project name"
  README.md
  node_modules/
  package.json
  public/
    index.html
    favicon.ico
  src/
    App.css
    App.js
    App.test.js
    index.css
    index.js
    logo.svg

과 같은 구조로 파일들이 생성될 것 이다.

README.md : React 앱에 대한 설명 파일

node_modules : npm install ~ 로 설치한 모듈들 모여있는 폴더

package.json : 프로젝트에 의존성 관리

 

index.html : 최초 로딩되는 html 파일 , 해당 파일의 <div id="root"></div> 주목!

index.js : ReactDOM.render(JSX 태그, document.getElementById("root") : 바로 위 설명한 root에 jsx 태그들이 렌더링 된다고생각하면 된다.

App.js : 보통 기본 react 프로젝트는 jsx 태그 내용에 <App></App>이 들어가 있을 것이다.

App.js의 파일 이름이 (정확히 말하면 import한 이름이) 태그 안에 들어간 것이다. 

즉 App.js는 하나의 컴포넌트이고, 이 컴포넌트가 사용된 것이다.

즉 App.js 의 내용이 root부분에 들어가(?) 렌더링 된다고 생각하면 된다.

 

컴포넌트

앞서 말한 컴포넌트에 대해 전문적인 글을 인용한뒤 필자의 설명을 붙이겠다.

 

컴포넌트는 우리가 UI를 독립적이고 재사용 가능하게 나눌 수 있게 해줍니다. 그리고 각각을 독립적으로 생각할 수 있게도 만들어줍니다. 
개념적으로, 컴포넌트는 자바스크립트 함수입니다. 컴포넌트는 임의의 props라 불리는 인풋을 받아서 화면에 무엇이 보여질지 기술(describing)하는 리액트 엘리먼트를 반환합니다.

 

무슨 말인지,, 너무 어렵다.

역시 개발자는 코드로 이야기해야 빠르지 않은가

바로 코드를 보자

import React from 'react';

function Hello() {
  return <div>안녕하세요</div>
}

export default Hello;

------------------------------------

import React from 'react';

class Hello extends Component {
  render() {
  	return <div>안녕하세요</div>
  }
}

export default App;

어찌됬건 React도 java script 이기때문에 js를 다뤄봤다면 익숙하면서도 이상한 문법을 볼 수 있을 것이다.

점선으로 구분된 두 코드는 동일한 코드이다. 

첫번째로 작성된 함수형 컴포넌트가 추천된다고하니 함수형으로 진행하겠다.

(함수형 컴포넌트 vs 클래스형 컴포넌트가 궁금하신 분들은 직접 검색을 해보시길..

기회가 되면 필자도 글을 통해 다뤄보겠다)

 

위와 같은 js 파일 하나가 하나의 컴포넌트가 되고,

앞서 index.js에서 <App></App> 으로 불러온 것 처럼

Hello.js를 임포트 후 <Hello></Hello>로 집어넣으면

Hello파일의 return 부분에 있는 태그가 렌더링 되는 것이다.

 

기초적인 리액트 시작 방법을 알아보았다.

다음 글에 계속

 

+ Recent posts