Debug (gỡ lỗi) React component thông qua React Developer Tools
- 11-10-2022
- Toanngo92
- 0 Comments
Trong bài viết này, chúng ta sẽ cài đặt tiện ích mở rộng React Developer Tools trong Chrome. Chúng sẽ sử dụng các công cụ dành cho nhà phát triển trong bảng điều khiển JavaScript của Chrome để khám phá component tree của dự án.
Đến cuối bước này, bạn sẽ cài đặt Công cụ nhà phát triển React trong trình duyệt của mình và bạn sẽ có thể khám phá và lọc các thành phần theo tên.
React Developer Tools là một plugin dành cho trình duyệt Chrome và Firefox. Khi bạn thêm tiện ích mở rộng, bạn đang thêm các công cụ bổ sung vào bảng điều khiển dành cho nhà phát triển. Truy cập trang plugin của Chrome dành cho Công cụ dành cho nhà phát triển React để cài đặt tiện ích mở rộng.
Mục lục
Xác định Context và component props trong thời gian thực
Trong bước này, chúng ta sẽ bắt đầu với việc tạo một ứng dụng để phân tích văn bản nhập vào. Ứng dụng sẽ xác định và đếm số từ, số ký tự và tần suất ký tự của văn bản trong trường nhập. Khi tạo ứng dụng, bạn sẽ sử dụng React Developers Tools để khám phá state hiện tại và các props của từng component. Chúng ta cũng sẽ sử dụng React Developer Tools để xem context hiện tại trong các thành phần được lồng sâu vào nhau. Cuối cùng, bạn sẽ sử dụng các công cụ để xác định các component re-render (hiển thị lại) khi state thay đổi.
Đến cuối bước này, chúng ta sẽ có thể sử dụng React Developer tools để khám phá một ứng dụng đang hoạt động và quan sát các điểm và trạng thái hiện tại.
Khởi tạo dự án mới
npx create-react-app reactdebugdemo
npm install react-proptypes
Sửa file App.js
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="myApp">
<label htmlFor="text">
Add Your Text Here:
<br/>
<textarea
id="text"
name="text"
rows="10"
cols="100"
>
</textarea>
</label>
</div>
);
}
export default App;
Output:
Bước tiếp theo, tạo file context/TextContext.js:
import React, { createContext } from "react";
const TextContext = createContext();
export default TextContext;
ửa file App.js bắt sự kiện onchange để cập nhật state cho ứng dụng:
import logo from './logo.svg';
import './App.css';
import TextContext from './context/TextContext';
import { useState } from 'react';
function App() {
const [text,setText] = useState('');
return (
<TextContext.Provider value={text}>
<div className="myApp">
<label htmlFor="text">
Add Your Text Here:
<br />
<textarea
id="text"
name="text"
rows="10"
cols="100"
onChange={(event) => setText(event.target.value)}
>
</textarea>
</label>
</div>
</TextContext.Provider>
);
}
export default App;
Output:
Khi cập nhật trong dữ liệu trong textarea, state cũng được cập nhật và chúng ta có thể theo dõi state ngay tại bảng điều khiển của trình duyệt.
Trong tình huống này, chúng ta thấy component App hiện đang bọc một context provider, trong tình huống này chính là TextContext, để hiển thị rõ ràng tên của Provider này trong React Developer Tools, chúng ta có thể sửa file TextContext.js như sau:
import React, { createContext } from "react";
const TextContext = createContext();
TextContext.displayName = "TextContext";
export default TextContext;
Output:
Trong trường hợp này, state cho component Ứng dụng là props value của TextContext.Provider. Nhấp vào TextContext.Provider trong React Developer Tools và bạn sẽ thấy rằng giá trị này cũng phản ánh giá trị đầu vào mà bạn đã gán cho state:
Tiếp theo, thêm một component có tên là TextInformation. Component này sẽ là nơi chứa các component con có phân tích dữ liệu cụ thể, chẳng hạn như số lượng từ.
Bên trong component, chúng ta sẽ tạo ra 3 component riêng biệt: CharacterCount, WordCount và CharacterMap.
Component TextInformation sẽ sử dụng useReducer Hook để chuyển đổi hiển thị của từng component. Tạo một hàm giảm để chuyển đổi giá trị hiển thị của từng thành phần và một nút để chuyển đổi từng thành phần bằng sự kiện onClick:
import React, { useReducer } from "react";
const tabsReducer = (state,action) =>{
return {
...state,
[action]: !state[action]
}
}
const TextInformation = () => {
const [tabs,toggleTabs] = useReducer(tabsReducer,{
characterCount: true,
wordCount: true,
characterCount: true
});
return (
<div className="button-wrap">
<button onClick={(event) => toggleTabs('characterCount')} >Character Count</button>
<button onClick={(event) => toggleTabs('wordCount')} >Word Count</button>
<button onClick={(event) => toggleTabs('characterMap')} >Character Map</button>
</div>
)
}
export default TextInformation
Giải thích: tạo một reducer trong component TextInformation, trong reducer này mục đích để cập nhật giá trị của state, trong tình huống này state là một object lưu trữ các thuộc tính characterCount, wordCount và characterMap mang giá trị boolean. Mỗi khi cập nhật, trạng thái sẽ được cập nhật thành phủ định của giá trị hiện tại (true thành false, false thành true).
Sửa file App.js:
import logo from './logo.svg';
import './App.css';
import TextContext from './context/TextContext';
import { useState } from 'react';
import TextInformation from './components/TextInformation/TextInformation';
function App() {
const [text,setText] = useState('');
return (
<TextContext.Provider value={text}>
<div className="myApp">
<label htmlFor="text">
Add Your Text Here:
<br />
<textarea
id="text"
name="text"
rows="10"
cols="100"
onChange={(event) => setText(event.target.value)}
>
</textarea>
</label>
<TextInformation/>
</div>
</TextContext.Provider>
);
}
export default App;
Output:
Chúng ta thấy ở đây, khi bấm bất kì nút nào giá trị trong state được cậpp nhật và biểu diễn rõ ràng trên React Developer Tools, giúp chúng ta kiểm soát ứng dụng dễ dàng hơn. Và chúng ta hoàn toàn có thể cập nhật giá trị của state hiện tại để kiểm tra xem ứng dụng sẽ thay đổi thế nào ngay trên trình javascript debug.
Tiếp tục tạo các component characterCount, wordCount, characeterMap như nội dung mã phía dưới.
Tạo file components/CharacterCount/CharaceterCount.js:
import React, { useContext } from "react";
import propTypes from 'prop-types';
import TextContext from "../../context/TextContext";
const CharacterCount = ({show}) => {
const text = useContext(TextContext);
if(!show){
return null;
}
return (
<div>Character Count: {text.length}</div>
)
}
export default CharacterCount
CharacterCount.propTypes = {
show: propTypes.bool.isRequired
}
Tạo file components/WordCount/WordCount.js:
import React, { useContext } from "react";
import TextContext from "../../context/TextContext";
const WordCount = ({show}) =>{
const text = useContext(TextContext)
if(!show){
return null;
}
return (
<div>Word Count: {text.split(' ').length}</div>
)
}
export default WordCount
Tạo file components/CharacterMap/CharacterMap.js:
import React, { useContext } from "react";
import TextContext from "../../context/TextContext";
const itemize = (text) => {
const letters = text.split('')
.filter(l => l !== ' ')
.reduce((collection, item) => {
const letter = item.toLowerCase();
return {
...collection,
[letter]: (collection[letter] || 0) + 1
}
}, {})
return Object.entries(letters)
.sort((a, b) => b[1] - a[1]);
}
const CharacterMap = ({ show }) => {
const text = useContext(TextContext);
if (!show) {
return null;
}
return (
<div>
Character Map:
{itemize(text).map(character => (
<div key={character[0]}>
{character[0]}: {character[1]}
</div>
))}
</div>
)
}
export default CharacterMap
Sửa file components/components/TextInformation.js:
import React, { useReducer } from "react";
import CharacterCount from "../CharacterCount/CharacterCount";
import CharacterMap from "../CharacterMap/CharacterMap";
import WordCount from "../WordCount/WordCount";
const tabsReducer = (state,action) =>{
const tabs = {...state};
const newObjState = Object.keys(tabs).reduce((element, index) => {
if(index == action){
return {...element,[index]: !element[action]}
}
return {...element, [index]: false};
}, {});
return {...newObjState};
}
const TextInformation = () => {
const [tabs,toggleTabs] = useReducer(tabsReducer,{
characterCount: true,
wordCount: false,
characterMap: false
});
return (
<div className="button-wrap">
<button onClick={(event) => toggleTabs('characterCount')} >Character Count</button>
<button onClick={(event) => toggleTabs('wordCount')} >Word Count</button>
<button onClick={(event) => toggleTabs('characterMap')} >Character Map</button>
<div className="tab-wrap">
<CharacterCount show={tabs.characterCount} />
<WordCount show={tabs.wordCount} />
<CharacterMap show={tabs.characterMap} />
</div>
</div>
)
}
export default TextInformation
Giải thích: mình thay đổi logic của reducer để khi bấm vào bất kì button nào,component liên quan tới tính năng sẽ hiển thị và các component còn lại sẽ được ẩn đi.
Output và cấu trúc thư mục:
Theo dõi component render qua các lần tương tác
Trong bước này, bạn sẽ sử dụng React Developer Tools Profiler để theo dõi việc hiển thị và render component khi bạn thao tasc ứng dụng. Chúng sẽ điều hướng các bản ghi hoặc hình ảnh trực quan về các chỉ số tối ưu hóa có liên quan của ứng dụng và sử dụng thông tin để xác định các thành phần không hiệu quả, giảm thời gian hiển thị và tăng tốc độ ứng dụng.
Đến cuối bước này, chúng ta sẽ biết cách xác định các component hiển thị trong quá trình tương tác của người dùng và cách viết các component để giảm thiểu vấn đề hiệu năng khi hiển thị.
Một cách nhanh chóng để xem các thành phần thay đổi lẫn nhau như thế nào là bật tính năng đánh dấu khi một thành phần được hiển thị lại. Điều này sẽ cung cấp cho bạn một cái nhìn tổng quan trực quan về cách các component phản ứng với việc thay đổi dữ liệu.
Trong React Developer Tools, bấm vào biểu tượng Settings hình icon bánh răng:
Chuyển sang tab General, chọn Highlight updates when components render.
Sau khi cấu hình, khi dữ liệu được cập nhật, các component thay đổi sẽ highlight màu xanh nhạt như hình dưới:
Việc hiển thị các re-render sẽ giúp chúng ta nhanh chóng nắm được các component có liên quan như thế nào, nhưng nó không cung cấp cho bạn nhiều dữ liệu để phân tích các component cụ thể. Để có thêm thông tin chi tiết, chúng ta tìm hiểu về profiler.
Profiler tool được thiết kế để giúp lập trình viên đo lường chính xác thời gian mỗi component cần để hiển thị. Điều này có thể giúp bạn xác định các component có thể chậm hoặc nhanh.
Mở lại settings và bỏ chọn hộp Highlight updates when components render. Sau đó nhấp vào tab Profiler trong bảng điều khiển.
Để sử dụng trình biên dịch, hãy nhấp vào vòng tròn màu xanh lam ở bên trái màn hình để bắt đầu ghi và nhấp lại vào nó khi bạn hoàn tất:
Sau khi record và stop record và thao tác trên ứng dụng, chúng ta thấy Profiler chỉ rõ ra cho chúng ta thời gian render component chi tiết:
Khi bạn kết thúc quá trình ghi, React Developer Tools sẽ tạo một bản ghi hiển thị mọi thành phần được hiển thị lại và mất bao lâu để hiển thị lại từng thành phần.
Trong trường hợp này, mọi lần nhấn phím đều khiến component re-render. Quan trọng hơn, nó cho biết mỗi lần hiển thị mất bao lâu và tại sao lại có độ trễ lâu. Khi test thêm, chúng ta nhận diện được Component CharacterMap mất khoảng 1 giây mỗi lần nhấn phím để hiển thị lại do phân tích cú pháp dữ liệu phức tạp trong các vòng lặp.
Chúng ta có thể thu thập nhiều thông tin hơn khi bấm vào tùy chọn Record why each component rendered while profiling. Trong Profiler settings.
Để xử lý vấn đề này, chúng ta sử dụng giải pháp liên quan đến một số loại bộ nhớ đệm thông qua memorization, một quá trình mà dữ liệu đã được tính toán được ghi nhớ thay vì được tính toán lại. Bạn có thể sử dụng thư viện như lodash / memoize hoặc memoize-one để lưu kết quả của hàm itemize hoặc bạn có thể sử dụng hàm memo trong React để ghi nhớ toàn bộ thành phần.
Nếu bạn sử dụng React memo, hàm sẽ chỉ hiển thị lại nếu props hoặc text thay đổi. Trong trường hợp này, bạn sẽ sử dụng React memo:
import React, { memo, useContext } from "react";
import TextContext from "../../context/TextContext";
const itemize = (text) => {
const letters = text.split('')
.filter(l => l !== ' ')
.reduce((collection, item) => {
const letter = item.toLowerCase();
return {
...collection,
[letter]: (collection[letter] || 0) + 1
}
}, {})
return Object.entries(letters)
.sort((a, b) => b[1] - a[1]);
}
const CharacterMap = ({ show }) => {
const text = useContext(TextContext);
if (!show) {
return null;
}
return (
<div>
Character Map:
{itemize(text).map(character => (
<div key={character[0]}>
{character[0]}: {character[1]}
</div>
))}
</div>
)
}
export default memo(CharacterMap)
Sau đó, chúng ta kiểm tra và thấy rằng khi sử dụng Memo, CharacterMap sẽ không bị rerender khi bấm nút sử dụng Component WordCount và CharacterCount.
Memorization rất hữu ích, nhưng bạn chỉ nên sử dụng nó khi gặp vấn đề về hiệu suất rõ ràng, như bạn đã làm trong trường hợp này. Nếu không, nó có thể tạo ra một vấn đề về hiệu suất: React sẽ phải kiểm tra các props mỗi khi nó hiển thị lại, điều này có thể gây ra sự chậm trễ trên các component nhỏ hơn.