state
: 리덕스에서 저장하고 있는 상태 값(데이터)render
: state를 기반으로 화면에 렌더링한다.dispatch
: dispatch가 subscribe에 등록된 구독자(함수)들을 모두 호출한다.store에 있는 state에 직접 접근할 수 없으므로
getState
로 값을 가져오고,subscribe
: 값이 변경됐을 때 구동될 함수들을 등록해준다.reducer
를 통해 state 값을 변경한다.function reducer(state, action) {
console.log(state, action);
if (state === undefined) { // 초기 state값 설정
return { color: "yellow" };
}
}
var store = Redux.createStore(reducer); // store 변수를 전역으로 선언
console.log(store.getState()); // store 안에 있는 state의 값을 가져온다.
store의 dispatch를 호출하면 dispatch는 reducer 함수를 호출한다.
(이 때, reducer는 이전의 state 값과 action을 인자로 가진다.)
Object.assign({}, state, {color:'red'});
빈 객체를 복사하고, state 값을 넣어준 후(복제한 후) 새로운 값을 넣어준다.var newState;
if(action.type === 'CHANGE_COLOR') {
newState = Object.assign({}, state, {color:'red'}); // 완전히 독립된, 복제된 결과들이 return 된다.
}
return newState;
function red() {
var state = store.getState();
document.querySelector("#red").innerHTML = `
<div class="container" id="component_red" style="background-color:${state.color}">
<h1>red</h1>
<input type="button" value="fire">
</div>
`;
}
store.subscribe(red); // state 값이 바뀔 때마다 red 함수가 호출된다.
red();
Redux가 없는 코드는 각각의 component(부품) 간의 의존성이 너무 높다.
예를 들어, 특정 component의 코드를 수정하거나 삭제하면(Red 삭제), 다른 component와(Blue, Green) 연결된 코드가 얽혀있어 오류가 발생한다. 그러므로 component 추가, 수정, 삭제 시, 기존의 component를 모두 수정해야 한다.
하지만, Redux를 통해 중앙 집중형 관리를 하면 각각의 component는 action(상태가 바뀌었다는 것)을 store에 dispatch(통보)하면 된다.
이에 따른 자신의 변화를 작성 후, store에 subscribe(구독)하면 state가 바뀔 때마다 통보받기 때문에 다른 component와의 연결 없이 자신의 모양을 자유롭게 바꿀 수 있다. 즉, 수정해도 다른 component들은 영향을 받지 않게 된다.
요약하자면, Redux를 통해 각 Module의 독립성을 보장받을 수 있다.
Redux devtools 도구의 활용
Redux devtools를 사용하여 스토어에 전달한 action들을 리플레이할 수 있다.
즉, time traveling이 가능하다.
또한 사용자가 애플리케이션 사용 중에 문제가 발생할 경우, 해당 상태를 다운로드하여 문제를 파악할 수 있다.
var store = Redux.createStore();
function reducer(state, action) {
if (state === undefined) {
return {
content: [{...}, {...}]
}
}
}
var store = Redux.createStore(reducer);
function TOC() {
var state = store.getSate();
var i = 0;
var liTags = "";
while (i < state.contents.length) {
liTags = liTags + `
<li>
<a href="${state.contents[i].id}">${state.contents[i].title}</a>
</li>
`;
i = i + 1;
}
document.querySelector("#toc").innerHTML = `
<nav>
<ol>
${liTags}
</ol>
</nav>
`;
}
var newState;
if (action.type === 'SELECT') {
newState = Object.assign({}, state, {selected_id: action.id}
}
return newState;
}
<li>
<a onClick="event.preventDefault();
var action = { type: 'SELECT', id: ${state.content[i].id}}
store.dispatch(action)
" href="${state.contents[i].id}">
${state.contents[i].title}
</a>
</li>
dispatch가 subscribe한 함수들을 호출하면,
그에 따라 render가 동작하고 state값을 참조해 UI를 새로 그리게 된다.
(subscribe를 통해서 UI를 자동으로 업데이트)
즉, subscribe에 특정 함수를 전달해주면, 액션이 디스패치 되었을 때마다 전달해준 함수가 호출된다.
function artice() {
var state = store.getState();
var i = 0;
var atitle, aDesc;
whiel (i < state.contents.length) {
if (state.contents[i].id === state.selected_id) {
aTitle = state.contents[i].title;
aDesc = state.contents[i].desc;
break;
}
}
document.querySelector("#content").innerHTML = `
<article>
<h2>${aTitle}</h2>
${aDesc}
</article>
`;
}
...
//
store.subscribe(article);
// submit 버튼 제어
function control() {
<ul>
<li><a onclick="
event.preventDefault();
store.dispatch({
type: 'CHANGE_MODE',
mode: 'create'
})
" href="/create">create</a>
</li>
</ul>
}
// dispatch 함수
function article() {
var state = store.getState();
if (state.mode === "create") {
document.querySelector("#content").innerHTML = `
<article>
<form onsubmit="
var _title = this.title.value;
var _desc = this.desc.value;
store.dispatch({ type:'CREATE', title: _title, desc: _desc })
">
...
</form>
</article>
}
// reducer 함수
function reducer(state, action) {
if (state === undefined) {
return {
max_id: 2,
mode: "create",
contents: [
{ id: 1, title: "HTML", desc: "HTML is ..." },
],
}
}
// 새로운 state 생성
var newState;
if (action.type) === "CREATE") {
var newMaxId = state.max_id + 1;
var newContents = state.contents.concat();
newContents.push({
id: newMaxId,
title: action.title,
desc: action.desc,
});
}
// console.log(action, state, newState);
return newState;
}
// store 생성
var store = Redux.createStore(reducer);
// subscribe를 통한 자동 UI 업데이트
store.subscribe(article);
Create, Read, Delete 기능 구현
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.js"></script>
</head>
<body>
<div id="subject"></div>
<div id="toc"></div>
<div id="control"></div>
<div id="content"></div>
<script>
function subject() {
document.querySelector("#subject").innerHTML = `
<header>
<h1>WEB</h1>
Hello, WEB!
</header>
`;
}
function TOC() {
// TOC: Table Of Content (글 목록)
var state = store.getState();
var i = 0;
var liTags = "";
while (i < state.contents.length) {
// <li> 태그 안에 action을 만들고, dispatch로 action값을 전달해준다.
liTags =
liTags +
`
<li>
<atoken interpolation">${state.contents[i].id} }
store.dispatch(action);
" href="${state.contents[i].id}">
${state.contents[i].title}
</a>
</li>`;
i = i + 1;
}
document.querySelector("#toc").innerHTML = `
<nav>
<ol>
${liTags}
</ol>
</nav>
`;
}
function control() {
document.querySelector("#control").innerHTML = `
<ul>
<li><a href="/create">create</a></li>
<li><input onclick ="
store.dispatch({
type: 'DELETE',
})
" type="button" value="delete"></input></li>
</ul>
`;
}
function article() {
var state = store.getState();
if (state.mode === "create") {
// 글 작성 기능
document.querySelector("#content").innerHTML = `
<article>
<form>
<p>
<input type="text" name="title" placeholder="title">
</p>
<p>
<textarea name="desc" placeholder="description"></textarea>
</p>
<p>
<input type="submit">
</p>
</form>
</article>
`;
} else if (state.mode === "read") { // 읽기 기능
var i = 0;
var aTitle, aDesc;
while (i < state.contents.length) {
if (state.contents[i].id === state.selected_id) {
aTitle = state.contents[i].title;
aDesc = state.contents[i].desc;
break;
}
i = i + 1;
}
document.querySelector("#content").innerHTML = `
<aritcle>
<h2>${aTitle}</h2>
${aDesc}
</article>
`;
} else if (state.mode === "welcome") {
// 글 삭제 기능
document.querySelector("#content").innerHTML = `
<aritcle>
<h2>Welcome</h2>
Hello, Redux!
</article>
`;
}
}
// reducer 함수
function reducer(state, action) {
if (state === undefined) {
// state의 초기값 설정
return {
max_id: 2,
mode: "welcome",
selected_id: 1,
contents: [
{ id: 1, title: "HTML", desc: "HTML is ..." },
{ id: 2, title: "CSS", desc: "CSS is ..." },
],
};
}
// 새로운 state 생성
var newState;
if (action.type === "SELECT") {
newState = Object.assign(
// 기존 state를 복제 후, 새로운 state를 추가한다.
{},
state,
{ selected_id: action.id, mode: "read" }
);
} else if (action.type === "CREATE") {
var newMaxId = state.max_id + 1;
var newContents = state.contents.concat(); // 배열을 복사할 때는 assign 대신 concat을 사용한다.
newContents.push({
id: newMaxId,
title: action.title,
desc: action.desc,
});
newState = Object.assign({}, state, {
max_id: newMaxId,
contents: newContents,
mode: "read",
});
} else if (action.type === "DELETE") {
var newContents = [];
var i = 0;
while (i < state.contents.length) {
if (state.selected_id !== state.contents[i].id) {
newContents.push(state.contents[i]);
}
i = i + 1;
}
newState = Object.assign({}, state, {
contents: newContents,
mode: "welcome",
});
} else if (action.type === "CHANGE_MODE") {
newState = Object.assign({}, state, {
mode: action.mode,
});
}
// console.log(action, state, newState);
return newState;
}
// store 생성 (전역)
var store = Redux.createStore(reducer);
// state 값이 바뀌면 해당 함수가 호출되며, 자동으로 UI가 업데이트 된다.
store.subscribe(article);
store.subscribe(TOC);
subject();
TOC();
control();
article();
</script>
</body>
</html>
여태까지 눈팅중이었는데 미니 프로젝트 때 보다 한층 더 발전된 모습을 보여주고 계시는 것 같아 뿌듯합니다. 잘 보고 가요 누나!