บันทึกความเข้าใจจาก
⚛️ React ไปวันๆ EP.2 - ทำความเข้า useEffect ตั้งแต่เริ่มต้น
History
react v16.8 มีการเอา hook เข้ามา
Why so popular
ก่อนหน้าที่ hook เขามาเราเก็บ Logic ใน class
ก่อนหน้าที่ hook เข้ามาเราไม่สามารถเขียน state (logic)(ไม่มี life cycle) ให้อยู่ใน function component ได้
มี 2 patterns ในการ share logic
- Hight Order Component
- Render Function
ปัญหา
แต่ก็มีปัญหาต่อมาคือ Hight Order component Hell
มีการครอบ component หลายชั้นRender Function Hell
useEffect คือ?
let us run/cleanup a side-effect that synchronizes with some variables
Hook mental model
แต่ละการ render ของ functional component คือ 1 snapshot
Component
- Function => symchronnization,Immutable state
- Class => Life cycle Mutable state(เปลี่ยนกระทันหันไม่ได้) (ดูนาที 35.00)
Cleanup pitfalls
- cleanup ถูกเรียกครั้งเดียวเมื่อ component unmounted จาก Dom
- cleanup ถูกเรียกเมื่อ state changed
สิ่งที่กล่าวมาข้างบนคือ ผิด
จริงๆ แล้วสำหรับ useEffect cleanup ถูกเรียกทุกครั้ง
Dom จะ painted ก่อนแล้ว clearup ถึงจะ run
Dendency array
We don't want the effect to run on every render
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => {
console.log("cleanup");
clearInterval(id);
};
}, []);
return <h1>{count}</h1>;
}
code ด้านบนมันควรจะแสดง 1,2,3,4,5,6... (นาทีที่ 7.40)
แต่มันแสดงแค่ 1
การทำงานมันคือ useEffect
ทำงานแค่ครั้งเดียวแม้ว่า count จะเปลี่ยนก็ตาม
ที่นี้ลองใส่ count
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => {
console.log("cleanup");
clearInterval(id);
};
}, [count]);
return <h1>{count}</h1>;
}
Type dependency array
function Counter() {
const [count, setCount] = useState(0);
const [params, setParams] = useState({ params: "test" });
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>increase count</button>
<Child query={[params]} />
</div>
);
}
function Child({ query }) {
useEffect(() => {
console.log("I should see this once!", query);
}, [query]); // Object.is
return null;
}
เมื่อกดปุ่ม increse cout แล้ว function Child มันดันทำงานด้วยเพราะ ทุกการ re-render คือการสร้าง object param:"test"
ขึ้นมาใหม่ และ referrence ไม่เหมือนกัน
จะแก้ยังไง?
กลับไปใช้ useCompareEffect
### เจอแบบ Object มาแล้วถ้าเป็น function ละ
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>increase count</button>
<Child query={{ params: "test" }} />
</div>
);
}
const useDeepCompareCallback = () => {}
function Child({ query }) {
function fetchData(){
console.log("Api called with",query )
}
useEffect(() => {
fetchData();
}, [fetchData]); // this is correct
return null;
}
function fetchData()
ใช้ useCompareEffect
ไม่ได้ผล
วิธีแก้
- แบบง่ายก็ย้าย
fetchData()
เข้าไปในuseDeepCompareEffect()
- เราต้องทำให้
fetchData()
มันไม่ได้เปลี่ยนเวลา มี การ re-render เราเลยต้องใช้useCallBack
(นาทีที่20)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>increase count</button>
<Child query={{ params: "test" }} />
</div>
);
}
const useDeepCompareCallback = () => {}
function Child({ query }) {
const fetchData = useCallback(() => {
console.log("Api called with", query);
}, [query]); // Object.is
useEffect(() => {
fetchData();
}, [fetchData]); // this is correct
return null;
}
แต่เดียวก่อน ... useCallback()
ก็ยังจะต้องการ dependency อยู่ ไปดูวิธีแก้ที่ (ดูนาที22.28)
ใช้ useReducer แก้ปัญหา useState เยอะแล้วจะมั่ว
dispatch, setState จะไม่เปลี่ยน ref เวลา re-render
const initialState = {
count: 0,
step: 1
};
function Counter() {
// dispatch, setState, ref
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: "tick" });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
return (
<>
<h1>{count}</h1>
<input
value={step}
onChange={e => dispatch({ type: "step", step: Number(e.target.value) })}
/>
</>
);
}
function reducer(state, action) {
const { count, step } = state;
if (action.type === "tick") {
return { count: count + step, step };
} else if (action.type === "step") {
return { count, step: action.step };
} else {
throw new Error();
}
}
Top comments (0)