githubgithubgithub
moon
sun

Truyền array làm dependency trong useEffect

avatar
ThanhtrairoChủ Nhật, 30/06/2024
Truyền array làm dependency trong useEffect

Trong ứng dụng React chắc hẳn bạn phải làm sử dụng với hook useEffect rất nhiều.

Vì useEffect khá phổ biến và bài viết này một mình đang muốn cho mọi người biết cách làm sao để truyền array làm dependency trong useEffect nên mình sẽ không giải thích khái niệm mà đi luôn vào vấn đề ít người biết này nhé.

Cách thông thường như với các dependency khác (primitive type)

const MyComponent = () => {
  const [items, setItems] = useState([1, 2, 3]);

  useEffect(() => {
    console.log('Effect triggered');
    // do some thing
  }, [items]);

  const handleChange = () => {
    const newItems = calculatorItems()
    setItems(newItems)
  }

  return (
    <div>
      <button onClick={handleChange}>
        change
      </button>
    </div>
  );
};

Với mỗi lần change item, function trong Effect sẽ được trigger và chạy các logic bên trong đó. Vậy nếu items không thay đổi thì sao kiểu vẫn là cái mảng [1,2,3] đầy, kết quả là funtion trong Effect vẫn trigger và chạy các logic bên trong không thực sự cần thiết. React sẽ so sánh từng dependency với giá trị trước đó của nó bằng cách sử dụng phép so sánh Object.is.

const items = [1, 2, 3];
const newItems = [1, 2, 3];
items === newItems; // false

Bạn biết đầy vì là so sánh reference nên 2 mảng trông giống nhau nhưng chúng khác nhau về ô nhớ địa chỉ nên kết quả vẫn là false và function trong Effect vẫn trigger.

Phương pháp xử lý

Spread

useEffect(() => {
    console.log('Effect triggered');
    // do some thing
  }, [...items]);

Như mình đã nói ở trên React sẽ so sánh từng dependency với giá trị trước đó của nó bằng cách sử dụng phép so sánh Object.is.

const items = [1, 2, 3];
const newItems = [1, 2, 3];
const depsItemsToSpread =  // [1,2,3]const depsNewItemsToString = items; // [1,2,3]1 === 1 // true 2 === 2 // true 3 === 3 // true

Phương pháp này tuy khá đơn giản nhưng nó chỉ hoạt động nếu mảng chỉ có 1 cấp và mảng có cùng số phần tử nếu số phần tử thay đổi nó sẽ hoạt động không chính xác.

JSON.stringify

Phương thức tĩnh JSON.stringify() chuyển đổi một giá trị JavaScript thành chuỗi JSON, tùy ý thay thế các giá trị nếu hàm thay thế được chỉ định hoặc tùy ý chỉ bao gồm các thuộc tính được chỉ định nếu mảng thay thế được chỉ định.

Theo như định nghĩa ở trên thì phương thức JSON.stringify() sẽ chuyển giá trị JavaScript thành chuỗi JSON và nó là dạng primitive(so sánh giá trị). Ta áp dụng vào dependency cho useEffect.

useEffect(() => {
   console.log('Effect triggered');
    // do some thing
}, [JSON.stringify(items)]);

React sẽ so sánh như sau:

const items = [1, 2, 3];
const newItems = [1, 2, 3];
const itemsToString = JSON.stringify(items); // '[1,2,3]'const newItemsToString = JSON.stringify(items); // '[1,2,3]'
itemsToString === newItemsToString // true 

Vì nó thuộc kiểu primitive nên khi so sánh giá trị bằng nhau nên giá trị là true nên function trong Effect không trigger nên không cần phải chạy logic không cần thiết.

Tuy nhiên khi dùng phương thức JSON.stringify() cần phải lưu ý một số giá trị như undefined, function, Symbol sẽ không được hỗ trợ và sẽ mất đi. Ngoài ra còn một số kiểu như Date, RegExp, Map, Set, BigInt, ... đều bị biến đổi sai hoặc gây lỗi. Nếu biết chắc chắn mảng của bạn không chưa các giá trị mà JSON.stringify() không hỗ trợ thì dùng rất ok mà còn nhanh nữa.

Custom useEffect

Nếu rời vào những trường hợp mà dependency phức tạp thì 2 cách trên có vẻ không quá ổn. Chúng ta tiến hành triển khai custom lại hook useEffect.

function arrayEqual(a1, a2) {
  if (a1.length !== a2.length) return false;
  for (let i = 0; i < a1.length; i++) {
    if (a1[i] !== a2[i]) {
      return false;
    }
  }
  return true;
}

function useCustomEffect(cb, deps, equal) {
  const ref = useRef(deps);

  if (!equal || !equal(deps, ref.current)) {
    ref.current = deps;
  }

  useEffect(cb, [ref.current]);
}

sử dụng:

useCustomEffect(
    () => {
      console.log('run custom effect', JSON.stringify(items));
    },
    [items],
    (a, b) => arrayEqual(a[0], b[0])
  );

Với cách trên thì chúng ta có thể kiểm soát function trong Effect trigger dễ dàng hơn, tuy nhiên nó tương đối phức tạp và có thể làm giảm performace. Ngoải ra chúng ta cũng có thể sử dụng ngay thư viện use-deep-compare-effect để kiểm soát hoạt động Effect này.

Kết luận

Qua bài viết này mình thực hiện truyền array làm dependency list trong useEffect, với các cách như Spread, JSON.stringify, custom useEffect, sử dụng thư viện.

Trong thực tế tùy vào trường hợp nào từ đơn giản đến phức tạp sẽ chọn cách phù hợp.

Lưu ý: bạn có thể kiểm tra deps bằng cách thủ công trong function Effect, hoặc kiểm tra khi set lại state mà không nhất thiết phải xử lý như các cách trên.

Bài viết liên quan