Useeffect la gi

Nó cho phép bạn có thể sử dụng state và các chức năng khác của React mà không cần khởi tạo Class, tức là có thể sử dụng state trong function component.

Effect Hook cho phép thực hiện side effect bên trong các function component

và nay mình xin phép giới thiệu với mọi người một Hook được sử dụng phổ biến trong react đó là useEffect

UseEffect là gì?

mục đích useEffect() để quản lý vòng đời của của một component

nó phục vụ chúng ta sử dụng trong function component thay vì các lifecycle như trước đây trong class component.

useEffect() cho phép chúng ta xử lý logic trong lifecycle methods.

Cú pháp:

useEffect(callback, [deps])

Đối số thứ nhất: callback là hàm tự truyền vào để thực hiện side effect

Đối số thứ hai: là đối số không bắt buộc, là mảng chứa những sự phụ thuộc về mặt dữ liệu (deps)

Và useEffect được chia làm 3 TH:
useEffect(callback)
useEffect(callback, [])
useEffect(callback, [deps])

Điểm chung của 3TH trên đó là:
Callback luôn được gọi sau khi component được mounted có nghĩa là khi component lần đầu tiên được mount vào DOM thì sau đó nó sẽ gọi luôn useEffect()

Useeffect la gi

bằng chứng là khi trang được load lần đầu tiên thì text mounted được in ra

Và chúng ta sẽ đi vào từng trường hợp cụ thể

TH1: useEffect(callback)
Callback được gọi sau mỗi lần component re-render

Useeffect la gi

Useeffect la gi

Các bạn có thể thấy đối chiếu với tính chất chung thì đối số thứ nhất của useEffect là callback được gọi lần đầu khi component được mount và sau đó các bạn có thể nhìn thấy khi mình thay đổi giá trị của ô input đồng thời số lần chuỗi ‘mounted’ được in ra cũng tăng lên đồng nghĩa với việc callback đã được gọi lại khi component được re-render

TH2: useEffect(callback, [])
Chỉ gọi callback 1 lần sau khi component mounted
Thường sử dụng khi thực hiện logic gì đó 1 lần sau khi component được mounted và không muốn nó gọi lại khi component được re-render

Useeffect la gi

Useeffect la gi

Các bạn có thể thấy với trường hợp thứ hai này thì tính chất chung nó vẫn đúng khi useEffect được gọi lần đầu sau khi component được mounted vào DOM bằng chứng là nó in ra chuỗi ‘re-render' trước sau đó mới in ra chuối ‘mounted'
và nó cũng chỉ gọi callback đúng một lần sau khi component được mounted bằng chứng là khi mình nhập ký tự ‘a' thì nó chỉ in ra chuỗi ‘re-render'

TH3: useEffect(callback, [deps])
Callback sẽ được gọi lại mỗi khi deps thay đổi
Khi Component re-render thì thằng useEffect sẽ kiểm tra thằng deps này trước và sau khi render có khác nhau không. Nếu khác nhau nó sẽ gọi đến thằng callback này

Useeffect la gi

Vẫn giống như 2 trường hợp trên callback sẽ được gọi lại lần đầu khi component mounted tuy nhiên ở TH3 này khi lần đầu component render mình set type cho nó là ‘post' sau đó thì mình click sang button comments tức là nó lắng nghe sự kiện onClick và đồng thời nó gọi hàm setType và set value cho type là ‘comments'. Lúc này giá trị của type là ‘comments' đã khác giá trị lúc đầu component được mount là ‘post' cũng tức là thằng useEffect sẽ được gọi lại và lúc này nó sẽ vừa re-render lại component và đồng thời cũng gọi lại useEffect bằng chứng là chuỗi ‘re-render' và chuỗi ‘mounted' được in ra 2 lần

Trên đây là phần tìm hiểu của mình về 3TH của useEffect. Mong nhận được đóng góp ý kiến từ mọi người để bài của mình hoàn chỉnh hơn

Đây là một bài viết tương đối dài dòng về useEffect, bạn cần biết và đã đọc qua tài liệu về useEffect trên trang chính thức của React trước, và nếu chỉ thực sự cần biết sử dụng useEffect ra sao, bạn không cần đọc bài viết phân tách mổ xẻ sâu kiểu này.

Mỗi lần render là một giá trị Prop và State độc lập

Trước khi bắt đầu nói về 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 chúng ta cần nhắc lại quá trình render

Useeffect la gi
Useeffect la gi

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p> ...
      <button onClick={() => setCount(count + 1)}></button>
    </div>
  );
}

Khác với Vue, nó không phải là một dạng data binding, watcher, proxy, nó chỉ là một giá trị thông thường.

const count = 42;

<p> {count} </p>;

Đầu tiên giá trị khởi tạo của 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 sẽ =0. Khi chúng ta gọi 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
9, React sẽ gọi lại component một lần nữa, với giá trị 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 lúc này là 
function Counter() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert(`You clicked on: ${count}`);
    }, 3000);
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <button onClick={handleAlertClick}>Show alert</button>
      ...
    </div>
  );
}
1. Cứ vậy

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

Khi update một state, React gọi lại component, mỗi lần render như vậy, nó sẽ thấy một giá trị 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 mới. Sau đó React sẽ update lại DOM tương ứng.

Vấn đề mấu chốt cần nắm là giá trị 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 trong các lần render khác nhau là khác nhau.

function Counter() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert(`You clicked on: ${count}`);
    }, 3000);
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <button onClick={handleAlertClick}>Show alert</button>
      ...
    </div>
  );
}

Chúng ta thực hiện các bước sau

  • Bấm counter lên 3
  • Bấm “Show alert”
  • Bấm tiếp 
    function Counter() {
      const [count, setCount] = useState(0);
    
      function handleAlertClick() {
        setTimeout(() => {
          alert(`You clicked on: ${count}`);
        }, 3000);
      }
    
      return (
        <div>
          <p>{count}</p>
          <button onClick={() => setCount(count + 1)}>Click me</button>
          <button onClick={handleAlertClick}>Show alert</button>
          ...
        </div>
      );
    }
    4 cho counter lên 5 trước khi bị gọi timeout

Useeffect la gi
Useeffect la gi

Câu hỏi ở đây là nó sẽ alert ra 5 – giá trị cuối cùng, hay là 3 giá trị lúc chúng ta click

Chạy thử

Bạn có thấy kết quả quá vô lý?

Như đã nói ở trên, giá trị 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 là hằng số trên mỗi lần render. Function của chúng ta được gọi nhiều lần, mỗi lần gọi như vậy giá trị 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 bên trong là một số độc lập hoàn toàn với giá trị trước đó

Không phải đặc sản của React, viết dạng function như thế này bạn sẽ dễ hình dung hơn.

function sayHi(person) {
  const name = person.name;
  setTimeout(() => {
    alert(`Hello, ${name}`);
  }, 3000);
}

let someone = { name: "Dan" };
sayHi(someone);

someone = { name: "Yuzhi" };
sayHi(someone);

someone = { name: "Dominic" };
sayHi(someone);

Thế còn hàm xử lý event thì sao? cụ thể là hàm

function Counter() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert(`You clicked on: ${count}`);
    }, 3000);
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <button onClick={handleAlertClick}>Show alert</button>
      ...
    </div>
  );
}
7? Cũng như trên, hàm này là có các version khác nhau ở các lần render khác nhau.

Bài viết được quảng cáo là nói về 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 mà nãy giờ chưa đá động gì!

Quay lại với ví dụ từ trang chính thức của React

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

Câu hỏi là

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 đã làm cách nào để lấy được giá trị cuối cùng của 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8?

Lẽ nào đó có một dạng “data binding” hay “watching” ở đây để update giá trị 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 bên trong hàm effect? Hoặc giả React chơi chiêu dùng biến mutable bên trong component để luôn có được giá trị cuối?

Không hề!

Chúng ta đã biết: giá trị 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 là hằng số cho các lần render, event handle cũng độc lập trên các lần render khác nhau, effect cũng vậy luôn.

Không phải giá trị 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 thay đổi bên trong 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 bất biến, mà là 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 cũng bị thay đổi trên từng lần render.

// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}

Có thể mường tượng effect là một phần của kết quả lúc render

Giờ thử với 

function sayHi(person) {
  const name = person.name;
  setTimeout(() => {
    alert(`Hello, ${name}`);
  }, 3000);
}

let someone = { name: "Dan" };
sayHi(someone);

someone = { name: "Yuzhi" };
sayHi(someone);

someone = { name: "Dominic" };
sayHi(someone);
6

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ...
    setTimeout(() => {
          console.log(`You clicked ${count} times`);
        }, 3000);
    ...
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Nếu mà click vài lần với một khoảng thời gian bỏ nhỏ thì kết quả log ra là gì?

Thử ở đây

Bạn không chỉ nhận được 1 mà là một chuỗi các đoạn log ứng với số lần click.

Useeffect la gi
Useeffect la gi

Đương nhiên phải chạy như vậy mới đúng chứ, đâu có gì phải thắc mắc?

Bạn đã thử với 

function sayHi(person) {
  const name = person.name;
  setTimeout(() => {
    alert(`Hello, ${name}`);
  }, 3000);
}

let someone = { name: "Dan" };
sayHi(someone);

someone = { name: "Yuzhi" };
sayHi(someone);

someone = { name: "Dominic" };
sayHi(someone);
7 trong class component chưa?

componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}

Useeffect la gi
Useeffect la gi

Lý do? Giá trị 

function sayHi(person) {
  const name = person.name;
  setTimeout(() => {
    alert(`Hello, ${name}`);
  }, 3000);
}

let someone = { name: "Dan" };
sayHi(someone);

someone = { name: "Yuzhi" };
sayHi(someone);

someone = { name: "Dominic" };
sayHi(someone);
7 bên trong class component là một mutation (có thể thay đổi).

Nếu luôn muốn lấy giá trị sau cùng bên trong effect, cách dễ nhất là dùng 

function sayHi(person) {
  const name = person.name;
  setTimeout(() => {
    alert(`Hello, ${name}`);
  }, 3000);
}

let someone = { name: "Dan" };
sayHi(someone);

someone = { name: "Yuzhi" };
sayHi(someone);

someone = { name: "Dominic" };
sayHi(someone);
9

function Example() {
    const [count, setCount] = useState(0);
    ...
    const latestCount = useRef(count);
    ...

    useEffect(() => {
        ...
        latestCount.current = count;
        ...
        setTimeout(() => {
            ...
                console.log(`You clicked ${latestCount.current} times`);
            ...
        }, 3000);
    });
}

const count = 42;

<p> {count} </p>;
0

Nếu chúng ta render 

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
0, sau đó render 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
1. Cuối cùng chúng ta luôn nhận được Hello, Luu

React luôn đồng bộ cục DOM với giá trị hiện tại của 

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
2 và 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
3. Không cần phân biệt giữa 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
4 và 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
5 khi render. Có thể hình dung effect cũng tương tự như vậy, 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 cho phép đồng bộ những phần không nằm trong React tree với giá trị của 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
2 và 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
3

const count = 42;

<p> {count} </p>;
1

Câu thần chú cho việc này là: Quan trọng là đích đến, không phải quá trình

Chạy effect trên tất cả lúc chạy render sẽ không hay lắm, đôi khi có trường hợp lặp vô tận.

Trong quá trình re-render, React chỉ cập nhập đúng phần DOM đã thay đổi.

Ví dụ như

const count = 42;

<p> {count} </p>;
2

Sang

const count = 42;

<p> {count} </p>;
3

React sẽ thấy 2 object

const count = 42;

<p> {count} </p>;
4

Nó sẽ xác định được 

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
9 bị thay đổi và cần update, còn 
// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
0 thì không, nó sẽ làm như sau

const count = 42;

<p> {count} </p>;
5

Chúng ta cũng muốn effect làm điều tương tự, khi re-render chỉ apply những update cần thiết

Ví dụ với component này

const count = 42;

<p> {count} </p>;
6

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 không hề liên quan tới giá trị state 
// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
2, gọi 
// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
3 khi giá trị 
// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
2 thay đổi không phải là ý hay.

Đó là lý do tại sao chúng ta có thêm tham số 

// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
5 (một mảng) khi dùng 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7

const count = 42;

<p> {count} </p>;
7

Dịch ra ngôn ngữ con người là thế này: “Tao biết React mày không phân biệt được sự khác nhau bên trong function, nên tao hứa là tao chỉ dùng đến

// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
7 bên trong function này thôi, và chỉ giá trị 
// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
7 này update thì mày hả gọi nó”

Một là không nói dối, 2 là không nói dối nhiều lần

Đừng bao giờ lừa gạt React bằng cách đưa dependency không đúng cho nó, hậu quả nhãn tiền. Hợp lý, nhưng nhiều lập trình viên quen sử dụng 

// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
9 sẽ cố tình qua mặt

const count = 42;

<p> {count} </p>;
8

Bạn sẽ nghĩ là “Tao chỉ muốn chạy nó lúc mount thôi”. Nếu chúng ta chỉ định một dependency, tất cả giá trị bên trong component sử dụng bởi effect phải được khai báo cụ thể. Bao gồm prop, state, function

Đôi khi mà làm như vậy nó phát sinh lỗi. Thí dụ như gọi fetch data liên tục hoặc socket được tạo không cần thiết. Cách giải quyets là không xóa chúng khỏi dependency

Trước khi nói về cách giải quyết, chúng ta xem vấn đề ở đây là gì khi so sánh Dependency

Hậu quả của việc dối trá

Nếu mảng dependency chứa tất cả giá trị sử dụng trong 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7, React biết được khi nào thì re-run nó

const count = 42;

<p> {count} </p>;
9

Useeffect la gi
Useeffect la gi

Nhưng nếu chúng ta chỉ định 

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ...
    setTimeout(() => {
          console.log(`You clicked ${count} times`);
        }, 3000);
    ...
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
1, nó không re-run sau lần đầu tiên

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
0

Useeffect la gi
Useeffect la gi

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
1

Rõ ràng là 2 thằng dependency không khác nhau, nên nó sẽ không chạy effect

Trong tình huống này, vấn đề khá là hiển nhiên, nhưng trực giác có thể đánh lừa bạn trong các tình huống khác, lấy ví dụ, chúng ta muốn giá trị 

// lần render đầu tiên
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${0} times`;
  });
  // ...
}

// sau khi click
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${1} times`;
  });
  // ...
}

// click thêm lần nữa
function Counter() {
  // ...
  useEffect(() => {
    document.title = `You clicked ${2} times`;
  });
  // ..
}
2 tăng đều sau mỗi giây. Với một class, trực giác sẽ mách bảo: “Set up cái interval một lần, rồi dứt tình vứt áo một lần”, kiểu như thế này, khi chuyển qua dùng 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
7 bạn sẽ nghĩ đến dùng 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ...
    setTimeout(() => {
          console.log(`You clicked ${count} times`);
        }, 3000);
    ...
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
1 cho mảng phụ thuộc “Tao chỉ muốn tình một đêm”, đúng không?

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
2

Theo như lập luận rất hay gặp “danh sách phụ thuộc cho phép chúng ta chỉ định việc re-render effect khi nào”, và ở đây ta chỉ muốn trigger nó một lần vì nó là interval, nhưng tại sao lại có vấn đề ở đây?

Chúng ta đang muốn effect này chỉ chạy lần đầu tiên mà thôi, đưa vào dependencies là 

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ...
    setTimeout(() => {
          console.log(`You clicked ${count} times`);
        }, 3000);
    ...
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
1 có vẻ hợp lý, React sẽ bỏ qua hết những lần sau, nhưng chúng ta đang lừa dối React, vì bên trong chúng ta có sử dụng giá trị 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8, chúng ta có giá trị phụ thuộc mà không khai báo. Thực tế 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ...
    setTimeout(() => {
          console.log(`You clicked ${count} times`);
        }, 3000);
    ...
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
7 sẽ gọi liên tục sau 1 giây, chứ không dừng lại sau lần gọi đầu tiên.

Ở lần render đầu tiên, 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 = 0, vì thế 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ...
    setTimeout(() => {
          console.log(`You clicked ${count} times`);
        }, 3000);
    ...
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
9 ở lần render đầu tiên nghĩa là 
componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}
0, nhưng vì không re-run effect thêm lần nào nữa, chúng ta cứ gọi mãi 
componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}
0 ở những lần tiếp theo

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
3

Những con bug như thế này sẽ rất rất khó để mò ra được, vì thế hãy luôn thành thật với React, khai báo hết dependency đang có.

Useeffect la gi
Useeffect la gi

2 cách để thú thật với React về dependency

Nên chọn cách một, cách 2 chỉ áp dụng khi cần thiết

Cách 1: luôn là người trung thực, chính trực đạo đức hết mực, luôn khai báo đầy đủ thông tin bạn trai, bạn gái, ba má, chú bác nào bạn đang phụ thuộc cho cơ quan thuế

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
4

Tuy nhiên thế này, khi giá trị 

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 thay đổi, cái interval của chúng ta sẽ bị xóa và đặt lại lần nữa sau những lần render, nó không phải là cái chúng ta mong muốn nó hoạt động như vậy

Useeffect la gi
Useeffect la gi

Cách 2 là thay đổi tư duy, giảm bớt anh trai nuôi, em gái nuôi không cần thiết

Chúng ta không nói xạo, chúng ta giảm bớt số lượng những thứ phụ thuộc cho việc re-run effect

Để làm được việc này, chúng ta phải hỏi bản thân: chúng ta dùng count để làm gì? Có vẻ như chúng ta chỉ dùng nó cho việc gọi hàm 

componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}
3, chúng ta không thực sự cần giá trị 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 nếu chúng ta biết được giá trị trước đó, trường hợp trên, chúng ta có thể không cần dùng đến giá trị 
// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
8 mà dùng previous state

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
5

Useeffect la gi
Useeffect la gi

Chạy thử

Tính năng update của Google Docs

Khi nói về effect, định hướng lập trình chúng ta là đồng bộ hóa, có một khái niệm khá thú vị khi thực hiện đồng bộ hóa là chúng ta thường không đồng bộ toàn bộ nội dung. Lấy ví dụ như Google Docs, nó không thực sự truyền tải cả trang lên phía server, làm như vậy hiệu năng sẽ rất tệ, cái nó làm là gửi đi một thông tin chứa cái mà user đang muốn thực hiện.

Tốt nhất truyền đi thật ít thông tin từ effect (chỉ những thông tin cần thiết nhất) vào trong component. Hàm 

componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}
6 sẽ gửi đi ít thông tin hơn so với hàm 
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ...
    setTimeout(() => {
          console.log(`You clicked ${count} times`);
        }, 3000);
    ...
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
9 đứng trên một khía cạnh nào đó vì nó không phụ thuộc giá trị hiện tại, sử dụng ít state nhất có thể để đạt được kết quả là một trong các nguyên lý chính của đợt cập nhập React với effect

Tuy nhiên không phải lúc nào cuộc sống cũng đơn giản với bạn như vậy, nếu chúng ta muốn tính toán giá trị của state mới dựa trên một prop, 2 giá trị state phụ thuộc lẫn nhau, 

componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}
8 là không đủ. Chúng ta có người chị em hàng xóm tên 
componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}
9

// Lần đầu render
function Counter() {
  const count = 0; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function này được gọi lại lần nữa
function Counter() {
  const count = 1; // trả về bởi `useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// sau khi click, function được gọi lại lần nữa
function Counter() {
  const count = 2; // trả về bởi useState()  // ...
  <p>You clicked {count} times</p>;
  // ...
}
6

Cách dùng 

componentDidUpdate() {
    setTimeout(() => {
        console.log(`You clicked ${this.state.count} times`);
    }, 3000)
}
9 như vậy là một dạng cheat mode của hook, cho phép chúng ta bỏ qua các dependency ngầm khỏi effect, và chặn re-run không không cần thiết

Chạy thử

Bài viết này vẫn còn, và nếu bạn vẫn còn muốn đào sâu hơn nữa, có thể tìm đọc bài viết gốc của Dan A Complete Guide to useEffect