├── .gitignore ├── .idea ├── .gitignore ├── encodings.xml ├── jsLibraryMappings.xml ├── markdown-navigator-enh.xml ├── markdown-navigator.xml ├── misc.xml ├── modules.xml ├── redux-vs-mobx.iml └── vcs.xml ├── 1.redux ├── index.js ├── index2.js ├── package-lock.json └── package.json ├── 2.redux ├── actions │ ├── post.js │ └── user.js ├── index2.js ├── package-lock.json ├── package.json └── reducers │ ├── index.js │ ├── post.js │ └── user.js ├── 3.react-redux ├── App.jsx ├── actions │ ├── post.js │ └── user.js ├── client.jsx ├── index.html ├── package-lock.json ├── package.json ├── reducers │ ├── index.js │ ├── post.js │ └── user.js ├── store.js └── webpack.config.js ├── 4.react-redux-immer ├── App.jsx ├── actions │ ├── post.js │ └── user.js ├── client.jsx ├── index.html ├── package-lock.json ├── package.json ├── reducers │ ├── index.js │ ├── post.js │ └── user.js ├── store.js └── webpack.config.js ├── 5.react-redux-toolkit ├── App.jsx ├── actions │ ├── post.js │ └── user.js ├── client.jsx ├── index.html ├── package-lock.json ├── package.json ├── reducers │ ├── index.js │ ├── postSlice.js │ └── userSlice.js ├── store.js └── webpack.config.js ├── 6.mobx ├── index.js ├── package-lock.json └── package.json ├── 7.mobx ├── index.js ├── package-lock.json └── package.json ├── 8.mobx-react ├── App.jsx ├── Context.jsx ├── client.jsx ├── index.html ├── package-lock.json ├── package.json ├── store.js └── webpack.config.js ├── 9.mobx-react ├── App.jsx ├── Context.jsx ├── client.jsx ├── index.html ├── package-lock.json ├── package.json ├── store.js ├── useStore.js └── webpack.config.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional REPL history 59 | .node_repl_history 60 | 61 | # Output of 'npm pack' 62 | *.tgz 63 | 64 | # Yarn Integrity file 65 | .yarn-integrity 66 | 67 | # dotenv environment variables file 68 | .env 69 | .env.test 70 | 71 | # parcel-bundler cache (https://parceljs.org/) 72 | .cache 73 | 74 | # next.js build output 75 | .next 76 | 77 | # nuxt.js build output 78 | .nuxt 79 | 80 | # vuepress build output 81 | .vuepress/dist 82 | 83 | # Serverless directories 84 | .serverless/ 85 | 86 | # FuseBox cache 87 | .fusebox/ 88 | 89 | # DynamoDB Local files 90 | .dynamodb/ 91 | 92 | ### JetBrains template 93 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 94 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 95 | 96 | # User-specific stuff 97 | .idea/**/workspace.xml 98 | .idea/**/tasks.xml 99 | .idea/**/usage.statistics.xml 100 | .idea/**/dictionaries 101 | .idea/**/shelf 102 | 103 | # Generated files 104 | .idea/**/contentModel.xml 105 | 106 | # Sensitive or high-churn files 107 | .idea/**/dataSources/ 108 | .idea/**/dataSources.ids 109 | .idea/**/dataSources.local.xml 110 | .idea/**/sqlDataSources.xml 111 | .idea/**/dynamic.xml 112 | .idea/**/uiDesigner.xml 113 | .idea/**/dbnavigator.xml 114 | 115 | # Gradle 116 | .idea/**/gradle.xml 117 | .idea/**/libraries 118 | 119 | # Gradle and Maven with auto-import 120 | # When using Gradle or Maven with auto-import, you should exclude module files, 121 | # since they will be recreated, and may cause churn. Uncomment if using 122 | # auto-import. 123 | # .idea/modules.xml 124 | # .idea/*.iml 125 | # .idea/modules 126 | # *.iml 127 | # *.ipr 128 | 129 | # CMake 130 | cmake-build-*/ 131 | 132 | # Mongo Explorer plugin 133 | .idea/**/mongoSettings.xml 134 | 135 | # File-based project format 136 | *.iws 137 | 138 | # IntelliJ 139 | out/ 140 | 141 | # mpeltonen/sbt-idea plugin 142 | .idea_modules/ 143 | 144 | # JIRA plugin 145 | atlassian-ide-plugin.xml 146 | 147 | # Cursive Clojure plugin 148 | .idea/replstate.xml 149 | 150 | # Crashlytics plugin (for Android Studio and IntelliJ) 151 | com_crashlytics_export_strings.xml 152 | crashlytics.properties 153 | crashlytics-build.properties 154 | fabric.properties 155 | 156 | # Editor-based Rest Client 157 | .idea/httpRequests 158 | 159 | # Android studio 3.1+ serialized cache file 160 | .idea/caches/build_file_checksums.ser 161 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/markdown-navigator-enh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 49 | 50 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/redux-vs-mobx.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /1.redux/index.js: -------------------------------------------------------------------------------- 1 | const { createStore } = require('redux'); 2 | 3 | const reducer = (prevState, action) => { // 새로운 state 만들어주기 4 | switch (action.type) { 5 | case 'CHANGE_COMP_A': 6 | return { 7 | ...prevState, 8 | compA: action.data, 9 | }; 10 | case 'CHANGE_COMP_B': 11 | return { 12 | ...prevState, 13 | compB: action.data, 14 | }; 15 | case 'CHANGE_COMP_C': 16 | return { 17 | ...prevState, 18 | compC: action.data, 19 | }; 20 | default: 21 | return prevState; 22 | } 23 | }; 24 | 25 | const initialState = { 26 | compA: 'a', 27 | compB: 12, 28 | compC: null, 29 | }; 30 | 31 | const store = createStore(reducer, initialState); 32 | store.subscribe(() => { // react-redux 안에 들어있어요. 33 | console.log('changed'); // 화면 바꿔주는 코드 여기서 34 | }); 35 | 36 | console.log('1st', store.getState()); 37 | 38 | const changeCompA = (data) => { 39 | return { // action 40 | type: 'CHANGE_COMP_A', 41 | data, 42 | }; 43 | }; 44 | 45 | // store.dispatch({ 46 | // type: 'CHANGE_COMP_A', 47 | // data: 'b', 48 | // }); 49 | store.dispatch(changeCompA('b')); 50 | 51 | console.log('2nd', store.getState()); 52 | -------------------------------------------------------------------------------- /1.redux/index2.js: -------------------------------------------------------------------------------- 1 | const { createStore } = require('redux'); 2 | 3 | const reducer = (prevState, action) => { // 새로운 state 만들어주기 4 | switch (action.type) { 5 | case 'LOG_IN': 6 | return { 7 | ...prevState, 8 | user: action.data, 9 | }; 10 | case 'LOG_OUT': 11 | return { 12 | ...prevState, 13 | user: null, 14 | }; 15 | case 'ADD_POST': 16 | return { 17 | posts: [...prevState.posts, action.data], 18 | }; 19 | default: 20 | return prevState; 21 | } 22 | }; 23 | 24 | const initialState = { 25 | user: null, 26 | isLoggingIn: true, 27 | posts: [], 28 | comments: [], 29 | favorites: [], 30 | history: [], 31 | likes: [], 32 | followers: [], 33 | }; 34 | 35 | const store = createStore(reducer, initialState); 36 | store.subscribe(() => { // react-redux 안에 들어있어요. 37 | console.log('changed'); // 화면 바꿔주는 코드 여기서 38 | }); 39 | 40 | console.log('1st', store.getState()); 41 | 42 | const logIn = (data) => { 43 | return { // action 44 | type: 'LOG_IN', 45 | data, 46 | }; 47 | }; 48 | 49 | const logOut = () => { 50 | return { // action 51 | type: 'LOG_OUT', 52 | }; 53 | }; 54 | 55 | const addPost = (data) => { 56 | return { 57 | type: 'ADD_POST', 58 | data, 59 | } 60 | }; 61 | 62 | // -------------------------------------- 63 | 64 | store.dispatch({ 65 | type: 'LOG_IN_REQUEST', 66 | }); 67 | 68 | store.dispatch(logIn({ 69 | id: 1, 70 | name: 'zerocho', 71 | admin: true, 72 | })); 73 | console.log('2nd', store.getState()); 74 | 75 | store.dispatch(addPost({ 76 | userId: 1, 77 | id: 1, 78 | content: '안녕하세요. 리덕스', 79 | })); 80 | console.log('3rd', store.getState()); 81 | store.dispatch(addPost({ 82 | userId: 1, 83 | id: 2, 84 | content: '두번째 리덕스', 85 | })); 86 | console.log('4th', store.getState()); 87 | 88 | store.dispatch(logOut()); 89 | console.log('5th', store.getState()); 90 | -------------------------------------------------------------------------------- /1.redux/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1.redux", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "js-tokens": { 8 | "version": "4.0.0", 9 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 10 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 11 | }, 12 | "loose-envify": { 13 | "version": "1.4.0", 14 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 15 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 16 | "requires": { 17 | "js-tokens": "^3.0.0 || ^4.0.0" 18 | } 19 | }, 20 | "redux": { 21 | "version": "4.0.4", 22 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz", 23 | "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==", 24 | "requires": { 25 | "loose-envify": "^1.4.0", 26 | "symbol-observable": "^1.2.0" 27 | } 28 | }, 29 | "symbol-observable": { 30 | "version": "1.2.0", 31 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", 32 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /1.redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1.redux", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "redux": "^4.0.5" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /2.redux/actions/post.js: -------------------------------------------------------------------------------- 1 | const addPost = (data) => { 2 | return { 3 | type: 'ADD_POST', 4 | data, 5 | } 6 | }; 7 | 8 | module.exports = { 9 | addPost, 10 | }; 11 | -------------------------------------------------------------------------------- /2.redux/actions/user.js: -------------------------------------------------------------------------------- 1 | const logIn = (data) => { // async action creator 2 | return (dispatch, getState) => { // async action 3 | dispatch(logInRequest(data)); 4 | try { 5 | setTimeout(() => { 6 | dispatch(logInSuccess({ 7 | userId: 1, 8 | nickname: 'zerocho' 9 | })); 10 | }, 2000); 11 | } catch (e) { 12 | dispatch(logInFailure(e)); 13 | } 14 | }; 15 | }; 16 | 17 | const logInRequest = (data) => { 18 | return { 19 | type: 'LOG_IN_REQUEST', 20 | data, 21 | } 22 | }; 23 | 24 | const logInSuccess = (data) => { 25 | return { 26 | type: 'LOG_IN_SUCCESS', 27 | data, 28 | } 29 | }; 30 | 31 | const logInFailure = (error) => { 32 | return { 33 | type: 'LOG_IN_FAILURE', 34 | error, 35 | } 36 | }; 37 | 38 | const logOut = () => { 39 | return { // action 40 | type: 'LOG_OUT', 41 | }; 42 | }; 43 | 44 | module.exports = { 45 | logIn, 46 | logOut, 47 | }; 48 | -------------------------------------------------------------------------------- /2.redux/index2.js: -------------------------------------------------------------------------------- 1 | const { createStore, compose, applyMiddleware } = require('redux'); 2 | const reducer = require('./reducers'); 3 | const { addPost } = require('./actions/post'); 4 | const { logIn, logOut } = require('./actions/user'); 5 | 6 | const initialState = { 7 | user: { 8 | isLoggingIn: true, 9 | data: null, 10 | }, 11 | posts: [], 12 | }; 13 | 14 | const firstMiddleware = (store) => (next) => (action) => { 15 | console.log('로깅', action); 16 | next(action); 17 | }; 18 | 19 | const thunkMiddleware = (store) => (next) => (action) => { 20 | if (typeof action === 'function') { // 비동기 21 | return action(store.dispatch, store.getState); 22 | } 23 | return next(action); // 동기 24 | }; 25 | 26 | const enhancer = applyMiddleware( 27 | firstMiddleware, 28 | thunkMiddleware, 29 | ); 30 | 31 | const store = createStore(reducer, initialState, enhancer); 32 | 33 | console.log('1st', store.getState()); 34 | 35 | // -------------------------------------- 36 | 37 | 38 | store.dispatch(logIn({ 39 | id: 1, 40 | name: 'zerocho', 41 | admin: true, 42 | })); 43 | console.log('2nd', store.getState()); 44 | // 45 | // store.dispatch(addPost({ 46 | // userId: 1, 47 | // id: 1, 48 | // content: '안녕하세요. 리덕스', 49 | // })); 50 | // console.log('3rd', store.getState()); 51 | // store.dispatch(addPost({ 52 | // userId: 1, 53 | // id: 2, 54 | // content: '두번째 리덕스', 55 | // })); 56 | // console.log('4th', store.getState()); 57 | // 58 | // store.dispatch(logOut()); 59 | // console.log('5th', store.getState()); 60 | -------------------------------------------------------------------------------- /2.redux/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1.redux", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "js-tokens": { 8 | "version": "4.0.0", 9 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 10 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 11 | }, 12 | "loose-envify": { 13 | "version": "1.4.0", 14 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 15 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 16 | "requires": { 17 | "js-tokens": "^3.0.0 || ^4.0.0" 18 | } 19 | }, 20 | "redux": { 21 | "version": "4.0.4", 22 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz", 23 | "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==", 24 | "requires": { 25 | "loose-envify": "^1.4.0", 26 | "symbol-observable": "^1.2.0" 27 | } 28 | }, 29 | "symbol-observable": { 30 | "version": "1.2.0", 31 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", 32 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /2.redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2.redux", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "redux": "^4.0.5" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /2.redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | const { combineReducers } = require('redux'); 2 | const userReducer = require('./user'); 3 | const postReducer = require('./post'); 4 | 5 | module.exports = combineReducers({ 6 | user: userReducer, 7 | posts: postReducer, 8 | }); 9 | -------------------------------------------------------------------------------- /2.redux/reducers/post.js: -------------------------------------------------------------------------------- 1 | const initialState = []; 2 | 3 | const postReducer = (prevState = initialState, action) => { // 새로운 state 만들어주기 4 | switch (action.type) { 5 | case 'ADD_POST': 6 | return [...prevState, action.data]; 7 | default: 8 | return prevState; 9 | } 10 | }; 11 | 12 | module.exports = postReducer; 13 | -------------------------------------------------------------------------------- /2.redux/reducers/user.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | isLoggingIn: false, 3 | data: null, 4 | }; 5 | 6 | const userReducer = (prevState = initialState, action) => { // 새로운 state 만들어주기 7 | switch (action.type) { 8 | case 'LOG_IN': 9 | return { 10 | ...prevState, 11 | data: action.data, 12 | }; 13 | case 'LOG_OUT': 14 | return { 15 | ...prevState, 16 | data: null, 17 | }; 18 | default: 19 | return prevState; 20 | } 21 | }; 22 | 23 | module.exports = userReducer; 24 | -------------------------------------------------------------------------------- /3.react-redux/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | const { logIn, logOut } = require('./actions/user'); 4 | 5 | const App = () => { 6 | const user = useSelector((state) => state.user); 7 | const posts = useSelector((state) => state.posts); 8 | const dispatch = useDispatch(); 9 | 10 | const onClick = useCallback(() => { 11 | dispatch(logIn({ 12 | id: 'zerocho', 13 | password: '비밀번호', 14 | })); 15 | }, []); 16 | 17 | const onLogout = useCallback(() => { 18 | dispatch(logOut()); 19 | }, []); 20 | 21 | return ( 22 |
23 | {user.isLoggingIn 24 | ?
로그인 중
25 | : user.data 26 | ?
{user.data.nickname}
27 | : '로그인 해주세요.'} 28 | {!user.data 29 | ? 30 | : } 31 |
32 | ); 33 | }; 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /3.react-redux/actions/post.js: -------------------------------------------------------------------------------- 1 | const addPost = (data) => { 2 | return { 3 | type: 'ADD_POST', 4 | data, 5 | } 6 | }; 7 | 8 | module.exports = { 9 | addPost, 10 | }; 11 | -------------------------------------------------------------------------------- /3.react-redux/actions/user.js: -------------------------------------------------------------------------------- 1 | const logIn = (data) => { // async action creator 2 | return (dispatch, getState) => { // async action 3 | dispatch(logInRequest(data)); 4 | try { 5 | setTimeout(() => { 6 | dispatch(logInSuccess({ 7 | userId: 1, 8 | nickname: 'zerocho' 9 | })); 10 | }, 2000); 11 | // axios.post().then().catch()으로 나중에 대체 12 | } catch (e) { 13 | dispatch(logInFailure(e)); 14 | } 15 | }; 16 | }; 17 | 18 | const logInRequest = (data) => { 19 | return { 20 | type: 'LOG_IN_REQUEST', 21 | data, 22 | } 23 | }; 24 | 25 | const logInSuccess = (data) => { 26 | return { 27 | type: 'LOG_IN_SUCCESS', 28 | data, 29 | } 30 | }; 31 | 32 | const logInFailure = (error) => { 33 | return { 34 | type: 'LOG_IN_FAILURE', 35 | error, 36 | } 37 | }; 38 | 39 | const logOut = () => { 40 | return { // action 41 | type: 'LOG_OUT', 42 | }; 43 | }; 44 | 45 | module.exports = { 46 | logIn, 47 | logOut, 48 | }; 49 | -------------------------------------------------------------------------------- /3.react-redux/client.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import store from './store'; 6 | import App from './App'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.querySelector('#root'), 13 | ); 14 | -------------------------------------------------------------------------------- /3.react-redux/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 리덕스연결 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /3.react-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3.react-redux", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack serve --env development --hot" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "react": "^17.0.1", 13 | "react-dom": "^17.0.1", 14 | "react-redux": "^7.2.2", 15 | "redux": "^4.0.5", 16 | "redux-devtools-extension": "^2.13.8" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.12.10", 20 | "@babel/preset-env": "^7.12.11", 21 | "@babel/preset-react": "^7.12.10", 22 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", 23 | "babel-loader": "^8.2.2", 24 | "react-refresh": "^0.11.0", 25 | "webpack": "^5.11.0", 26 | "webpack-cli": "^4.9.1", 27 | "webpack-dev-server": "^4.7.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /3.react-redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | const { combineReducers } = require('redux'); 2 | const userReducer = require('./user'); 3 | const postReducer = require('./post'); 4 | 5 | module.exports = combineReducers({ 6 | user: userReducer, 7 | posts: postReducer, 8 | }); 9 | -------------------------------------------------------------------------------- /3.react-redux/reducers/post.js: -------------------------------------------------------------------------------- 1 | const initialState = []; 2 | 3 | const postReducer = (prevState = initialState, action) => { // 새로운 state 만들어주기 4 | switch (action.type) { 5 | case 'ADD_POST': 6 | return [...prevState, action.data]; 7 | default: 8 | return prevState; 9 | } 10 | }; 11 | 12 | module.exports = postReducer; 13 | -------------------------------------------------------------------------------- /3.react-redux/reducers/user.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | isLoggingIn: false, 3 | data: null, 4 | }; 5 | 6 | const userReducer = (prevState = initialState, action) => { // 새로운 state 만들어주기 7 | switch (action.type) { 8 | case 'LOG_IN_REQUEST': 9 | return { 10 | ...prevState, 11 | data: null, 12 | isLoggingIn: true, 13 | }; 14 | case 'LOG_IN_SUCCESS': 15 | return { 16 | ...prevState, 17 | data: action.data, 18 | isLoggingIn: false, 19 | }; 20 | case 'LOG_IN_FAILURE': 21 | return { 22 | ...prevState, 23 | data: null, 24 | isLoggingIn: false, 25 | }; 26 | case 'LOG_OUT': 27 | return { 28 | ...prevState, 29 | data: null, 30 | }; 31 | default: 32 | return prevState; 33 | } 34 | }; 35 | 36 | module.exports = userReducer; 37 | -------------------------------------------------------------------------------- /3.react-redux/store.js: -------------------------------------------------------------------------------- 1 | const { createStore, compose, applyMiddleware } = require('redux'); 2 | const { composeWithDevTools } = require('redux-devtools-extension'); 3 | 4 | const reducer = require('./reducers'); 5 | const { addPost } = require('./actions/post'); 6 | const { logIn, logOut } = require('./actions/user'); 7 | 8 | const initialState = { 9 | user: { 10 | isLoggingIn: false, 11 | data: null, 12 | }, 13 | posts: [], 14 | }; 15 | 16 | const firstMiddleware = (store) => (next) => (action) => { 17 | console.log('로깅', action); 18 | next(action); 19 | }; 20 | 21 | const thunkMiddleware = (store) => (next) => (action) => { 22 | if (typeof action === 'function') { // 비동기 23 | return action(store.dispatch, store.getState); 24 | } 25 | return next(action); // 동기 26 | }; 27 | 28 | const enhancer = process.env.NODE_ENV === 'production' 29 | ? compose( 30 | applyMiddleware( 31 | firstMiddleware, 32 | thunkMiddleware, 33 | ), 34 | ) 35 | : composeWithDevTools( 36 | applyMiddleware( 37 | firstMiddleware, 38 | thunkMiddleware, 39 | ), 40 | ); 41 | 42 | const store = createStore(reducer, initialState, enhancer); 43 | 44 | module.exports = store; 45 | -------------------------------------------------------------------------------- /3.react-redux/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); 3 | 4 | module.exports = { 5 | name: 'react-redux', 6 | mode: 'development', 7 | devtool: 'eval', 8 | resolve: { 9 | extensions: ['.js', '.jsx'], 10 | }, 11 | entry: { 12 | app: './client', 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.jsx?$/, 17 | loader: 'babel-loader', 18 | options: { 19 | presets: [ 20 | ['@babel/preset-env', { 21 | targets: {browsers: ['last 2 chrome versions']}, 22 | debug: true, 23 | }], 24 | '@babel/preset-react', 25 | ], 26 | plugins: ["react-refresh/babel"] 27 | }, 28 | exclude: path.join(__dirname, 'node_modules'), 29 | }], 30 | }, 31 | plugins: [ 32 | new ReactRefreshWebpackPlugin() 33 | ], 34 | output: { 35 | path: path.join(__dirname, 'dist'), 36 | filename: '[name].js', 37 | publicPath: '/dist', 38 | }, 39 | devServer: { 40 | devMiddleware: { publicPath: '/dist' }, 41 | static: { directory: path.resolve(__dirname) }, 42 | hot: true 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /4.react-redux-immer/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | const { logIn, logOut } = require('./actions/user'); 4 | 5 | class App extends Component { 6 | onClick = () => { 7 | this.props.dispatchLogIn({ 8 | id: 'zerocho', 9 | password: '비밀번호', 10 | }); 11 | }; 12 | 13 | onLogout = () => { 14 | this.props.dispatchLogOut(); 15 | }; 16 | 17 | render() { 18 | const { user } = this.props; 19 | return ( 20 |
21 | {user.isLoggingIn 22 | ?
로그인 중
23 | : user.data 24 | ?
{user.data.nickname}
25 | : '로그인 해주세요.'} 26 | {!user.data 27 | ? 28 | : } 29 |
30 | ); 31 | } 32 | } 33 | 34 | const mapStateToProps = (state) => ({ 35 | user: state.user, 36 | posts: state.posts, 37 | }); // reselect 38 | 39 | const mapDispatchToProps = (dispatch) => ({ 40 | dispatchLogIn: (data) => dispatch(logIn(data)), 41 | dispatchLogOut: () => dispatch(logOut()), 42 | }); 43 | 44 | export default connect(mapStateToProps, mapDispatchToProps)(App); 45 | -------------------------------------------------------------------------------- /4.react-redux-immer/actions/post.js: -------------------------------------------------------------------------------- 1 | const addPost = (data) => { 2 | return { 3 | type: 'ADD_POST', 4 | data, 5 | } 6 | }; 7 | 8 | module.exports = { 9 | addPost, 10 | }; 11 | -------------------------------------------------------------------------------- /4.react-redux-immer/actions/user.js: -------------------------------------------------------------------------------- 1 | const logIn = (data) => { // async action creator 2 | return (dispatch, getState) => { // async action 3 | dispatch(logInRequest(data)); 4 | try { 5 | setTimeout(() => { 6 | dispatch(logInSuccess({ 7 | userId: 1, 8 | nickname: 'zerocho' 9 | })); 10 | }, 2000); 11 | // axios.post().then().catch()으로 나중에 대체 12 | } catch (e) { 13 | dispatch(logInFailure(e)); 14 | } 15 | }; 16 | }; 17 | 18 | const logInRequest = (data) => { 19 | return { 20 | type: 'LOG_IN_REQUEST', 21 | data, 22 | } 23 | }; 24 | 25 | const logInSuccess = (data) => { 26 | return { 27 | type: 'LOG_IN_SUCCESS', 28 | data, 29 | } 30 | }; 31 | 32 | const logInFailure = (error) => { 33 | return { 34 | type: 'LOG_IN_FAILURE', 35 | error, 36 | } 37 | }; 38 | 39 | const logOut = () => { 40 | return { // action 41 | type: 'LOG_OUT', 42 | }; 43 | }; 44 | 45 | module.exports = { 46 | logIn, 47 | logOut, 48 | }; 49 | -------------------------------------------------------------------------------- /4.react-redux-immer/client.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import store from './store'; 6 | import App from './App'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.querySelector('#root'), 13 | ); 14 | -------------------------------------------------------------------------------- /4.react-redux-immer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 리덕스연결 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /4.react-redux-immer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4.react-redux-immer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack serve --env development --hot" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@babel/plugin-proposal-class-properties": "^7.12.1", 13 | "immer": "^9.0.7", 14 | "react": "^17.0.1", 15 | "react-dom": "^17.0.1", 16 | "react-redux": "^7.2.2", 17 | "redux": "^4.0.5", 18 | "redux-devtools-extension": "^2.13.8" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.12.10", 22 | "@babel/preset-env": "^7.12.11", 23 | "@babel/preset-react": "^7.12.10", 24 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", 25 | "babel-loader": "^8.2.2", 26 | "react-refresh": "^0.11.0", 27 | "webpack": "^5.11.0", 28 | "webpack-cli": "^4.9.1", 29 | "webpack-dev-server": "^4.7.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /4.react-redux-immer/reducers/index.js: -------------------------------------------------------------------------------- 1 | const { combineReducers } = require('redux'); 2 | const userReducer = require('./user'); 3 | const postReducer = require('./post'); 4 | 5 | module.exports = combineReducers({ 6 | user: userReducer, 7 | posts: postReducer, 8 | }); 9 | -------------------------------------------------------------------------------- /4.react-redux-immer/reducers/post.js: -------------------------------------------------------------------------------- 1 | const { produce } = require('immer'); 2 | 3 | const initialState = []; 4 | 5 | const postReducer = (prevState = initialState, action) => { // 새로운 state 만들어주기 6 | return produce(prevState, (draft) => { 7 | switch (action.type) { 8 | case 'ADD_POST': 9 | draft.push(action.data); 10 | break; 11 | default: 12 | break; 13 | } 14 | }); 15 | }; 16 | 17 | module.exports = postReducer; 18 | -------------------------------------------------------------------------------- /4.react-redux-immer/reducers/user.js: -------------------------------------------------------------------------------- 1 | const { produce } = require('immer'); 2 | 3 | const initialState = { 4 | isLoggingIn: false, 5 | data: null, 6 | }; 7 | 8 | // nextState = produce(prevState, (draft) => {}) 9 | 10 | const userReducer = (prevState = initialState, action) => { // 새로운 state 만들어주기 11 | return produce(prevState, (draft) => { 12 | switch (action.type) { 13 | case 'LOG_IN_REQUEST': 14 | draft.data = null; 15 | draft.isLoggingIn = true; 16 | break; 17 | case 'LOG_IN_SUCCESS': 18 | draft.data = action.data; 19 | draft.isLoggingIn = false; 20 | break; 21 | case 'LOG_IN_FAILURE': 22 | draft.data = null; 23 | draft.isLoggingIn = false; 24 | break; 25 | case 'LOG_OUT': 26 | draft.data = null; 27 | break; 28 | default: 29 | break; 30 | } 31 | }); 32 | }; 33 | 34 | module.exports = userReducer; 35 | -------------------------------------------------------------------------------- /4.react-redux-immer/store.js: -------------------------------------------------------------------------------- 1 | const { createStore, compose, applyMiddleware } = require('redux'); 2 | const { composeWithDevTools } = require('redux-devtools-extension'); 3 | 4 | const reducer = require('./reducers'); 5 | const { addPost } = require('./actions/post'); 6 | const { logIn, logOut } = require('./actions/user'); 7 | 8 | const initialState = { 9 | user: { 10 | isLoggingIn: false, 11 | data: null, 12 | }, 13 | posts: [], 14 | }; 15 | 16 | const firstMiddleware = (store) => (next) => (action) => { 17 | console.log('로깅', action); 18 | next(action); 19 | }; 20 | 21 | const thunkMiddleware = (store) => (next) => (action) => { 22 | if (typeof action === 'function') { // 비동기 23 | return action(store.dispatch, store.getState); 24 | } 25 | return next(action); // 동기 26 | }; 27 | 28 | 29 | const enhancer = process.env.NODE_ENV === 'production' 30 | ? compose( 31 | applyMiddleware( 32 | firstMiddleware, 33 | thunkMiddleware, 34 | ), 35 | ) 36 | : composeWithDevTools( 37 | applyMiddleware( 38 | firstMiddleware, 39 | thunkMiddleware, 40 | ), 41 | ); 42 | 43 | const store = createStore(reducer, initialState, enhancer); 44 | 45 | module.exports = store; 46 | -------------------------------------------------------------------------------- /4.react-redux-immer/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); 3 | 4 | module.exports = { 5 | name: 'react-redux-immer', 6 | mode: 'development', 7 | devtool: 'eval', 8 | resolve: { 9 | extensions: ['.js', '.jsx'], 10 | }, 11 | entry: { 12 | app: './client', 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.jsx?$/, 17 | loader: 'babel-loader', 18 | options: { 19 | presets: [ 20 | ['@babel/preset-env', { 21 | targets: {browsers: ['last 2 chrome versions']}, 22 | debug: true, 23 | }], 24 | '@babel/preset-react', 25 | ], 26 | plugins: ["react-refresh/babel", "@babel/plugin-proposal-class-properties"] 27 | }, 28 | exclude: path.join(__dirname, 'node_modules'), 29 | }], 30 | }, 31 | plugins: [ 32 | new ReactRefreshWebpackPlugin() 33 | ], 34 | output: { 35 | path: path.join(__dirname, 'dist'), 36 | filename: '[name].js', 37 | publicPath: '/dist', 38 | }, 39 | devServer: { 40 | devMiddleware: { publicPath: '/dist' }, 41 | static: { directory: path.resolve(__dirname) }, 42 | hot: true 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | 4 | const { logIn } = require('./actions/user'); 5 | const userSlice = require('./reducers/userSlice'); 6 | 7 | const App = () => { 8 | const user = useSelector((state) => state.user); 9 | const { list } = useSelector((state) => state.post); 10 | const dispatch = useDispatch(); 11 | 12 | const onClick = useCallback(() => { 13 | dispatch(logIn({ 14 | id: 'zerocho', 15 | password: '비밀번호', 16 | })); 17 | }, []); 18 | 19 | const onLogout = useCallback(() => { 20 | dispatch(userSlice.actions.logOut()); 21 | }, []); 22 | 23 | return ( 24 |
25 | {user.isLoggingIn 26 | ?
로그인 중
27 | : user.data 28 | ?
{user.data.nickname}
29 | : '로그인 해주세요.'} 30 | {!user.data 31 | ? 32 | : } 33 |
34 | ); 35 | }; 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/actions/post.js: -------------------------------------------------------------------------------- 1 | const { createAsyncThunk } = require('@reduxjs/toolkit'); 2 | 3 | const delay = (time, value) => new Promise((resolve, reject) => { 4 | setTimeout(() => { 5 | resolve(value); 6 | }, time); 7 | }); 8 | 9 | exports.addPost = createAsyncThunk('post/add', async (data, thunkAPI) => { 10 | return await delay(500, data); 11 | }); 12 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/actions/user.js: -------------------------------------------------------------------------------- 1 | const { createAsyncThunk } = require('@reduxjs/toolkit'); 2 | 3 | const delay = (time, value) => new Promise((resolve, reject) => { 4 | setTimeout(() => { 5 | resolve(value); 6 | }, time); 7 | }); 8 | 9 | exports.logIn = createAsyncThunk('user/logIn', async (data, thunkAPI) => { 10 | // throw new Error('비밀번호가 틀렸습니다.'); 11 | return await delay(500,{ 12 | userId: 1, 13 | nickname: 'zerocho' 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/client.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import store from './store'; 6 | import App from './App'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.querySelector('#root'), 13 | ); 14 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 리덕스연결 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5.react-redux-toolkit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack serve --env development --hot" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@reduxjs/toolkit": "^1.5.0", 13 | "react": "^17.0.1", 14 | "react-dom": "^17.0.1", 15 | "react-redux": "^7.2.2" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.12.10", 19 | "@babel/preset-env": "^7.12.11", 20 | "@babel/preset-react": "^7.12.10", 21 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", 22 | "babel-loader": "^8.2.2", 23 | "react-refresh": "^0.11.0", 24 | "webpack": "^5.11.0", 25 | "webpack-cli": "^4.9.1", 26 | "webpack-dev-server": "^4.7.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/reducers/index.js: -------------------------------------------------------------------------------- 1 | const { combineReducers } = require('redux'); 2 | const userSlice = require('./userSlice'); 3 | const postSlice = require('./postSlice'); 4 | 5 | module.exports = combineReducers({ 6 | user: userSlice.reducer, 7 | post: postSlice.reducer, 8 | }); 9 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/reducers/postSlice.js: -------------------------------------------------------------------------------- 1 | const { createSlice } = require('@reduxjs/toolkit'); 2 | const { addPost } = require('../actions/post'); 3 | 4 | const initialState = { 5 | list: [], 6 | }; 7 | 8 | const postSlice = createSlice({ 9 | name: 'post', 10 | initialState, 11 | reducers: {}, 12 | extraReducers: (builder) => builder 13 | .addCase(addPost.pending, (state, action) => { 14 | 15 | }) 16 | .addCase(addPost.fulfilled, (state, action) => { 17 | state.list.push(action.payload); 18 | }) 19 | .addCase(addPost.rejected, (state, action) => { 20 | 21 | }), 22 | }); 23 | 24 | module.exports = postSlice; 25 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/reducers/userSlice.js: -------------------------------------------------------------------------------- 1 | const { createSlice } = require('@reduxjs/toolkit'); 2 | const { logIn } = require('../actions/user'); 3 | 4 | const initialState = { 5 | isLoggingIn: false, 6 | data: null, 7 | }; 8 | 9 | const userSlice = createSlice({ 10 | name: 'user', 11 | initialState, 12 | reducers: { 13 | logOut(state, action) { 14 | state.data = null; 15 | } 16 | }, 17 | extraReducers: (builder) => builder 18 | .addCase(logIn.pending, (state, action) => { 19 | state.data = null; 20 | state.isLoggingIn = true; 21 | }) 22 | .addCase(logIn.fulfilled, (state, action) => { 23 | state.data = action.payload; 24 | state.isLoggingIn = false; 25 | }) 26 | .addCase(logIn.rejected, (state, action) => { 27 | state.error = action.payload; 28 | }) 29 | }) 30 | 31 | module.exports = userSlice; 32 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/store.js: -------------------------------------------------------------------------------- 1 | const { configureStore } = require('@reduxjs/toolkit'); 2 | 3 | const reducer = require('./reducers'); 4 | 5 | const firstMiddleware = () => (next) => (action) => { 6 | console.log('로깅', action); 7 | next(action); 8 | }; 9 | 10 | const store = configureStore({ 11 | reducer, 12 | middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(firstMiddleware), 13 | }); 14 | 15 | module.exports = store; 16 | -------------------------------------------------------------------------------- /5.react-redux-toolkit/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); 3 | 4 | module.exports = { 5 | name: 'react-redux-toolkit', 6 | mode: 'development', 7 | devtool: 'eval', 8 | resolve: { 9 | extensions: ['.js', '.jsx'], 10 | }, 11 | entry: { 12 | app: './client', 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.jsx?$/, 17 | loader: 'babel-loader', 18 | options: { 19 | presets: [ 20 | ['@babel/preset-env', { 21 | targets: {browsers: ['last 2 chrome versions']}, 22 | debug: true, 23 | }], 24 | '@babel/preset-react', 25 | ], 26 | plugins: ["react-refresh/babel"] 27 | }, 28 | exclude: path.join(__dirname, 'node_modules'), 29 | }], 30 | }, 31 | plugins: [ 32 | new ReactRefreshWebpackPlugin() 33 | ], 34 | output: { 35 | path: path.join(__dirname, 'dist'), 36 | filename: '[name].js', 37 | publicPath: '/dist', 38 | }, 39 | devServer: { 40 | devMiddleware: { publicPath: '/dist' }, 41 | static: { directory: path.resolve(__dirname) }, 42 | hot: true 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /6.mobx/index.js: -------------------------------------------------------------------------------- 1 | const { observable, autorun, reaction, action, runInAction, computed } = require('mobx'); 2 | 3 | const state = observable({ 4 | compA: 'a', 5 | compB: 12, 6 | compC: null, 7 | }); 8 | 9 | autorun(() => { 10 | console.log('A changed', state.compA); 11 | console.log('C changed', state.compC); 12 | }); 13 | 14 | reaction(() => { 15 | return state.compB; 16 | }, () => { 17 | console.log('B reaction', state.compB); 18 | }); 19 | 20 | runInAction(() => { 21 | state.compA = 'c'; 22 | state.compB = 25; 23 | state.compC = 'c'; 24 | }); 25 | 26 | runInAction(() => { 27 | state.compC = 'd'; 28 | }); 29 | -------------------------------------------------------------------------------- /6.mobx/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5.mobx", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "mobx": { 8 | "version": "6.0.4", 9 | "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.0.4.tgz", 10 | "integrity": "sha512-wT2QJT9tW19VSHo9x7RPKU3z/I2Ps6wUS8Kb1OO+kzmg7UY3n4AkcaYG6jq95Lp1R9ohjC/NGYuT2PtuvBjhFg==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /6.mobx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "6.mobx", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node index" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "mobx": "^6.0.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /7.mobx/index.js: -------------------------------------------------------------------------------- 1 | const { observable, autorun, reaction, action, runInAction } = require('mobx'); 2 | 3 | const userState = observable({ 4 | isLoggingIn: true, 5 | data: null, 6 | }); 7 | 8 | const postState = observable([]); 9 | 10 | runInAction(() => { 11 | postState.push({ id: 1, content: '안녕하세요.' }); 12 | userState.data = { 13 | id: 1, 14 | nickname: 'zerocho', 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /7.mobx/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "7.mobx", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "mobx": { 8 | "version": "6.0.4", 9 | "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.0.4.tgz", 10 | "integrity": "sha512-wT2QJT9tW19VSHo9x7RPKU3z/I2Ps6wUS8Kb1OO+kzmg7UY3n4AkcaYG6jq95Lp1R9ohjC/NGYuT2PtuvBjhFg==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /7.mobx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "7.mobx", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "author": "", 8 | "license": "ISC", 9 | "dependencies": { 10 | "mobx": "^6.0.4" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /8.mobx-react/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import { observable } from 'mobx'; 4 | 5 | import { userStore, postStore } from './store'; 6 | 7 | class App extends Component { 8 | state = observable({ 9 | name: '', 10 | password: '', 11 | }); 12 | 13 | onClick = () => { 14 | userStore.logIn({ 15 | nickname: 'zerocho', 16 | password: '비밀번호', 17 | }); 18 | }; 19 | 20 | onLogout = () => { 21 | userStore.logOut(); 22 | }; 23 | 24 | onChangeName = (e) => { 25 | this.state.name = e.target.value; 26 | }; 27 | 28 | onChangePassword = (e) => { 29 | this.state.password = e.target.value; 30 | }; 31 | 32 | render() { 33 | return ( 34 |
35 | {userStore.isLoggingIn 36 | ?
로그인 중
37 | : userStore.data 38 | ?
{userStore.data.nickname}
39 | : '로그인 해주세요.'} 40 | {!userStore.data 41 | ? 42 | : } 43 |
{postStore.data.length}
44 | 45 | 46 |
47 | ); 48 | } 49 | } 50 | 51 | export default observer(App); 52 | -------------------------------------------------------------------------------- /8.mobx-react/Context.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { userStore, postStore } from './store'; 3 | 4 | export const storeContext = React.createContext({ 5 | userStore, 6 | postStore, 7 | }); 8 | 9 | export const StoreProvider = ({ children }) => { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export default StoreProvider; 18 | -------------------------------------------------------------------------------- /8.mobx-react/client.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.querySelector('#root'), 8 | ); 9 | -------------------------------------------------------------------------------- /8.mobx-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 리덕스연결 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /8.mobx-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "8.mobx-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack serve --env development" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@babel/plugin-proposal-class-properties": "^7.12.1", 13 | "mobx": "^6.0.4", 14 | "mobx-react": "^7.0.5", 15 | "react": "^17.0.1", 16 | "react-dom": "^17.0.1" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.12.9", 20 | "@babel/preset-env": "^7.12.7", 21 | "@babel/preset-react": "^7.12.7", 22 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", 23 | "babel-loader": "^8.2.2", 24 | "react-refresh": "^0.11.0", 25 | "webpack": "^5.9.0", 26 | "webpack-cli": "^4.2.0", 27 | "webpack-dev-server": "^4.7.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /8.mobx-react/store.js: -------------------------------------------------------------------------------- 1 | const { observable } = require('mobx'); 2 | 3 | const userStore = observable({ 4 | isLoggingIn: false, 5 | data: null, 6 | logIn(data) { 7 | this.isLoggingIn = true; 8 | setTimeout(() => { 9 | this.data = data; 10 | this.isLoggingIn = false; 11 | postStore.data.push(1); 12 | }, 2000); 13 | }, 14 | logOut() { 15 | this.data = null; 16 | }, 17 | }); 18 | 19 | const postStore = observable({ 20 | data: [], 21 | addPost(data) { 22 | this.data.push(data); 23 | }, 24 | }); 25 | 26 | export { userStore, postStore }; 27 | -------------------------------------------------------------------------------- /8.mobx-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); 3 | 4 | module.exports = { 5 | name: 'mobx-react', 6 | mode: 'development', 7 | devtool: 'eval', 8 | resolve: { 9 | extensions: ['.js', '.jsx'], 10 | }, 11 | entry: { 12 | app: './client', 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.jsx?$/, 17 | loader: 'babel-loader', 18 | options: { 19 | presets: [ 20 | ['@babel/preset-env', { 21 | targets: {browsers: ['last 2 chrome versions']}, 22 | debug: true, 23 | }], 24 | '@babel/preset-react', 25 | ], 26 | plugins: ["react-refresh/babel", "@babel/plugin-proposal-class-properties"], 27 | }, 28 | exclude: path.join(__dirname, 'node_modules'), 29 | }], 30 | }, 31 | plugins: [ 32 | new ReactRefreshWebpackPlugin() 33 | ], 34 | output: { 35 | path: path.join(__dirname, 'dist'), 36 | filename: '[name].js', 37 | publicPath: '/dist/', 38 | }, 39 | devServer: { 40 | devMiddleware: { publicPath: '/dist' }, 41 | static: { directory: path.resolve(__dirname) }, 42 | hot: true 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /9.mobx-react/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { observer, useLocalObservable } from 'mobx-react'; 3 | 4 | import useStore from './useStore'; 5 | 6 | const App = () => { 7 | const { userStore, postStore } = useStore(); 8 | 9 | const state = useLocalObservable(() => ({ 10 | name: '', 11 | password: '', 12 | onChangeName(e) { 13 | this.name = e.target.value; 14 | }, 15 | onChangePassword(e) { 16 | this.password = e.target.value; 17 | } 18 | })); 19 | 20 | const onClick = useCallback(() => { 21 | userStore.logIn({ 22 | nickname: 'zerocho', 23 | password: '비밀번호', 24 | }); 25 | }, []); 26 | 27 | const onLogout = useCallback(() => { 28 | userStore.logOut(); 29 | }, []); 30 | 31 | return ( 32 |
33 | {userStore.isLoggingIn 34 | ?
로그인 중
35 | : userStore.data 36 | ?
{userStore.data.nickname}
37 | : '로그인 해주세요.'} 38 | {!userStore.data 39 | ? 40 | : } 41 |
{postStore.postLength}
42 | 43 | 44 |
45 | ); 46 | }; 47 | 48 | export default observer(App); 49 | -------------------------------------------------------------------------------- /9.mobx-react/Context.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { userStore, postStore } from './store'; 3 | 4 | export const storeContext = React.createContext({ 5 | userStore, 6 | postStore, 7 | }); 8 | 9 | export const StoreProvider = ({ children }) => { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export default StoreProvider; 18 | -------------------------------------------------------------------------------- /9.mobx-react/client.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.querySelector('#root'), 9 | ); 10 | -------------------------------------------------------------------------------- /9.mobx-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 리덕스연결 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /9.mobx-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "9.mobx-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack serve --env development --hot" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@babel/plugin-proposal-class-properties": "^7.12.1", 13 | "mobx": "^6.0.4", 14 | "mobx-react": "^7.0.5", 15 | "react": "^17.0.1", 16 | "react-dom": "^17.0.1" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.12.9", 20 | "@babel/preset-env": "^7.12.7", 21 | "@babel/preset-react": "^7.12.7", 22 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", 23 | "babel-loader": "^8.2.2", 24 | "react-refresh": "^0.11.0", 25 | "webpack": "^5.9.0", 26 | "webpack-cli": "^4.2.0", 27 | "webpack-dev-server": "^4.7.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /9.mobx-react/store.js: -------------------------------------------------------------------------------- 1 | const { observable, action } = require('mobx'); 2 | 3 | const postStore = observable({ 4 | data: [], 5 | addPost(data) { 6 | this.data.push(data); 7 | }, 8 | get postLength() { 9 | return this.data.length; 10 | }, 11 | }); 12 | 13 | const userStore = observable({ 14 | isLoggingIn: false, 15 | data: null, 16 | logIn(data) { 17 | this.isLoggingIn = true; 18 | setTimeout(action(() => { 19 | this.data = data; 20 | this.isLoggingIn = false; 21 | postStore.data.push(1); 22 | }), 2000); 23 | }, 24 | logOut() { 25 | this.data = null; 26 | }, 27 | }); 28 | 29 | export { userStore, postStore }; 30 | -------------------------------------------------------------------------------- /9.mobx-react/useStore.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { storeContext } from './Context'; 3 | import { userStore, postStore } from './store'; 4 | 5 | function useStore() { 6 | // const store = React.useContext(storeContext); 7 | // if (!store) { 8 | // // this is especially useful in TypeScript so you don't need to be checking for null all the time 9 | // throw new Error('useStore must be used within a StoreProvider.'); 10 | // } 11 | return { userStore, postStore }; 12 | } 13 | 14 | export default useStore; 15 | -------------------------------------------------------------------------------- /9.mobx-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); 3 | 4 | module.exports = { 5 | name: 'lotto-dev', 6 | mode: 'development', 7 | devtool: 'eval', 8 | resolve: { 9 | extensions: ['.js', '.jsx'], 10 | }, 11 | entry: { 12 | app: './client', 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.jsx?$/, 17 | loader: 'babel-loader', 18 | options: { 19 | presets: [ 20 | ['@babel/preset-env', { 21 | targets: {browsers: ['last 2 chrome versions']}, 22 | debug: true, 23 | }], 24 | '@babel/preset-react', 25 | ], 26 | plugins: ["react-refresh/babel"], 27 | }, 28 | exclude: path.join(__dirname, 'node_modules'), 29 | }], 30 | }, 31 | plugins: [ 32 | new ReactRefreshWebpackPlugin() 33 | ], 34 | output: { 35 | path: path.join(__dirname, 'dist'), 36 | filename: '[name].js', 37 | publicPath: '/dist', 38 | }, 39 | devServer: { 40 | devMiddleware: { publicPath: '/dist' }, 41 | static: { directory: path.resolve(__dirname) }, 42 | hot: true 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2021년 1월 부로 강좌가 리뉴얼되었습니다. 2 | 3 | 2020년까지의 코드를 원하시는 분은 old 브랜치로 가시면 됩니다. 다만, 최신 코드를 실무에 적용하시는 것을 강력 추천드립니다. 4 | --------------------------------------------------------------------------------