TCP란?

  • 서버와 클라이언트간 신뢰성 있는 데이터 송수신을 위해 만들어진 프로토콜입니다.
  • 연결 지향성 프로토콜이라고도 부릅니다.
  • 데이터를 나눠서 보낼수 있으며, 데이터를 받는쪽에서 나눠 받은 데이터를 재조립합니다. 만약 누락된 데이터가 존재하면 다시 요청해서 받아와 완전한 데이터를 만듭니다.
  • TCP로 서버/클라이언트간 연결이 된 경우 데이터를 양방향으로 주고 받을수 있습니다.
  • 데이터의 순서가 뒤바뀌는 일 없이 안정적이라 신뢰가 가능합니다.
  • UDP에 비해 데이터 송수신 비용(부담)이 크다는 특성을 가졌습니다.
  • UDP보다 전송 속도가 느립니다.

UDP란?

  • TCP와 다르게 비연결성 프로토콜입니다.
  • 데이터를 보내고 제대로 받았는지 확인하지 않아, 데이터가 제대로 도착했는지 보장하지 않는 신뢰도가 비교적 낮습니다.
  • 데이터를 순차적으로 보내도 받는 쪽에서는 다른 순서로 전달받을 수 있습니다.
  • 데이터를 보내기만 하고 별 다른 처리를 하지 않기 때문에 TCP에 비해 비용(부담)이 적다는 특성을 가졌습니다.
  • TCP보다 전송 속도가 빠릅니다.

소켓이란 무엇인가?

    1. 소켓에 대한 간단한 설명
    • 소켓의 역할?그럼 네트워크에서의 소켓은? 우리가 네트워크에서 데이터를 송수신하기 위해 반드시 거쳐야 하는 연결부에 해당합니다!
    • 현실로 비유하자면 마치 벽에 있는 콘센트 구멍과 비슷합니다! 우리가 전기를 사용하기 위해 반드시 거쳐야 하는 연결부에 해당하죠!
    • 소켓의 종류?아주 일반적으로는 안정적인 데이터 송수신을 위해 TCP 소켓을 사용하는 경우가 대부분이지만, 일부 패킷이 손실되어도 괜찮거나 빠른 전송 속도가 필요한 경우 UDP 소켓을 사용하기도 합니다.
    • 소켓의 역할은 언제나 같지만 종류는 여러가지가 있습니다! 대표적으로 TCP, UDP 프로토콜을 사용하는 2가지의 소켓이 있는데요,
    • 패킷이란?
    • 네트워크 소켓이 현실의 콘센트와 비슷하다면, 패킷은 쉽게 말해 콘센트 배선에 흐르는 전기와 비슷합니다. 소켓을 통해 송수신하는 데이터 덩어리 하나가 한개의 패킷이라고 표현합니다.
    1. 웹소켓은 무엇인가?
    실시간 웹 서비스를 제공하기 위해 만들어진 Socket이라고 생각하면 됩니다.
  • 최근 Google Docs 등 여러 협업툴이 실시간 공동 편집 기능, 웹 메신저 등에서 많이 사용되는 기술로 최근 점점 많이 사용하는 기술이지만 일부 브라우저들이 웹소켓을 지원하지 않기 때문에 모든 브라우저에서의 동작을 보장하지는 못합니다.
    1. socket.io?
    자바스크립트를 사용해 웹소켓을 사용하길 원한다면 가장 많이 사용되는 라이브러리입니다. 그러나 이 라이브러리는 순수한 웹소켓 기술만 이용한 라이브러리가 아닙니다.이 어려움을 해결하기 위해 socket.io는 웹소켓을 사용할 수 없는 브라우저인 경우 서버에서 데이터를 일정 간격마다 받아오는 polling 기능으로 실시간 기능 구현을 가능케 해줍니다.
  • 위에서 말했듯 웹소켓 기술은 아직 모든 브라우저에서 동작하지는 못하기 때문에, 모든 사용자를 고려해야 하는 경우 실시간성 기능 구현에 어려움이 생기게 됩니다.
    1. socket.io는 웹소켓과 다르다?
    그렇습니다! 엄밀히 따지면 socket.io는 웹소켓을 포함하여, 웹소켓을 사용하지 못하는 환경에서도 웹소켓과 비슷하게 사용이 가능하도록 구현해놓은 라이브러리입니다.추가 참고 내용: https://d2.naver.com/helloworld/1336
  • 그렇기 때문에 socket.io는 웹소켓과 완전히 동일하다고 오해하지 않으시길 바랍니다!

프로젝트 폴더를 생성하고

모듈부터 설치한다.

npm i socket.io -S

간단한 app.js를 만들었다.

const io = require("socket.io")(3000, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
  },
});

io.on("connection", (socket) => {
  console.log("새로운 소켓이 연결됐어요!");

  socket.on("message", (data) => {
    console.log(data);
  });
});
io.on("connection", (socket) => {
  socket.send("Hello!");

  socket.on("message", (data) => {
    console.log(data);
  });
});
node app.js

쇼핑몰 실시간 구매 알림 구현

저번 시간에서 클라이언트와 소켓 연결도 하고, 소켓으로 데이터를 간단하게 주고 받는 방법에 대해서 알아봤다면, 이번에는 정말로 누군가 구매를 하면 실시간으로 알림이 뜨도록 구현을 해볼게요!

app.js를 고친다!

const express = require("express");
const { Server } = require("http"); // 1. 모듈 불러오기
const socketIo = require("socket.io"); // 1. 모듈 불러오기

... // 생략

const app = express();
const http = Server(app); // 2. express app을 http 서버로 감싸기
const io = socketIo(http); // 3. http 객체를 Socket.io 모듈에 넘겨서 소켓 핸들러 생성

// 4. 소켓 연결 이벤트 핸들링
io.on("connection", (sock) => {
  console.log("새로운 소켓이 연결됐어요!");

  sock.on("disconnect", () => {
    console.log(sock.id, "연결이 끊어졌어요!");
  });
});

... // 생략

// 5. app 대신 http 객체로 서버 열기
http.listen(8080, () => {
  console.log("서버가 요청을 받을 준비가 됐어요");
});

 

sock.emit("BUY_GOODS", {
    nickname: "서버가 보내준 구매자 닉네임",
    goodsId: 10, // 서버가 보내준 상품 데이터 고유 ID
    goodsName: "서버가 보내준 구매자가 구매한 상품 이름",
    date: "서버가 보내준 구매 일시",
  });

이런식으로 이벤트 데이터를 줘야 한다!

sock.on("BUY", (data) => {
    console.log(data);
  });

 

const express = require("express");
const Http = require("http");
const socketIo = require("socket.io");
const { Op } = require("sequelize");
const jwt = require("jsonwebtoken");
const { User, Goods, Cart } = require("./models");
const authMiddleware = require("./middlewares/auth-middleware");

const app = express();
const http = Http.createServer(app);
const io = socketIo(http);
const router = express.Router();

const socketIdMap = {};

function emitSamePageViewerCount() {
  const countByUrl = Object.values(socketIdMap).reduce((value, url) => {
    return {
      ...value,
      [url]: value[url] ? value[url] + 1 : 1,
    };
  }, {});

  for (const [socketId, url] of Object.entries(socketIdMap)) {
      const count = countByUrl[url];
      io.to(socketId).emit("SAME_PAGE_VIEWER_COUNT", count);
  }
}

io.on("connection", (socket) => {
  socketIdMap[socket.id] = null;
  console.log("누군가 연결했어요!");

  socket.on("CHANGED_PAGE", (data) => {
    console.log("페이지가 바뀌었대요", data, socket.id);
    socketIdMap[socket.id] = data;

    emitSamePageViewerCount();
  });

  socket.on("BUY", (data) => {
    const payload = {
      nickname: data.nickname,
      goodsId: data.goodsId,
      goodsName: data.goodsName,
      date: new Date().toISOString(),
    };
    console.log("클라이언트가 구매한 데이터", data, new Date());
    socket.broadcast.emit("BUY_GOODS", payload);
  });

  socket.on("disconnect", () => {
    delete socketIdMap[socket.id];
    console.log("누군가 연결을 끊었어요!");
    emitSamePageViewerCount();
  });
});

완성하였다!

sql과 어제 만든 로그인 회원가입 페이지를 연결해보았다.

 

npm i sequelize mysql2 -S
npm i sequelize-cli -D
npx sequelize init
npx sequelize db:create		//db 생성
npx sequelize model:generate --name User --attributes email:string,nickname:string,password:string

그 후 간단히 스키마 조정 후에

'use strict';
const {
  Model, INTEGER
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  };
  User.init({
    userId: {
      primaryKey: true,
      type: DataTypes.INTEGER,

    },
    email: DataTypes.STRING,
    nickname: DataTypes.STRING,
    password: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'User',
  });
  return User;
};
'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Users', {
      userId: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      email: {
        type: Sequelize.STRING
      },
      nickname: {
        type: Sequelize.STRING
      },
      password: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Users');
  }
};
npx sequelize db:migrate

모든 몽구스 문법을 mysql에 맞게 바꾸어 주었다.

 

...
const { Op } = require("sequelize");
const { User } = require("./models");

...

router.post("/users", async (req, res) => {
  const { email, nickname, password, confirmPassword } = req.body;

  if (password !== confirmPassword) {
    res.status(400).send({
      errorMessage: "패스워드가 패스워드 확인란과 다릅니다.",
    });
    return;
  }

  // email or nickname이 동일한게 이미 있는지 확인하기 위해 가져온다.
  const existsUsers = await User.findAll({
    where: {
      [Op.or]: [{ email }, { nickname }],
    },
  });
  if (existsUsers.length) {
    res.status(400).send({
      errorMessage: "이메일 또는 닉네임이 이미 사용중입니다.",
    });
    return;
  }

  await User.create({ email, nickname, password });
  res.status(201).send({});
});

 

router.post("/auth", async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({
    where: {
      email,
    },
  });

  // NOTE: 인증 메세지는 자세히 설명하지 않는것을 원칙으로 한다: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#authentication-responses
  if (!user || password !== user.password) {
    res.status(400).send({
      errorMessage: "이메일 또는 패스워드가 틀렸습니다.",
    });
    return;
  }

  res.send({
    token: jwt.sign({ userId: user.userId }, "customized-secret-key"),
  });
});
const jwt = require("jsonwebtoken");
const { User } = require("../models");

module.exports = (req, res, next) => {
  const { authorization } = req.headers;
  const [authType, authToken] = (authorization || "").split(" ");

  if (!authToken || authType !== "Bearer") {
    res.status(401).send({
      errorMessage: "로그인 후 이용 가능한 기능입니다.",
    });
    return;
  }

  try {
    const { userId } = jwt.verify(authToken, "customized-secret-key");
    User.findByPk(userId).then((user) => {
      res.locals.user = user;
      next();
    });
  } catch (err) {
    res.status(401).send({
      errorMessage: "로그인 후 이용 가능한 기능입니다.",
    });
  }
};
npm remove mongoose -S

마지막으로 몽구스 제거로 끝!

 

Validation이란 무엇인가?

특별한게 아니지만 개발을 하면서 가장 중요한것중 하나입니다. Validation은 말 그대로 어떤것을 검증한다고 보면 됩니다.

function is1(value) {
	return value === 1;
}

위 코드는 단순히 값이 1인지 아닌지 판단해서 Boolean 타입의 값을 반환하는 함수입니다.

이렇게 단순한 함수조차 Validation. 즉, 검증을 위한 코드가 됩니다.

라이브러리 joi가 그 예가 될 수 있습니다.
joi - npm (npmjs.com)

 

joi

Object schema validation

www.npmjs.com

joi.dev - 17.4.2 API Reference

 

joiSite

## Build Setup

joi.dev

나중에 로그인구현에서 쓰게 될 예정입니다.

 

JWT 

  • JSON 형태의 데이터를 안전하게 교환하여 사용할 수 있게 해줍니다.
  • 인터넷 표준으로서 자리잡은 규격입니다.
  • 여러가지 암호화 알고리즘을 사용할 수 있습니다.
  • header.payload.signature 의 형식으로 3가지의 데이터를 포함한다. (개미처럼 머리, 가슴, 배) 때문에 JWT 형식으로 변환 된 데이터는 항상 2개의 . 이 포함된 데이터여야 합니다.
  • header(머리)는 signature(배)에서 어떤 암호화를 사용하여 생성된 데이터인지 표현합니다.
  • payload(가슴)는 개발자가 원하는 데이터를 저장합니다.
  • signature(배)는 이 토큰이 변조되지 않은 정상적인 토큰인지 확인할 수 있게 도와줍니다.
  • JWT는 암호 키를 몰라도 Decode가 가능합니다. 변조만 불가능 할 뿐, 누구나 복호화하여 보는것은 가능하다는 의미가 됩니다!
  • 때문에 민감한 정보(개인정보, 비밀번호 등)는 담지 않도록 해야합니다.
  • 특정 언어에서만 사용 가능한것은 아닙니다! 단지 개념으로서 존재하고, 이 개념을 코드로 구현하여 공개된 코드를 우리가 사용하는게 일반적입니다.

 

정보열람(decode) 누구나 가능하지만

발급하는 것도 검증하는 것도 서버만 가능

 

기본세팅

$ npm i express mongoose jsonwebtoken joi -S
//익스프레스와 몽구스 jwt joi를 한번에 설치해 준다.

 

간단한 jwt인증 미들웨어를 구현해 보았다.

const jwt = require("jsonwebtoken");
const User = require("../models/user")


module.exports = (req, res, next) => {
    const { authorization } = req.headers;
    const [toKenType, tokenValue] = authorization.split(' ');
    console.log(tokenValue)

    if (toKenType !== 'Bearer'){
        res.status(401).send({
            errorMessage: '로그인 후 사용하세요',
        })
        return;
    }

    try {
        const { userId } = jwt.verify(tokenValue, "my-secret-key");

        User.findById(userId).then((user)=> {
            res.locals.user = user;
            next();
        });
        
    } catch (error) {
        res.status(401).send({
            errorMessage: "로그인 후 사용하세요",
        })
        return
    }

}

 

 

joi 라이브러리를 이용해서 간단히 로그인과 회원가입 페이지를 만들어 보았다.

const postUsersSchema = Joi.object({
    nickname: Joi.string().required(),
    email: Joi.string().email().required(),
    password: Joi.string().required(),
    confirmPassword: Joi.string().required(),
})

router.post("/users", async (req, res) => {
    try{
        const { nickname, email, password, confirmPassword } = await postUsersSchema.validateAsync(req.body);

        if (password !== confirmPassword) {
          res.status(400).send({
            errorMessage: "패스워드가 패스워드 확인란과 동일하지 않습니다.",
          });
          return;
        }
      
        const existUsers = await User.find({
          $or: [{ email }, { nickname }],
        });
        if (existUsers.length) {
          res.status(400).send({
            errorMessage: "이미 가입된 이메일 또는 닉네임이 있습니다.",
          });
          return;
        }
      
        const user = new User({ email, nickname, password });
        await user.save();
      
        res.status(201).send({});
    } catch(err) {
        console.log(err)
        res.status(400).send({
            errorMessage: "요청한 데이터 형식이 올바르지 않습니다.",
        })
    }
  
});



const postAuthSchema = Joi.object({
    email: Joi.string().email().required(),
    password: Joi.string().required(),
})
router.post("/auth", async (req, res) => {
    try{
        const { email, password } = await postAuthSchema.validateAsync(req.body);

        const user = await User.findOne({ email, password }).exec();
      
        if (!user) {
          res.status(400).send({
            errorMessage: "이메일 또는 패스워드가 잘못됐습니다.",
          });
          return;
        }
      
        const token = jwt.sign({ userId: user.userId }, "my-secret-key");
        res.send({
          token,
        });
    } catch (err) {
        res.status(400).send({
            errorMessage: "요청한 데이터 형식이 올바르지 않습니다.",
    })
    }
});

 

shinsw627/basicCrud (github.com)

 

GitHub - shinsw627/basicCrud

Contribute to shinsw627/basicCrud development by creating an account on GitHub.

github.com

이번 기회에 깃허브를 대폭 정리했다.
매니저님께서 지원회사 마다 다르긴 하겠지만 블로그 보다는 GITHUB를 더 중점적으로, 우선적으로 본다고 하셔서 이제 깃허브에 더 신경을 쓸 예정이다.

shinsw627/Algorithm_Prac (github.com)

 

GitHub - shinsw627/Algorithm_Prac

Contribute to shinsw627/Algorithm_Prac development by creating an account on GitHub.

github.com


이제 알고리즘 문제도 깃허브에 올릴 예정이다.
하루에 한 문제씩 그것도 마지막에 올리면 아무래도 보기 불편할 것 같기 때문이다.

오늘은 기본주차 과제 (항해2주차 과제를 정리해서 올리는 것에 집중하였다.)

사실 크게 바꾸진 않았지만 서버에 올리는 부분이나 pm2 사용법 등등 여러가지 다시 되돌아 보게 되었다.



node aws 배포 관련 참조할만한 명령어.


ssh -i /c/Users/Heizelnut/Desktop/키파일명.확장자 ubuntu@ec2의 ip주소	//서버연결
sudo iptables -t nat -L //현재 ip설정 확인
sudo iptables -t nat -D PREROUTING 1 //설정된거 젤위에꺼 하나 지우기
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000 //80포트 3000으로 바꾸기
sudo apt-get install -y nodejs //node 설치



pm2 관련

sudo -s //관리자 권한 취득
npm install -g pm2 //pm2 설치
pm2 list //pm2 리스트 보기
pm2 start 파일이름 //pm2로 파일 실행
pm2 stop 파일이름 //pm2 파일 종료



깃허브 첫 사용 시 가이드! (마스터로 사용)

자신이 만든 프로젝트의 경로에서 실행하면 된다!

git init		//깃 이니시에이팅!
git remote add origin https://github.com/유저아이디/프로젝트네임		//깃 repository를 리모트!
git add .
git commit -m "First commit"
git push origin master

 

처음 시작 시 깃허브!  main 브랜치를 써보자!

git init
git remote add origin https://github.com/유저아이디/프로젝트네임
git add .
git commit -m "first commit"
git branch -M main
git push -u origin main

State 관리 2
함수형 컴포넌트에서 state 관리 - useState()

새 컴포넌트를 만들어서 해볼까요? Nemo.js 파일을 만들고 시작합니다!

Nemo 컴포넌트 만들기

import React from "react";

const Nemo = (props) => {

    // 반환할 리액트 요소가 없을 때는 null을 넘겨주세요! 처음 껍데기 잡으실때도 null을 넘겨주면 굳!
    return null;
}

export default Nemo;

(2) App에서 <Nemo/>불러오기

// import 먼저 하고
import Nemo from "./Nemo";
...
      <div className="App">
{/*컴포넌트 불러다 쓰기*/}
        <Nemo/>
     ...

...

(3) useState()로 count를 state로 등록하자

// count에는 state 값이, setCount는 count라는 state 값을 수정하는 함수가 될거예요.
  // useState(초기값): () 안에 초기값을 넣어줍니다.
  const [count, setCount] = React.useState(3);

(4) 뷰를 만들고(=반환할 리액트 요소를 만들고),

const nemo_count = Array.from({ length: count }, (v, i) => i);
  // 반환할 리액트 요소가 없을 때는 null을 넘겨주세요!
  return (
    <div className="App">
      {nemo_count.map((num, idx) => {
        return (
          <div
            key={idx}
            style={{
              width: "150px",
              height: "150px",
              backgroundColor: "#ddd",
              margin: "10px",
            }}
          >
            nemo
          </div>
        );
      })}

      <div>
        <button>하나 추가</button>
        <button>하나 빼기</button>
      </div>
    </div>
  );

(5) 함수를 만들어서,

const addNemo = () => {
    // setCount를 통해 count에 저장된 값을 + 1 해줍니다.
    setCount(count + 1);
  };

  const removeNemo = () => {
    // setCount를 통해 count에 저장된 값을 - 1 해줍니다.
    // 이번엔 if문 대신 삼항 연산자로 해볼거예요!
    setCount(count > 0 ? count - 1 : 0);
  };

(6) 연결하자!

<div>
        {/* 함수를 호출합니다. */}
        <button onClick={addNemo}>하나 추가</button>
        <button onClick={removeNemo}>하나 빼기</button>
      </div>

(7) 완성본 코드

import React from "react";

const Nemo = (props) => {
  // count에는 state 값이, setCount는 count라는 state 값을 수정하는 함수가 될거예요.
  // useState(초기값): () 안에 초기값을 넣어줍니다.
  const [count, setCount] = React.useState(3);

  const addNemo = () => {
    // setCount를 통해 count에 저장된 값을 + 1 해줍니다.
    setCount(count + 1);
  };

  const removeNemo = () => {
    // setCount를 통해 count에 저장된 값을 - 1 해줍니다.
    // 이번엔 if문 대신 삼항 연산자로 해볼거예요!
    setCount(count > 0 ? count - 1 : 0);
  };

  const nemo_count = Array.from({ length: count }, (v, i) => i);
  // 반환할 리액트 요소가 없을 때는 null을 넘겨주세요!
  return (
    <div className="App">
      {nemo_count.map((num, idx) => {
        return (
          <div
            key={idx}
            style={{
              width: "150px",
              height: "150px",
              backgroundColor: "#ddd",
              margin: "10px",
            }}
          >
            nemo
          </div>
        );
      })}

      <div>
        {/* 함수를 호출합니다. */}
        <button onClick={addNemo}>하나 추가</button>
        <button onClick={removeNemo}>하나 빼기</button>
      </div>
    </div>
  );
};

export default Nemo;

 

state를 관리 (사용) 할 때에는 useState!!! 기억하기

 

 

Event Listener

클래스형 컴포넌트에서 event listener 구독하기

이벤트 리스너는 어디에 위치해야할까요? 클릭을 하건, 마우스를 올리건 DOM 요소가 있어야 이벤트가 발생하는 지 지켜볼 수 있겠죠? → 네! componentDidMount()에 넣어주면 됩니다.

일단, ref부터 잡아볼까요!

constructor(props) {
    super(props);

    this.state = {
      count: 3, // 숫자넣기!
    };

    // div에 ref를 먼저 잡아줍시다.
    this.div = React.createRef();
  }

...

	return (
      <div className="App" ref={this.div}>
        <Nemo/>
      </div>
    );
...

이제 addEventListener()를 이용해서 이벤트를 등록합니다.

hoverEvent = (e) => {
    // 콘솔로 이 이벤트가 누구에게서 일어났는 지 확인할 수 있습니다.
    console.log(e.target);

    if(e.target.className === 'app'){
      // 이벤트의 장본인의 배경 색을 바꿔볼까요?
      e.target.style.background = "#eee";
    }
  }

  componentDidMount() {
    // 리액트 요소가 잘 잡혔나 확인해봅시다!
    console.log(this.div);

    // 마우스를 올렸을 때, 이벤트가 일어나는 지 확인해봅시다.
    this.div.current.addEventListener("mouseover", this.hoverEvent);
  }
  • 이벤트는 꼭 컴포넌트가 사라지면 지워주세요!

 

componentWillUnmount() {
    // 컴포넌트가 사라질 때 이벤트를 지워줍니다.
    this.div.current.removeEventListener("mouseover", this.hoverEvent);
  }

 

 

라우팅이란

SPA는 주소를 어떻게 옮길 수 있을까? html은 딱 하나를 가지고 있지만, SPA도 브라우저 주소창대로 다른 페이지를 보여줄 수 있어요. 이렇게 브라우저 주소에 따라 다른 페이지를 보여주는 걸 라우팅이라고 부릅니다.

SPA란

Single Page Application! 말 그대로 서버에서 주는 html이 1개 뿐인 어플리케이션이에요. 전통적인 웹사이트는 페이지를 이동할 때마다 서버에서 html, css, js(=정적자원들)을 내려준다면, SPA는 딱 한번만 정적자원을 받아옵니다.

 

 

알고리즘 

function solution(s){
    let answer = "";
    
    answer = s.filter(function(v, i){
        console.log(v, i)
        if(s.indexOf(v) === i ) return true;    //인덱스오브로 처음 등장하는 인덱스값과 현재인덱스값이 같을때만 true를 줘서 filter함수에서 true만 담게 한다.

        
        
    });


    
    return answer
}
let str = ["good","time","good","time","student"];
console.log(solution(str))

 

 

 

클래스란?
객체 지향 프로그래밍에서 클래스는 특정 객체를생성하기 위해 변수와 함수를 정의하는 일종의 틀을 말한다. 객체를 정의하기 위한 상태와 함수로 구성되어 있다. 객체 단위로 코드를 그룹화하고 쉽게 재사용하려고 사용한다.

class Cat {
	// 생성자 함수
  constructor(name) {
		// 여기서 this는 이 클래스입니다.
		this.name = name; 
	}

	// 함수
	showName(){
		console.log(this.name);
	}
}

let cat = new Cat('perl');
cat.showName();
console.log(cat);

클래스를 상속한다는 건, 이미 만들어 둔 어떤 클래스를 가지고 자식 클래스를 만든다는 것이다.

class Cat {
	// 생성자 함수
  constructor(name) {
		// 여기서 this는 이 클래스입니다.
		this.name = name; 
	}

	// 함수
	showName(){
		return this.name;
	}
}

// extends는 Cat 클래스를 상속 받아 온단 뜻입니다.
class MyCat extends Cat {
	// 생성자 함수
  constructor(name, age) {
		// super를 메서드로 사용하기
		super(name); 
		this.age = age; 
	}
	
	// 부모 클래스가 가진 것과 같은 이름의 함수를 만들 수 있습니다.
	// 오버라이딩한다고 해요.
	showName(){
		// super를 키워드로 사용하기
		return '내 고양이 이름은 '+super.showName()+'입니다.';
	}
	
	showAge(){
		console.log('내 고양이는 '+this.age+'살 입니다!');
	}
}

let my_cat = new MyCat('perl', 4);
my_cat.showName();
my_cat.showAge();

super 키워드

  • 메소드로 사용할 수 있다.(constructor 안에서)
    • 부모의 constructor를 호출하면서 인수를 전달한다.
    • this를 쓸 수 있게 해준다.
  • 키워드로 사용할 수 있다.
    • 부모 클래스에 대한 필드나 함수를 참조할 수 있다.

 

let, const와 Scope

스코프(Scope)가 뭘까? 우리가 어떤 변수를 선언했을 때, 그 변수를 사용할 수 있는 유효범위를 스코프라고 부른다. 변수에 접근할 수 있는 범위다.

var: 함수 단위
let : block 단위 (변수 : let으로 선언한 변수는 값이 변할 수 있다.)
const : block 단위(상수: 한번 선언한 값은 바꿀 수 없다.)

function scope(){
	const a = 0;
	let b = 0;
	var c = 0;

	// {} 증괄호 안에 든 내용을 블럭이라고 표현해요.
	
	if(a === 0){
		const a = 1;
		let b = 1;
		var c = 1;
		console.log(a, b, c);
	}
	// 앗! c는 값이 변했죠? 
	// 그렇습니다. var는 함수 단위라서 if문 밖에서 선언한 값이 변했어요.
	// let과 const로 선언한 겂은 어떤가요? if문 안쪽 내용이 바깥 내용에 영향을 끼치지 않죠?
	console.log(a, b, c);
}


Spread 연산자 (Spread 문법)
어떤 객체 안에 있는 요소들을 객체 바깥으로 꺼내주는 연산자다.

let array = [1,2,3,4,5];
// ... <- 이 점 3개를 스프레드 문법이라고 불러요.
// 배열 안에 있는 항목들(요소들)을 전부 꺼내준다는 뜻입니다.
// 즉 [...array]은 array에 있는 항목을 전부 꺼내 
// 새로운 배열([] => 이 껍데기가 새로운 배열을 뜻하죠!)에 넣어주겠단 말입니다!
let new_array = [...array];

console.log(new_array);
//[ 1, 2, 3, 4, 5 ]

삼항 연산자는 if문의 단축 형태입니다. 사용법: 조건 ? 참일 경우 : 거짓일 경우

let info = {name: "mean0", id: 0};

let is_me = info.name === "mean0"? true : false;

console.log(is_me);
//true

Array 내장 함수
map
배열에 속한 항목을 변환할 때 많이 사용한다.
어떤 배열에 속한 항목을 원하는 대로 변환하고, 변환한 값을 새로운 배열로 만들어 준다.
원본 배열은 값이 변하지 않는다.
간단히 설명하면 원 배열의 값을 하나하나 불러와서 array_item에 담아서 오른쪽 함수 안의 구문을 반복한다는 것이다. for of와 조금 비슷하다.

const array_num = [0, 1, 2, 3, 4, 5];

const new_array = array_num.map((array_item) =>{ 
	return array_item + 1;
});
// 새 배열의 값은 원본 배열 원소에 +1 한 값입니다.
console.log(new_array);
// 원본 배열은 그대로 있죠!
console.log(array_num);
//[ 1, 2, 3, 4, 5, 6 ]
//[ 0, 1, 2, 3, 4, 5 ]

 

filter
어떤 조건을 만족하는 항목들만 골라서 새 배열로 만들어주는 함수이다.
원본 배열은 변하지 않고, 원하는 배열을 하나 더 만들 수 있다.

const array_num = [0, 1, 2, 3, 4, 5];

// forEach(콜백함수)
const new_array = array_num.filter((array_item) => {
	// 특정 조건을 만족할 때만 return 하면 됩니다!
	// return에는 true 혹은 false가 들어가야 해요.
	return array_item > 3;
});

console.log(new_array);
//[ 4, 5 ]

concat
배여로가 배열을 합치거나 배열에 특정 값을 추가해주는 함수이다.
원본 배열은 변하지 않는다.

const array_num01 = [0, 1, 2, 3];
const array_num02 = [3, 4, 5];

const merge = array_num01.concat(array_num02);

// 중복 항목(숫자 3)이 제거되었나요? 아니면 그대로 있나요? :)
console.log(merge);
//[0, 1, 2, 3, 3, 4, 5]

concat은 중복 항목을 제거해주지 않는다.
다른 내장함수와 함께 사용해서 제거해야 한다.
중복 제거를 원할 시에는 Set()을 사용하면 된다.

const array_num01 = [0, 1, 2, 3];
const array_num02 = [3, 4, 5];
// Set은 자바스크립트의 자료형 중 하나로, 
// 중복되지 않는 값을 가지는 리스트입니다. :)!
// ... <- 이 점 3개는 스프레드 문법이라고 불러요.
// 배열 안에 있는 항목들(요소들)을 전부 꺼내준다는 뜻입니다.
// 즉 [...array_num01]은 array_num01에 있는 항목을 전부 꺼내 
// 새로운 배열([] 이 껍데기가 새로운 배열을 뜻하죠!)에 넣어주겠단 말입니다!
const merge = [...new Set([...array_num01, ...array_num02])];

// 중복 항목(숫자 3)이 제거되었나요? 아니면 그대로 있나요? :)
console.log(merge);

from

쓰임새가 다양하다.

배열로 만들고자 하는 것이나 유사배열을 복사해서 새로운 배열로 만들 때 사용한다.

새로운 배열을 만들 때 사용한다. (초기화한다고도 표현한다.)

유사배열이란?
[a,b,c,b] 이 모양으로 생겼지만 배열의 내장 함수를 사용하지 못하는 것들이다. DOM nodelist같은 게 유사배열이다.

// 배열화 하자!
const my_name = "mean0";
const my_name_array = Array.from(my_name);
console.log(my_name_array);

// 길이가 문자열과 같고, 0부터 4까지 숫자를 요소로 갖는 배열을 만들어볼거예요. 
const text_array = Array.from('hello', (item, idx) => {return idx});

console.log(text_array);


// 새 배열을 만들어 보자!(=> 빈 배열을 초기화한다고도 해요.)
// 길이가 4고, 0부터 3까지 숫자를 요소로 갖는 배열을 만들어볼거예요. 
const new_array = Array.from({length: 4}, (item, idx)=>{ return idx;});

console.log(new_array);
//[ 'm', 'e', 'a', 'n', '0' ]
//[ 0, 1, 2, 3, 4 ]
//[ 0, 1, 2, 3 ]

 

quiz.
고양이들만 새 배열에 넣기.

const animals = ["복슬 강아지", "검정 고양이", "노란 햄스터", "강아지", "노랑 고양이", "고양이", "흰 토끼"];

let cats = [];
for (let i = 0; i < animals.length; i++) {
	let animal = animals[i];
	// indexOf는 파라미터로 넘겨준 텍스트가 몇 번째 위치에 있는 지 알려주는 친구입니다.
	// 파라미터로 넘겨준 텍스트가 없으면 -1을 반환해요!
	// 즉 아래 구문은 고양이라는 단어를 포함하고 있니? 라고 묻는 구문이죠!
	if (animal.indexOf("고양이") !== -1) {
		cats.push(animal);
	}
}
console.log(cats);

filter로 변환하면 아래와 같다.

const animals = ["복슬 강아지", "검정 고양이", "노란 햄스터", "강아지", "노랑 고양이", "고양이", "흰 토끼"];

let cats = animals.filter((animal) => {
    return animal.indexOf("고양이") !== -1;
})

console.log(cats)

 

=======================================

리액트로 넘어가 보자.

nvm 을 이용해 원하는 버전 node를 설치한다.

nvm install [설치할 버전]
nvm install 14.17.6
nvm ls # nvm으로 설치한 노드 버전 리스트 확인 명령어
node -v # 노드 버전 확인 명렁어
nvm use 사용할 노드 버전

npm으로 yarn을 설치했다. -g는 글로벌이라는 의미이다.

npm install -g yarn

 

# 옵션 global은 전역에 이 패키지를 깔겠다는 뜻입니다.
yarn add global create-react-app

CRA란 웹사이트를 만들 때 필요한 것을 몽땅 때려넣은 만든 패키지이다. 레고의 해리포터 성 만들기 같은 거라고 생각하면 편하다.

첫 리액트 프로젝트(week-1)를 만든다.

# yarn create react-app [우리의 첫 리액트 프로젝트 이름]
# 우리가 설치한 create-react-app 패키지를 써서 프로젝트를 만들어요.
# 주의! 꼭 sparta_react 폴더 경로에서 입력해주세요!
yarn create react-app week-1

week-1을 만들었으니 이동해서 시동해보자

cd week-1 # week-1 폴더로 이동합니다.
yarn start

하하 귀여운 첫페이지가 완성되었다.

JSX

HTML을 품은 JS === JSX!

 

import './App.css';

function App() {
  const div_back_styles = {
          width: '100vw',
          maxWidth: '400px',
          margin: '30px auto',
          flexDirection: 'column',

  }
  const color_green = {color:"green"}
  const div_styles = {
          height : "70px",
          borderBottom : "2px solid black",
          padding: "20px",
          width: '100vw',
          maxWidth: '400px',
          margin: '30px auto',
          flexDirection: 'column'
  }
  
  return (
    <div className="App">
      <div style={div_back_styles}>
      <div style={div_styles}>
        <h1 style={color_green}>안녕하세요!</h1>
      </div>
      <p style={{textAlign:'left'}}>이름을 입력해주세요.</p>
      <input type="text"/>
      </div>
    </div>
  );
}

export default App;

간단한 퀴즈를 풀어보았다.

 

Component란?

위에서 리액트는 레고라고 말씀 드렸죠? 컴포넌트는 블록이다!

이 웹사이트를 HTML로 간단히 표현해보면 아래와 같다.

<!DOCTYPE html>
<html lang="en">
  <head>
  </head>
  <body>
    <header> 
        ...
    </header>
    <div class="container">
        <div id="image-banner">
            ...
        </div>
        <div id="contents-1">
            ...
        </div>
    </div>
    <footer>
        ...
    </footer>
  </body>
</html>

이 코드를 조각조각 내보면 아래와 같이 나눌 수 있을 거예요. 나눈 조각 하나하나를 컴포넌트라고 부른다.

  1. <header/>
  2. <container/>
    1. <imagebanner/>
    2. <contents1/>
  3. <footer/>

즉, 이 웹 사이트는, 크게 <header/>, <container/>, <footer/> 세 개의 컴포넌트가 있고, <container/> 컴포넌트는, <imagebanner/>, <contents1/> 컴포넌트로 이루어져 있는 거죠!

State

state는 Component가 가지고 있는 데이터입니다.

Props

props는 Component가 부모 Component로부터 받아온 데이터입니다.

 

Component

함수형 컴포넌트

// 리액트 패키지를 불러옵니다.
import React from 'react'; 

// 함수형 컴포넌트는 이렇게 쓸 수도 있고
// function Bucketlist(props){
//     return (
//         <div>버킷 리스트</div>
//     );
// }

// 이렇게 쓸 수도 있어요. =>가 들어간 함수를 화살표 함수라고 불러요.
// 저희는 앞으로 화살표 함수를 사용할거예요.
// 앗 () 안에 props! 부모 컴포넌트에게 받아온 데이터입니다.
// js 함수가 값을 받아오는 것과 똑같이 받아오네요.
const BucketList = (props) => {

    // 컴포넌트가 뿌려줄 ui 요소(리엑트 엘리먼트라고 불러요.)를 반환해줍니다.
    return (
        <div>
            버킷 리스트
        </div>
    );
}

// 우리가 만든 함수형 컴포넌트를 export 해줍니다.
// export 해주면 다른 컴포넌트에서 BucketList 컴포넌트를 불러다 쓸 수 있어요.
export default BucketList;

클래스형 컴포넌트

import React from 'react';
import logo from './logo.svg';
import './App.css';
// BucketList 컴포넌트를 import 해옵니다.
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from './BucketList';

// 클래스형 컴포넌트는 이렇게 생겼습니다!
class App extends React.Component {

  constructor(props){
    super(props);
    // App 컴포넌트의 state를 정의해줍니다.
    this.state = {
      list: ['영화관 가기', '매일 책읽기', '수영 배우기'],
    };
  }

  // 랜더 함수 안에 리액트 엘리먼트를 넣어줍니다!
  render() {
      return (
      <div className="App">
        <h1>내 버킷리스트</h1>
        {/* 컴포넌트를 넣어줍니다. */}
        <BucketList/>
      </div>
    );
  }
}

export default App;

 

1주차 과제도 해보았다.

import logo from './logo.svg';
import './App.css';
import React from "react";

import Start from "./Start";

class App extends React.Component{
  constructor(props){
    super(props);

    this.state = {
      name: "고양이"
    };
  }

  render () {
    return (
    <div className="App">
      { <Start name={this.state.name}/>   /*name으로 state의name값을 보낸다. */}
    </div>
    )
  }
}

export default App;
import React from "react";
import img from "./neko.jpg";

const Start = (props) => {
  // 컬러셋 참고: https://www.shutterstock.com/ko/blog/pastel-color-palettes-rococo-trend/
  return (
    <div
      style={{
        display: "flex",
        height: "100vh",
        width: "100vw",
        overflow: "hidden",
        padding: "16px",
        boxSizing: "border-box",
      }}
    >
      <div
        className="outter"
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          flexDirection: "column",
          height: "100vh",
          width: "100vw",
          overflow: "hidden",
          padding: "0px 10vw",
          boxSizing: "border-box",
          maxWidth: "400px",
        }}
      >
        <img src={img} style={{ width: "80%", margin: "16px" }} />
        <h1 style={{ fontSize: "1.5em", margin: "0px", lineHeight: "1.4" }}>
          나는{" "}
          <span
            style={{
              backgroundColor: "#fef5d4",
              padding: "5px 10px",
              borderRadius: "30px",
            }}
          >
            { props.name        /*여기서 props로 부모에게서 받은 리스트의 name값을 받아온다. */}
          </span>
          에 대해 얼마나 알고 있을까?
        </h1>
        <input
          type="text"
          style={{
            padding: "10px",
            margin: "24px 0px",
            border: "1px solid #dadafc",
            borderRadius: "30px",
            width: "100%",
            // backgroundColor: "#dadafc55",
          }}
          placeholder="내 이름"
        />
        <button
          style={{
            padding: "8px 24px",
            backgroundColor: "#dadafc",
            borderRadius: "30px",
            border: "#dadafc",
          }}
        >
          시작하기
        </button>
      </div>
    </div>
  );
};

export default Start;

 

 

2주차!

SCSS를 쓰기위해

yarn add node-sass@4.14.1 open-color sass-loader classnames

로 설치해준다.

yarn add styled-components

styled-components란?

컴포넌트 스타일링 기법 중, 제가 가장 좋아하는 방식입니다! 😎 왜 좋아하냐구요? 많은 이유가 있지만, 두 개만 꼽아볼게요.

  • class 이름 짓기에서 해방됩니다!
  • 컴포넌트에 스타일을 적기 때문에, 간단하고 직관적입니다!

CSS-in-JS 라이브러리 중 하나입니다! 컴포넌트에 스타일을 직접 입히는 방식이라고 편하게 생각하셔도 됩니다!

 

라이프 사이클이란?

  • 컴포넌트의 라이프 사이클(= 컴포넌트 생명주기)은 정말 중요한 개념입니다! 컴포넌트가 렌더링을 준비하는 순간부터, 페이지에서 사라질 때까지가 라이프 사이클이에요.
  • 아래 도표는 어떻게 라이프 사이클이 흘러가는 지 그린 도표입니다.
      • 컴포넌트는 생성되고 → 수정(업데이트)되고 → 사라집니다.
      • 생성은 처음으로 컴포넌트를 불러오는 단계입니다.
      • 수정(업데이트)는 사용자의 행동(클릭, 데이터 입력 등)으로 데이터가 바뀌거나, 부모 컴포넌트가 렌더링할 때 업데이트 됩니다. 아래의 경우죠!
        • props가 바뀔 때
        • state가 바뀔 때
        • 부모 컴포넌트가 업데이트 되었을 때(=리렌더링했을 때)
        • 또는, 강제로 업데이트 했을 경우! (forceUpdate()를 통해 강제로 컴포넌트를 업데이트할 수 있습니다.)
      • 제거는 페이지를 이동하거나, 사용자의 행동(삭제 버튼 클릭 등)으로 인해 컴포넌트가 화면에서 사라지는 단계입니다.

  • 컴포넌트는 생성되고 → 수정(업데이트)되고 → 사라집니다.
  • 생성은 처음으로 컴포넌트를 불러오는 단계입니다.
  • 수정(업데이트)는 사용자의 행동(클릭, 데이터 입력 등)으로 데이터가 바뀌거나, 부모 컴포넌트가 렌더링할 때 업데이트 됩니다. 아래의 경우죠!
    • props가 바뀔 때
    • state가 바뀔 때
    • 부모 컴포넌트가 업데이트 되었을 때(=리렌더링했을 때)
    • 또는, 강제로 업데이트 했을 경우! (forceUpdate()를 통해 강제로 컴포넌트를 업데이트할 수 있습니다.)
  • 제거는 페이지를 이동하거나, 사용자의 행동(삭제 버튼 클릭 등)으로 인해 컴포넌트가 화면에서 사라지는 단계입니다.

라이프 사이클 함수는 클래스형 컴포넌트에서만 사용할 수 있습니다. 라이프 사이클을 아는 건 중요한데 왜 우리는 클래스형 컴포넌트보다 함수형 컴포넌트를 많이 쓰냐구요? 리액트 공식 매뉴얼에서 함수형 컴포넌트를 더 권장하기 때문입니다! (리액트 16.8버전부터 등장한 React Hooks으로 라이프 사이클 함수를 대체할 수 있거든요.) 더 많은 라이프 사이클 함수는 공식 문서에서 확인할 수 있어요 😉

 

 

06. Ref! 리액트에서 돔요소를 가져오려면?

React.createRef()

  • 이제 가상돔이 뭔지, 돔이 뭔진 알겠죠? 라이프 사이클도 알구요. 그런데 만약에, 내가 어떤 인풋박스에서 텍스트를 가져오고 싶으면 어떻게 접근해야할까요? (render()가 끝나고 가져오면 될까요? 아니면 mount가 끝나고? 아니, 그 전에 가상돔에서 가져오나? 아니면 DOM에서? 😖) → 답은, 리액트 요소에서 가져온다!

 

  • React 요소를 가지고 오는 방법
import React from "react";
import logo from "./logo.svg";
// BucketList 컴포넌트를 import 해옵니다.
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import styled from "styled-components";

// 클래스형 컴포넌트는 이렇게 생겼습니다!
class App extends React.Component {
  constructor(props) {
    super(props);
    // App 컴포넌트의 state를 정의해줍니다.
    this.state = {
      list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
    };
    // ref는 이렇게 선언합니다! 
    this.text = React.createRef();
  }

  componentDidMount(){
		// 콘솔에서 확인해보자!
    console.log(this.text);
		console.log(this.text.current);
  }

  // 랜더 함수 안에 리액트 엘리먼트를 넣어줍니다!
  render() {
    
    return (
      <div className="App">
        <Container>
          <Title>내 버킷리스트</Title>
          <Line />
          {/* 컴포넌트를 넣어줍니다. */}
          {/* <컴포넌트 명 [props 명]={넘겨줄 것(리스트, 문자열, 숫자, ...)}/> */}
          <BucketList list={this.state.list} />
        </Container>

        <div>
          <input type="text" ref={this.text}/>
        </div>
      </div>
    );
  }
}

const Container = styled.div`
  max-width: 350px;
  min-height: 80vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Title = styled.h1`
  color: slateblue;
  text-align: center;
`;

const Line = styled.hr`
  margin: 16px 0px;
  border: 1px dotted #ddd;
`;

export default App;

 

********************************************************

새 프로젝트를 시작할 때 해야할 순서!!!!!

+ 예시로 네모 카운트 숫자에 따라 계속 찍어 내는 프로젝트

(1) 새 CRA 만들기

yarn create react-app nemo

(2) index.js에서 <React.StrictMode> 부분을 지우기

(3) App.js를 class형 컴포넌트로 바꾸고 시작!

// App component를 class형으로!
import React from 'react';

class App extends React.Component {

  constructor(props){
    super(props);

    this.state = {}
  }

  componentDidMount(){

  }
  
  render(){

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

export default App;

state에 count라는 변수를 추가하고, count 숫자만큼 네모칸을 화면에 띄우기

import React from "react";

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      count: 3, // 숫자넣기!
    };
  }

  componentDidMount() {}

  render() {
    // 배열을 만듭니다.
    // Array.from()은 배열을 만들고 초기화까지 해주는 내장 함수입니다.
    // Array.from()의 첫번째 파라미터로 {length: 원하는 길이} 객체를,
    // 두번째 파라미터로 원하는 값을 반환하는 콜백함수를 넘겨주면 끝!
    // array의 내장함수 대부분은 콜백 함수에서 (현재값, index넘버)를 인자로 씁니다.
    const nemo_count = Array.from({ length: this.state.count }, (v, i) => i);

    // 콘솔로 만들어진 배열을 확인해봅니다. 숫자가 0부터 순서대로 잘 들어갔나요?
    console.log(nemo_count);

    return (
      <div className="App">
        {nemo_count.map((num, idx) => {
          return (
            <div key={idx}
              style={{
                width: "150px",
                height: "150px",
                backgroundColor: "#ddd",
                margin: "10px",
              }}
            >
              nemo
            </div>
          );
        })}
      </div>
    );
  }
}

export default App;

더하기, 빼기 버튼을 만들고,

return (
      <div className="App">
        {nemo_count.map((num, idx) => {
          return (
            <div key={idx}
              style={{
                width: "150px",
                height: "150px",
                backgroundColor: "#ddd",
                margin: "10px",
              }}
            >
              nemo
            </div>
          );
        })}

        <div>
          <button>하나 추가</button>
          <button>하나 빼기</button>
        </div>
      </div>
    );

함수를 만들어서

addNemo = () => {
    // this.setState로 count를 하나 더해줍니다!
    this.setState({ count: this.state.count + 1 });
  };

  removeNemo = () => {
    // 네모 갯수가 0보다 작을 순 없겠죠! if문으로 조건을 걸어줍시다.
    if (this.state.count > 0) {
      // this.setState로 count를 하나 빼줍니다!
      this.setState({ count: this.state.count - 1 });
    }else{
      window.alert('네모가 없어요!');
    }
  };

연결하자!

		<div>
          {/* 함수를 호출합니다. 이 클래스 안의 addNemo 함수를 불러오기 때문에 this.addNemo로 표기해요. */}
          <button onClick={this.addNemo}>하나 추가</button>
          <button onClick={this.removeNemo}>하나 빼기</button>
        </div>

 

오늘은 여기까지만... 너무 지친다...

항해 2주차도 지나간다...
주특기 기본과정 노드강의를 들었는데
솔직히 웹개발기초반에서 하던 것들과 크게 다른게 없이 API CRUD 말고는 크게 배운 것이 없는 것 같았다.
기초반이 짧았어서 심화반이 두렵다... 헉헉 
막연한 두려움에 node교과서 라는 책을 사서 일단 보고는 있는데 기본적인 단어들 부터 검색하면 읽어야 하는 지경이라 조금 속도가 더디다.(예를 들면 스택, 큐 이런 단어들 조차 너무 생소해서 검색해서 이해해야한다 ㅎㅎ;;)

이번 WIL의 키워드
- Node.js : Restful API, package.json
- Spring : DI, IoC, Bean
- React : DOM, 서버리스

여러 사이트들을 돌아다니면서 읽어보고 まとめて 정리해 보았다.

 

먼저 REST API

REST 란, 자원을 표현으로구분하여 해당 자원의 정보를 주고받는 모든 것을 의미한다.
즉, 자원의 표현에 의한 정보전달을 뜻한다.
REST는 RepreSentational State Transfer 의 약자로 "상태를 유지하지 않는" 것을 뜻한다.



자원 : 해당 소프트웨어가 관리하는 모든 것
자원의 표현 : 자원을 표현하는 지정 방식, 또는 이름

정보전달
데이터가 요청되는 시점에 자원의 정보를 전달한다.
JSON 또는 XML을 통해 데이터를 주고받는 것이 일반적이다.
WWW( world wide web) 과 같은 분산 하이퍼미디어 시스템을 위한 SW개발 아키텍처의 한 형식이다.
REST는 기본적으로 웹의 기존기술과 HTTP프로토콜을 그대로 활용하기 때문에 웹의 장점을 최대화 한 아키텍쳐 스타일이다. REST는 네트워크 상에서 클라이언트와 서버 간 통신 방식 중 하나이다.

| HTTP 란?

- HyperText Transfer Protocol의 준말로 링크 기반으로 데이터를 요청하고 받겠다는 것.
- 클라이언트와 서버가 요청을 하고 응답을 하기 위해 따르는 프로토콜.
- HTML 문서를 주고 받을 수 있음, 뿐만 아니라 이미지, 동영상, 오디오, 텍스트 문서 등을 주고 받을 수 있음.

 RESTful API

API: Application Programming Interface

REST RESTful API는 REST 특징을 지키면서 API를 제공하는 것이다.

즉, HTTP 통신에서 어떤 자원에 대한 CRUD 요청을 Resource와 Method로 표현하여 특정한 형태로 전달하는 방식으로

Get, Post 등의 방식(Method)을 사용하여 요청을 보내며,

요청을 위한 자원은 특정한 형태(Representation of Resource)으로 표현한다.

풀어 설명하자면, 어떤 자원에 대해 CRUD(Create, Read, Update, Delete) 연산을 수행하기 위해 URI(Resource)로 요청을 보내는 것. 자원을 이름으로 구분해 해당 자원의 상태를 주고받는 모든 것을 의미한다고도 할 수 있겠다.

예를 들어, 우리는 게시글을 작성하기 위해 http://localhost:8080/board 라는 URI에 POST방식을 사용하여 JSON형태의 데이터를 전달할 수 있다. 위와 같이 CRUD 연산에 대한 요청을 할 때, 요청을 위한 Resource(자원, URI)와 이에 대한 Method(행위, POST) 그리고 Representation of Resource(자원의 형태, JSON)을 사용하면 표현이 명확해지므로 이를 REST라 하며, 이러한 규칙을 지켜서 설계된 API를 Rest API 또는 Restful한 API라고 한다. 그리고 위에서 살짝 언급하였듯이, 이러한 Rest API는 Resource(자원), Method(행위), Representation of Resource(자원의 형태)로 구성된다.

이러한 REST 기반의 API를 웹으로 구현한 것이 REST API, RESTful API다.

 RESTful API를 사용하는 이유

큰 특징으로는 '애플리케이션 분리 및 통합', '다양한 클라이언트의 등장'이다.

애플리케이션의 복잡도가 증가하면서 애플리케이션을 어떻게 분리하고 통합하느냐가 주요 이슈가 되었고, 이에 자바 진영에서는 과거 EJB(Enterprise Java Beans), SOA(Service Oriented Architecture)에 이어 최근에는 MSA(Micro Service Architecture)와 함께 REST가 떠오르고 있는 것이다.

그리고 모바일과 같은 다양한 클라이언트의 등장하면서 최근의 서버 프로그램은 다양한 브라우저와 안드로이폰, 아이폰과 같은 모바일 디바이스에서도 통신을 할 수 있어야 한다.

이러한 멀티 플랫폼에 대한 지원을 위해 Backend 하나로 다양한 Device를 대응할 수 있는 REST의 필요성이 증대되었다.

 

 

 

GraphQL

GraphQL 은 Graph Query Language 의 줄임말이다.
Query Language 란 무엇인가?
Query Language 는 정보를 얻기 위해 보내는 질의문(Query)을 만들기 위해 사용되는 Computer 언어의 일종이다.
GraphQL 은 이런 Query Language 중에서도 Server API 를 통해 정보를 주고받기 위해 사용하는 Query Language 이다.

* RESTful과의 차이점
· GraphQL API는 주로 하나의 Endpoint 에 사용함
· 요청할 때 사용한 Query 문에 따라 응답의 구조가 달라진다.

GraphQL vs RESTful 장단점

GraphQL 은 다음과 같은 장점을 가진다.

  1. HTTP 요청의 횟수를 줄일 수 있다.
    • RESTful 은 각 Resource 종류 별로 요청을 해야하고, 따라서 요청 횟수가 필요한 Resource 의 종류에 비례한다.
      반면 GraphQL 은 원하는 정보를 하나의 Query 에 모두 담아 요청하는 것이 가능하다.
  2. HTTP 응답의 Size 를 줄일 수 있다.
    • RESTful 은 응답의 형태가 정해져있고, 따라서 필요한 정보만 부분적으로 요청하는 것이 힘들다.
      반면 GraphQL 은 원하는 대로 정보를 요청하는 것이 가능하다.

GraphQL 은 다음과 같은 단점을 가진다.

  1. File 전송 등 Text 만으로 하기 힘든 내용들을 처리하기 복잡하다.
  2. 고정된 요청과 응답만 필요할 경우에는 Query 로 인해 요청의 크기가 RESTful API 의 경우보다 더 커진다.
  3. 재귀적인 Query 가 불가능하다. (결과에 따라 응답의 깊이가 얼마든지 깊어질 수 있는 API 를 만들 수 없다.)

 

결론!!

File 전송과 같이 RESTful 이 더 유리한 API 가 있을 수 있고,
다양한 정보를 주고받는 것 같이 GraphQL 이 더 유리한 API 가 있을 수 있다.

GraphQL 은 여러 장점을 가지고 Server 의 구조를 단순화 시켜줄 수 있는 좋은 Query Language 이다.
다만, GraphQL 의 장점이 언제나 의미를 가지는 것은 아니며, 어떤 조건에서 사용하는지, 어떤 목표로 사용하는지에 따라서
장점으로 작용하기도, 단점으로 작용하기도 한다.
훌륭한 API 개발자가 되기 위해서는
이런 장단점을 잘 파악하여 GraphQL 만 쓸 것인지,
RESTful structure 또한 사용할 것인지,
혹은 RESTful structure 만 사용할 것인지를 결정하는 것이 중요하다.

 

SpringBoot REST API, 'RES.. : 네이버블로그 (naver.com)

 

SpringBoot REST API, 'RESTful API란?'

[SpringBoot] REST API로 변경하기 'REST API란?' 어제 진행중이었던 개인 프로...

blog.naver.com

REST API & GraphQL 기본 정리 : 네이버 블로그 (naver.com)

 

REST API & GraphQL 기본 정리

· 수업노트 정리 # REST API 는 무엇인가? REST는 RepreSentational State Transfer 의 약...

blog.naver.com

RESTful API vs GraphQL : 네이버 블로그 (naver.com)

 

RESTful API vs GraphQL

# 근시일 내에 GraphQL로 API를 만들어봐야겠다... [요약] 클라이언트로부터 특정 요청이 들어왔을 ...

blog.naver.com

 

[REST vs. GraphQL for API.. : 네이버블로그 (naver.com)

 

[REST vs. GraphQL for API] -REST

REST 와 GraphQL의 차이점, 장단점에 대해 알아보자 API란 Client와 Server 사이의 대화 규칙/...

blog.naver.com

[GraphQL] REST API 의 단점 : 네이버 블로그 (naver.com)

 

[GraphQL] REST API 의 단점

나는 SpringBoot 와 GraphQL 서버를 개발하고있다. 그래서 더욱더 REST API 와 GraphQL A...

blog.naver.com

GraphQL과 RESTful API · 안녕 프로그래밍 (holaxprogramming.com)

 

GraphQL과 RESTful API · 안녕 프로그래밍

GraphQL 은 Server API 를 구성하기 위해 Facebook 에서 만든 Query Language 이다. 이 글에서는 Server API 가 무엇인지, 그리고 GraphQL 이 어떤 역할을 하는지에 대해서 알아볼 것이다. Server API Server API (혹은 Serv

www.holaxprogramming.com

 

 

 

package.json 이란??

노드의 현재 프로그램에 대한 정보를 저장하고 있는 파일이다. 패키지의 의존중인 버전에 관한 정보들을 담고 있다
npm 패키지의 중심이라고 할 수 있다.

담겨져 있는 정보
반드시 작성해야 하는 필드는 name과 version 두개이다.

name: 프로젝트의 이름을 뜻한다. 이름을 지을 때, 대문자는 쓸 수 없고 node 또는 js라는 단어가 있으면 안된다.

verseion : 이는 몇 번째 배포 판인지를 적는 란이다.

description : 프로젝트를 간단히 설명하는 란으로, 해당 프로젝트를 처음 보게 된 사람들을 위해 간략한 설명을 적는 곳이다.

repository : 여기에 저희가 해당 데이터를 저장하고 있는 데이터 저장소에 대한 정보를 적는 란이다. type 과 url을 담고 있다.

scripts : 이곳은 terminal에서 자주 쓰는 명령어들에게 NPM 단축 명령어를 지정해 주는 곳으로 npm run "명령어"를 적으면 해당 명령어에 담긴 작업이 실행 된다.

author : 이 프로젝트의 제작자의 정보를 적는 란이다.

contributers : 프로젝트 제작에 기여한 사람을 적는 란이다. author에 한명만 기입이 되므로 나머지 개발 인원들이 해당되겠다.

dependencies: 일반적인 경우에 의존하고 있다는 것이다.

devDependencies: 개발 모드일때만 의존하고 있다는 것이다.

peerDependencies: 직접 필요로 하지는 않아도 호환되는 패키지의 목록이다.

 

[#코드한토막] 12화 nodeJS의 주민등록.. : 네이버블로그 (naver.com)

 

[#코드한토막] 12화 nodeJS의 주민등록증, package.json

코드한토막 안녕하세요~ 여러분의 평범한 일상러 윤똑입니다! 오늘은 코드한토막의 열두 번째 시간입니다. ...

blog.naver.com

package.json이란? : 네이버 블로그 (naver.com)

 

package.json이란?

package.json: 노드의 현제 프로그램에 대한 정보를 저장하고 있는 파일이다. 패키지의 관한 정보와 의존중...

blog.naver.com

 

 

 

이후는 프론트엔드 과제이지만 나중에 협업을 위해서 그냥 알아둘려고 정리하였다.

생각보다 리엑트의 세계도 재밌어보인다. Virtual DOM이라니!

DOM 이란?

문서 객체 모델(The Document Object Model, 이하 DOM) 은 HTML, XML 문서의 프로그래밍 interface 이다. DOM은 문서의 구조화된 표현(structured representation)을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다. DOM 은 구조화된 nodes와 property 와 method 를 갖고 있는 objects로 문서를 표현한다. 이들은 웹 페이지를 스크립트 또는 프로그래밍 언어들에서 사용될 수 있게 연결시켜주는 역할을 담당한다.

웹 페이지는 일종의 문서(document)다.  이 문서는 웹 브라우저를 통해 그 내용이 해석되어 웹 브라우저 화면에 나타나거나 HTML 소스 자체로 나타나기도 한다. 동일한 문서를 사용하여 이처럼 다른 형태로 나타날 수 있다는 점에 주목할 필요가 있다. DOM 은 동일한 문서를 표현하고, 저장하고, 조작하는 방법을 제공한다. DOM 은 웹 페이지의 객체 지향 표현이며, 자바스크립트와 같은 스크립팅 언어를 이용해 DOM 을 수정할 수 있다.

W3C DOMWHATWG DOM 표준은 대부분의 브라우저에서 DOM 을 구현하는 기준이다. 많은 브라우저들이 표준 규약에서 제공하는 기능 외에도 추가적인 기능들을 제공하기 때문에 사용자가 작성한 문서들이 각기 다른 DOM 이 적용된 다양한 브라우저 환경에서 동작할 수 있다는 사실을 항상 인지하고 있어야 한다.

 

DOM 과 자바스크립트

이 문서의 대부분의 예제와 같이, 위에서 사용된 예제는 JavaScript이다. 위의 예제는 자바스크립트로 작성되었지만 문서(document) 와 문서의 요소(element) 에 접근하기 위해 DOM 이 사용되었다. DOM 은 프로그래밍 언어는 아니지만 DOM 이 없다면 자바스크립트 언어는 웹 페이지 또는 XML 페이지 및 요소들과 관련된 모델이나 개념들에 대한 정보를 갖지 못하게 된다. 문서의 모든 element - 전체 문서, 헤드, 문서 안의 table, table header, table cell 안의 text - 는 문서를 위한 document object model 의 한 부분이다. 때문에, 이러한 요소들을 DOM 과 자바스크립트와 같은 스크립팅 언어를 통해 접근하고 조작할 수 있는 것이다.  

초창기에는 자바스크립트와 DOM 가 밀접하게 연결되어 있었지만,  나중에는 각각 분리되어 발전해왔다. 페이지 콘텐츠(the page content)는 DOM 에 저장되고 자바스크립트를 통해 접근하거나 조작할 수 있다. 이것을 방정식으로 표현하면 아래와 같다:

API (web or XML page) = DOM + JS (scripting language)

DOM(Document Object Model)

DOM은 문서 객체 모델이라고 하는데 결국은 브라우저에서 다룰 HTML 문서를 파싱하여 "문서의 구성요소들을 객체로 구조화하여 나타낸 것"이다.

Virtual DOM이란?

 "DOM을 추상화한 가상의 객체"

어떤 문제를 해결하기 위한 기술인가?

  • DOM 조작에 의한 렌더링이 비효율적인 문제
  • SPA(Single Page Application)특징으로 DOM 복잡도 증가에 따른 최적화 및 유지 보수가 더 어려워지는 문제

결론적으로 DOM을 반복적으로 직접 조작하면 그 만큼 브라우저가 렌더링을 자주하게 되고, 그 만큼 PC 자원을 많이 소모하게되는 문제를 해결하기 위한 기술이다.

간단히 짚고 넘어가는 브라우저의 렌더링 방법

앞서 DOM을 반복 조작하면 렌더링도 자주한다고 얘기했는데 그건 브라우저의 렌더링 방식이 그렇게 되어있기 때문이다.

간단하게 설명하면 과정은 아래와 같다

  1. HTML을 파싱하여 DOM 객체를 생성하고, CSS를 파싱하여 스타일 규칙을 만든다.
  2. 이 두개를 합쳐서 실제로 웹 브라우저에 보여져야할 요소를 표현한 "렌더 트리" 라는 것을 만든다.
  3. 이 렌더 트리를 기준으로 레이아웃을 배치하고 색을 칠하는 등의 작업을 한다.

이 과정에서 문제가 되는 경우는 현대의 웹처럼 변경해야할 대상도 많고 변경도 많은 경우이다.

프로그래밍에 의해 DOM을 변경해야하고 변경할 구성 요소가 100개면 위의 과정을 100번을 하는 비효율적인 작업을 해왔다.

정확히는 DOM을 변경하는게 문제가 아니고 렌더링을 여러번 하는게 문제이다.

어떻게 해결했는가? (동작 원리)

해결 방법은 바로 Virtual DOM이라는 DOM을 추상화한 가상의 객체를 메모리에 만들어 놓는 것이다.

Virtual DOM은 DOM과 유사한 역할을 담당할 객체이다.

즉, 변경 사항을 DOM에 직접 수정하는게 아니라 중간 단계로 Virtual DOM을 수정하고 Virtual DOM을 통해서 DOM을 수정하게 했다.

실질적인 방법은 Virtual DOM에 변경 내역을 한 번에 모으고(버퍼링) 실제 DOM과 변경된 Virtual DOM의 차이를 판단한 후, 구성요소의 변경이 부분만 찾아 변경하고 그에 따른 렌더링을 한 번만 하는 것으로 해결했다.

주의사항과 한계

  • 0.1초마다 화면에 데이터가 변경된다면? Virtual DOM으로 0.5초씩 모아가지고 렌더링을 적게할 수 있을까? → 안된다. 동시에 변경되는 것에 한해서만 렌더링된다.
  • React나 Vue등을 이용해서 Virtual DOM을 쓰면 무조건 빠른가? → 아니다. 똑같이 최적화를 해야한다. (슬라이드를 옮기거나 무한 스크롤등의 움직임이 있을 때는 Virtual DOM을 이용해서 반복 렌더링을 하지 않도록 해줘야한다.)
  • Virtual DOM은 메모리에 존재한다. DOM에 준하는 무거운 객체(Virtual DOM)가 메모리에 상주(?)하고 있기 때문에 메모리의 사용이 많이 늘어날 수 밖에 없다.
  • Virtual DOM을 조작하는 것도 엄청나게 많은 컴포넌트를 조작하게 된다면 오버헤드가 생기기 마련이다. Virtual DOM 제어가 DOM 직접 제어에 비해 상대적으로 비용이 적게 들 뿐이다.







 

 

 

DOM 소개 - Web API | MDN (mozilla.org)

 

DOM 소개 - Web API | MDN

이 문서는 DOM에 대한 개념을 간략하게 소개하는 문서이다: DOM 이 무엇이며, 그것이 어떻게 HTML, XML (en-US) 문서들을 위한 구조를 제공하는지, 어떻게 DOM 에 접근하는지, API 가 어떻게 사용되는지에

developer.mozilla.org

 

Virtual DOM 동작 원리와 이해 (with 브라우저의 렌더링 과정) (tistory.com)

 

Virtual DOM 동작 원리와 이해 (with 브라우저의 렌더링 과정)

Virtual DOM? 1. Virtual DOM이란? → "DOM을 추상화한 가상의 객체" DOM을 추상화한 가상의 객체라고 표현해봤습니다. (개인이 내린 정의) 그러면 우선 저 문장을 이해하기 위해서 DOM이란 뭔지 알아야합니

jeong-pro.tistory.com

 

서버리스란?

서버리스(Serverless)는 말 그대로 ‘서버(Server)가 없다(-less)’는 뜻으로 애플리케이션의 확장을 관리할 필요가 없는 클라우드 컴퓨팅 모델을 가리킵니다. 이름 때문에 물리적인 서버가 아예 없고 클라이언트에서 모든 것을 처리하는 구조로 착각할 수 있습니다. 하지만 실제로 서버가 없는 구조는 아닙니다. 서버에서 처리하는 작업을 클라우드 기반의 서비스로 처리해서 서버 구축 및 관리 비용을 줄이는 구조입니다. 따라서 개발 기간과 비용을 단축할 수 있을 뿐 아니라, 서버 운영과 유지 보수의 어려움을 크게 줄일 수 있습니다.

이점이 또 있습니다. 서버리스는 개발자가 인프라는 대부분 무시하고 코드에 집중할 수 있도록 합니다. 서버 사양이 개발자에게 영향을 주지 않으므로 ‘서버리스’인 것입니다. 물론 물리적 서버는 여전히 존재하지만 클라우드 제공업체가 서비스 중 서버가 하는 관리한다는 점이 다르며 이 점이 개발자 및 서비스 환경이 서버에 신경을 쓰지 않아도 된다는 이점을 이끌어냅니다.

 

작동 방식

서버를 사용해야 하는 경우, 그러니까 표준 IaaS(Infrastructure as a Service) 모델에서 사용자는 용량 단위를 미리 구매합니다. 이는 애플리케이션을 실행하기 위해 ‘상시 가용할 수 있는’ 서버 구성 요소에 대한 비용을 지불하는 것입니다. 서버리스의 모델들은 이와 다릅니다. 이벤트를 실행할 애플리케이션 코드를 트리거(특정한 작용에 대응하여 자동으로 반작용이 실행되게끔 만드는 논리적 장치)하면 클라우드 제공업체는 해당 코드에 리소스(서버 자원)를 동적으로 할당하고, 사용자는 코드 실행이 끝나면 비용을 지불하지 않습니다.

장점과 단점

장점도 있고 단점도 있습니다. 장단점을 살펴보고 어떤 환경에 어울릴지 그려보세요.

1. 장점

– 서버 관리가 필요 없습니다.

– 유연한 확장성: 애플리케이션을 활용하여 자유자재로 확장을 구현할 수 있습니다.

– 높은 가용성: 고정된 서버가 없음은 제한된 가용성이 아님을 의미합니다. 가용성을 위한 별도의 설계가 필요 없습니다.

– 유휴 용량에 대한 비용: 사용하지 않는 것에 대해선 비용을 지불할 필요가 없습니다. 실행하지 않을 때는 비용이 발생하지 않습니다.

– 개발자 생산성을 높이고 운영 비용을 줄일 수 있습니다.

 

2. 단점

– 콜드스타트(Cold-Start): 빠른 응답이 필요한 제품(서비스)의 경우 서버리스로의 전환은 부적합할 수 있습니다. 이는 실행할 함수를 호출하기 위해 컨테이너가 실행되는 대기 시간이 존재하기 때문인데, 서버를 항시 가동하지 않는 서버리스의 특징에 기인합니다.

– 무상태(Stateless)적인 기능의 구현 불가: 작은 기능으로 잘게 나눠진 함수들은 요청마다 새로 기동하도록 호출되기 때문에 기동 전후의 상태를 공유할 수 없습니다. 또한 변수와 데이터의 공유가 불가능하며, 데이터를 로컬 스토리지에서 읽고 쓸 수 없습니다.

– 벤더 종속의 문제: 함수가 사용할 수 있는 최대 메모리, 최대 처리 가능 시간 제약 등의 제약을 서버리스 서비스 벤더가 설정하며 서버리스 사용자 및 서버리스 기반의 서비스는 이에 종속됩니다. 이에 맞춰 벤더가 설정한 제약을 벗어나는 큰 기능은 잘게 나누어 구현해야 합니다.

 

파이어베이스란?

파이어베이스는 “앱을 개발하고, 개선하고, 키워갈 수 있는” 도구 모음(toolset)이며, 이러한 도구가 없다면 개발자들은 일반적으로 서비스의 상당 부분을 직접 만들어내야만 합니다. 그런데 개발자들은 앱의 사용자 경험(UX)에 집중을 해야 하기 때문에, 그런 세세한 부분들까지 전부 만드는 걸 좋아하지 않습니다. 그런 부분들로는 분석, 인증, 데이터베이스, 구성 설정, 파일 저장, 푸시(push) 메시지 등, 열거하자면 끝이 없습니다. 파이어베이스로 만든 이러한 서비스들이 클라우드에 호스팅 되면, 개발자의 입장에서는 거의 아무런 노력을 들이지 않고도 앱의 규모를 확장할 수 있습니다.

 

 

 

서버리스, 도대체 그것은 무엇인가? - Wishket

 

서버리스, 도대체 그것은 무엇인가?

서버리스(Serverless)는 말 그대로 ‘서버(Server)가 없다(-less)’는 뜻으로 애플리케이션의 확장을 관리할 필요가 없는 클라우드 컴퓨팅 모델을 가리킵니다. 이름 때문에 물리적인 서버가 아예 없고

blog.wishket.com

(1) 서버리스는 서버가 없는걸까? 8분 개념 설명! - YouTube

파이어베이스(Firebase)란 무엇인가? 파.. : 네이버블로그 (naver.com)

 

파이어베이스(Firebase)란 무엇인가? 파이어베이스 심층 탐구 : 상편

*잠깐, 이 글을 소개해드리는 위시켓은 2019년 시밀러웹 방문자 수 기준, 국내 1위 IT아웃소싱 플랫폼입...

blog.naver.com

허허... 하다하다 서버를 클라우딩하다니... 신기하군;; 
진짜 개발쪽을 알아보면 알아볼수록 이 쪽 세계는 변화무쌍해서 어디로 튈지 모르는게 너무 신기하다...
그 변화를 따라가려면 계속 공부해야하고 실력을 키워나가야 한다는 것...
그리고 변화를 주도하려면 끊임없이 창의적 생각을 해야하며 발전해나가야 한다는 것!

 

또 다시 다가온 알고리즘의 시간

중복문자제거
소문자로 된 한개의 문자열이 입력되면 중복된 문자를 제거하고 출력하는 프로그램을 작성하
세요.
제거된 문자열의 각 문자는 원래 문자열의 순서를 유지합니다.
▣ 입력설명
첫 줄에 문자열이 입력됩니다.
▣ 출력설명
첫 줄에 중복문자가 제거된 문자열을 출력합니다.
▣ 입력예제 1
ksekkset
▣ 출력예제 1
kset

풀이

처음엔 이중포문을 돌릴 생각을 했는데 결과값을 가져오는게 쉽지 않았다.
답안을 보고서야 알았다... indexOf

indexOf를 사용하면 편하다.
indexOf('찾을문자', 찾기시작할index넘버)

function solution(s){
    let answer = "";
    for(let i = 0; i < s.length; i++){
        // console.log(s[i],i, s.indexOf(s[i]))
        if(s.indexOf(s[i])===i){    //indexOf가 배열에서 최초로 발견되는 index넘버값을 
                                    //알려주므로 현재 index넘버값과 최초발견 index넘버값이 같은 것들을 추리면 중복 문자를 제거할 수 있다.
            answer +=s[i]
        }
    }
    return answer
}
console.log(solution("ksekkset"))

 

'Today I Learned' 카테고리의 다른 글

12 TIL 리액트 박살내기!  (0) 2021.09.29
11 TIL 리액트의 날  (0) 2021.09.27
9TIL 항해99 13일차  (0) 2021.09.25
7 TIL  (0) 2021.09.23
6 TIL  (0) 2021.09.23

+ Recent posts