https://github.com/ssi02014/blog
๐ป MERN ์คํ์ผ๋ก ๋ง๋ค์ด๋ณด๋ ๋๋ง์ ๋ธ๋ก๊ทธ
https://github.com/ssi02014/blog
Last synced: 8 months ago
JSON representation
๐ป MERN ์คํ์ผ๋ก ๋ง๋ค์ด๋ณด๋ ๋๋ง์ ๋ธ๋ก๊ทธ
- Host: GitHub
- URL: https://github.com/ssi02014/blog
- Owner: ssi02014
- Created: 2021-02-18T13:23:10.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2021-06-16T16:15:24.000Z (over 4 years ago)
- Last Synced: 2024-12-30T06:45:35.740Z (9 months ago)
- Language: JavaScript
- Homepage:
- Size: 1.73 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ๐ป Blog
### MERN(Mongodb, Express, React, Node) Stack์ผ๋ก ๋ง๋ ๋๋ง์ ๋ธ๋ก๊ทธ๐
## ๐ Blog App ์ข ์์ฑ ๋ค์ด๋ก๋
1. **" npm i or yarn install "** ์ server ํด๋์์ ์ ๋ ฅํด์ฃผ์ธ์. **(๋ฐฑ์๋ ์ข ์์ฑ ๋ค์ด๋ฐ๊ธฐ)**
2. **" npm i or yarn install "** ์ client ํด๋์์ ์ ๋ ฅํด์ฃผ์ธ์. **(ํ๋ก ํธ์๋ ์ข ์์ฑ ๋ค์ด๋ฐ๊ธฐ)**
3. **.env** ํ์ผ์ server ํด๋ ๋ด๋ถ์ ๋ง๋ค์ด์ฃผ์ ์ผ ๋ฉ๋๋ค. **(๋ฐ์ ์ฐธ๊ณ )๐**
4. **.env** ํ์ผ์ client ํด๋ ๋ด๋ถ์ ๋ง๋ค์ด์ฃผ์ ์ผ ๋ฉ๋๋ค. **(๋ฐ์ ์ฐธ๊ณ )๐**
## ๐ Main Development Stack
### ๐จ๐ปโ๐ป Backend
1. Node.js
2. Express
3. MongoDB
### ๐จ๐ปโ๐ป Frontend
1. React
2. Redux, Redux-Saga
3. Infinite Scroll(Intersection Observer)
4. Ckeditor5
5. Styling: Bootstrap4(reactstrap), SCSS
### ๐จ๐ปโ๐ป Cloud
1. Aws EC2
2. Aws S3
## ๐ ์ปค๋ฐ ๋ฉ์์ง
- Add: ํน์ ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋๋ฅผ ๊ตฌํํ์์ ๋
- Modify: ์ด๋ฏธ ๊ตฌํ๋ ๊ธฐ๋ฅ์ ์์ ํ๋๋ฐ, ๊ธฐ๋ฅ์ ํฅ์์ด ์ด๋ฃจ์ด์ก์ ๋
- Close(Closes, Closed): ์ผ๋ฐ์ ์ธ ๊ฐ๋ฐ ์ด์๋ฅผ ์๋ฃํ์ ๋
- Refactor: ๋ฆฌํฉํ ๋ง ํ์ ๋(๊ธฐ๋ฅ ํฅ์์ ์๋๋ค. ์ค๋ณต ์ฝ๋ ์ ๊ฑฐ ๋ฐ ๋ณ์ & ํจ์ ๋ฑ ์ฝ๋ ๋์์ธ ๋ณ๊ฒฝ)
- Delete: ๋ถํ์ํ ์ฝ๋ ์ ๊ฑฐ
- Fix(Fixex, Fixed): ๋ฒ๊ทธ ํฝ์ค๋ ํซ ํฝ์ค ์ด์๋ฅผ ์๋ฃํ์ ๋
- Merge: Branch๋ฅผ merge ํ์ ๋
- Conflict: ์ถฉ๋์ ํด๊ฒฐํ์ ๋
- Docs: README.md์ ๊ฐ์ ๋ฌธ์ ์์ ํ์ ๋
## ๐ Projects Board

## ๐ Server: ํ์ต ๋ด์ฉ ๋ฐ ์ด์
### ๐ 1. Server์์ Babel ํ๊ฒฝ ์ค์
- **Babel**: ์๋ฐ์คํฌ๋ฆฝํธ ์ปดํ์ผ๋ฌ, ์ต์ ๋ฒ์ ์ ์๋ฐ์คํฌ๋ฆฝํธ ๋ฌธ๋ฒ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ดํดํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ดํดํ ์ ์๋ ๋ฌธ๋ฒ์ผ๋ก ๋ณํํ๋ค.
```javascript
// ./server/.babelrc
// ์๋ฒ์์ ๋ฐ๋ฒจ ํ๊ฒฝ ์ค์
{
"presets": ["@babel/preset-env"]
}
```
### ๐ 2. Mongoose ์ค๋ฅ ๋ฐ ํด๊ฒฐ์ฑ
1. **mongoose version 5.11.16์ผ๋ก ์ธํ ์ค๋ฅ๐ฅ**
```
- DeprecationWarning: Listening to events on the Db class has been deprecated and will be removed in the next major version.
- โ npm install mongoose@5.11.15 (๋ฒ์ ๋ฎ์ถ๊ธฐ)
```
## ๐ 3. sever: .env
```js
//๋ณธ์ธ์ mongoDB cluster ์์ฑ ์์ ๋ง๋ connection URI๋ฅผ ๋ฃ์ด์ฃผ์ธ์.
MONGO_URI = "mongodb+srv://:@blog.io9gx.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"PORT="7000" //server๋ฅผ ์คํ์ํฌ port
JWT_SECRET = "Minjae" //์๋ฌด ๋ฌธ์์ด์ด๋ ๋ฃ์ผ์ ๋ ์๊ด์์ต๋๋ค.
```## ๐ 4. client: .env
```js
REACT_APP_BASIC_SERVER_URL = "http://localhost:7000"
REACT_APP_BASIC_IMAGE_URL = "https://<๋ณธ์ธs3๋ฒํท>.s3.ap-northeast-2.amazonaws.com/<๋ฒํท ์ ๊ฐ์ฒด ํด๋๋ช (ex.upload)>/<์ด๋ฏธ์งURL>"
```## ๐ 5. .env ์ฃผ์ ์ฌํญ
```
1. React์์ ์ฌ์ฉํ ๋๋ ์ ๋์ฌ๋ก REACT_APP_ ๋ฃ์ด์ผ๋๋ค.
2. ๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ํ๋ ค๋ฉด ์๋ฒ๋ฅผ ๋ค์ ์์ํด์ผ ๋๋ค.
3. src ํด๋๊ฐ ์๋ root ํด๋ ์ฆ(package.json๊ณผ ๋์ผํ ์์น)์ ์์ด์ผ ํ๋ค.
```
### ./confing/index.js๋ก .env ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
```javascript
import dotenv from 'dotenv';dotenv.config();
export default {
MONGO_URI: process.env.MONGO_URI,
JWT_SECRET: process.env.JWT_SECRET,
PORT: process.env.PORT,
}
```
## ๐ Client: ํ์ต ๋ด์ฉ ๋ฐ ์ด์
### ๐ 1. node-sass ์ต์ ๋ฒ์ ์ค๋ฅ ํด๊ฒฐ์ฑ
```
- โ npm install node-sass@4.14.1
```
### ๐ 2. node-sass ์ต์ ๋ฒ์ ์ค๋ฅ ํด๊ฒฐ์ฑ
```
- ๐ฅError: Could not find router reducer in state tree, it must be mounted under "router"
- ์ด์ : connected-react-router๊ฐ ์์ง history v5๋ฅผ ์ ๋๋ก ๋ฐ์ํ์ง ๋ชปํด ๋ฐ์ํ๋ ๋ฌธ์ .- โ npm i history@4.7.2 (๋ฒ์ ๋ฎ์ถ๊ธฐ)
```
### ๐ 3. reactstrap ์ฌ์ฉ๋ฒ
### reactstarp: https://reactstrap.github.io/
```javascript
//Header
import { Row, Col } from 'reactstrap';
//class์ text-center, m-auto ๊ฐ์ด ์ถ๊ฐํด์ ์ ์ฉ์ํจ๋ค.
Read Our Blog
This is Minjae's Side Project Blog
```
### ๐ 4. font-awesome ์ฌ์ฉ๋ฒ
```javascript
1. yarn add or npm i @fortawesome/fontawesome-svg-core
2. yarn add or npm i @fortawesome/free-solid-svg-icons
3. yarn add or npm i @fortawesome/react-fontawesomeimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMouse } from '@fortawesome/free-solid-svg-icons';
```
### ๐ 4. CKEditor5 Setting
### CKEditor:https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/react.html
```javascript
1. npm run eject (์ด์ ๋ชจ๋ ๋ณ๊ฒฝ ์ฌํญ commit ์๋ฃ๋์ผํจ)2. yarn add @babel/plugin-transform-react-jsx @babel/plugin-transform-react-jsx-self
3. plugin Install
- yarn add @ckeditor/ckeditor5-adapter-ckfinder @ckeditor/ckeditor5-alignment @ckeditor/ckeditor5-autoformat @ckeditor/ckeditor5-basic-styles @ckeditor/ckeditor5-block-quote @ckeditor/ckeditor5-build-balloon @ckeditor/ckeditor5-build-classic @ckeditor/ckeditor5-build-inline @ckeditor/ckeditor5-dev-utils @ckeditor/ckeditor5-dev-webpack-plugin @ckeditor/ckeditor5-easy-image @ckeditor/ckeditor5-editor-balloon @ckeditor/ckeditor5-editor-classic @ckeditor/ckeditor5-essentials @ckeditor/ckeditor5-font @ckeditor/ckeditor5-heading @ckeditor/ckeditor5-image @ckeditor/ckeditor5-indent @ckeditor/ckeditor5-link @ckeditor/ckeditor5-list @ckeditor/ckeditor5-media-embed @ckeditor/ckeditor5-paragraph @ckeditor/ckedito5-paste-from-office @ckeditor/ckeditor5-react @ckeditor/ckeditor5-table @ckeditor/ckeditor5-theme-lark @ckeditor/ckeditor5-typing @ckeditor/ckeditor5-upload4. webpack.config.js Setting
- webpack: ์น์ ๊ตฌ์ฑํ๋ .js, .css, .jpg, .png ๊ฐ์ static assets์ ํ๋์ ํ์ผ๋ก ํฉ์ณ์ค//webpack.config.js 34๋ฒ์งธ ์ค์ ์์ค ์ถ๊ฐ
const { styles } = require('@ckeditor/ckeditor5-dev-utils');
const CKEditorWebpackPlugin = require("@ckeditor/ckeditor5-dev-webpack-plugin");//webpack.config.js 467๋ฒ์งธ ์ค์ ์์ค ์ถ๊ฐ (inputSource ๊ฒ์)
{
test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
use: [ 'raw-loader' ]
},
{
test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
use: [
{
loader: 'style-loader',
options: {
injectType: 'singletonStyleTag',
attributes: {
'data-cke': true
}
}
},
{
loader: 'postcss-loader',
options: styles.getPostCssConfig( {
themeImporter: {
themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
},
minify: true
} )
}
]
},//webpack.config.js 505๋ฒ์งธ ์ค์ ์์ค ์ถ๊ฐ (cssRegex ๊ฒ์)
{
test: cssRegex,
exclude: [
cssModuleRegex,
/ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
],
}//webpack.config.js 527๋ฒ์งธ ์ค์ ์์ค ์ถ๊ฐ (cssModuleRegex ๊ฒ์)
{
test: cssModuleRegex,
exclude: [
/ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
],
}
//webpack.config.js 585๋ฒ์งธ ์ค์ ์์ค ์ถ๊ฐ
{
loader: require.resolve( 'file-loader' ),
// Exclude `js` files to keep the "css" loader working as it injects
// its runtime that would otherwise be processed through the "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpack's internal loaders.
exclude: [
/\.(js|mjs|jsx|ts|tsx)$/,
/\.html$/,
/\.json$/,
/ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
/ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/
],
options: {
name: 'static/media/[name].[hash:8].[ext]',
}
}5. EdiotrConfig.js ํ์ผ ์์ฑ ๋ฐ ์์ค ์ถ๊ฐ
- 'https://www.ssaple.net/posts/5ef0b5c69e4ac10611c45a57' ์ฐธ๊ณ6. UploadAdaper.js ํ์ผ ์์ฑ ๋ฐ ์์ค ์์
- 'https://ckeditor.com/docs/ckeditor5/latest/framework/guides/deep-dive/upload-adapter.html#the-complete-implementation' ์ฐธ๊ณ_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open('POST', `http://localhost:7000/api/post/image`, true ); //์ฌ๊ธฐ ์์
xhr.responseType = 'json';
}// funtcion MyCustomUploadAdapterPlugin -> My init(ํ์ดํ ํจ์)
const Myinit = ( editor ) => { //๊ทธ๋ฅ ํจ์์์ ํ์ดํ ํจ์๋ก ์์
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new MyUploadAdapter( loader );
};
}
export default Myinit;
```### ๐ 5. CKEditor5 ์ฌ์ฉ ๋ฐฉ๋ฒ ๋ณ๊ฒฝ
```javascript
//๋ณ๊ฒฝ ์
import CKEditor from '@ckeditor/ckeditor5-react';
//๋ณ๊ฒฝ ํ
import { CKEditor } from '@ckeditor/ckeditor5-react';
```
```javascript
//๋ณ๊ฒฝ ์
//๋ณ๊ฒฝ ํ
```
### ๐ 6. CKEditor5 Styling
```scss
//CKEditor5 Setting
.ck {
.ck-editor {
min-width: 100%;
}
}.ck-editor__editable {
max-height: 80rem;
min-height: 40rem;
min-width: 100%;
}.ck-editor__editable_inline {
max-height: 80rem;
min-height: 40rem;
min-width: 100%;
}
```### ๐ 7. ProtectRoute ๊ตฌํ
```javascript
export const EditProtectedRoute = ({component: Component, ...rest}) => {
const {userId} = useSelector(state => state.auth);
const {creatorId} = useSelector(state => state.post);
return (
{
if(userId === creatorId) {
return
} else {
return (
)
}
}}
/>
)
}
```
### ๐ 8. comment input value reset
```javascript
1. useRef ์ฌ์ฉ
const resetValue = useRef(null);2. input์๋ค Ref ๋ฃ๊ธฐ
3. resetValue.current.value ์ด๊ธฐํ ๋ฐ formValue()์ด๊ธฐํ
const onSubmit = async e => {(...)
resetValue.current.value = '';
setformValue("");
}
```
## ๐ Redux
### ๐ 1. Redux-Saga Effect
```
1. put: ํน์ ์ก์ ์ dispatch ํ๋ค.
2. takeEvery: ๋ค์ด์ค๋ ๋ชจ๋ ์ก์ ์ ๋ํด ํน์ ์์ ์ ์ฒ๋ฆฌํด์ค๋ค.
3. takeLatest: ๊ธฐ์กด์ ์งํ ์ค์ด๋ ์์ ์ด ์๋ค๋ฉด ์ทจ์ํ๊ณ , ๊ฐ์ฅ ๋ง์ง๋ง์ผ๋ก ์คํ๋ ์์ ๋ง ์ํํ๋ค.
4. all: ์ฌ๋ฌ ์ฌ๊ฐ๋ฅผ ํฉ์ณ ์ฃผ๋ ์ญํ ์ ํ๋ค.
5. fork: ํจ์๋ฅผ ์คํ์์ผ์ฃผ๋ ์ดํํธ, ํ์ง๋ง ๋น๋๊ธฐ ์คํ์ ํ๋ค.
6. call: promise๋ฅผ ๋ฐํํ๋ ํจ์๋ฅผ ํธ์ถํ๊ณ , ๊ฒฐ๊ณผ๊ฐ ๋ฐํ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค(์ฆ, ๋๊ธฐ๋ก ์คํ)
```
### ๐ Redux, Redux-Saga Setting
```javascript
1. Connected React Router
- ๋ฆฌ๋์ค์์ history ๊ฐ์ฒด ๊ด๋ฆฌ๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ์ฃผ์์ฌํญ router ๋ฆฌ๋์๋ช ์ router๋ก ๊ณ ์ !
- history๋ history ๋ชจ๋์์ createBrowserHistory๋ก ๋ฐ์์ฌ ์ ์๋ค.
- ์ฐธ๊ณ : https://hokeydokey.tistory.com/m/74?category=7831092. createSagaMiddleware
- redux-saga ๋ฏธ๋ค์จ์ด๋ฅผ Redux Subspace ๋ฏธ๋ค์จ์ด๋ก ์์ฑํ๋ ๊ธฐ๋ฅ
- const sagaMiddleware = createSagaMiddleware();3. redux์ store ์์ฑ, ๋ฆฌ๋์์ ๋ฏธ๋ค์จ์ด ์ฌ์ฉ
- const store = createStore(reducers, applyMiddleware(sagaMiddleware)4. ํญ์ store ๋ณด๋ค ์๋์์ ์ฝ๋๊ฐ ์์ฑ๋์ด์ผ ํ๋ค. rootSaga๋ฅผ ์ธ์๋ก ๋๋ค.
- sagaMiddleware.run(rootSaga)6.
ReactDOM.render(
,
document.querSelector('#root');
)
```### ๐ 3. Redux-Saga Process
```javascript
1. ๐ ์ก์ ํ์ ์ ์ ์์
export const LOGIN_REQUEST = "LOGIN_REQUEST";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAILURE = "LOGIN_FAILURE";2. ๐ ์ด๊ธฐ ๊ฐ ๋ฐ Reducer ํจ์ ์์ฑ
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
isLoading: false,
...
}const authReducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_REQUEST:
return {
...state,
errorMsg: "",
isLoading: true,
}
default:
return state
}export default authReducer;
3. ๐ rootReducer์ ํตํฉ
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import authReducer from './authReducer';
import postReducer from './PostReducer';
import commentReducer from './commentReducer';const createRootReducer = (history) => combineReducers({
router: connectRouter(history),
auth: authReducer,
post: postReducer,
comment: commentReducer,
})export default createRootReducer;
4. ๐ Saga ์์ฑ
function* logout(action) {
try {
yield put({
type: LOGOUT_SUCCESS,
});
} catch (e) {
yield put({
type: LOGOUT_FAILURE,
});
console.log(e);
}
}function* watchLogout() {
yield takeEvery(LOGOUT_REQUEST, logout);
}
//authSaga() ์ฌ๋ฌ Saga ํตํฉ
export default function* authSaga() {
yield all([
fork(watchLoginUser),
fork(watchLogout),
fork(watchLoadingUser),
]);
}5. ๐ rootSaga์ ํตํฉ
import { all, fork } from 'redux-saga/effects';
import postSaga from './postSaga';
import authSaga from './authSaga';
import commentSaga from './commentSaga';export default function* rootSaga() {
yield all([
fork(authSaga),
fork(postSaga),
fork(commentSaga),
]);
}6. ๐ dispatch๋ก ์ก์ ๋ฐ์
const handleToggle = () => {
dispatch({
type: CLEAR_ERROR_REQUEST,
})
setModal(!modal);
};
```
## ๐ useParams()
- react-router-dom์ useParams Hook์ ์ฌ์ฉํ๋ฉด ํ๋์ ์ปดํฌ๋ํธ์์ ์ ๋ฌ๋ฐ์ URL Parameter๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
```js
import { useParams } from 'react-router';let { categoryName } = useParams();
```
- useParams()๋ฅผ ์ฌ์ฉํ์ง ์๋๋ผ๋ `match` ๊ฐ์ฒด๋ฅผ ํตํด์๋ URL Parameter๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
```js
const categoryName = props.match.params.categoryName;
```
## ๐ Infinite Scroll
- IntersectionObserver ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ Infinite Scroll์ ๊ตฌํ
- useOnScreen์ด๋ผ๋ Custom hooks ๊ตฌํ
```js
const skipNumberRef = useRef(0);
const postCountRef = useRef(0);
const endMsg = useRef(false);postCountRef.current = postCount - 6;
const useOnScreen = (options) => {
const lastPostElementRef = useRef();useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setVisible(entry.isIntersecting); //true or falseif (entry.isIntersecting) {
let remainPostCount = postCountRef.current - skipNumberRef.current;if (remainPostCount >= 0) {
dispatch({
type: POSTS_LOADING_REQUEST,
payload: skipNumberRef.current + 6,
});
skipNumberRef.current += 6;
} else {
endMsg.current = true;
console.log(endMsg.current);
}
}
}, options);if (lastPostElementRef.current) {
observer.observe(lastPostElementRef.current);
}const LastElementReturnFunc = () => {
if (lastPostElementRef.current) {
observer.unobserve(lastPostElementRef.current);
}
};return LastElementReturnFunc;
}, [lastPostElementRef, options]);return lastPostElementRef;
};const lastPostElementRef = useOnScreen({
threshold: "0.5",
});
``````js
return (
<>
...
{loading && GrowingSpinner}
{loading ? (
""
) : endMsg ? (
๋ ์ด์์ ํฌ์คํธ๋ ์์ต๋๋ค.
) : (
""
)}
>
)
```
## ๐ console.log ์ ๊ฑฐ
- ํจํค์ง ๋ค์ด๋ก๋
```
npm i babel-plugin-transform-remove-console
```
- webpack.config.js์์ loaderMap ๋ฐ์ `["transform-remove-console", { exclude: ["error", "warn"] }]`์ถ๊ฐ
```js
plugins: [
[
require.resolve("babel-plugin-named-asset-import"),
{
loaderMap: {
svg: {
ReactComponent:
"@svgr/webpack?-svgo,+titleProp,+ref![path]",
},
},
},
],
["transform-remove-console", { exclude: ["error", "warn"] }],
isEnvDevelopment &&
shouldUseReactRefresh &&
require.resolve("react-refresh/babel"),
].filter(Boolean),
```