반응형

파이썬은 기계 학습 및 데이터 과학에서 가장 인기 있는 언어 중 하나로 부상했습니다. 다양한 기능을 제공하고 다양한 요구를 충족하는 풍부한 머신 러닝 라이브러리 생태계를 제공합니다....라고 하는데. 한번 시작해볼까 하다가도 라이브러리가 너무 많이 존재해서 뭐가뭔지 잘 모르겠네요. 그래서 그 중 가장 유명한 네 가지 파이썬 머신러닝 라이브러리인를 비교해보고자 합니다. 

1. Scikit-learn:

Scikit-learn은 전통적인 기계 학습 작업에 널리 사용되는 라이브러리입니다. 데이터 전처리, 피쳐 추출, 모델 선택 및 평가를 위한 포괄적인 도구 세트를 제공합니다. Scikit-learn의 주요 기능은 다음과 같습니다:
 <장점>

     -  간단하고 직관적인 API를 제공하므로 다양한 수준의 전문 지식을 가진 사용자가 접근 가능. 탄탄한 학습알고리즘.

     -  광범위한 알고리즘 지원: 분류, 회귀, 클러스터링, 차원 축소 등을 포함한 광범위한 기계 학습 알고리즘을 제공

     -  NumPy 및 Pandas와 같은 인기 있는 Python 라이브러리와 원활하게 통합

   <단점>

     - 딥러닝이나 강화학습은 다루지 않음


2. 텐서플로(TensorFlow)


텐서플로는 구글이 개발한 오픈소스 라이브러리이며 머신러닝 / 딥러닝을 쉽게 사용할 수 있도록 다양한 기능을 제공합니다. 데이터 플로우 그래프(Data Flow Graph)구조를 사용하여 풍부한 표현이 가능한 프레임워크입니다. 

   <장점>

     -  계산구조와 목표함수만 정의되면 자동으로 미분계산을 처리함

     -  텐서보드를 통해 파라미터 변화양상 및 DNN 구조를 알 수 있음

     -  이미지 인식, 반복 신경망 구성, 기계 번역, 필기 숫자 판별 등 각종 신경망 학습에 사용

     - 대규모 예측 모델 구성이 뛰어나 테스트부터 실제 서비스까지 거의 모든 딥러닝 프로젝트에 범용적으로 활용 가능

   <단점>

     -  메모리를 효율적으로 사용하지 못하고 있음

     -  Symbolic Loop 기능이 유연하지 못하며, 함수가 있어도 텐서 타입으로만 적용해야 함

     -  딥러닝 모델을 만드는데 기초 레벨부터 작업이 필요하여 초보자가 사용하기 어려움 

 

3. 케라스(Keras)

케라스는 텐서플로의 문제를 해결하기 위한 보다 단순화된 인터페이스를 제공하기 위해 역시 구글에서 개발된 오픈소스 신경망 라이브러리입니다. 케라스에서 제공하는 시퀀스 모델로 원하는 레이어를 쉽게 쌓을 수도 있고, 더 복잡한 모델을 구성할 땐 케라스 함수 API를 활용하여 구성할 수도 있습니다.

   <장점>

     -  매우 쉽게 모델 구현 가능

   <단점>

     -  디테일한 모델링이 불가하며, 코드만 보고 딥러닝 구조를 이해하기 어려움

     -  오류 발생시 케라스 자체의 문제인지, 백엔드 언어의 문제인지 특정하기 어려움

 


4. PyTorch:


PyTorch는 유연성과 동적 계산 그래프를 강조하며, 페이스북의 AI연구팀이 개발한 오픈소스 머신러닝 라이브러리입니다. 사용 편의성과 개발자 커뮤니티가 많아 큰 인기를 얻었습니다. 
   <장점>

     -  코드를 깔끔하고 직관적으로 작성 가능하며, 학습속도도 텐서플로보다 빠름

     -  메모리에서 연산을 하면서도 신경망 사이즈를 최적으로 바꾸면서 동작시킬 수 있음

     -  Numpy를 대체하면서도 GPU를 이용한 연산이 가능

     -  신속한 시제품 제작과 실험을 지원하기 때문에 연구계에서 선호하는 추세

   <단점>

     -  사용자층이 낮고 학습에 필요한 자료와 예제를 구하기 쉽지 않음 

 

 

전체적인 비교를 하자면...


- 학습 곡선: Scikit-learn, Keras
- 딥 러닝 지원: TensorFlow, Keras, PyTorch가 광범위한 신경망 아키텍처와 사전 훈련된 모델 지원
- 성능: TensorFlow 및 PyTorch는 GPU 가속을 활용하여 대규모 모델을 학습하는 데 매우 효율적. Scikit-learn은 CPU 기반이기 때문에 계산 집약적인 작업에 어려움.
- 생태계 및 커뮤니티: Scikit-learn, TensorFlow, Pytorch?

진짜 머신러닝을 공부하게될지는 잘 모르겠지만, 우선 한다면 Tensorflow가 가장 적당해 보이네요. 시간될 때 주가예측 한번 해보면 좋을 것 같습니다.

 

그럼 이만~~

반응형
반응형

1. store/store.js 수정
store.js 파일에 url request가 가능하도록 각 기능마다 axios 기능을 추가해줍니다. 아울러, 최초 Load시에는 DB의 정보들을 불러올 수 있도록 onLoad함수를 추가해주었습니다.

import { createStore } from 'vuex';
import axios from 'axios';

axios.defaults.baseURL = 'http://localhost:3000';
axios.defaults.headers.post['Content-Type']='application/json;charset=utf-8';
axios.defaults.headers.post['Access-Control-Allow-Origin']='*';

export const store = createStore({
// export default createStore({
  state:{
    user:{
      // {username: '홍길동', email: 'abcd@abcd.com', password: '1234'}
    },
    posts: [
      // {_id: '1', title: 'apple', body: 'Im apple', creator: 'apple'},
      // {_id: '2', title: 'banana', body: 'Im banana', creator: 'banana'},
      // {_id: '3', title: 'orange', body: 'Im orange', creator: 'orange'}
    ]
  },
  mutations:{
    updateState(state, payload) {
      Object.keys(payload).forEach((key) => {
        state[key] = payload[key];        
      });
    },
    LOGIN(state, payload){
      state.user = payload;
    },
    DELETEPOST(state, post_id){            
      const delete_index = state.posts.findIndex(p=> p._id === post_id);      // 삭제할 포스트의 index 찾기
      state.posts.splice(delete_index,1);      
    },
    UPDATEPOST(state,updatePost){      
      const update_store_item = state.posts.find(p => p._id === updatePost._id);      
      // console.log(updatePost)
      update_store_item.title = updatePost.title;
      update_store_item.body = updatePost.body;
    }
  },
  actions:{ 
    async login(context, name){            
      const res = await axios.post('/auth/register',name);      
      context.commit('LOGIN', res.data);
    },  
    async onLoad(context){
      const res = await axios.get('/posts');
      const { post } = res.data;
      console.log(post);
      context.commit("updateState", {
        posts: [...context.state.posts, ...post],
      })
    },
    async addPost(context, newPost){
      // const id= context.state.posts.length+1;      
      // const post={
      //   _id: id,
      //   title: newPost.title,
      //   body: newPost.body,
      //   creator: newPost.title
      // }
      const res = await axios.post('/posts', newPost);
      const post = res.data;  
      context.commit("updateState", {
        posts: [...context.state.posts, post],
      })         
    },
    async deletePost(context, post_id){
      await axios.delete(`/posts/${post_id}`);  
      context.commit("DELETEPOST", post_id);
    } ,
    async updatePost(context, updatePost){
      const { _id } = updatePost;
      await axios.patch(`/posts/${_id}`, updatePost);
      context.commit("UPDATEPOST", updatePost)
    }    
  }
});

 
2. view/HomeView.vue 수정
유저가 존재할 경우와 존재하지 않을 경우를 구분하여 표시하도록 파일을 수정해줍니다.

<template>
  <div class="logincheck">
    <div v-if="isloggedin">안녕하세요, {{ user.username }}님</div>    
    <router-link to="/kakaologin" v-else>Kakao Login</router-link>
  </div> 
  <h1 class="appname">My Todo App</h1>  
  <AddPost />
  <ListofPost />
</template>

<script>
import ListofPost from '../components/ListofPost.vue'
import AddPost from '../components/AddPost.vue'

export default {
  name: 'HomeView',
  components: {
    ListofPost,
    AddPost,
  },
  computed:{
    user(){
      return this.$store.state.user
    },
    isloggedin(){
      return this.$store.state.user.username? true : false;
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;  
  color: #2c3e50;
  margin-top: 60px;  
}
.appname {
  color:#373738;
  text-align: center;
}
.addbutton{
  margin: auto;
  display:flex;
  align-items:center;
  justify-content: center;
  width:50px;
  height:50px;  
  border-radius: 50%;
  background-color:blue ; 
  font-size: 36px;
  font-weight: bold;
  color:beige;
}
.logincheck{  
  text-align: right;  
  margin-right: 15px;
}
</style>

 
3. components/ListofPost.vue 수정
최초 로딩시 DB 데이터를 불러오도록 onLoad 함수를 호출합니다.

<template>
  <ul>
    <li v-for="post in posts.slice().reverse()" :key=post>
      <EachofPost 
        :id=post._id
        :titleOfPost=post.title 
        :bodyOfPost=post.body 
        :creator=post.creator />
    </li>
  </ul>
</template>
<script>
import EachofPost from './EachofPost.vue'

export default {
  name: 'ListofPost',
  components: {
    EachofPost,
  },
  computed:{
    posts(){
      return this.$store.state.posts;
    },    
  }, 
  created(){
      this.$store.dispatch('onLoad');  // 추가
  }
}
</script>

<style>
ul{
  padding-left:0;
}
li{
  list-style: none;
}
</style>

 

반응형
반응형

이번에는 Express.js를 이용해서 Backend를 구성해보도록 하겠습니다. Backend 서버는 Express지만,
그 외에도 추가로 MongoDB에 적당한 클러스터 생성이 필요합니다. 그리고 Kakao 로그인 연동을 위해 Kakao Developers
사이트에서 앱 생성이 필요합니다. 이부분은 많은 지문을 필요로 하는 부분이라 여기서 다루지는 않겠습니다.
 
1. 환경파일(.env) 생성
Backend 프로젝트 폴더에 .env 파일을 생성해서 비밀 환경정보 등을 저장합니다.

PORT=3000
NODE_ENV='development'
MONGO_URI='mongodb+srv://몽고디비ID:몽고디비PW@cluster0.juisc.mongodb.net/test'
KAKAO_ID=00sdfsdf6432sdfa792sdfssdfsdf28이거내가고친거임
KAKAO_REDIRECT_URI=http://localhost:3000/auth/kakao/callback
SECRET=mysecretkeytest

 
2. 패키지 설치
필요한 패키지들을 설치합니다.  설치할 땐 "npm i 패키지이름", 개발환경 의존성은 "npm i -D 패키지이름"

"dependencies": {
    "body-parser": "^1.20.2",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "express-session": "^1.17.3",
    "mongoose": "^7.0.4",
    "passport": "^0.6.0",
    "passport-local": "^1.0.0",
    "passport-local-mongoose": "^8.0.0",
    "pm2": "^5.3.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.22"
  }

 
3. index.js
cors가 조금 문제스럽습니다. 아직도 정확히 작동하는 법은 몰라서 이것저것 남겨놓습니다.
라우팅은 포스팅 관련된 postRoute와 사용자관리 관련된 userRoute 두가지로 구성했습니다. 

import express from 'express';
import session from 'express-session';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import dotenv from 'dotenv';
import mongoose from 'mongoose';
import postRoute from './routes/postRoute.js';
import userRoute from './routes/userRoute.js';
import cors from 'cors';
import path from 'path';

dotenv.config();

const app = express();
const __dirname = path.resolve();
const { PORT, MONGO_URI, SECRET } = process.env;

app.set('port', PORT || 3000);

mongoose.connect(MONGO_URI)
    .then(() => {
        console.log('Connected to MongoDB');
    })
    .catch(e => {
        console.error(e);
    });

app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(SECRET));
app.use(session({
    resave: false,
    saveUninitialized: false,
    secret: SECRET,
    cookie: {
        httpOnly: true,
        secure: false
    }
}));
// const options = {
//     origin: "http://localhost:4000", // 접근 권한을 부여하는 도메인
//     credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
//     optionsSuccessStatus: 200, // 응답 상태 200으로 설정
//   };
  
// app.use(cors(options));
app.use(cors());

app.all('/*', function(req, res, next){
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    next();
})
app.use('/posts',postRoute);
app.use('/auth',userRoute);
var router = express.Router();
app.get('/', function(req,res,next){
    res.sendFile(path.join(__dirname, './public','index.html'))
})

app.listen(app.get('port'), ()=> {
  console.log(`${app.get('port')}번 포트에서 서버 실행 중..`);
});

 
4. models/Post.js
포스트 모델을 구성합니다. 

import mongoose from "mongoose";

let PostSchema = new mongoose.Schema({
  title: String,
  body: String,
  publishedDate: Date,  
  creator: {
      _id:{
          type: mongoose.Schema.Types.ObjectId,
          ref: "User"
      },
      username: String,   
      password:String,
      email: String   
  },  
});

let Post = mongoose.model("Post", PostSchema);
export default Post;

 
5. models/User.js
사용자 모델을 생성합니다.

import mongoose from 'mongoose';
import passportLocalMongoose from 'passport-local-mongoose'


let UserSchema = new mongoose.Schema({
    username: String,
    password:String,
    email: String
});

UserSchema.plugin(passportLocalMongoose);
const User = mongoose.model("User", UserSchema);
export default User;

 
6. routes/postRoute.js

import express from 'express';
import Post from '../models/Post.js';
import User from '../models/User.js';
import bodyParser from 'body-parser'

const router = express.Router();
router.use(express.json());

// 전체 리스트 읽기
router.get('/', (req, res) => {
  Post.find()
    .then((post) =>{
      res.send({post});
    })  
});

// 신규 포스트 생성
router.post('/', async (req, res) => {  
  const { title, body } = req.body;  
  // const creator = req.body.email;
  console.log(req.body);
  const post = new Post({
    title, 
    body, 
    creator: req.body.creator,
  });
  try{
    await post.save();
    // console.log(post);    
    res.body = post;
    res.send(post);
  } catch (e) {
    console.error(e);
  }

});

// 특정 포스트 조회
router.get('/:id', async (req, res) => {
  const { id } = req.params;
  console.log(id);
  try {
    const post = await Post.findById(id).exec();
    if(!post){
      res.sendStatus(404);
      return;
    }
    res.body = post;
    res.send(post);
  } catch (e) {
    console.error(e);
  }
});

// 특정 포스트 업데이트
router.patch('/:id', async (req, res) => {
  const { id } = req.params;
  // const { title, body } = req.body;
  try{
    const post = await Post.findByIdAndUpdate(id, req.body, {new:true,}).exec();
    if(!post){
      console.sendStatus(404);
      return;
    }
    res.body = post;
    res.send(post);
  } catch(e){
    console.error(e);
  }

});

// 특정 포스트 삭제
router.delete('/:id', async (req, res) => {
  const { id } = req.params;
  // console.log(req.params);
  try{
    await Post.findByIdAndRemove(id).exec();
    res.sendStatus(204);
  } catch(e){
    console.error(e);
  }
});

export default router;

 
7. routes/userRoute.js
유저 인증은 passport-local 라이브러리를 이용한 방식으로 진행합니다. Front에서 Kakao로 인증하고, 사용자 정보를 Backend로 넘겨주면 처리합니다. 이때 URL은 '/register' 만 사용하는데, 등록된 사용자면 login을 시도하고 등록 안된 사용자면 신규로 user테이블에 생성합니다. 즉 무조건 가입/로그인 되는 방식입니다.

import express from 'express';
import User from '../models/User.js';
import passport from 'passport';
import Localstrategy from 'passport-local';

const router = express.Router();

passport.use(new Localstrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

router.use(passport.initialize());
router.use(passport.session());

const isLoggedIn = (req, res, next) => {
    if (req.isAuthenticated()) {
      return next();
    }
    res.redirect('/login');
  };

router.post('/register', async (req, res, next) => {
  const { username, email, password } = req.body;

  try{
    const existingUser = await User.findOne({ email });
    // console.log(existingUser);  //DB에서 존재하는 emial인지 확인
    if (existingUser) {
        // User already exists, redirect to login page or show error message        
        passport.authenticate('local')(req, res, () => {
            // Authentication successful
            // console.log(req.user);
            console.log("Login success!");
            return res.send(req.user);
        });
    } else {

        const newUser = new User({ username, email });
        await User.register(newUser,password); // 패스워드만 따로 보내야 해싱처리 등이 적용됨 

        passport.authenticate("local")(req, res, () => {
            // console.log(req.user);
            console.log("Success! You are registered and logged in!");
            return res.send(req.user);
        });
    }
  } catch (err) {
    return next(err);
  }  
});

router.get('/protected', isLoggedIn, (req, res) => {
    res.send('Protected content');  
});

router.get("/logout", (req, res) => {
    req.logout();
    res.redirect("back");
});



export default router;

 
passport-kakao 라이브러리도 있는데, backend에서 구현하면 계속 cors문제를 해결 못했습니다. 그래서 front에서 인증하고, 사용자 정보를 backend로 넘기는 방식으로 구현함..ㅠㅠ

반응형
반응형

Kakao 로그인을 프론트에서 작업하고, 사용자 정보 중 이름, 메일주소 등 몇가지만 백엔드에 등록하는 방식으로 진행하겠습니다. 이를 위해서는 우선 main.js에 아래의 코드를 추가해야 합니다.

 

1. main.js

import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store/store.js'
import router from './router'

createApp(App)
.use(router)
.use(store)
.mount('#app')

// 아래 추가
window.Kakao.init('ce8a7d7f75e0322350135c938a5f7182');

 

2. components/KakaoLogin.vue 신규 생성

<template>
	<div class="kakaobox">
		<a id="custom-login-btn" @click="kakaoLogin()">
		<!-- <a id="custom-login-btn" @click="kakaoLogin()"> -->
			<img src="//k.kakaocdn.net/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg" width="222" />
		</a>
	</div>
</template>

<script>
import axios from 'axios';

export default{
	
methods: {	
	kakaoLogin(){
		window.Kakao.Auth.login({
			scope: 'profile_nickname,profile_image, account_email',
			success:() =>{
				this.getKakaoAccount();					
			},
			fail: error => {
				console.log(error);
			},
		});
	},
	getKakaoAccount(){
		window.Kakao.API.request({
			url:'/v2/user/me',				
			success : async res => {
				const kakao_account = res.kakao_account;
				const data={
					username: kakao_account.profile.nickname,
					email : kakao_account.email,
					password: "1234"
				}
				await axios.post('/auth/register', data);
				alert("로그인 성공!");
				// console.log(result);

				this.$store.dispatch('login', data);
				this.$router.push('/')
			},
			fail: error => {
				console.log(error);
			}
		})
	}
}

}
</script>
<style scoped>
.kakaobox{
	text-align: center;
}
</style>

 

3. store/store.js 수정

import { createStore } from 'vuex';
import axios from 'axios';


axios.defaults.baseURL = 'http://localhost:3000';
axios.defaults.headers.post['Content-Type']='application/json;charset=utf-8';
axios.defaults.headers.post['Access-Control-Allow-Origin']='*';


export const store = createStore({
// export default createStore({
  state:{
    user:{
      // {username: '홍길동 email: 'abcd@abcd.com', password: '1234'}
    },
    posts: [
      // {_id: '1', title: 'apple', body: 'Im apple'},
      // {_id: '2', title: 'apple', body: 'Im apple'},
      // {_id: '3', title: 'apple', body: 'Im apple'}
    ]
  },
  mutations:{
    LOGIN(state, payload){
      state.user = payload;
    },
    updateState(state, payload) {      
      // read, create
      Object.keys(payload).forEach((key) => {
        state[key] = payload[key];        
      });
    },
    DELETEPOST(state, post_id){            
      const delete_index = state.posts.findIndex(p=> p._id === post_id);      // 삭제할 포스트의 index 찾기
      state.posts.splice(delete_index,1);      
    },
    UPDATEPOST(state,updatePost){      
      const update_store_item = state.posts.find(p => p._id === updatePost._id);      
      // console.log(updatePost)
      update_store_item.title = updatePost.title;
      update_store_item.body = updatePost.body;
    }
  },
  actions:{   
    addPost(context, newPost){      
      const id= context.state.posts.length+1;      
      const post={
        _id: id,
        title: newPost.title,
        body: newPost.body,
        creator: newPost.title
      }
      context.commit("updateState", {
        posts: [...context.state.posts, post],
      })              
    },
    deletePost(context, post_id){
      context.commit("DELETEPOST", post_id);
    },
    updatePost(context, updatePost){
      context.commit("UPDATEPOST", updatePost)
    },
    login(context, name){     
      context.commit('LOGIN', name);
    }
  }
});

 

4. views/HomeView.vue 파일 수정

<template>  
  <div class="logincheck">
    <div v-if="isloggedin">안녕하세요, {{ user.username }}님</div>    
    <router-link to="/kakaologin" v-else>Kakao Login</router-link>
  </div>
  <h1 class="appname">Real Estate Groth Status</h1>  

  <AddPost />
  <ListofPost />
</template>

<script>
import ListofPost from '../components/ListofPost.vue'
import AddPost from '../components/AddPost.vue'

export default {
  name: 'HomeView',
  components: {
    ListofPost,
    AddPost,
  },
  computed:{
    user(){
      return this.$store.state.user
    },
    isloggedin(){
      return this.$store.state.user.username? true : false;
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;  
  color: #2c3e50;
  margin-top: 60px;  
}
.appname {
  color:#373738;
  text-align: center;
}
.addbutton{
  margin: auto;
  display:flex;
  align-items:center;
  justify-content: center;
  width:50px;
  height:50px;  
  border-radius: 50%;
  background-color:blue ; 
  font-size: 36px;
  font-weight: bold;
  color:beige;
}
.logincheck{  
  text-align: right;  
  margin-right: 15px;
}
</style>

반응형
반응형

Post를 수정하는 기능을 구현하겠습니다.
 
1. components/EachofPost.vue 수정
v-if 기능을 이용하여 edit모드인지 아닌지에 따라 다른 화면을 보여주도록 구성합니다.

<template> 
  <div class="post" v-if="edit">
    <div class="post_header">
      <input class="title" type="text" name="title" v-model="title_update" />
      <button class="edit" type="submit" value="Add" @click="updateItem" >v</button>    
    </div>
    <div class="post_body">
      <textarea name="body" v-model="body_update" />   
    </div>    
  </div>   
  <div class="post" v-else>
    <div class="post_header">
      <div class="title">
        {{ id }}
        {{ titleOfPost }}                      
      </div>  
      <button class="edit" @click="editItem">v</button> 
      <button class="close" @click="removeItem">x</button>  
    </div>    
    <div class="post_body">
      {{ bodyOfPost }}
      <div class="creator">
        by {{  creator }}        
      </div>
    </div>  
    
  </div> 
</template>

<script>
export default {
  props:['id','titleOfPost', 'bodyOfPost','creator'],  
  data(){
    return{
      edit: false,
      title_update:this.titleOfPost,
      body_update:this.bodyOfPost,
    }
  } , 
  methods:{
    removeItem(){
      this.$store.dispatch("deletePost", this.id)
    },
    editItem(){
      this.edit = !this.edit;
    },
    updateItem(){
      this.$store.dispatch("updatePost", {
        _id:this.id, 
        title: this.title_update, 
        body: this.body_update
      })
      this.editItem();
    }
  }
}

</script>

<style>

.post{
  /* background-color:blanchedalmond; */
  margin: 5px auto;
  border: 1px solid grey;
  border-radius: 10px;  
  width:100%;
  min-height:100px;
}
.post .post_header{  
  display: flex;
  padding: 5px;
}
.post .post_header .title{
  display:inline-block;
  text-align: left;
  font-size: 24px;  
  height: 50px;
  width:100%;
  color: black;
  border: 0px solid;
  /* background-color:blueviolet; */
}
.post .post_header .close{
  position: relative;  
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}
.post .post_header .edit{
  position: relative;  
  box-sizing: border-box;
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}

.post .post_body{
  font-size: 16px;
  color:cadetblue;
  text-align: left;
  padding: 5px;
  border: 0px solid;
  /* background-color: orange; */
  width: 100%;
  min-height: 25px;
}
.post .post_body .creator{
  text-align: right;
  left:0;
  right: 0;
  top: 0;
  bottom: 0;
  margin-right: 10px;
  margin-bottom: 5px;
  /* right: 10px; */
}
.post .post_body textarea{
  width: 95%;
  min-height:24px;
  border: 0px;
  font-size: 16px;
  color:cadetblue;
}
</style>

 

 

2. store/store.js

import { createStore } from 'vuex';
import axios from 'axios';


axios.defaults.baseURL = 'http://localhost:3000';
axios.defaults.headers.post['Content-Type']='application/json;charset=utf-8';
axios.defaults.headers.post['Access-Control-Allow-Origin']='*';


export const store = createStore({
// export default createStore({
  state:{
    user:{
      // {username: '홍길동', email: 'abcd@abcd.com', password: '1234'}
    },
    posts: [
      // {_id: '1', title: 'apple', body: 'Im apple'},
      // {_id: '2', title: 'apple', body: 'Im apple'},
      // {_id: '3', title: 'apple', body: 'Im apple'}
    ]
  },
  mutations:{    
    updateState(state, payload) {
      // ['movies','message','loading'], 액션에서 넘어온 값(payload)를 기존 state에 할당해준다.
      // read, create
      Object.keys(payload).forEach((key) => {
        state[key] = payload[key];        
      });
    },
    DELETEPOST(state, post_id){            
      const delete_index = state.posts.findIndex(p=> p._id === post_id);      // 삭제할 포스트의 index 찾기
      state.posts.splice(delete_index,1);      
    },
    UPDATEPOST(state,updatePost){      
      const update_store_item = state.posts.find(p => p._id === updatePost._id);      
      // console.log(updatePost)
      update_store_item.title = updatePost.title;
      update_store_item.body = updatePost.body;
    }
  },
  actions:{   
    addPost(context, newPost){      
      const id= context.state.posts.length+1;      
      const post={
        _id: id,
        title: newPost.title,
        body: newPost.body,
        creator: newPost.title
      }
      context.commit("updateState", {
        posts: [...context.state.posts, post],
      })   
      // console.log(context.state.user)        
    },
    deletePost(context, post_id){
      context.commit("DELETEPOST", post_id);
    },
    updatePost(context, updatePost){
      context.commit("UPDATEPOST", updatePost)
    }
  }
});

 

 
 
 
반응형
반응형

Store를 적용해보겠습니다. 

npm i vuex axios  // axios는 나중에 backend 통신을 위해 미리 설치

 
1. store/store.js
기본 post 리스트를 생성하고, 포스트 추가/삭제기능을 구현해놓는다. 기능은 추후 Backend와의 연동을 위해 actions에 구현해놓습니다.

import { createStore } from 'vuex';
// import axios from 'axios';


export const store = createStore({
// export default createStore({
  state:{
    posts: [
      {_id: '1', title: 'apple', body: 'Im apple', creator: 'apple'},
      {_id: '2', title: 'banana', body: 'Im banana', creator: 'banana'},
      {_id: '3', title: 'orange', body: 'Im orange', creator: 'orange'}
    ]
  },
  mutations:{
    updateState(state, payload) {
      Object.keys(payload).forEach((key) => {
        state[key] = payload[key];        
      });
    },
    DELETEPOST(state, post_id){            
      const delete_index = state.posts.findIndex(p=> p._id === post_id);      // 삭제할 포스트의 index 찾기
      state.posts.splice(delete_index,1);      
    },
  },
  actions:{     
    addPost(context, newPost){      
      const id= context.state.posts.length+1;      
      const post={
        _id: id,
        title: newPost.title,
        body: newPost.body,
        creator: newPost.title
      }
      context.commit("updateState", {
        posts: [...context.state.posts, post],
      })   
      // console.log(context.state.user)        
    },
    deletePost(context, post_id){
      context.commit("DELETEPOST", post_id);
    }       
  }
});

 
2. main.js 수정

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { store } from './store/store.js'  // 추가

createApp(App)
.use(router)
.use(store)		// 추가
.mount('#app')

 
3. components/ListofPost.vue수정

<template>
  <ul>
    <li v-for="post in posts.slice().reverse()" :key=post>
      <EachofPost 
        :id=post._id
        :titleOfPost=post.title 
        :bodyOfPost=post.body 
        :creator=post.creator />
    </li>
  </ul>
</template>
<script>
import EachofPost from './EachofPost.vue'

export default {
  name: 'ListofPost',
  components: {
    EachofPost,
  },
  
// 아래 추가 부분
  computed:{
    posts(){
      return this.$store.state.posts;
    }
  }
// 여기까지 추가, 아래는 주석 or 삭제

//   data(){
//     return {
//       posts: [
//         {
//           _id:1,
//           title: 'myPost',
//           body: 'My Post 1',
//           creator: 'apple'
//         },
//         {
//           _id:2,
//           title: 'myPost2',
//           body: 'My Post 2',
//           creator: 'banana'
//         }
//       ]
//     }
//   }
// }
  
}
</script>

<style>
ul{
  padding-left:0;
}
li{
  list-style: none;
}
</style>

 
4. components/AddPost.vue
포스트를 추가하는 기능을 구현합니다. 편의상 작성자는 title과 동일하게 가져가겠습니다.

<template>  
  <div class="new_post">
    <div class="new_post_header">
      <input type="text" name="title" v-model="title" class="title"/>
      <input type="submit" value="Add" @click="addPost" class="submit">
    </div>
    <div class="new_post_body">
      <textarea class="contents" name="body" v-model="body" />
    </div>  
  </div>  
</template>

<script>
export default {  
  data(){
    return {
      title:'제목',
      body:'내용',
    };
  },
  methods:{
    addPost(){
      this.$store.dispatch('addPost',{
        title: this.title,
        body: this.body,
        creator: this.title,
      })
    }
  }
}
</script>

<style>

.new_post{
background-color:#E0EDFB;
/* margin: 5px auto; */
border: 1px solid grey;
border-radius: 10px;  
box-sizing: content-box;
width:100%;
}
.new_post .new_post_header{    
display: flex;
padding: 5px;
}
.new_post .new_post_header .title{
display:inline-block;
text-align: left;
font-size: 24px;  
height: 50px;
width:100%;
color: black;
border: 0px solid black;
background-color:transparent;
}
.new_post .new_post_header .submit{
position: relative;  
font-size:20px;  
color:dimgrey;
background-color: transparent;
border-color: transparent;
}

.new_post .new_post_body{
position: relative;  
font-size: 16px;
color:cadetblue;
text-align: left;

/* padding: 5px; */
}

.new_post .new_post_body .contents{  
position: relative;    
border: 0px;    
left:0;
right:0;
margin: 5px;
width:99%;  
box-sizing: border-box;
background-color:transparent;
}
</style>

 
5. components/EachofPost.vue
포스트를 삭제하는 기능을 구현합니다.

<template>  
  <div class="post">
    <div class="post_header">
      <div class="title">
        {{ id }}
        {{ titleOfPost }}                      
      </div>  
      <button class="close" @click="removeItem">x</button>  
    </div>    
    <div class="post_body">
      {{ bodyOfPost }}
      <div class="creator">
        by {{  creator }}        
      </div>
    </div>  
    
  </div> 
</template>

<script>
export default {
  props:['id','titleOfPost', 'bodyOfPost','creator'],   
  // 아래 추가
  methods:{
    removeItem(){
      this.$store.dispatch("deletePost", this.id)
    }
  }
  // 여기까지 추가부분
}

</script>

<style>

.post{
  /* background-color:blanchedalmond; */
  margin: 5px auto;
  border: 1px solid grey;
  border-radius: 10px;  
  width:100%;
  min-height:100px;
}
.post .post_header{  
  display: flex;
  padding: 5px;
}
.post .post_header .title{
  display:inline-block;
  text-align: left;
  font-size: 24px;  
  height: 50px;
  width:100%;
  color: black;
  border: 0px solid;
  /* background-color:blueviolet; */
}
.post .post_header .close{
  position: relative;  
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}
.post .post_header .edit{
  position: relative;  
  box-sizing: border-box;
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}

.post .post_body{
  font-size: 16px;
  color:cadetblue;
  text-align: left;
  padding: 5px;
  border: 0px solid;
  /* background-color: orange; */
  width: 100%;
  min-height: 25px;
}
.post .post_body .creator{
  text-align: right;
  left:0;
  right: 0;
  top: 0;
  bottom: 0;
  margin-right: 10px;
  margin-bottom: 5px;
  /* right: 10px; */
}
.post .post_body textarea{
  width: 95%;
  min-height:24px;
  border: 0px;
  font-size: 16px;
  color:cadetblue;
}
</style>
반응형
반응형

1. router/index.js 수정

불필요한 라우팅을 삭제합니다.

import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/HomeView.vue';


const routes = [
  {
    path:'/',
    name: 'Home',
    component: Home
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router;

 

2. 불필요한 view도 삭제합니다.

views/AboutView.vue --삭제

 

3. App.vue 수정

<template>    
  <router-view />
</template>

<script>
 
</script>

<style>
 
</style>

 

4. views/HomeView.vue 수정

Todo 대신 Post라는 이름으로 진행합니다. 홈뷰에서는 전체 Post를 가져오는 컴포넌트와, 신규 Post를 작성하는 컴포넌트를 둘 예정입니다.

<template> 
  <h1 class="appname">My Todo App</h1>  
  <AddPost />
  <ListofPost />
</template>

<script>
import ListofPost from '../components/ListofPost.vue'
import AddPost from '../components/AddPost.vue'

export default {
  name: 'HomeView',
  components: {
    ListofPost,
    AddPost,
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;  
  color: #2c3e50;
  margin-top: 60px;  
}
.appname {
  color:#373738;
  text-align: center;
}
.addbutton{
  margin: auto;
  display:flex;
  align-items:center;
  justify-content: center;
  width:50px;
  height:50px;  
  border-radius: 50%;
  background-color:blue ; 
  font-size: 36px;
  font-weight: bold;
  color:beige;
}
.logincheck{  
  text-align: right;  
  margin-right: 15px;
}
</style>

 

5. components/ListofPost.vue

data로는 개별 포스트의 id, 타이틀, 내용, 작성자 정보를 갖고 있습니다. (나중에 store에서 가져올 예정)

각각의 포스트를 돌면서 EachofPost 컴포넌트에 값을 넣어줍니다.

각각의 EachofPost에는 해당 값이 Props로 전달됩니다.

<template>
  <ul>
    <li v-for="post in posts.slice().reverse()" :key=post>
      <EachofPost 
        :id=post._id
        :titleOfPost=post.title 
        :bodyOfPost=post.body 
        :creator=post.creator />
    </li>
  </ul>
</template>
<script>
import EachofPost from './EachofPost.vue'

export default {
  name: 'ListofPost',
  components: {
    EachofPost,
  },
  data(){
    return {
      posts: [
        {
          _id:1,
          title: 'myPost',
          body: 'My Post 1',
          creator: 'apple'
        },
        {
          _id:2,
          title: 'myPost2',
          body: 'My Post 2',
          creator: 'banana'
        }
      ]
    }
  }
}
</script>

<style>
ul{
  padding-left:0;
}
li{
  list-style: none;
}
</style>

 

6. components/EachofPost.vue

Props로 받아오는 속성들을 정의해줍니다. 그리고 화면에 출력합니다.

<template>  
  <div class="post">
    <div class="post_header">
      <div class="title">
        {{ id }}
        {{ titleOfPost }}                      
      </div>  
    </div>    
    <div class="post_body">
      {{ bodyOfPost }}
      <div class="creator">
        by {{  creator }}        
      </div>
    </div>  
    
  </div> 
</template>

<script>
export default {
  props:['id','titleOfPost', 'bodyOfPost','creator'],    
}
</script>

<style>

.post{
  /* background-color:blanchedalmond; */
  margin: 5px auto;
  border: 1px solid grey;
  border-radius: 10px;  
  width:100%;
  min-height:100px;
}
.post .post_header{  
  display: flex;
  padding: 5px;
}
.post .post_header .title{
  display:inline-block;
  text-align: left;
  font-size: 24px;  
  height: 50px;
  width:100%;
  color: black;
  border: 0px solid;
  /* background-color:blueviolet; */
}
.post .post_header .close{
  position: relative;  
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}
.post .post_header .edit{
  position: relative;  
  box-sizing: border-box;
  font-size:20px;  
  width:20px;
  height:20px;  
  color:dimgrey;
  background-color: transparent;
  border-color: transparent;
}

.post .post_body{
  font-size: 16px;
  color:cadetblue;
  text-align: left;
  padding: 5px;
  border: 0px solid;
  /* background-color: orange; */
  width: 100%;
  min-height: 25px;
}
.post .post_body .creator{
  text-align: right;
  left:0;
  right: 0;
  top: 0;
  bottom: 0;
  margin-right: 10px;
  margin-bottom: 5px;
  /* right: 10px; */
}
.post .post_body textarea{
  width: 95%;
  min-height:24px;
  border: 0px;
  font-size: 16px;
  color:cadetblue;
}
</style>

 

7. components/AddPost.vue

포스트 추가하는 부분을 모양만 만들어놓습니다.

<template>  
  <div class="new_post">
    <div class="new_post_header">
      <input type="text" name="title" v-model="title" class="title"/>
      <input type="submit" value="Add" class="submit">
    </div>
    <div class="new_post_body">
      <textarea class="contents" name="body" v-model="body" />
    </div>  
  </div>  
</template>

<script>
export default {  
data(){
  return {
    title:'제목',
    body:'내용',
  };
}
}
</script>

<style>

.new_post{
background-color:#E0EDFB;
/* margin: 5px auto; */
border: 1px solid grey;
border-radius: 10px;  
box-sizing: content-box;
width:100%;
}
.new_post .new_post_header{    
display: flex;
padding: 5px;
}
.new_post .new_post_header .title{
display:inline-block;
text-align: left;
font-size: 24px;  
height: 50px;
width:100%;
color: black;
border: 0px solid black;
background-color:transparent;
}
.new_post .new_post_header .submit{
position: relative;  
font-size:20px;  
color:dimgrey;
background-color: transparent;
border-color: transparent;
}

.new_post .new_post_body{
position: relative;  
font-size: 16px;
color:cadetblue;
text-align: left;

/* padding: 5px; */
}

.new_post .new_post_body .contents{  
position: relative;    
border: 0px;    
left:0;
right:0;
margin: 5px;
width:99%;  
box-sizing: border-box;
background-color:transparent;
}
</style>

 

<결과>

 

반응형
반응형

1. Vue 프로젝트 생성

vue_front 폴더로 이동하여 vue_프로젝트를 생성합니다.

cd vue_front

npm i -g @vue/cli // vuejs 프로젝트가 처음일 경우 글로벌로 vue cli를 설치해줍니다.

vue create . // 현재의 폴더에 vue 프로젝트를 생성합니다.

 

2. Vue-router 설치

vue-router를 설치하면 프로젝트 구조가 조금 달라집니다. 따라서 vue-router를 먼저 설치하고 진행합니다.

vue add router

 

3. vuex / axios 설치 및 port 설정

npm i vuex axios
npm i nodemon --save-dev

 

package.json의 scripts 부분을 수정하여 4000번 포트에서 서비스하도록 수정합니다.

 "scripts": {
    "serve": "vue-cli-service serve --port 4000",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },

 

반응형

+ Recent posts