DEV Community

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

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

Nantipat
大蛇丸 mode 🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀
・2 min read

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

⚛️ 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

Discussion (0)