Menu

Lifting state up trong React: chia sẻ state giữa component

Lifting state up là chuyển state ra khỏi hai component và đặt vào component cha chung gần nhất, nơi truyền giá trị và hàm cập nhật xuống lại qua props. Tìm hiểu khi nào cần lift, cách làm trong ba bước và khi nào nên giữ state cục bộ.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Lifting state up nghĩa là chuyển state ra khỏi các component cần dùng chung nó và đặt vào component cha chung gần nhất. Component cha giữ giá trị, truyền nó xuống dưới dạng prop, kèm theo một hàm để các component con gọi khi muốn thay đổi, nhờ vậy mọi component con đều render từ một bản state duy nhất.

Nhấn "Show" trên bất kỳ panel nào: panel đó mở ra và panel đang mở sẽ đóng lại. Mỗi lúc chỉ mở được một panel vì các panel không tự quyết định điều đó. App giữ openIndex, còn mỗi Panel chỉ nhận isOpen và một hàm onOpen.

Vấn đề: state cần thống nhất với nhau

Bắt đầu từ cách làm hiển nhiên nhất, trong đó mỗi panel tự sở hữu state isOpen của mình. Từng panel đều chạy được, nhưng chúng không biết gì về nhau, nên chẳng có gì ngăn hai panel cùng mở một lúc.

Mở cả hai panel: chúng cùng mở. State trong React là riêng tư của component khai báo nó, nên component anh em không thể đọc hay đặt lại. Khi hai component cần thống nhất với nhau, state phải nằm ở phía trên cả hai.

Lifting state trong ba bước

Để biến ví dụ thứ hai thành ví dụ đầu tiên, bạn cần sửa ba chỗ.

  1. Bỏ state khỏi component con. Xóa useState trong Panel và đọc isOpen từ props. Component con không còn tự quyết định mình có đang mở hay không.
  2. Truyền giá trị và cách thay đổi nó từ component cha. Panel nhận isOpen và một callback onOpen. Component con gọi onOpen() khi nút của nó được nhấn; nó không biết component cha sẽ làm gì với lời gọi đó.
  3. Thêm state vào component cha chung. App khai báo openIndex và chuyển nó thành props cho từng panel: isOpen={openIndex === 1} và onOpen={() => setOpenIndex(1)}.

Component cha chung gần nhất là component thấp nhất render tất cả các component cần đến state. Ở đây đó là App. Nếu các panel nằm trong một component Faq, state sẽ đặt ở Faq, chứ không cao hơn.

Sau khi lift, Panel được component cha điều khiển theo đúng nghĩa của một controlled input: nó hiển thị những gì props nói và báo thay đổi qua một callback. Trang về controlled và uncontrolled component trình bày cùng ý tưởng này cho các phần tử form.

Một nguồn dữ liệu duy nhất

Khi hai phần của màn hình hiển thị cùng một sự thật, hãy lưu sự thật đó một lần và tính mọi thứ còn lại từ nó. Bộ chuyển đổi nhiệt độ là ví dụ kinh điển: ô độ C và ô độ F phải luôn khớp nhau, nên chúng không thể mỗi ô giữ một con số riêng.

Gõ vào ô nào thì ô còn lại cũng thay đổi theo. State chỉ là một sự thật: con số người dùng gõ gần nhất và thang đo của nó. Ô còn lại được tính từ đó trong lúc render, nên ô bạn đang gõ luôn giữ đúng những gì bạn gõ. Đổi state ban đầu thành { value: '212', scale: 'f' }, dòng thông báo bên dưới các ô sẽ chuyển thành "Water boils."

Giữ số độ C và số độ F trong hai state riêng nghĩa là mọi handler đều phải cập nhật cả hai, và chỉ cần một handler quên, hai ô sẽ lệch nhau. Một giá trị duy nhất thì không thể tự mâu thuẫn với chính nó.

Truyền hàm cập nhật xuống dưới

Component con chỉ thay đổi được state của cha thông qua một hàm mà cha đưa cho nó. Bạn có thể truyền thẳng hàm set (onSelect={setColor}) hoặc một hàm làm nhiều việc hơn (onOpen={() => setOpenIndex(1)}). Đặt tên prop theo kiểu sự kiện như onSelect hay onChange, thay vì setSelected, giúp component con không cần biết cha lưu giá trị ra sao, nhờ đó sau này cha có thể đổi cách lưu mà không phải động đến con.

ColorPicker và Preview không bao giờ nói chuyện trực tiếp với nhau. Bộ chọn màu báo lựa chọn lên trên, App lưu lại, và giá trị mới chảy xuống cả hai.

Khi nào không nên lift state

Lift state có cái giá của nó. Khi state nằm ở component cha, mỗi thay đổi sẽ khiến cha render lại và theo mặc định là cả mọi component con, kể cả những component không dùng state đó. Chỉ lift đến component cha chung gần nhất, và để state chỉ một component dùng nằm yên trong component đó.

Gõ vào ô và theo dõi console: mỗi lần gõ phím chỉ có SearchBox render. Giờ hãy chuyển query lên App và truyền nó xuống SearchBox qua props. Khi đó mỗi lần gõ phím đều log thêm ProductList, dù danh sách không hề dùng đến query. Nếu danh sách thật sự lọc theo query, lift state lại là lựa chọn đúng, vì lúc đó cả hai component đều phụ thuộc vào cùng một giá trị.

Câu hỏi cần đặt ra là "ai cần đọc giá trị này?" Nếu câu trả lời là một component, state ở lại đó. Nếu là nhiều component, nó được đưa lên component cha chung gần nhất của chúng.

Khi lift quá đà

Đôi khi component cha chung gần nhất nằm rất cao trên cây, và giá trị phải đi qua nhiều component chỉ để chuyền tiếp. Đó là prop drilling. Vài tầng props thì không sao và vẫn dễ theo dõi. Khi cùng một giá trị đi qua rất nhiều tầng, hoặc gần như component nào cũng cần nó (người dùng đang đăng nhập, theme, ngôn ngữ), hãy đọc nó bằng useContext thay vì tự truyền tay. Context thay đổi cách giá trị đến được các component con; bản thân state vẫn nằm ở một component cha, nên nó vẫn được lift.

Câu hỏi thường gặp

Lifting state up trong React là gì?

Là chuyển một phần state từ các component đang dùng nó lên component cha chung gần nhất. Component cha sở hữu state và truyền giá trị, cùng một hàm để thay đổi giá trị đó, xuống các component con qua props.

Hai component anh em chia sẻ state trong React như thế nào?

Component anh em không đọc được state của nhau. Hãy đặt state vào component cha chung, truyền giá trị cho cả hai và truyền hàm cập nhật (hoặc một handler như onChange) cho component cần thay đổi nó. Khi đó cả hai đều render từ cùng một giá trị.

Component con cập nhật state của component cha bằng cách nào?

Component cha truyền một hàm qua prop, ví dụ onSelect={setSelected} hoặc onSelect={(id) => setSelected(id)}, và component con gọi hàm đó. State vẫn nằm ở cha; con chỉ yêu cầu thay đổi.

Khi nào không nên lifting state up?

Khi chỉ có một component dùng state đó. Đưa state lên cao hơn mức cần thiết khiến component cha render lại sau mỗi thay đổi và rải props qua những component không liên quan. Hãy giữ state càng gần nơi dùng nó càng tốt.

Có cách nào thay cho việc lift state lên quá cao?

Nếu bạn thấy mình truyền cùng một props qua nhiều tầng, hãy đọc giá trị dùng chung bằng context (useContext), hoặc sắp xếp lại để các component cần nó nằm gần nhau hơn.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu