DEV Community

Cover image for ทำความเข้า useEffect ตั้งแต่เริ่มต้น
Nantipat
Nantipat

Posted on

ทำความเข้า useEffect ตั้งแต่เริ่มต้น

บันทึกความเข้าใจจาก

⚛️ React ไปวันๆ EP.2 - ทำความเข้า useEffect ตั้งแต่เริ่มต้น

History

Screenshot_7

react v16.8 มีการเอา hook เข้ามา

Why so popular

ก่อนหน้าที่ hook เขามาเราเก็บ Logic ใน class
Screenshot_8

ก่อนหน้าที่ 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 ถูกเรียกทุกครั้ง

Screenshot_9

Screenshot_10

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>; } 
Enter fullscreen mode Exit fullscreen mode

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>; } 
Enter fullscreen mode Exit fullscreen mode

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; } 
Enter fullscreen mode Exit fullscreen mode

เมื่อกดปุ่ม 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; } 
Enter fullscreen mode Exit fullscreen mode

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; } 
Enter fullscreen mode Exit fullscreen mode

แต่เดียวก่อน ... 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(); } } 
Enter fullscreen mode Exit fullscreen mode

CodeSanbox

Top comments (0)