class Contract {
constructor(name) {
this.name = name;
}
sign() {
const capturedName = this.name;
setTimeout(() => console.log("서명인: ", capturedName), 3000);
}
}
// const contract = new Contract("user");
contract.sign();
contract.name = "user2";
function createContract(name) {
const sign = () => {
setTimeout(() => console.log("서명인: ", name), 3000);
};
return {
sign,
};
}
const contract = createContract("user3")
contract.sign()
Class는 변수의 상태를 변경할 수 있는 여지가 있어서 안에 있는 메소드들이 영향을 받을 수 있다.
이를 방지하기 위해 closure를 사용해서 function scope안에 변수를 선언하는 방법이다.
sign은 선언된 시점의 lexical environment를 기억한다. 이떄 createContract("user3")을 호출했을 때 sign이 선언이 됨으로 name을 기억할 수 있다.
마찬가지로 리액트에서도 클래스 컴포넌트를 쓰게 되면 props 가 의도치 않게 변경될 위험이 있다.
따라서 요즘 리액트에서는 함수 컴포넌트를 권장한다.
- this 또한 class component에서는 bind를 사용해야할 때가 존재하지만 function component 에서는 this가 존재하지 않는다.
- class component는 생명주기에 따라 로직을 작성하게 된다. mount ,unmount 시점에 각각 고려해야할 것 들이 존재한다.
- function component 는 생명주기가 없고 한번 실행되면 끝이 나버린다.
상태 훅
function NameField() {
const name = "user1"
const handleChange = (e) => {
// name 참조 불가능
name = "user2"
console.log(name)
}
return <input value={name} onChange={handleChange}></input>
}
export default () => <NameField></NameField>
한번 렌더링된 NameField의 name의 값을 변경할 수 가 없다. state 같은것이 필요한 시점이다.
const MyReact = (function MyReact() {
let firstName;
let isInitialized = false;
function useName(initialValue = "") {
const { forceUpdate } = userForceUpdate();
if (!isInitialized) {
firstName = initialValue;
isInitialized = true;
}
const setFirstName = (value) => {
if (firstName === value) return;
firstName = value;
console.log("setFirstName = ", firstName)
forceUpdate();
};
console.log("firstName = " , firstName)
return [firstName, setFirstName];
}
function userForceUpdate() { // for rendering purpose
const [value, setValue] = React.useState(1);
const forceUpdate = () => setValue(value + 1);
return { forceUpdate };
}
return {
useName,
};
})();
export default MyReact;
useName을 innerfunction으로 즉시실행함수에서 return 하고 있다. useName이 선언된 시점의 lexicalscope이 저장이되니
useName function안의 setFirstName이 참조하는 firstName은 지속적으로 참조가 가능하다.
userForceUpdate는 re rendering을 위한 목적이지 여기서 중요한 부분이 아니다.
function NameField() {
const [firstName, setFirstName] = MyReact.useName("user1");
const handleChange = (e) => {
setFirstName(e.target.value);
console.log(firstName)
};
return <input value={firstName} onChange={handleChange}></input>;
}
export default () => <NameField></NameField>;
useName은 firstName, setFirstName 을 tuple로 반환을 하게 된다.
onChange에 setFirstName이 호출되고 name 값이 변경이되고 -> forceUpdate를 통해 re rendering이 진행된다.
하지만 현재로써는 변수 하나만 state로 관리가 되고 2개이상은 관리할 수 없다.
const MyReact = (function MyReact() {
let memorizedStates = [];
let isInitialized = [];
function useState(cursor ,initialValue = "") {
const { forceUpdate } = userForceUpdate();
if (!isInitialized[cursor]) {
memorizedStates[cursor] = initialValue;
isInitialized[cursor] = true;
}
const state = memorizedStates[cursor]
const setState = (nextState) => {
if (state === nextState) return;
memorizedStates[cursor] = nextState;
forceUpdate();
};
return [state, setState];
}
function userForceUpdate() {
// for rendering purpose
const [value, setValue] = React.useState(1);
const forceUpdate = () => setValue(value + 1);
return { forceUpdate };
}
return {
useState: useState,
};
})();
export default MyReact;
state 자료구조를 배열로 변경하고 cursor를 통해 여러개의 state를 관리할 수 있도록 변경했다.
const MyReact = (function MyReact() {
let memorizedStates = [];
let isInitialized = [];
function useState(cursor ,initialValue = "") {
const { forceUpdate } = userForceUpdate();
if (!isInitialized[cursor]) {
memorizedStates[cursor] = initialValue;
isInitialized[cursor] = true;
}
const state = memorizedStates[cursor]
const setState = (nextState) => {
if (state === nextState) return;
memorizedStates[cursor] = nextState;
forceUpdate();
};
return [state, setState];
}
function userForceUpdate() {
// for rendering purpose
const [value, setValue] = React.useState(1);
const forceUpdate = () => setValue(value + 1);
return { forceUpdate };
}
return {
useState: useState,
};
})();
export default MyReact;
2개이상의 state를 관리하기가 용이해졌다.
const MyReact = (function MyReact() {
const memorizedStates = [];
const isInitialized = [];
let cursor = 0;
function useState(initialValue = "") {
const { forceUpdate } = userForceUpdate();
if (!isInitialized[cursor]) {
memorizedStates[cursor] = initialValue;
isInitialized[cursor] = true;
}
const state = memorizedStates[cursor];
const setStateAt = (_cursor) => (nextState) => {
if (state === nextState) return;
memorizedStates[_cursor] = nextState;
console.log("_cursor =", _cursor, nextState);
forceUpdate();
};
const setState = setStateAt(cursor);
console.log(cursor, state);
cursor += 1;
return [state, setState];
}
function userForceUpdate() {
// for rendering purpose
const [value, setValue] = React.useState(1);
const forceUpdate = () => {
setValue(value + 1);
cursor = 0
};
return { forceUpdate };
}
return {
useState: useState,
};
})();
export default MyReact;
하지만 사용하는쪽에서 cursor를 굳이 주입해줄 필요없이 react hook에서 관리하는 쪽이 낫다.
forceUpdate를 통해 re rendering 될때마다 MyReact.useState 가 매번 재호출된다. 이때마다 cursor는 처음 주입된 순서를 유지해야한다.
현재 2개 이니 0, 1 이런식으로 re rendering 될 때마다 cursor의 값이 0, 1이 되어야한다.
이를 위해 setState에서 _cursor를 사용하게 함으로 closure에 lexical scope에 setState가 선언될때 주입받은 값을 저장하도록 한다.
또한 setStateAt(cursor) 에서 cursor의 값이 re rendering 될 때마다 증가하면 안됨으로 re render를 해주는 forceUpdate 시점에서 cursor =0 으로 하여 0,1 이 유지 되도록 한다.
useState의 역할은 함수 컴포넌트 안에서 지속할 수 있는 값, 상태를 관리할 수 있는 방법을 제공한다.
뿐만 아니라 화면을 re render 하는 역할도 한다.
이러한 훅을 쓸떄는 함수 컴포넌트 본문 최상단에서 호출해야한다. 컴포넌트가 실행될 때 훅이 순서대로 호출되어야만 리액트는 훅이 사용하는 상태를 배열에서 제대로 찾을 수 있기 때문이다.
조건문 안에 위치해서 실행되지않는 경우가 생기면 리액트는 state를 제대로 관리 할 수 없다.
'FrontEnd > [리액트 2부] 고급 주제와 훅' 카테고리의 다른 글
[3.5장 컨텍스트 훅] , custom hook (0) | 2024.03.16 |
---|---|
[3.2장 상태 훅] (0) | 2024.03.12 |
[2.4장 다이얼로그1][2.5장 다이얼로그2] (0) | 2024.03.07 |
[2.2장 라우터 1] [2.3장 라우터 2] (0) | 2024.03.04 |
[2.1장 컨텍스트] (0) | 2024.03.01 |