Day 37/100 #100DaysOfCode #React #ReactNative
Nay mình đi tìm hiểu về thằng useCallback trong React, vừa qua mình đã đi qua các vấn đề xử lý như : useEffect, memo, useMemo rồi , bạn nào muốn xem lại thì callback lại bài trước nghe
+ useCallback là một tính năng giúp ta tranh việc gọi lại function không cần thiết, trả về một hàm gọi lại đã đã được ghi nhớ trước đó.
Đầu tiên mình có một ví dụ sau đây:
import React ,{useState,useCallback,memo} from 'react' const Product = memo({products,addProduct})=>{ console.log("show product") return( <> <ul> { products.length>0 && products.map(item=><li key={item.id}>{item.name} - <mark>{item.price}</mark></li>) } </ul> <button onClick={()=>addProduct({ "id":products.length+1, "name":"Book Laravel 8", "price":300 })}>ADD PRODUCT</button> </> ) }; const DATA = [ { "id":1, "name":"Book Laravel 8", "price":300 }, { "id":2, "name":"Book React Native", "price":400 } ] const UserCallback=()=>{ const [count,setCount] = useState(0) const [products,setProducts] = useState(DATA); const addProduct = (product)=>{ setProducts([...products,setProducts]); } return ( <> <h1>Using useCallback in React</h1> <h3>Count: {count}</h3> <button onClick={()=>setCount(count+1)}>CLICK</button> <Product products = {products} addProduct={addProduct}/> </> ) } export default UserCallback;
Các bạn chạy đoạn code trên các bạn thấy, khi ta bấm vào button onClick={()=>setCount(count+1)}, thì component re-render lại , nó khiến cho function addProduct tiếp tục tính toán lại, mặc dù ta không có đụng chạm gì đến function addProduct, đồng thời ta cũng có sử dụng memo để nhận props,nếu props có thay đổi thì re-render lại, nhưng nó không hoạt động được
Cho nên bạn thấy console ra "show product" cho mỗi lần bấm setCount
Tại vì sao? đó chính là do React nó sử dụng "bình đẳng tham chiếu" object.is(), vì cứ mỗi lần component re-render là function addProduct được tạo lại mới, chính vì thế mà khiến cho memo nó nhận được props đã thay đổi và nó re-render lại.
Vậy bình đẳng tham chiếu hoạt động ra sau , bạn có thể xem tại đây, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
Object.is() xác định xem hai giá trị có cùng giá trị hay không . Hai giá trị giống nhau nếu một trong các giá trị sau được giữ nguyên:
+ Cả hai điều undefined hoặc null
+ Cả hai là true hoặc false
+ Cả hai là string, có cùng độ dài và ký tự
+ Cả hai giá trị đều Objects trỏ đến một vị trí bộ nhớ
+ Cả hai giá trị đều Numbers có cùng giá trị hoặc NaN
VD: Bạn thử một ví dụ như sau. Ta có một cái State và một button changeSite, bạn thấy site trỏ tới name, site trỏ tới page
const [site,setSite] = useState({"name":"Hoanguyenit","page":"https://hoanguyenit.com"}) const changeSite = ()=>{ site.name = "Skipperhoa"; site.page = "https://100daysofcode.hoanguyenit.com"; console.log(site); setSite(site); } <h1>{site.name} - {site.page}</h1> <button onClick={changeSite}>Change Site</button> //console.log {"name":"skipperhoa",page:"https://100daysofcode.hoanguyenit.com"}
Nhưng <h1>(nó không thay đổi)</h1>, nó không re-render lại DOM, chính vì nó bị "Bình đẳng tham chiếu(Referential Equality)"
Mặc dù giá trị khác nhau, nhưng cả hai giá trị điều trỏ vào một bộ nhớ chính xác. Cho nên không re-render lại , khiến cho người dùng không nhận được dữ liệu đã thay đổi
"Hai giá trị được coi là bằng nhau khi cả hai đều là các đối tượng trỏ đến vị trí bộ nhớ chính xác."->object.is()
Quá trình này được gọi là Bình đẳng tham chiếu, vì các đối tượng được coi là bình đẳng dựa trên vị trí bộ nhớ của chúng chứ không phải giá trị.
cách khắc phục là dùng setSite({...site,name:"Skipperhoa",page:"https://100daysofcode.hoanguyenit.com"}) để nó tạo lại mới, thì component nó sẽ re-render lại DOM
Quay lại ví vụ trên , khi bấm setCount đã tác động đến state làm nó thay đổi, dẫn đến component re-render lại toàn bộ, để khác phục gọi lại function không cần thiết ta dùng useCallback, trả về function ghi nhớ trước đó
const addProduct = useCallback((product) => { setProducts([...products,product]) }, [products]);
Bạn thấy code trên, có dùng useCallback, nó ngăn chặn việc gọi lại function không cần thiết, nó chỉ gọi lại tính toán function khi state products có sự thay đổi thôi
Okay, giờ run lại code ta click vào setCount, nó không gọi lại function addProduct nửa, vì ta có dùng useCallback rồi, nó chỉ gọi tính toán lại function addProduct khi state products thay đổi thôi, bạn có thể click và button addProduct, bạn sẽ thấy console "show product" được console ra