-
Notifications
You must be signed in to change notification settings - Fork 0
[서인] State-Management #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import Component from './core/Component'; | ||
| import { store, setA, setB } from './store'; | ||
|
|
||
| const InputA = () => ` | ||
| <input id="stateA" type="number" value="${store.getState().a}" size="5"/> | ||
| `; | ||
|
|
||
| const InputB = () => ` | ||
| <input id="stateB" type="number" value="${store.getState().b}" size="5"/> | ||
| `; | ||
|
|
||
| const Calculator = () => ` | ||
| <p>a + b = ${store.getState().a + store.getState().b}</p> | ||
| `; | ||
|
|
||
| export class App extends Component { | ||
| template(): string { | ||
| return ` | ||
| ${InputA()} | ||
| ${InputB()} | ||
| ${Calculator()} | ||
| `; | ||
| } | ||
|
|
||
| setEvent(): void { | ||
| const { $el } = this; | ||
|
|
||
| $el.querySelector('#stateA')?.addEventListener('change', ({ target }) => { | ||
| if (target && target instanceof HTMLInputElement) { | ||
| store.dispatch(setA(Number(target.value))); | ||
| } | ||
| }); | ||
|
|
||
| $el.querySelector('#stateB')?.addEventListener('change', ({ target }) => { | ||
| if (target && target instanceof HTMLInputElement) { | ||
| store.dispatch(setB(Number(target.value))); | ||
| } | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { observable, observe } from './observer'; | ||
|
|
||
| export type TComponentData = Record<string, any>; | ||
|
|
||
| export interface IState { | ||
| [key: string]: number; | ||
| } | ||
|
|
||
| export default class Component< | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
요즘은 인터페이스 앞에 I를 붙이지 않는다는 네이밍 컨벤션을 본적 있어서 관련한 아티클을 봐보면 좋을것 같아요
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 찾아보니 헝가리안 표기법을 지양하는 개발자들이 많군요 참고할게요 |
||
| State extends TComponentData = IState, | ||
| Props = TComponentData, | ||
| > { | ||
| state!: State; | ||
| props: Props; | ||
| $el: HTMLElement; | ||
|
|
||
| constructor($el: HTMLElement, props: Props) { | ||
| this.$el = $el; | ||
| this.props = props; | ||
| this.setup(); | ||
| } | ||
|
|
||
| setup() { | ||
| this.state = observable(this.initState()) as State; | ||
| observe(() => { | ||
| this.render(); | ||
| this.setEvent(); | ||
| this.mounted(); | ||
| }); | ||
| } | ||
|
|
||
| initState() { | ||
| return {}; | ||
| } | ||
| template() { | ||
| return ''; | ||
| } | ||
| render() { | ||
| this.$el.innerHTML = this.template(); | ||
| } | ||
| setEvent() {} | ||
| mounted() {} | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { TAction } from '../store'; | ||
| import { observable } from './observer'; | ||
|
|
||
| interface IStateA { | ||
| a: number; | ||
| b: number; | ||
| } | ||
|
|
||
| interface IStateB { | ||
| b: any; | ||
| a: number; | ||
| } | ||
|
|
||
| type IReducerState = | ||
| | (IStateA & { [key: string]: any }) | ||
| | (IStateB & { [key: string]: any }); | ||
|
|
||
| interface IReducer { | ||
| (state?: IReducerState, action?: TAction): IReducerState; | ||
| } | ||
|
|
||
| export const createStore = (reducer: IReducer) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| const initialState: IReducerState = reducer(); | ||
| const state = observable(initialState); | ||
|
|
||
| const frozenState: { [key: string]: any } = {}; | ||
| Object.keys(state).forEach((key) => { | ||
| Object.defineProperty(frozenState, key, { | ||
| get: () => state[key], | ||
| }); | ||
| }); | ||
|
|
||
| const dispatch = (action: TAction) => { | ||
| const newState = reducer(state, action); | ||
|
|
||
| for (const [key, value] of Object.entries(newState)) { | ||
| if (key in state) { | ||
| state[key] = value; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const getState = () => frozenState; | ||
|
|
||
| return { getState, dispatch }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| type TObserver = () => void; | ||
|
|
||
| let currentObserver: TObserver | null = null; | ||
|
|
||
| export const observe = (fn: TObserver): void => { | ||
| currentObserver = fn; | ||
| fn(); | ||
| currentObserver = null; | ||
| }; | ||
| export const observable = <T extends Record<string, any>>(obj: T): T => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 옵저버 패턴에는 subscribe, unsubscribe, notify가 보통은 필수적으로 들어가는데, 현재 코드에서는 unsubscribe가 없네요. 이런 부분도 고민해보면 좋을것 같아요. |
||
| Object.keys(obj).forEach((key) => { | ||
| let _value = obj[key]; | ||
| const observers: Set<TObserver> = new Set(); | ||
|
|
||
| Object.defineProperty(obj, key, { | ||
| get() { | ||
| if (currentObserver) observers.add(currentObserver); | ||
| return _value; | ||
| }, | ||
|
|
||
| set(value) { | ||
| _value = value; | ||
| observers.forEach((fn) => fn()); | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| return obj; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,14 @@ | ||
| // your code | ||
| import { App } from './App'; | ||
|
|
||
| class Main { | ||
| constructor() { | ||
| const $app: Element | null = document.querySelector('#app'); | ||
| if ($app instanceof HTMLElement) { | ||
| new App($app, {}); | ||
| } else { | ||
| console.error('Element with ID "app" not found.'); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| new Main(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { createStore } from './core/ReduxStore'; | ||
|
|
||
| export type TAction = { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| type?: string; | ||
| payload?: any; | ||
| }; | ||
|
|
||
| const initState = { | ||
| a: 10, | ||
| b: 20, | ||
| }; | ||
|
|
||
| export const SET_A = 'SET_A'; | ||
| export const SET_B = 'SET_B'; | ||
|
|
||
| export const store = createStore((state = initState, action: TAction = {}) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| switch (action.type) { | ||
| case 'SET_A': | ||
| return { ...state, a: action.payload }; | ||
| case 'SET_B': | ||
| return { ...state, b: action.payload }; | ||
| default: | ||
| return state; | ||
| } | ||
| }); | ||
|
|
||
| export const setA = (payload: number) => ({ type: SET_A, payload }); | ||
| export const setB = (payload: number) => ({ type: SET_B, payload }); | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋아요 👍