Skip to main content

React ถ้าใช้ useEffect ควรเขียน Cleanup functions วิธีใช้งานแบบผิด ๆ ที่มือใหม่อาจจะพลาด

Kongvut Sangkla

Intro

  • StrictMode problems
  • useEffect and deps problems
  • Primitive and Non-Primitive
  • Cleanup function

Basic of useEffect

ตามปกติแล้ว useEffect() คือ React Hooks ตัวหนึ่งที่จะทำงานโดยขึ้นอยู่กับ Dependency (deps)

นั่นหมายความว่า ถ้าค่าของ deps มีการเปลี่ยนแปลง useEffect ก็จะทำงาน

Live Editor
Result
Loading...

Primitive and Non-Primitive data types

วิธีด้านบนเป็นการใช้งาน useEffect แบบพื้นฐานที่ถูกต้อง (แต่ก็ยังไม่ใช่วิธีที่ดีที่สุด) แต่ก่อนที่จะมาดูวิธีที่ดีที่สุด มาทำความเข้าใจประเภทของตัวแปรแบบ Primitive and Non-Primitive data types กันก่อน

Primitive

  • string
  • number
  • booleans
  • null
  • undefined
  • bigint
  • symbol

Non-Primitive

  • object
  • arrays
  • function

Primitive vs. Non-Primitive

Primitive

  • Primitive values มีคุณสมบัติ immutable
  • Primitive สามารถใช้ values compared ด้วย value
  • Primitive Data types เป็น predefined
  • Primitive Data types จะมีการกำหนด values

Non-Primitive

  • Non-Primitive values มีคุณสมบัติ mutable
  • Non-Primitive เก็บ values ด้วย Address
  • Non-Primitive สามารถ compare ด้วย reference
Primitive vs. Non-Primitive
//Primitive data types String, number, booleans, null, undefined
const a = "Pikachu"
const b = "Pikachu"
a === b //true
a === "Pikachu" //true

"Pikachu" === "Pikachu" //true
const c = 1
const d = 1
c === d //true
d === 1 //true

true === true //true
false === false //true
null === null //true
undefined === undefined //true
null === undefined //false

//Non-Primitive Object, arrays
const x = { name: "Pikachu" }
const y = { name: "Pikachu" }
x === y //false

const z = y
z === y //true

[] === []//false
[1] === [1]//false
1 === 1//true

Best practices How to use useEffect

จากหัวข้อที่แล้ว Basic of useEffect ทำให้เรารู้ว่า useEffect ทำงานขึ้นอยู่กับค่า Dependency (deps) แต่ deps ก็เป็นได้ทั้ง Primitive and Non-Primitive data types ที่นี้มาดูวิธีการใช้ useEffect ที่ดีที่สุด

Live Editor
Result
Loading...
tip

สรุปก็คือ การกำหนดค่า Dependency ให้กับ useEffect ควรเป็น Data type แบบ

  • Primitive จะดีที่สุด เพื่อมั่นใจว่า useEffect จะทำงานได้ถูกต้องที่ควรจะเป็น
  • Non-Primitive ก็สามารถใช้ได้ (แต่ต้องมีคุณบัติ Memoized ด้วย)

อ่านเนื้อหาเพิ่มเติมเกี่ยวกับการใช้งาน useMemo และ useCallback อย่างละเอียดได้ที่นี่ "การใช้งานและความแตกต่างระหว่าง useMemo และ useCallback ของ React Hooks"

useEffect with Cleanup function

ในตัวอย่างด้านล่างนี้จะเป็นการใช้งาน useEffect และเพิ่ม Cleanup function

Live Editor
Result
Loading...

How cleanup function work

ลำดับการทำงาน

  1. Wait! before running the effect
  2. Okey done! You can run
  3. useEffect runs!
  4. useEffect runs2!
  5. useEffect runs3!
Live Editor
Result
Loading...

When should we use the cleanup function?

เมื่อใดที่เราควรใช้ cleanup function

  • ใช้ทำ Unsubscribed หลักการคือสร้างตัวแปรเพื่อกำหนดสถานะการทำงาน จากนั้น return สถานะที่ตรงข้ามกับสถานะเริ่มต้น
  • ใช้เพื่อ Abort request (Cancelling) อื่น ๆ ที่ยังทำงานอยู่ เช่น เมื่อเรากดไปที่หน้าเพจใหม่ก็ให้ยกเลิก requests ก่อนหน้า (หมายถึงถ้า request นั้นยังทำงานไม่เสร็จ)

Unsubscribed

() => {
//Cleanup with Unsubscribed
useEffect(() => {
let subscribed = true

// Some work/process function
(() => {
// Process with some logic
// ...
// and then check if subscribed === true
if (subscribed) {
console.log("work is ready!")
//setData(data)
//console.log(data)
}
})()

// Cleanup function
// and set subscribed = false
return () => {
console.log("cancelled!")
subscribed = false
}
}, [])

return (
<>
...
</>
)
}

Abort request

Abort - With Fetch()

Cleanup function ด้วยวิธี Abort request กับ Fetch() ซึ่งเป็น Native method ของ JS

() => {
const [posts, setPosts] = useState({})

useEffect(() => {
// Create AbortController and get signal
const controller = new AbortController()
const signal = controller.signal

// fetch with signal
fetch(`https://jsonplaceholder.typicode.com/posts`, { signal })
.then((res) => res.json())
.then((data) => {
setPosts(data)
})
.catch((err) => {
if (err === "AbortError") {
console.log("Request canceled!")
} else {
//todo:handle error
}
})

// Cleanup function with controller abort
return () => {
controller.abort()
}
}, [])

return (
<>
...
</>
)
}

Abort - With Axios

Cleanup function ด้วยวิธี Abort request กับ Library ของ Axios

import axios from "axios"

() => {
const [posts, setPosts] = useState({})

useEffect(() => {
// Create AbortController and get signal
const controller = new AbortController()

//axios.get() with signal config
axios
.get(`https://jsonplaceholder.typicode.com/posts`, {
signal: controller.signal,
})
.then((res) => {
setPosts(res.data)
})
.catch((err) => {
if (axios.isCancel(err)) {
console.log("Request canceled!")
} else {
//todo:handle error
}
})

// Cleanup function with controller abort
return () => {
controller.abort()
}
}, [])

return (
<>
...
</>
)
}

Summary

สรุปคือ

  • React StrictMode จะเกิด Re-render 2 ครั้ง ถ้าใช้ UseEffect ก็จะถูกเรียก 2 ครั้ง เป็นเรื่องปกติ
  • ขณะใช้ StrictMode การเขียน Cleanup function จะสามารถแก้ไขปัญหาและทำให้ UseEffect ทำงานแค่ 1 ครั้ง
  • ตัวแปรประเภท Primitive and Non-Primitive มีผลในการกำหนด deps ของ UseEffect
  • เวลาใช้ useEffect ไม่เขียน Cleanup function ก็ได้ใช่ไหม ? = ได้ แต่ควรเขียนดีกว่า
  • ถ้าไม่ได้เขียน Cleanup function แล้วใช้ StrictMode ในขณะ dev
    • เมื่อใช้ useEffect จะทำงาน 2 ครั้ง
    • แต่เมื่อ build เป็น Production แล้วก็จะทำงานแค่ 1 ครั้ง
    • แต่สุดท้ายแล้วก็จะมีปัญหาเรื่อง Component unmounted, Re-render และการ Set states ที่ไม่จำเป็น
  • คำตอบสุดท้ายคือควรเขียน Cleanup function อยู่ดี 😄

ขอบคุณที่อ่านจนจบครับ 😊

References

Loading...