Một số component của bạn có thể cần điều khiển và đồng bộ hóa với các hệ thống bên ngoài React. Ví dụ: bạn có thể cần tập trung vào một input bằng API của trình duyệt, phát và tạm dừng trình phát video được triển khai mà không cần React hoặc kết nối và lắng nghe tin nhắn từ một máy chủ từ xa. Trong chương này, bạn sẽ tìm hiểu các lối thoát hiểm cho phép bạn “bước ra ngoài” React và kết nối với các hệ thống bên ngoài. Hầu hết logic ứng dụng và luồng dữ liệu của bạn không nên dựa vào các tính năng này.
Trong chương này
- Cách “ghi nhớ” thông tin mà không cần render lại
- Cách truy cập các phần tử DOM được quản lý bởi React
- Cách đồng bộ hóa các component với các hệ thống bên ngoài
- Cách loại bỏ các Effect không cần thiết khỏi component của bạn
- Vòng đời của một Effect khác với vòng đời của một component như thế nào
- Cách ngăn một số giá trị kích hoạt lại Effect
- Cách làm cho Effect của bạn chạy lại ít thường xuyên hơn
- Cách chia sẻ logic giữa các component
Tham chiếu các giá trị bằng ref
Khi bạn muốn một component “ghi nhớ” một số thông tin, nhưng bạn không muốn thông tin đó kích hoạt các lần render mới, bạn có thể sử dụng ref:
const ref = useRef(0);
Giống như state, ref được React giữ lại giữa các lần re-render. Tuy nhiên, việc đặt state sẽ re-render một component. Thay đổi một ref thì không! Bạn có thể truy cập giá trị hiện tại của ref đó thông qua thuộc tính ref.current
.
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('Bạn đã nhấp ' + ref.current + ' lần!'); } return ( <button onClick={handleClick}> Nhấp vào tôi! </button> ); }
Một ref giống như một túi bí mật của component mà React không theo dõi. Ví dụ: bạn có thể sử dụng ref để lưu trữ ID timeout, các phần tử DOM và các đối tượng khác không ảnh hưởng đến đầu ra render của component.
Ready to learn this topic?
Đọc Tham chiếu các giá trị bằng Ref để tìm hiểu cách sử dụng ref để ghi nhớ thông tin.
Read MoreThao tác với DOM bằng ref
React tự động cập nhật DOM để khớp với đầu ra render của bạn, vì vậy các component của bạn sẽ không thường xuyên cần thao tác với nó. Tuy nhiên, đôi khi bạn có thể cần truy cập vào các phần tử DOM được quản lý bởi React—ví dụ: để tập trung một node, cuộn đến nó hoặc đo kích thước và vị trí của nó. Không có cách tích hợp sẵn để thực hiện những việc đó trong React, vì vậy bạn sẽ cần một ref đến DOM node. Ví dụ: nhấp vào nút sẽ tập trung vào input bằng một ref:
import { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Tập trung vào input </button> </> ); }
Ready to learn this topic?
Đọc Thao tác với DOM bằng Ref để tìm hiểu cách truy cập các phần tử DOM được quản lý bởi React.
Read MoreĐồng bộ hóa với Effect
Một số component cần đồng bộ hóa với các hệ thống bên ngoài. Ví dụ: bạn có thể muốn điều khiển một component không phải React dựa trên state của React, thiết lập kết nối máy chủ hoặc gửi nhật ký phân tích khi một component xuất hiện trên màn hình. Không giống như các trình xử lý sự kiện, cho phép bạn xử lý các sự kiện cụ thể, Effect cho phép bạn chạy một số code sau khi render. Sử dụng chúng để đồng bộ hóa component của bạn với một hệ thống bên ngoài React.
Nhấn Play/Pause một vài lần và xem cách trình phát video vẫn được đồng bộ hóa với giá trị prop isPlaying
:
import { useState, useRef, useEffect } from 'react'; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() => { if (isPlaying) { ref.current.play(); } else { ref.current.pause(); } }, [isPlaying]); return <video ref={ref} src={src} loop playsInline />; } export default function App() { const [isPlaying, setIsPlaying] = useState(false); return ( <> <button onClick={() => setIsPlaying(!isPlaying)}> {isPlaying ? 'Tạm dừng' : 'Phát'} </button> <VideoPlayer isPlaying={isPlaying} src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" /> </> ); }
Nhiều Effect cũng “dọn dẹp” sau khi chúng chạy. Ví dụ: một Effect thiết lập kết nối với máy chủ trò chuyện sẽ trả về một hàm dọn dẹp cho React biết cách ngắt kết nối component của bạn khỏi máy chủ đó:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; export default function ChatRoom() { useEffect(() => { const connection = createConnection(); connection.connect(); return () => connection.disconnect(); }, []); return <h1>Chào mừng đến với phòng chat!</h1>; }
Trong quá trình phát triển, React sẽ ngay lập tức chạy và dọn dẹp Effect của bạn thêm một lần nữa. Đây là lý do tại sao bạn thấy "✅ Đang kết nối..."
được in hai lần. Điều này đảm bảo rằng bạn không quên triển khai hàm dọn dẹp.
Ready to learn this topic?
Đọc Đồng bộ hóa với Effect để tìm hiểu cách đồng bộ hóa các component với các hệ thống bên ngoài.
Read MoreBạn có thể không cần Effect
Effect là một lối thoát hiểm khỏi mô hình React. Chúng cho phép bạn “bước ra ngoài” React và đồng bộ hóa các component của bạn với một số hệ thống bên ngoài. Nếu không có hệ thống bên ngoài nào liên quan (ví dụ: nếu bạn muốn cập nhật state của một component khi một số prop hoặc state thay đổi), bạn sẽ không cần Effect. Việc loại bỏ các Effect không cần thiết sẽ giúp code của bạn dễ theo dõi hơn, chạy nhanh hơn và ít bị lỗi hơn.
Có hai trường hợp phổ biến mà bạn không cần Effect:
- Bạn không cần Effect để chuyển đổi dữ liệu để render.
- Bạn không cần Effect để xử lý các sự kiện của người dùng.
Ví dụ: bạn không cần Effect để điều chỉnh một số state dựa trên state khác:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 🔴 Tránh: state dư thừa và Effect không cần thiết
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}
Thay vào đó, hãy tính toán càng nhiều càng tốt trong khi render:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Tốt: được tính toán trong khi render
const fullName = firstName + ' ' + lastName;
// ...
}
Tuy nhiên, bạn cần Effect để đồng bộ hóa với các hệ thống bên ngoài.
Ready to learn this topic?
Đọc Bạn có thể không cần Effect để tìm hiểu cách loại bỏ các Effect không cần thiết.
Read MoreVòng đời của các effect phản ứng
Effect có vòng đời khác với component. Component có thể mount, update hoặc unmount. Một Effect chỉ có thể làm hai việc: bắt đầu đồng bộ hóa một cái gì đó và sau đó dừng đồng bộ hóa nó. Chu kỳ này có thể xảy ra nhiều lần nếu Effect của bạn phụ thuộc vào các prop và state thay đổi theo thời gian.
Effect này phụ thuộc vào giá trị của prop roomId
. Prop là các giá trị phản ứng, có nghĩa là chúng có thể thay đổi khi re-render. Lưu ý rằng Effect tái đồng bộ hóa (và kết nối lại với máy chủ) nếu roomId
thay đổi:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Chào mừng đến với phòng {roomId}!</h1>; } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Chọn phòng chat:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
React cung cấp một quy tắc linter để kiểm tra xem bạn đã chỉ định các dependency của Effect một cách chính xác hay chưa. Nếu bạn quên chỉ định roomId
trong danh sách các dependency trong ví dụ trên, linter sẽ tự động tìm thấy lỗi đó.
Ready to learn this topic?
Đọc Vòng đời của các sự kiện phản ứng để tìm hiểu vòng đời của một Effect khác với vòng đời của một component như thế nào.
Read MoreTách các sự kiện khỏi Effect
Trình xử lý sự kiện chỉ chạy lại khi bạn thực hiện lại cùng một tương tác. Không giống như trình xử lý sự kiện, Effect tái đồng bộ hóa nếu bất kỳ giá trị nào chúng đọc, như prop hoặc state, khác với lần render cuối cùng. Đôi khi, bạn muốn kết hợp cả hai hành vi: một Effect chạy lại để đáp ứng với một số giá trị nhưng không phải các giá trị khác.
Tất cả code bên trong Effect đều phản ứng. Nó sẽ chạy lại nếu một số giá trị phản ứng mà nó đọc đã thay đổi do re-render. Ví dụ: Effect này sẽ kết nối lại với chat nếu roomId
hoặc theme
đã thay đổi:
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Đã kết nối!', theme); }); connection.connect(); return () => connection.disconnect(); }, [roomId, theme]); return <h1>Chào mừng đến với phòng {roomId}!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Chọn phòng chat:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Sử dụng giao diện tối </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Điều này không lý tưởng. Bạn chỉ muốn kết nối lại với chat nếu roomId
đã thay đổi. Việc chuyển đổi theme
không nên kết nối lại với chat! Di chuyển code đọc theme
ra khỏi Effect của bạn vào một Effect Event:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { const onConnected = useEffectEvent(() => { showNotification('Đã kết nối!', theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Chào mừng đến với phòng {roomId}!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Chọn phòng chat:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Sử dụng giao diện tối </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Code bên trong Effect Event không phản ứng, vì vậy việc thay đổi theme
không còn khiến Effect của bạn kết nối lại.
Ready to learn this topic?
Đọc Tách các sự kiện khỏi Effect để tìm hiểu cách ngăn một số giá trị kích hoạt lại Effect.
Read MoreLoại bỏ các dependency của Effect
Khi bạn viết một Effect, linter sẽ xác minh rằng bạn đã bao gồm mọi giá trị phản ứng (như prop và state) mà Effect đọc trong danh sách các dependency của Effect. Điều này đảm bảo rằng Effect của bạn vẫn được đồng bộ hóa với các prop và state mới nhất của component của bạn. Các dependency không cần thiết có thể khiến Effect của bạn chạy quá thường xuyên hoặc thậm chí tạo ra một vòng lặp vô hạn. Cách bạn loại bỏ chúng phụ thuộc vào trường hợp.
Ví dụ: Effect này phụ thuộc vào đối tượng options
được tạo lại mỗi khi bạn chỉnh sửa input:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); const options = { serverUrl: serverUrl, roomId: roomId }; useEffect(() => { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [options]); return ( <> <h1>Chào mừng đến với phòng {roomId}!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Chọn phòng chat:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Bạn không muốn chat kết nối lại mỗi khi bạn bắt đầu nhập tin nhắn vào chat đó. Để khắc phục sự cố này, hãy di chuyển việc tạo đối tượng options
vào bên trong Effect để Effect chỉ phụ thuộc vào chuỗi roomId
:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]); return ( <> <h1>Chào mừng đến với phòng {roomId}!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Chọn phòng chat:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Lưu ý rằng bạn không bắt đầu bằng cách chỉnh sửa danh sách dependency để loại bỏ dependency options
. Điều đó sẽ là sai. Thay vào đó, bạn đã thay đổi code xung quanh để dependency trở nên không cần thiết. Hãy nghĩ về danh sách dependency như một danh sách tất cả các giá trị phản ứng được sử dụng bởi code Effect của bạn. Bạn không cố ý chọn những gì để đưa vào danh sách đó. Danh sách mô tả code của bạn. Để thay đổi danh sách dependency, hãy thay đổi code.
Ready to learn this topic?
Đọc Loại bỏ các dependency của Effect để tìm hiểu cách làm cho Effect của bạn chạy lại ít thường xuyên hơn.
Read MoreSử dụng lại logic với Hook tùy chỉnh
React đi kèm với các Hook tích hợp sẵn như useState
, useContext
và useEffect
. Đôi khi, bạn sẽ ước có một Hook cho một mục đích cụ thể hơn: ví dụ: để tìm nạp dữ liệu, để theo dõi xem người dùng có trực tuyến hay không hoặc để kết nối với phòng chat. Để thực hiện việc này, bạn có thể tạo Hook của riêng mình cho nhu cầu của ứng dụng.
Trong ví dụ này, Hook tùy chỉnh usePointerPosition
theo dõi vị trí con trỏ, trong khi Hook tùy chỉnh useDelayedValue
trả về một giá trị “chậm hơn” giá trị bạn đã truyền một số mili giây nhất định. Di chuyển con trỏ qua khu vực xem trước của sandbox để xem một vệt chấm chuyển động theo con trỏ:
import { usePointerPosition } from './usePointerPosition.js'; import { useDelayedValue } from './useDelayedValue.js'; export default function Canvas() { const pos1 = usePointerPosition(); const pos2 = useDelayedValue(pos1, 100); const pos3 = useDelayedValue(pos2, 200); const pos4 = useDelayedValue(pos3, 100); const pos5 = useDelayedValue(pos4, 50); return ( <> <Dot position={pos1} opacity={1} /> <Dot position={pos2} opacity={0.8} /> <Dot position={pos3} opacity={0.6} /> <Dot position={pos4} opacity={0.4} /> <Dot position={pos5} opacity={0.2} /> </> ); } function Dot({ position, opacity }) { return ( <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> ); }
Bạn có thể tạo Hook tùy chỉnh, kết hợp chúng với nhau, truyền dữ liệu giữa chúng và sử dụng lại chúng giữa các component. Khi ứng dụng của bạn phát triển, bạn sẽ viết ít Effect thủ công hơn vì bạn sẽ có thể sử dụng lại các Hook tùy chỉnh mà bạn đã viết. Ngoài ra còn có nhiều Hook tùy chỉnh tuyệt vời được duy trì bởi cộng đồng React.
Ready to learn this topic?
Đọc Sử dụng lại logic với Hook tùy chỉnh để tìm hiểu cách chia sẻ logic giữa các component.
Read MoreTiếp theo là gì?
Hãy chuyển đến Tham chiếu các giá trị bằng Ref để bắt đầu đọc trang này theo từng trang!