(Vanilla JS๋ก ์ํ๊ด๋ฆฌ ์์คํ ๋ง๋ค๊ธฐ by ํฉ์ค์ผ๋) 2ํธ
๐พ ๊ธฐ์ ์ฑ ์คํฐ๋
23๋
1์๋ถํฐ ํ๋ ์ค์ธ ๊ต์ก์์, ๋ป์ด ๋ง๋ ๋๋ฃ๋ค๊ณผ ํจ๊ป ์งํํ๊ฒ ๋ ์คํฐ๋
์์ผ๋ก๋ ๊พธ์คํ ๊ธฐ์ ์์ ์ ์ฝ๊ณ ํจ๊ป ๋ฐ์ ํ๋ ์๊ฐ์ด ๋์์ผ๋ฉด ์ข๊ฒ ๋ค!
๋ค์ด๊ฐ๊ธฐ์ ์์
์ด ๋ด์ฉ์ ๊ฐ๋ฐ์ ํฉ์ค์ผ - Vanilla Javascript๋ก ์ํ๊ด๋ฆฌ ์์คํ ๋ง๋ค๊ธฐ์ ๊ณต๋ถํ๋ฉฐ ์์ฑํ ๊ธ์ ๋๋ค. ๋๋ถ๋ถ์ ๋ด์ฉ์ ํฉ์ค์ผ๋์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ์๊ณ ๋ช๊ฐ์ ๊ฐ๋ ๋ด์ฉ ์ ๋๋ง ์ถ๊ฐ ํน์ ๋ด์ฉ ์์ฝ์ด ๋์ด์์ต๋๋ค. ์์ธํ ์ฌํญ์ ํฉ์ค์ผ๋์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์!(์ ๋ง ๋๋ฌด ์ข์ ๊ธ์ด์์~!)
Vanilla Javascript๋ก ์ํ๊ด๋ฆฌ ์์คํ ๋ง๋ค๊ธฐ
1. ์ค์ ์ง์ค์ ์ํ๊ด๋ฆฌ
- ํ๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์ ์ ์ผ ์ค์ํ ๊ฒ์
์ํ๊ด๋ฆฌ
์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก DOM์ ๋ ๋๋งํ๊ธฐ ๋๋ฌธ์ด๋ค.
- ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก ์ปดํฌ๋ํธ์ depth๊ฐ ๊น์ด์ง๋ฉฐ ๋๋ถ์ด ์ํ๊ด๋ฆฌ๋ ๊ต์ฅํ ์ด๋ ค์์ง๋ค.
์ด ๋, ์ํ๋ฅผ ์์์ ์๋๋ก ํ๋ํ๋ ์ ๋ฌํ์ง ์๊ณ
์ค์ ์ง์ค์ ์ญํ
์ ํ๋ฉด์ ๋์์์์ธก ๊ฐ๋ฅํ ๋ฐฉ์
์ผ๋ก ๋ค๋ฃฐ ์ ์๋ค๋ฉด ์ด๋จ๊น?
2. Observer Pattern์ ๋ํด ์ดํดํ๊ธฐ
์ค์ ์ง์ค์ ์ ์ฅ์
๋ฅผStore
๋ผ๊ณ ํด๋ณด์.Store
์Component
์ ๊ด๊ณ- ํ๋์ Store๊ฐ ์ฌ๋ฌ ๊ฐ์ Component์์ ์ฌ์ฉ๋ ์ ์๋ค.
- Store๊ฐ ๋ณ๊ฒฝ๋ ๋, Store๊ฐ ์ฌ์ฉ๋๊ณ ์๋ Component๋ ๋ณ๊ฒฝ๋์ด์ผ ํ๋ค.
// store์ ์ด๊ธฐ state๋ฅผ ์ ๋ฌํ๋ฉด์ ์์ฑ
const store = new Store({
a: 11,
b: 16,
});
// ์ปดํฌ๋ํธ 2๊ฐ ์์ฑ
// Q. ์ฌ๊ธฐ์ Component์ parameter๋ก ๊ฐ์ฒด๊ฐ ์ ๋ฌ๋๋ ๊ฒ ๋ง๋..? ์ด์ฐจํผ ์๋์์ ๋ฐ๋ก ๊ตฌ๋
ํ๋๋ฐ?
const component1 = new Component({ subscribe: [store] });
const component2 = new Component({ subscribe: [store] });
// ์ปดํฌ๋ํธ๊ฐ store๋ฅผ ๊ตฌ๋
component1.subscribe(store); // a + b = ${store.state.a + store.state.b}
component2.subscribe(store); // a * b = ${store.state.a * store.state.b}
// store์ state๋ฅผ ๋ณ๊ฒฝํ๋ค.
store.setState({
a: 7,
b: 19,
});
// store๊ฐ ๋ณ๊ฒฝ๋์์์ ์๋ฆฐ๋ค.
store.notify();
์์ ๊ฐ์ ํํ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ Observer Pattern
์ด๋ผ๊ณ ํ๋ค.
Observer Pattern
์ ๊ฐ์ฒด์ ์ํ ๋ณํ๋ฅผ ๊ด์ฐฐํ๋ ๊ด์ฐฐ์๋ค, ์ฆ ์ต์ ๋ฒ๋ค์ ๋ชฉ๋ก์ ๊ฐ์ฒด์ ๋ฑ๋กํ๋ค.์ํ ๋ณํ๊ฐ ์์ ๋๋ง๋ค ๋ฉ์๋ ๋ฑ์ ํตํด ๊ฐ์ฒด๊ฐ ์ง์ ๋ชฉ๋ก์ ๊ฐ ์ต์ ๋ฒ์๊ฒ ํต์งํ๋๋ก ํ๋ค.
- ๋์์ธ ํจํด์ ํ๋์ด๋ค.
๋ฐํ/๊ตฌ๋ ๋ชจ๋ธ
์ด๋ผ๊ณ ๋ ๋ถ๋ฅธ๋ค.
1. Publisher(๋ฐํ)
class Publisher {
#state;
#observers = new Set(); // ์ฌ๊ธฐ์ ๊ตฌ๋
์๋ค(๊ด์ฐฐ์๋ค)์ด ์ ์ฅ๋๋ค.
constructor(state) {
this.#state = state;
// Object.defineProperty()
// state ๊ฐ์ฒด์ key๊ฐ์ ์ํํ๋ฉด์ this์ ํด๋น key๊ฐ์ผ๋ก `() => this.#state[key]` value๋ฅผ ๊ฐ๊ฒ ํ๋ค.
// Q. ๊ทธ๋ฐ๋ฐ ์ด๊ฑธ ์ํ๋๊ฑด์ง ๋ชจ๋ฅด๊ฒ ๋ค.
// - ์์ฑ๋ ์ธ์คํด์ค์์ state๊ฐ ๊ฐ๋ ๊ฐ๋ค์ ๋ฐ๋ก ์ ๊ทผํ ์ ์์ด์..?
// A. ๊ฐ ๋ณ๊ฒฝ์ด ์ผ์ด๋์ง ์๊ฒ ์ฒ๋ฆฌํ๋ ๊ฒ ๊ฐ๋ค.
Object.keys(state).forEach(key => Object.defineProperty(this, key, {
get: () => this.#state[key]
}
)
);
}
setState(newState) {
this.#state = {...this.#state, ...newState};
this.notify();
}
register(observer) {
this.#observers.add(observer);
}
// Q. notify์ ํ๋ผ๋ฏธํฐ๊ฐ ์ ๋ฌ๋์ง ์๋ ๋ถ๋ถ์ด ์ ์ดํด๊ฐ ์๋๋ค.
notify() {
this.#observers.forEach(observer => observer());
}
}
- Publisher: ๋ฐํ์
- setState(): ๋ฐํ์์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ณ ๊ตฌ๋ ์๋ฅผ ์คํํจ์ผ๋ก์จ ์ํ๋ณํ๋ฅผ ์๋ฆฐ๋ค.
- register(): ๊ตฌ๋ ์(๊ด์ฐฐ์)๋ฅผ ๋ฑ๋กํ๋ค.
- notify(): ๊ตฌ๋ ์(๊ด์ฐฐ์; ํจ์)๋ฅผ ์คํํ๋ค.
์ฌ๊ธฐ์ ํต์ฌ์ setState()๋ฅผ ํตํด ์ํ ๋ณ๊ฒฝ ์, notify()๊ฐ ์คํ๋๋ฉฐ ๊ตฌ๋ ์๋ค์ด ์คํ๋๋ค๋ ์ ์ด๋ค. ์ฆ,
๋ด๋ถ ์ํ๊ฐ ๋ณํ ๋ ๊ตฌ๋ ์์๊ฒ ์๋ฆฌ๋ ๊ฒ
2. Subscriber(๊ตฌ๋ )
class Subscriber {
#fn;
constructor(fn) {
this.#fn = fn;
}
subscribe(publisher) {
publisher.register(this.#fn);
}
}
- Subscriber: ๊ตฌ๋ ์
- #fn: Publisher ์ ์ฅ์์ observer ๋๋ค.
- subscribe(): publisher๋ฅผ ๋ฐ์์ observer๋ฅผ ๋ฑ๋กํ๋ค.
๋ฐํ๊ธฐ๊ด์ ๊ตฌ๋ ํ๋ค.
๋ฐํ๊ธฐ๊ด์์ ๋ณํ๊ฐ ์๊ฒผ์ ๋ ํ๋ ์ผ
์ ์ ์ํด์ผ ํ๋ค. ์ด๊ฒ ์ฌ์ค publisher์๊ฒ๋ observer๊ฐ ๋๋ค.
3. ์ ์ฉํ๊ธฐ
const initailState = {a: 10, b: 20};
// Q. ์ publisher๋ผ๊ณ ์ํ๊ณ ์ํ๋ผ๊ณ ํ์๊น..?
const publisher = new Publisher(initailState);
// Q-a. publisher๋ ์ธ๋ถ ๊ฐ์ฒด์ธ๋ฐ, ์ด๊ฑธ ์ฐธ์กฐํ๊ณ ์๋ ๊ฒ ๋ง๋..?
const addCalculator = new Subscriber(() => console.log(`a + b = ${publisher.a + publisher.b}`));
// Q-b. publisher๋ฅผ ๊ตฌ๋
ํ๋ ๊ฑด ์ฌ๊ธฐ์ ํ๋ฉด์..?
addCalculator.subscribe(publisher);
3. ๋ฆฌํฉํ ๋ง
์์ ์ฝ๋๋ฅผ ๋จ์ํ๊ฒ observable
๊ณผ observe
์ ๊ด๊ณ์๋ง ์ง์คํด์ ๋ค๋ค๋ณด์.
- observable์ observe์์ ์ฌ์ฉ๋๋ค.
- observable์ ๋ณํ๊ฐ ์๊ธฐ๋ฉด, observe์ ๋ฑ๋ก๋ ํจ์๊ฐ ์คํ๋๋ค.
1. Object.defineProperty ์ดํดํ๊ธฐ
MDN ๋ฌธ์์ ์ค๋ช ์ ์๋์ ๊ฐ๋ค.
๊ฐ์ฒด์ ์ง์ ์๋ก์ด ์์ฑ์ ์ ์ํ๊ฑฐ๋ ์ด๋ฏธ ์กด์ฌํ๋ ์์ฑ์ ์์ ํ ํ, ๊ทธ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
// ์ฌ๊ธฐ์ ๋ถํฐ ์๋๊น์ง์ ์ฝ๋๋ฅผ class ํ๋๋ผ๊ณ ์๊ฐํ๋ฉด ์ดํดํ๊ธฐ ํธํ๋ค.
let a = 10;
const state = {};
Object.defineProperty(state, 'a', {
get() {
console.log(`ํ์ฌ a์ ๊ฐ์ ${a} ์
๋๋ค.`);
return a;
},
set(value) {
a = value;
console.log(`๋ณ๊ฒฝ๋ a์ ๊ฐ์ ${a} ์
๋๋ค.`)
}
})
Object.defineProperty(targetObject, property, descriptor)
object
: ์์ฑ์ ์ ์ํ ๊ฐ์ฒดproperty
: ์๋ก ์ ์ํ๊ฑฐ๋ ์์ ํ๋ ค๋ ์์ฑ์ ์ด๋ฆ ๋๋ Symboldescriptor
: ์๋ก ์ ์ํ๊ฑฐ๋ ์์ ํ๋ ค๋ ์์ฑ์ ๊ธฐ์ ํ๋ ๊ฐ์ฒด
Object.defineProperty(targetObject, property, descriptor)
๋ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ๊ฑฐ๋ ๊ฐ์ฒด์ ์ด๋ค ๋ณํ๊ฐ ์๊ธธ ๋, ์ค๊ฐ์ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ํ์๋ฅผ ์ง์ด๋ฃ์ ์ ์๊ฒ ํด์ค๋ค.
2. ์ฌ๋ฌ ๊ฐ์ ์์ฑ ๊ด๋ฆฌํ๊ธฐ
const state = {
a: 10,
b: 20,
};
const stateKeys = Object.keys(state);
for (const key of stateKeys) {
let _value = state[key];
Object.defineProperty(state, key, {
get () {
console.log(`ํ์ฌ state.${key}์ ๊ฐ์ ${_value} ์
๋๋ค.`);
return _value;
},
set (value) {
_value = value;
console.log(`๋ณ๊ฒฝ๋ state.${key}์ ๊ฐ์ ${_value} ์
๋๋ค.`);
}
})
}
console.log(`a + b = ${state.a + state.b}`);
state.a = 100;
state.b = 200;
์์ ์ฝ๋์์ console.log
๋ง observer
๋ผ๋ ํจ์๋ก ๋ณ๊ฒฝํด๋ณด์.
const state = {
a: 10,
b: 20,
};
const stateKeys = Object.keys(state);
const observer = () => console.log(`a + b = ${state.a + state.b}`);
for (const key of stateKeys) {
let _value = state[key];
Object.defineProperty(state, key, {
get () {
return _value;
},
set (value) {
_value = value;
observer(); // setํ ๋ ์ด๋ค ํจ์๋ฅผ ์คํํ๊ฒ ํ๋ ๊ฒ์ด๋ค.
}
})
}
observer();
state.a = 100;
state.b = 200;
์ง๊ธ๊น์ง์ ๊ณผ์ ์ ์กฐ๊ธ ์ฝ๊ฒ ์ค๋ช ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ํ๋ฆ์ด๋ค.
- obj.a๋ก ์ด๋ค ๊ฐ์ ์ ๊ทผํ ๋(get), ์ค๊ฐ์ ๋ญ๊ฐ ์ด๋ค ์ฝ๋๋ฅผ ์คํํ๊ณ ์ถ์ ๊ฒ. ์ต์ ๋ฒ ํจํด์์๋ ์ด ์ค๊ฐ์ ๊ตฌ๋ ํ๋ ๋ฉ์๋(subscribe)๊ฐ ๋ค์ด๊ฐ๋ค.
- obj.a์ ์ด๋ค ๊ฐ์ ํ ๋นํ ๋(set), ์ค๊ฐ์ ๋ญ๊ฐ ์ด๋ค ์ฝ๋๋ฅผ ์คํํ๊ณ ์ถ์ ๊ฒ. ์ต์ ๋ฒ ํจํด์์๋ ์ด ์ค๊ฐ์ ์๋ฆผํ๋ ๋ฉ์๋(notify)๊ฐ ๋ค์ด๊ฐ๋ค.
- ๋ง์น ์ค๊ฐ์ ๊ณผ์ ์ ๊ฐ๋ก์ฑ์ ๋ฌด์ธ๊ฐ๋ฅผ ์คํํ๋ ๊ฒ. Proxy ํจํด๊ณผ ์ ์ฌํ๋ค.
3. ์ฌ๋ฌ ๊ฐ์ Observer ๊ด๋ฆฌํ๊ธฐ
let currentObserver = null;
const state = {
a: 10,
b: 20,
};
const stateKeys = Object.keys(state);
for (const key of stateKeys) {
let _value = state[key];
// Q. ์๋ observers๋ for ๋ฌธ ๋ฐ์์ ์ ์ธํด๋ ๋๋๋ฐ, ๊ตณ์ด ์ฌ๊ธฐ์ ํ ์ด์ ๊ฐ ์์๊น?
// A. for๋ฌธ ์์์ ๊ฐ๊ฐ์ key์ ๋ํ closer๋ก ๊ฐ๊ณ ์์ด์ผ ๊ฐ key๊ฐ get ๋์ ๋, ๋ณธ์ธ์๊ฒ ํด๋นํ๋ ํจ์์๋ง ์ ๊ทผํ๋ค.
// ๋ง์ฝ ๋ฐ์ ๋นผ๋๋ฉด ๋ชจ๋ key๊ฐ๋ค์ ๋ํ observers๊ฐ ๊ณต์ ๋๋ฏ๋ก ๊ทธ ์์ ๋ชจ๋ ํจ์๊ฐ ์คํ๋์ด๋ฒ๋ฆฐ๋ค.
const observers = new Set();
Object.defineProperty(state, key, {
get () {
if (currentObserver) observers.add(currentObserver);
return _value;
},
set (value) {
_value = value;
observers.forEach(observer => observer());
}
})
}
const ๋ง์
_๊ณ์ฐ๊ธฐ = () => {
currentObserver = ๋ง์
_๊ณ์ฐ๊ธฐ;
// ์๋์์ state.a๋ฅผ getํ๋ ์๊ฐ defineProperty์ get ๋ฉ์๋๊ฐ ์คํ๋๋ค.
console.log(`a + b = ${state.a + state.b}`);
}
const ๋บ์
_๊ณ์ฐ๊ธฐ = () => {
currentObserver = ๋บ์
_๊ณ์ฐ๊ธฐ;
console.log(`a - b = ${state.a - state.b}`);
}
๋ง์
_๊ณ์ฐ๊ธฐ();
// ์๋์์ state.a์ ์ด๋ค ๊ฐ์ ํ ๋นํ๋ ์๊ฐ defineProperty์ set ๋ฉ์๋๊ฐ ์คํ๋๋ค.
state.a = 100;
๋บ์
_๊ณ์ฐ๊ธฐ();
state.b = 200;
state.a = 1;
state.b = 2;
ํจ์๊ฐ ์คํ๋ ๋
currentObserver๊ฐ ์คํ์ค์ธ ํจ์๋ฅผ ์ฐธ์กฐํ๋๋ก ๋ง๋ ๋ค.
state
์ ํ๋กํผํฐ๊ฐ ์ฌ์ฉ๋ ๋(get์ ํธ์ถํ ๋),
4. ํจ์ํ
์์ ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉํ๊ธฐ ์ํด onserve
์ observable
ํจ์๋ก ๊ตฌํํด๋ณด์.
let currentObserver = null;
const observe = fn => {
currentObserver = fn;
// ์๋์์ fn์ด ์คํ๋ ๋, currentObserver๊ฐ ์กด์ฌํ๊ฒ ๋๋ฏ๋ก observers์ fn์ด ๋ฑ๋ก๋๋ค.
fn();
currentObserver = null;
}
const observable = obj => {
Object.keys(obj).forEach(key => {
let _value = obj[key];
const observers = new Set();
Object.defineProperty(obj, key, {
get () {
// get์ผ ๋๋ observers์ currentObserver๊ฐ ์ถ๊ฐ๋๊ธฐ๋ง ํ๋ค.
if (currentObserver) observers.add(currentObserver);
return _value;
},
set (value) {
_value = value;
// set์ผ ๋๋ observers์ ์๋ currentObserver๋ค์ด ํ๋์ฉ ํธ์ถ๋๋ค.
observers.forEach(fn => fn());
}
})
})
return obj;
}
์๋์ ๊ฐ์ด ์ฌ์ฉํ๋ค.
// Q. ์๋ ์ฝ๋ ๋ค์ ํ๋ฒ ์๊ฐํด๋ณด๊ธฐ
const ์ํ = observable({ a: 10, b: 20 });
observe(() => console.log(`a = ${์ํ.a}`));
observe(() => console.log(`b = ${์ํ.b}`));
observe(() => console.log(`a + b = ${์ํ.a} + ${์ํ.b}`));
observe(() => console.log(`a * b = ${์ํ.a} + ${์ํ.b}`));
observe(() => console.log(`a - b = ${์ํ.a} + ${์ํ.b}`));
์ํ.a = 100;
์ํ.b = 200;
4. DOM์ ์ ์ฉํ๊ธฐ
1. ์ผ๋จ ๊ตฌํํด๋ณด๊ธฐ
๊ตฌ์กฐ๋ ๋ค๋ก ํ๊ณ , ๊ธฐ๋ฅ๋ง ๊ตฌํํด๋ณด๊ธฐ.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Store๋ฅผ ์ ์ฉํด๋ณด์</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.js"></script>
</body>
</html>
src/main.js
import { observable, observe } from "./core/observer.js";
const state = observable({
a: 10,
b: 20,
});
const $app = document.querySelector('#app');
const render = () => {
$app.innerHTML = `
<p>a + b = ${state.a + state.b}</p>
<input id="stateA" value="${state.a}" />
<input id="stateB" value="${state.b}" />
`;
$app.querySelector('#stateA').addEventListener('change', ({ target }) => {
state.a = Number(target.value);
})
$app.querySelector('#stateB').addEventListener('change', ({ target }) => {
state.b = Number(target.value);
})
}
/* observe ํจ์๊ฐ ํธ์ถ๋๋ฉด render๋ ํธ์ถ๋๋ฉด์ ๊ทธ ์์ ์๋ state.a์ state.b๊ฐ getter๋ก ํธ์ถ๋ ๋
render ํจ์๊ฐ ๊ฐ๊ฐ์ observers์ ๋ฑ๋ก๋๋ค.
๊ทธ๋ฆฌ๊ณ #stateA์ #stateB์ ์ด๋ค ๊ฐ์ ์
๋ ฅํ๊ณ ๋ณํ๋ฅผ ๊ฐ์งํ๋ฉด ๋ฐ๋ก state.a์ state.b์ setter๊ฐ ํธ์ถ๋๋ฉด์
๊ทธ ์์ observer์ค ํ๋๋ก ์กด์ฌํ๋ render ํจ์๊ฐ ์คํ๋๋ฉด์ ui๋ฅผ ๋ณ๊ฒฝํ๋ค.
*/
observe(render);
2. Component๋ก ์ถ์ํํ๊ธฐ
src/core/Component.js
import { observable, observe } from './observer.js';
export class Component {
state;
props;
$el;
constructor ($el, props) {
// $el์ด ๋ถ๋ชจ ์๋ฆฌ๋จผํธ์ด๋ค. ํด๋น ์ปดํฌ๋ํธ๊ฐ ์ถ๊ฐ๋ target
this.$el = $el;
this.props = props;
this.setup();
}
setup() {
// ์ปดํฌ๋ํธ๋ง๋ค store๊ฐ ์๋ ๋๋์ด๋ค. ์ฌ๊ธฐ์ state๋ store๋ผ๊ณ ํ๋ ๊ฒ ์ข๋ ์์ฐ์ค๋ฝ์ง ์๋ ์ถ๋ค.
this.state = observable(this.initState()); // state๋ฅผ ๊ด์ฐฐํ๋ค.
observe(() => { // state๊ฐ ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ, ํจ์๊ฐ ์คํ๋๋ค.
this.render();
this.setEvent();
this.mounted();
});
}
initState() { return {} }
template () { return ''; }
render () { this.$el.innerHTML = this.template(); }
setEvent () {}
mounted () {}
}
์ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก src/App.js
์ Component
๋ฅผ ์ ์ฉํด๋ณด์.
import {Component} from "./core/Component.js";
export class App extends Component {
// constructor์ super๋ฅผ ์์์ ๋ฃ์ด์ค๋ค.
initState () {
return {
a: 10,
b: 20,
}
}
template () {
// ์ด ๋, this.state๋ ์ด๋ฏธ observable์ด ์ ์ฉ๋ ๊ฐ์ฒด์ด๋ค.
const { a, b } = this.state;
return `
<input id="stateA" value="${a}" size="5" />
<input id="stateB" value="${b}" size="5" />
<p>a + b = ${a + b}</p>
`;
}
setEvent () {
const { $el, state } = this;
$el.querySelector('#stateA').addEventListener('change', ({ target }) => {
// state.a์ ๊ฐ์ผ ํ ๋น๋ ๋(setter), `render, setEvent, mounted`๊ฐ ์คํ๋๋ค.
state.a = Number(target.value);
})
$el.querySelector('#stateB').addEventListener('change', ({ target }) => {
state.b = Number(target.value);
})
}
}
3. ๊ณ ๋ฏผํด๋ณด๊ธฐ
์ด๋ ๊ฒ Component ๋ด๋ถ์์ ๊ด๋ฆฌ๋๋ state(store ๋๋โฆ!)์ observable์ ์์์ ์ฌ์ฉํ ๊ฒฝ์ฐ, setState
๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์๊ณผ ํฌ๊ฒ ๋ค๋ฅด์ง ์๋ค๊ณ ๋๋ ์ ์๋ค.
setState() ๋ํ state๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค render
๋ฅผ ์คํํ๋ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ด๋ค.(์ง์ ์ฌ์ฉํ๋๋, setter๋ฅผ ํ์ ๋ ์คํํ๋๋์ ์ฐจ์ด ๋๋)
observer
๋ ์ด๋ ๊ฒ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ์ฌ์ฉํ๊ธฐ๋ณด๋จ์ค์ ์ง์ค์ ์ ์ฅ์
๋ฅผ ๊ด๋ฆฌํ ๋ ๋งค์ฐ ํจ๊ณผ์ ์ด๋ค!!!
4. ์ปดํฌ๋ํธ ์ธ๋ถ์ ์ํ ๋ง๋ค์ด์ฃผ๊ธฐ
์์ฃผ ๊ฐ๋จํ Store๋ฅผ ๋ง๋ค์ด์ ๊ด๋ฆฌํด๋ณด์.
src/store.js
import { observable } from './core/observer.js'
export const store = {
state: observable({
a: 10,
b: 20,
}),
setState (newState) {
for (const [key, value] of Object.entries(newState)) {
if (!this.state[key]) continue;
this.state[key] = value;
}
}
}
src/App.js
import { Component } from "./core/Component.js";
import { store } from './store.js';
const InputA = () => `
<input id="stateA" value="${store.state.a}" size="5" />
`;
const InputB = () => `
<input id="stateB" value="${store.state.b}" size="5" />
`
const Calculator = () => `
<p>a + b = ${store.state.a + store.state.b}</p>
`
export class App extends Component {
template () {
return `
${InputA()}
${InputB()}
${Calculator()}
`;
}
setEvent () {
const { $el } = this;
$el.querySelector('#stateA').addEventListener('change', ({ target }) => {
store.setState({ a: Number(target.value) });
})
$el.querySelector('#stateB').addEventListener('change', ({ target }) => {
store.setState({ b: Number(target.value) });
})
}
}
- InputA, InputB, Calculator 3๊ฐ์ ์ปดํฌ๋ํธ๊ฐ store๋ฅผ ์ฐธ์กฐํ๊ณ ์๊ณ , store๊ฐ ๋ณ๊ฒฝ๋์์ ๋ ์ปดํฌ๋ํธ๊ฐ ์๋์ผ๋ก ๋ ๋๋ง๋๋ค.
- ์ฌ๊ธฐ์
Flux ํจํด
์ ์ฌ์ฉํ๋ฉดRedux
๋Vuex
๊ฐ ๋๋ ๊ฒ์ด๋ค.
5. Flux Pattern
- Redux๋ Vuex์์ ์ฌ์ฉ๋๋ Flux ํจํด
๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ
Dispatcher -> Store
Store -> View
View -> Action
Action -> Dispatcher
๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ
์๋ฐ์ดํฐ์ ๋ณํ
๋ฅผ ํจ์ฌ ์์ธกํ๊ธฐ ์ฝ๊ฒ ๋ง๋ ๋ค.
๋ํ, ์ฐ๋ฆฌ๊ฐ ๋์ผ๋ก ๋ณด๋ UI์ ๋ฐ๋ผ ์ผ๋์ผ ๋์์ผ๋ก state์ ๋ณํ๋ฅผ ํ์ธํ ์ ์๋ค๋ฉด ์ผ๋ง๋ ์ข๊ฒ ๋๊ฐ?!
6. Vuex ๊ฐ์ Store ๋ง๋ค๊ธฐ
Vuex๊ฐ ์ด๋ค interface๋ฅผ ๊ฐ์ง๊ณ ์๋์ง ๊ฐ๋จํ๊ฒ ์ดํด๋ณด์.
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
});
- ์ฐธ๊ณ :
Redux reducer๊ณผ Vuex mutations์ ์ฐจ์ด
์ ๋ํ GPT์ ๋ต๋ณ
์ง๋ฌธ: redux reducer and vuex mutations are simillar, right?
๋ต๋ณ: Yes,Redux reducers and Vuex mutations are similar
in their purpose and functionality.Both Redux and Vuex are state management libraries
used in JavaScript applications, andreducers/mutations are used to modify the state in a predictable way.
Redux is commonly used in React applications, while Vuex is used in Vue.js applications.Both Redux reducers and Vuex mutations take the current state and an action/payload as input, and return a new state as output.
The new state is based on the current state and the action/payload passed to the reducer/mutation. The main difference between the two is in how they are defined and used. In Redux,reducers are pure functions that take the current state and an action, and return a new state. Reducers are combined together using the combineReducers function, and are called by the Redux store when an action is dispatched.
In Vuex, mutations are functions that directly modify the state.
They are defined as methods in the Vuex store, and are called using the commit method when an action is dispatched. Overall, while there are some differences in their implementation, Redux reducers and Vuex mutations serve a similar purpose and are used to achieve similar goals in state management.
- mutations: state๋ฅผ ๋ณ๊ฒฝ์ํจ๋ค.
store.commit('increment'); // state์ count ๊ฐ์ด 1 ์ฆ๊ฐํ๊ฒ ๋๋ค.
console.log(store.state.count); // 1
์ฆ, commit
์ผ๋ก mutations์ ๋ฉ์๋๋ฅผ ์คํํ๋ ๊ตฌ์กฐ์ด๋ค.
Vuex๋ ๊ฐ๋ ๋ง ์์๋ณด๊ณ ๊ตฌํ์ฌํญ์ ์๋ตํ๊ฒ ์ต๋๋ค.
7. Redux ๋ง๋ค๊ธฐ
๋ง์ฐฌ๊ฐ์ง๋ก Redux
๊ฐ ์ฌ์ฉ๋๋ ํํ๋ฅผ ์ดํด๋ณด์.
import { createStore } from 'redux'
/**
* ์ด๊ฒ์ด (state, action) => state ํํ์ ์์ ํจ์์ธ ๋ฆฌ๋์์
๋๋ค.
* ๋ฆฌ๋์๋ ์ก์
์ด ์ด๋ป๊ฒ ์ํ๋ฅผ ๋ค์ ์ํ๋ก ๋ณ๊ฒฝํ๋์ง ์์ ํฉ๋๋ค.
*
* ์ํ์ ๋ชจ์์ ๋น์ ๋ง์๋๋ก์
๋๋ค: ๊ธฐ๋ณธํ(primitive)์ผ์๋, ๋ฐฐ์ด์ผ์๋, ๊ฐ์ฒด์ผ์๋,
* ์ฌ์ง์ด Immutable.js ์๋ฃ๊ตฌ์กฐ์ผ์๋ ์์ต๋๋ค. ์ค์ง ์ค์ํ ์ ์ ์ํ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํด์๋ ์๋๋ฉฐ,
* ์ํ๊ฐ ๋ฐ๋๋ค๋ฉด ์๋ก์ด ๊ฐ์ฒด๋ฅผ ๋ฐํํด์ผ ํ๋ค๋ ๊ฒ์
๋๋ค. => ๋ถ๋ณ์ฑ
*
* ์ด ์์ ์์ ์ฐ๋ฆฌ๋ `switch` ๊ตฌ๋ฌธ๊ณผ ๋ฌธ์์ด์ ์ผ์ง๋ง,
* ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์ ๋ง๊ฒ
* (ํจ์ ๋งต ๊ฐ์) ๋ค๋ฅธ ์ปจ๋ฒค์
์ ๋ฐ๋ฅด์
๋ ์ข์ต๋๋ค.
*/
function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// ์ฑ์ ์ํ๋ฅผ ๋ณด๊ดํ๋ Redux ์ ์ฅ์๋ฅผ ๋ง๋ญ๋๋ค.
// API๋ก๋ { subscribe, dispatch, getState }๊ฐ ์์ต๋๋ค.
let store = createStore(counter)
// subscribe()๋ฅผ ์ด์ฉํด ์ํ ๋ณํ์ ๋ฐ๋ผ UI๊ฐ ๋ณ๊ฒฝ๋๊ฒ ํ ์ ์์ต๋๋ค.
// ๋ณดํต์ subscribe()๋ฅผ ์ง์ ์ฌ์ฉํ๊ธฐ๋ณด๋ค๋ ๋ทฐ ๋ฐ์ธ๋ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์๋ฅผ ๋ค์ด React Redux)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
// ํ์ง๋ง ํ์ฌ ์ํ๋ฅผ localStorage์ ์์์ ์ผ๋ก ์ ์ฅํ ๋๋ ํธ๋ฆฌํฉ๋๋ค.
store.subscribe(() => console.log(store.getState()))
// ๋ด๋ถ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ์ ์ผํ ๋ฐฉ๋ฒ์ ์ก์
์ ๋ณด๋ด๋ ๊ฒ๋ฟ์
๋๋ค.
// ์ก์
์ ์ง๋ ฌํํ ์๋, ๋ก๊น
ํ ์๋, ์ ์ฅํ ์๋ ์์ผ๋ฉฐ ๋์ค์ ์ฌ์คํํ ์๋ ์์ต๋๋ค.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด createStore
๊ฐ subscribe, dispatch, getState
๋ฑ์ ๋ฉ์๋๋ฅผ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ๊ฒ์ ์ ์ ์๋ค.
const createStore = (reducer) => {
return {subscribe, dispatch, getState};
}
์์ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๋จํ๊ฒ ๊ตฌํ์ ํด๋ณด์.
import { observable } from './observer.js';
export const createStore = (reducer) => {
// reducer๊ฐ ์คํ๋ ๋ ๋ฐํํ๋ ๊ฐ์ฒด(state)๋ฅผ observable๋ก ๋ง๋ค์ด์ผ ํ๋ค.
// Q. ์ฌ๊ธฐ์ state๋ฅผ observableํ๋ ๊ฒ ์๋ฏธ๊ฐ ์๋..???
// observable์ ์ ์ฉํ initialState๋ฅผ ๋ฐ๋ ๊ฑธ๋ก ์ดํดํ๋ฉด ๋ ๊น?
// ์ด๋ค ๊ฒ๋ observe ํ์ง ์๋๋ค๋ฉด, observable์ ์๋ฌด๋ฐ ์๋ฏธ๊ฐ ์์ง ์๋..?
const state = observable(reducer());
// getState๊ฐ ์ค์ state๋ฅผ ๋ฐํํ๋ ๊ฒ์ด ์๋๋ผ frozenState๋ฅผ ๋ฐํํ๋๋ก ๋ง๋ค์ด์ผ ํ๋ค.
// state์ ๋์ผํ ๋ด์ฉ์ด์ง๋ง, getter๋ง ๋๋ฉด์ state === frozenState๊ฐ false
const frozenState = {};
Object.keys(state).forEach(key => {
Object.defineProperty(frozenState, key, {
get: () => state[key], // get๋ง ์ ์ํ์ฌ set์ ํ์ง ๋ชปํ๋๋ก ๋ง๋๋ ๊ฒ์ด๋ค.
})
});
// dispatch๋ก๋ง state์ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ค.
const dispatch = (action) => {
const newState = reducer(state, action);
for (const [key, value] of Object.entries(newState)) {
// state์ key๊ฐ ์๋ ๊ฒฝ์ฐ ๋ณ๊ฒฝ์ ์๋ตํ๋ค.
if (!state[key]) continue;
state[key] = value;
}
}
const getState = () => frozenState;
// subscribe๋ observe๋ก ๋์ฒดํ๋ค.
return { getState, dispatch };
}
์์ createStore
๋ฅผ ์ฌ์ฉํ์ฌ store๋ฅผ ๋ง๋ค์ด๋ณด์.
src/store.js
import {createStore} from './core/Store.js';
// ์ด๊ธฐ state์ ๊ฐ์ ์ ์ํด์ค๋ค.
const initState = {
a: 10,
b: 20,
};
// dispatch์์ ์ฌ์ฉ๋ type๋ค์ ์ ์ํด์ค๋ค.
export const SET_A = 'SET_A';
export const SET_B = 'SET_B';
// reducer๋ฅผ ์ ์ํ์ฌ store์ ๋๊ฒจ์ค๋ค.
export const store = createStore((state = initState, action = {}) => {
switch (action.type) {
case 'SET_A' :
return { ...state, a: action.payload }
case 'SET_B' :
return { ...state, b: action.payload }
default:
return state;
}
});
/* ์๋์ ๊ฐ์ด reducer ํจ์๋ฅผ ์ ์ํด์ ์ ๋ฌํด๋ ์ข์๋ฏ
const reducer = (state, action) => {
switch (action.type) {
case 'SET_A' :
return { ...state, a: action.payload }
case 'SET_B' :
return { ...state, b: action.payload }
default:
return state;
}
}
*/
// reducer์์ ์ฌ์ฉ๋ action์ ์ ์ํด์ค๋ค.
export const setA = (payload) => ({ type: SET_A, payload });
export const setB = (payload) => ({ type: SET_B, payload });
์ด์ App์์ Store๋ฅผ ์ฌ์ฉํด๋ณด์.
src/App.js
import { Component } from "./core/Component.js";
import {setA, setB, store} from './store.js';
const InputA = () => `<input id="stateA" value="${store.getState().a}" size="5" />`;
const InputB = () => `<input id="stateB" value="${store.getState().b}" size="5" />`;
const Calculator = () => `<p>a + b = ${store.getState().a + store.getState().b}</p>`;
export class App extends Component {
template () {
return `
${InputA()}
${InputB()}
${Calculator()}
`;
}
setEvent () {
const { $el } = this;
$el.querySelector('#stateA').addEventListener('change', ({ target }) => {
// dispatch๋ฅผ ํตํด์ ๊ฐ์ ๋ณ๊ฒฝ์ํจ๋ค.(commit์ด๋ผ๊ณ ์คํ ์์)
store.dispatch(setA(Number(target.value)));
})
$el.querySelector('#stateB').addEventListener('change', ({ target }) => {
// dispatch๋ฅผ ํตํด์ ๊ฐ์ ๋ณ๊ฒฝ์ํจ๋ค.(commit์ด๋ผ๊ณ ์คํ ์์)
store.dispatch(setB(Number(target.value)));
})
}
}
์ด๋ ๊ฒ ์์ ๊ฐ์ด ๊ฐ๋จํ redux
๋ฅผ ๋ง๋ค์ด๋ณผ ์ ์๋ค.