반응형

1. App key 발급

https://apis.map.kakao.com/web/guide/ 사이트에 접속해서 순서대로 진행합니다.

우선 카카오 개발자사이트 (https://developers.kakao.com) 접속하여 회원가입을 하고,

애플리케이션 추가하기를 통해 앱 생성을 합니다.

생성된 앱(mymap)을 클릭해 들어가면 사용할 수 있는 앱 키가 제공됩니다.

 

이렇게 간단히 앱키 발급이 완료되었습니다.

 

2. Sample React App 생성

리액트 앱을 생성하고, 간단히 소스를 추가해보겠습니다.

 

1) public/index.html

아래 코드를 /public/index.html 파일의 <head> 내부에 붙여넣습니다.

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다."></script>

2) 지도를 띄우는 코드 작성

</src/container/MapContainer.js>

import React, { useEffect } from 'react';

const { kakao } = window;

const MapContainer = () => {

  useEffect(() => {
    const container = document.getElementById('myMap');
    const options = {
      center: new kakao.maps.LatLng(33.450701, 126.570667),
      level: 3
    };
    const map = new kakao.maps.Map(container, options);
  }, []);

  return (
    <div id='myMap' style={{
      width: '800px', 
      height: '800px'
    }}></div>
  );
}

export default MapContainer;

</src/App.js>

import './App.css';
import MapContainer from './container/MapContainer';

function App() {
  return (
    <div className="App">
      <header className="App-header">
      <MapContainer />
      </header>
    </div>
  );
}

export default App;

 

3. 결과

반응형
반응형

 

0. Gatsby-cli 설치

npm install -g gatsby-cli

 

1. Gatsby 프로젝트 생성

gatsby new [프로젝트명]
or
npx gatsby-cli new [프로젝트명]	// gatsby-cli설치 없이 수행할 때

yarn remove gatsby-plugin-manifest gatsby-plugin-gatsby-cloud // 필요없는 라이브러리 삭제

 

2. 타입스크립트 설치

yarn add typescript --dev
yarn add gatsby-plugin-typescript

 

3. gatsby-config.js 파일 수정

module.exports = {
  siteMetadata: {
    title: `Gatsby Default Starter`,
    description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
    author: `@gatsbyjs`,
    siteUrl: `https://gatsbystarterdefaultsource.gatsbyjs.io/`,
  },
  plugins: [
    `gatsby-plugin-typescript`,	//추가
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `contents`, // image -> contents
        path: `${__dirname}/contents`,// image -> contents
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-manifest`,  //삭제?
      options: {
        name: `gatsby-starter-default`, //삭제?
        short_name: `starter`,
        start_url: `/`,
        background_color: `#663399`,
        theme_color: `#663399`,
        display: `minimal-ui`,
        icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
      },
    },
    `gatsby-plugin-gatsby-cloud`, //삭제?
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    // `gatsby-plugin-offline`,
  ],
}

프로젝트 폴더 하위에 /contents 폴더를 생성합니다.

 

 

4. tsconfig.json 파일 생성(타입스크립트 활성화)

yarn tsc --init

 

5. tsconfig.json 파일 수정

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "allowJs": true,	//수정
    "jsx": "preserve",	//수정
    "strict": true,	
    "noUnusedLocals": true,		//수정
    "noUnusedParameters": true,		//수정
    "noImplicitReturns": true,		//수정
    "baseUrl": "./src",			//수정
    "paths": {				//수정
      "components/*": ["./components/*"],//추가
      "utils/*": ["./utils/*"],		//추가
      "hooks/*": ["./hooks/*"]		//추가
    },
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*.tsx"],	//추가
  "exclude": ["node_modules"]	//추가
}

 

6. gatsby-node.js 파일 수정

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: <https://www.gatsbyjs.com/docs/node-apis/>
 */

// You can delete this file if you're not using it

const path = require('path');

// Setup Import Alias
exports.onCreateWebpackConfig = ({ getConfig, actions }) => {
  const output = getConfig().output || {};

  actions.setWebpackConfig({
    output,
    resolve: {
      alias: {
        components: path.resolve(__dirname, 'src/components'),
        utils: path.resolve(__dirname, 'src/utils'),
        hooks: path.resolve(__dirname, 'src/hooks'),
      },
    },
  });
};

 

7. ESLint 및 Prettier 설치

yarn add eslint prettier eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest --dev

 

8. .eslintrc.json 파일 생성

{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "plugin:prettier/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module",
    "project": "./tsconfig.json"
  },
  "plugins": ["react", "@typescript-eslint"],
  "ignorePatterns": ["dist/", "node_modules/"],
  "rules": {}
}

 

9. .prettierrc 파일 수정

{
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "quoteProps": "as-needed",
  "trailingComma": "all",
  "bracketSpacing": true,
  "arrowParens": "avoid",
  "endOfLine": "lf"
}

 

10. VSCode 설정

자동으로 Formatting이 되도록 설정해봅시다.

이는 VSCode 설정에서 진행해야하니 상단 File 탭의 Preferences > Settings 메뉴를 선택해주세요.

설정 화면이 나타나면 검색창에 Format On Save를 입력하고, 해당 설정을 체크 표시해주세요.


마지막으로 저장 시 Prettier 옵션에 맞게 Formatting을 해주기 위해 우측 상단의 JSON 형태의 Setting 화면을 여는 버튼을 클릭한 다음, 아래와 같이 코드를 추가해주세요.

{
  ...,
  "editor.codeActionsOnSave": {
    "source.fixAll": true
  },
  ...
}

 

11. 테스트 코드 생성 (./src/components/Test.tsx)

import React, { FunctionComponent } from 'react';

const Text: FunctionComponent = function ({ text }) {
  return <div>{text}</div>;
};

export default Text;

 ./src/pages/index.js

import React, { FunctionComponent } from 'react';
import Text from 'components/Text';

const IndexPage: FunctionComponent = function () {
  return <Text text="Home" />;
};

export default IndexPage;

 

<결과>

yarn develop

localhost:8000/에서 Home 텍스트가 정상 출력되면 성공한 것입니다.

 

 

(ps) gatsby-new 없이 생성하기

npm init -y
npm install gatsby react react-dom
mkdir src
cd src
mkdir pages
....

package.json에 실행 스크립트 추가

"scripts": {    
    "dev": "gatsby develop"
  }
반응형
반응형

1. Library 설치

yarn add recoil

 

2. 구성

 

/src

    ㄴ components

        * UserList.jsx

    ㄴ recoil

        * state.js

    *App.js

    *Index.js

 

 

 

 

 

 

 

 

 

 

3. 코드

(1) src/App.js

import React, { ErrorBoundary } from "react";
import { RecoilRoot } from "recoil";
import UserList from "./components/UserList";

function App() {
  return (
    <div className="App">
      <RecoilRoot>
        <React.Suspense fallback={<div>로딩중...</div>}>
          <UserList />
        </React.Suspense>
      </RecoilRoot>
    </div>
  );
}

export default App;

(2) src/recoil/state.js

import { atom, selector } from "recoil";

export const userListState = atom({
  key: "userListState",
  default: [],
});

export const currentUserListQuery = selector({
  key: "currentUserListQuery",
  get: async ({ get }) => {
    get(userListState);

    const response = await fetch("https://jsonplaceholder.typicode.com/users");
    return await response.json();
  },
});

(3) src/components/UserList.jsx

App.js에서 React.Suspense를 사용할 경우 useRecoilValue를 사용할 수 있습니다. Suspense를 사용하지 않을 경우엔

비동기 전용 Hook인 useRecoilValueLoadable을 사용합니다.

import React from "react";
import { useRecoilValueLoadable, useRecoilValue } from "recoil";
import { currentUserListQuery } from "../recoil/state";

export default function UserList() {
  const users = useRecoilValue(currentUserListQuery);

  return (
    <ul>
      {users.map((user) => {
        return (
          <li key={user.id}>
            {user.username} · {user.email}
          </li>
        );
      })}
    </ul>
  );
}
반응형
반응형

1. Library 설치

yarn add swr

 

2. 폴더 구성

Counter는 참고로 작성했는데, 본 포스트에서 구성하지는 않겠습니다. 비동기로 서버에서 User목록만 불러와서 보여주는 부분만 코드로 남김니다.

 

/src

    ㄴcomponents

        *Users.jsx

    ㄴcustomHooks

        *useUsers.js

    *App.js

    *index.js

 

 

 

 

 

 

 

 

 

 

3. 코드

(1) App.js

import Users from "./components/Users";
import Counter from "./components/Counter";

function App() {
  return (
    <>
      <Counter />
      <Users />
    </>
  );
}

export default App;

(2) src/customHooks/useUsers.js

import useSWR from "swr";

export default () => {
  const { data, error } = useSWR(
    "https://jsonplaceholder.typicode.com/users",
    (url) => {
      return fetch(url).then((res) => res.json());
    }
  );
  return { data, error };
};

(3) src/components/Users.jsx

import useSWR from "swr";
import useUsers from "../customHooks/useUsers";

export default function Users() {
  const { data, error } = useUsers();

  return (
    <section>
      <h1>사용자 목록</h1>
      {data && (
        <ul>
          {data.map((user) => {
            return (
              <li key={user.id}>
                {user.username} ({user.email})
              </li>
            );
          })}
        </ul>
      )}
    </section>
  );
}

 

<결과>

반응형
반응형

 

1. Library 설치

yarn add react-query

 

2. 프로젝트 구조

 

/src

    ㄴcomponents

        * RQ1 (테스트용)

        * RQ2 (실 사용 컴포넌트)

    ㄴcustomHooks

        * useUsers.js

    *App.js

    *index.js

 

 

 

 

 

 

 

 

 

 

3. 코드

(1) App.js

import { QueryClient, QueryClientProvider } from "react-query";
import RQ2 from "./components/RQ2";

const queryClient = new QueryClient();

function App() {
  return (
    <div className="App">
      <QueryClientProvider client={queryClient}>
        <RQ2 />
      </QueryClientProvider>
    </div>
  );
}

export default App;

 

(2) src/customHooks/useUsers.js

import { useQuery } from "react-query";
import axios from "axios";

const fetchUsers = async () => {
  const { data } = await axios.get(
    `https://jsonplaceholder.typicode.com/users`
  );
  return data;
};

export default () => {
  const { status, data, error } = useQuery("users", fetchUsers);
  return { status, data, error };
};

(3) src/components/RQ2.jsx

import React from "react";
import useUsers from "../customHooks/useUsers";

const RQ1 = () => {
  const { data } = useUsers();

  return (
    <section>
      <h1>사용자 목록</h1>
      {data && (
        <ul>
          {data.map((user) => {
            return (
              <li key={user.id}>
                {user.username} ({user.email})
              </li>
            );
          })}
        </ul>
      )}
    </section>
  );
};

export default RQ1;
반응형
반응형

1. 프로젝트 생성

yarn create react-app .

2. 라이브러리 설치

yarn add redux react-redux redux-actions axios redux-saga redux-devtools-extension

3. 프로젝트 구성

프로젝트 구성은 아래와 같이 할 계획입니다. /src 하위부분만 살펴보면 되겠습니다.

/src

ㄴ components

      * Leader.jsx

ㄴ containers

      * LeaderContainer.jsx

ㄴ lib

      * api.js (서버에 요청하는 비동기 함수 정의부분)

ㄴ modules

      * index.js (redux와 saga를 통합하는 부분)

      * leader.js ( action 타입, action 생성함수, redux, saga 정의하는 부분)

* App.js (컴포넌트 로딩하는 부분)

* Index.js (스토어 생성, Middleware 적용)

 

 

 

 

 

 

 

 

(1) src/lib/api.js

우선 서버에 요청을 담당하는 함수를 먼저 정의합니다. JSONPlaceholder라는 곳에서 제공되는 가짜 API를 사용합니다.

import axios from "axios";

export const getPost = (id) =>
  axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);

export const getUsers = (id) =>
  axios.get(`https://jsonplaceholder.typicode.com/users`);

(2) src/modules/leader.js

Action Type과 Action Generator, Action Handler(Reducer), Saga를 정의합니다.

import { createAction, handleActions } from "redux-actions";
import { call, put, takeLatest } from "redux-saga/effects";
import * as api from "../lib/api";

const GET_POST = "leader/GET_POST"; // post 를 요청하는 액션
const GET_POST_SUCCESS = "leader/GET_POST_SUCCESS"; // post 요청 성공 시 데이터(post)를 전달하는 액션
const GET_POST_FAILURE = "leader/GET_POST_FAILURE"; // post 요청 실패 시 처리하는 액션

const GET_USERS = "leader/GET_USERS"; // users 를 요청하는 액션
const GET_USERS_SUCCESS = "leader/GET_USERS_SUCCESS"; // users 요청 성공 시 데이터(users) 를 전달하는 액션
const GET_USERS_FAILURE = "leader/GET_USERS_FAILURE"; // users 요청 실패 시 처리하는 액션

export const getPost = createAction(GET_POST, (id) => id); // payload가 id인 액션객체 생성
export const getUsers = createAction(GET_USERS); // payload가 없는 액션객체 생성

// state 의 초기값 설정
const initialState = {
  post: null,
  users: null,
};

//** Redux **//
// post/users 요청 성공 시 Data 처리하는 redux만 정의
// post/users 요청 실패 시 처리할 데이터 없음
// post/users 요청 액션은 Saga에서 수행
const leader = handleActions(
  {
    [GET_POST_SUCCESS]: (state, action) => ({
      ...state,
      post: action.payload,
    }),
    [GET_USERS_SUCCESS]: (state, action) => ({
      ...state,
      users: action.payload,
    }),
  },
  initialState
);

export default leader;

//** Saga **//
// GET_POST/GET_USERS 액션에 대한 서버요청을 위한 Saga
export function* leaderSaga() {
  yield takeLatest(GET_POST, getPostSaga);
  yield takeLatest(GET_USERS, getUsersSaga);
}

// GET_POST 액션 dispatch 시 실행될 함수. 비동기로 서버의 post 데이터 요청
function* getPostSaga(action) {
  try {
    const post = yield call(api.getPost, action.payload);
    // post 데이터요청하는 비동기함수인 getPost을 요청하며, 인자로 action.payload인 id를 넘김
    yield put({
      type: GET_POST_SUCCESS, // 요청 성공 액션을 dispatch함.
      payload: post.data, // 액션 dispatch할 때 payload로 post.data(포스트 내용)을 전달함
    });
  } catch (e) {
    yield put({
      type: GET_POST_FAILURE, // 요청 실패 액션을 dispatch함
      payload: e, // 액션 dispatch할 때 payload로 에러를 전달함
      error: true, // 액션 객체의 항목에 error: true를 넘겨줌
    });
  }
}

// GET_USERS 액션 dispatch 시 실행될 함수. 비동기로 서버의 users 데이터 요청
function* getUsersSaga() {
  try {
    const users = yield call(api.getUsers);
    // users 데이터를 요청하는 비동기함수인 getUsers를 요청하며, 전달인자는 없음.
    yield put({
      type: GET_USERS_SUCCESS,
      payload: users.data,
    });
  } catch (e) {
    yield put({
      type: GET_USERS_FAILURE,
      payload: e,
      error: true,
    });
  }
}

(3) src/modules/index.js

reducer, saga 를 통합해줍니다.

import { combineReducers } from "redux";
import leader, { leaderSaga } from "./leader";
import { all } from "redux-saga/effects";

const rootReducer = combineReducers({
  leader,
});

export default rootReducer;

export function* rootSaga() {
  yield all([leaderSaga()]);
}

(4) src/index.js (스토어 생성)

Saga를 불러오고, Saga를 포함한 미들웨어를 적용하여 스토어를 생성하고, Saga를 실행합니다.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

import createSagaMiddleware from "@redux-saga/core";
import { applyMiddleware, createStore } from "redux";
import rootReducer, { rootSaga } from "./modules";
import { composeWithDevTools } from "redux-devtools-extension";
import { Provider } from "react-redux";

const sagaMiddleware = createSagaMiddleware();
const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

(5) src/containers/LeaderContainer.jsx

이제 데이터를 받아올 통로는 만들어졌으므로, 데이터를 실제 요청하고, 받은 데이터는 하위로 보내주는 데이터 담당 Container를 작성합니다. 

import React from "react";
import { connect } from "react-redux";
import Leader from "../components/Leader";
import { getPost, getUsers } from "../modules/leader";
import { useEffect } from "react";

const LeaderContainer = ({ getPost, getUsers, post, users }) => {
  useEffect(() => {
    getPost(1); // 액션 생성함수 실행('leader/GET_POST' 액션타입, payload: id=1)
    getUsers(); // 액션 생성함수 실행
  }, [getPost, getUsers]); // getPost ,getUsers에 변경이 있을 시 마다 호출
  return <Leader post={post} users={users} />;
};

export default connect(
  ({ leader }) => ({
    // props로 전달해 줄 post/users에, state의 leader에서 각각의 값 추출하여 전달
    post: leader.post,
    users: leader.users,
  }),
  {
    getPost, // props로, dispatch 함수 중 Post요청 액션생성함수 전달
    getUsers, // props로, dispatch 함수 중 Users요청 액션생성함수 전달
  }
)(LeaderContainer);

 

(6) src/components/Leader.jsx

최종적으로 데이터를 받아서 표현할 컴포넌트를 구현합니다.

import React from "react";

const Leader = ({ post, users }) => {
  return (
    <div>
      <section>
        <h1>포스트</h1>
        {post && (
          <div>
            <h3>{post.title}</h3>
            <h3>{post.body}</h3>
          </div>
        )}
      </section>
      <hr />
      <section>
        <h1>사용자 목록</h1>
        {users && (
          <ul>
            {users.map((user) => {
              return (
                <li key={user.id}>
                  {user.username} ({user.email})
                </li>
              );
            })}
          </ul>
        )}
      </section>
    </div>
  );
};

export default Leader;

주의할 점은 {post && ...} 또는 {users && ...} 가 없을 경우 최초 로딩 시 아직 Data 전달이 안된 시점에는 에러를 띄우므로 데이터가 있을 경우에만 출력하도록 해야합니다.

 

(7) src/App.js

앱에서 불러와 출력합니다.

import "./App.css";
import LeaderContainer from "./containers/LeaderContainer";

function App() {
  return (
    <div className="App">
      <LeaderContainer />
    </div>
  );
}

export default App;

 

<결과>

 

 

~끝~

반응형
반응형

 

import useSWR from "swr";

function useCounter() {
  const { data, mutate } = useSWR("state", () => window.count);

  return {
    data: window.count || 0,
    mutate: (count) => {
      window.count = count;
      return mutate();
    },
  };
}

export default function Counter() {
  const { data, mutate } = useCounter();

  const handleInc = () => mutate(data + 1);
  const handleDec = () => mutate(data - 1);

  return (
    <div>
      <span>count: {data}</span>
      <button onClick={handleInc}>inc</button>
      <button onClick={handleDec}>dec</button>
    </div>
  );
}

 

반응형
반응형

1. next.config.js

module.exports = {
  reactStrictMode: true,
  trailingSlash: false, // 뒤쪽 슬래시를 붙일 것인지..
  env: {
    SANITY_PROJECT_ID: 'ozi5ivc6', //Sanity ID를 환경변수로 지정
  }
}
반응형

+ Recent posts