State and Lifecycle
올것이 왔다....!
State 올바르게 사용하기
React에서는 State를 직접 수정하면 안된다.
setState 방식을 사용하지 않고 state를 직접 수정한다면 컴포넌트를 다시 렌더링하지 않는다.
띠용!
여기서 우리가 state를 구현할 때 신경 쓸 사항이 하나 생겼다.
setState를 사용해야지만 리렌더링을 해주면 된다!
State 업데이트는 비동기적일 수 있다.
우리가 사용하는 state는 비동기적으로 업데이트를 할 수 있다.
그러므로 setState로 값을 지정해줄 때 자신의 값에 의존하면 안된다.
const [state, setState] = useState(1);
const func = () => {
setState(state + 1)
}
만약 비동기적으로 어디선가 setState를 업데이트를 한다고 생각하자!
그러면 + 1을 했을때, 2가 아닌 무언가의 값이 될 수 있다.
자 이것을 토대로 State를 만들기 전에 우선 자식 Element를 렌더링하는 방식을
Text가 아닌 Element 단위로 먼저 수정을 하자...!
구현
기존 방식의 경우 Text로 랜더링을 하는데, 그렇게 되면 리랜더링이 발생할 경우 모든 컴포넌트를
리랜더링하는 방법 말고는 해결 방법이 없다.
let domParser = new DOMParser();
const CrazyReactDOM = (function () {
let rootDom;
return {
createRoot(targetNode) {
rootDom = targetNode;
return this;
},
render(node) {
node.rootRender({}, rootDom);
},
createNode(Component) {
const component = Component;
let element = null;
return {
//
createElement: function (props) {
const childrenNode = [];
const rowElement = Component(props, childrenNode);
const originElement = domParser
.parseFromString(rowElement, "text/html")
.querySelector("body").children;
element = originElement[0];
let domArr = element.outerHTML.split("$NODE");
let textDom = "";
for (let i = 0; i < domArr.length; i++) {
if (i !== 0) textDom += "<div data-children='true'></div>";
textDom += domArr[i];
}
element.innerHTML = textDom;
const elementChildren = element.querySelectorAll(
`[data-children='true']`
);
for (let i = 0; i < elementChildren.length; i++) {
const child = elementChildren[i];
const parent = child.parentElement;
parent.appendChild(childrenNode[i]);
child.remove();
}
return element;
},
render: function (props, parentArr) {
const element = this.createElement(props);
parentArr.push(element);
setTimeout(() => {
console.log("???");
element.innerHTML = "<div></div>";
}, 10000);
return "$NODE";
},
rootRender: function (props, rootDom) {
const element = this.createElement(props);
rootDom.appendChild(element);
},
};
},
};
})();
export default CrazyReactDOM;
핵심은 createElement이다.
const originElement = domParser
.parseFromString(rowElement, "text/html")
.querySelector("body").children;
우선 현재 컴포넌트의 모든 HTML 텍스트를 받아오기 위해서 originElement를 만들어준다.
만약 자식 컴포넌트가 있다면
<div>
<div>
$NODE
$NODE
</div>
$NODE
</div>
형식으로 자식 컴포넌트 자리에 $NODE 텍스트를 넣어준다.
추후 해당 자리에 자식 컴포넌트를 랜더링할 목적이 있다.
let domArr = element.outerHTML.split("$NODE");
let textDom = "";
for (let i = 0; i < domArr.length; i++) {
if (i !== 0) textDom += "<div data-children='true'></div>";
textDom += domArr[i];
}
element.innerHTML = textDom;
이곳에서 $NODE 텍스트 자리에 자식 컴포넌트를 만들어주기 위해서 임시로
data-children='true'를 가지고 있는 div를 넣어준다.
결국 자식 컴포넌트를 만들어줄 자리에 어떻게든 컴포넌트를 만들어야지 자식 컴포넌트와
연결을 할 수 있기 때문이다.
정말 단순한 방식인데, 사실 이부분을 고민한다고 2일이 걸였다.... ㅠㅠ
const elementChildren = element.querySelectorAll(
`[data-children='true']`
);
for (let i = 0; i < elementChildren.length; i++) {
const child = elementChildren[i];
const parent = child.parentElement;
parent.appendChild(childrenNode[i]);
child.remove();
}
이제 임시로 만든 컴포넌트를 지우고 현재 컴포넌트를 넣어주는 작업을 한다.
먼저 앞서 넣어둔 data-children='true' 가진 태그를 전부 가져온다.
그리고 그 태그의 부모에 실제 자식 컴포넌트를 넣어주고 임시로 만든 컴포넌트는 삭제한다.
삭제를 하지 않으면 현재 컴포넌트의 부모 컴포넌트에서 다시 자식 컴포넌트를 넣을 때
querySelector에 같이 검출이 되어버린다.
이렇게하면 결국 현재 컴포넌트는 자식 컴포넌트를 텍스트가 아닌 실제 Element 형식으로
포함하게 되었다.
리액트에서는 정말 간단하고 쉽게 이루어진 작업을 실제로 구현하기 위해선
이렇게 복잡하게 생각해야 한다... ( 내가 실력이 부족한 부분도 있지만.... ㅠㅠ )
이로써! 리액트처럼 구동하는 것을 실제로 만들 수 있는 희망이 생겼다!
아주 커다란 산을 넘었다!!!!!!!