- Xác định các trường hợp render không cần thiết
- Tách các component update thường xuyên thành các component độc lập
shouldComponentUpdatevà PureComponent- Tránh việc đưa một object mới như là prop
- Production
- Code splitting
- Thay đổi DOM quá nhiều lần
- Callback
Toàn bộ source app ví dụ có thể lấy ở đây https://github.com/ohansemmanuel/Cardie-performace, khuyến khích các bạn nên tự lấy về và vọc.
Chạy lên nó sẽ có như vầy

Khi click vào button nó sẽ update lại thông tin nghề nghiệp

Xác định các trường hợp render không cần thiết
Phương pháp đơn giản nhất để kiểm tra là bật nút highlight update trên React Dev tool.

Cái viền màu xanh hiển thị xung quanh component cho biết nó đang gọi render.
Trong ví dụ này có thể thấy nguyên component App được re-render, không đúng như chúng ta mong muốn.

Tốt nhất là đúng cái phần hiển thị thông tin nghề nghiệp gọi render.
Tách các component update thường xuyên thành các component độc lập
Sau khi đã bắt được component nào đang re-render không cần thiết, tách component này theo phương pháp sau
Component App connect với redux store bằng hàm connect, nó nhận các props là name, location, likes và description

Khi user click cái button, cái description bị thay đổi. Nó làm cho component App re-render.
Nhớ lại kiến thức căn bản React, khi prop hoặc state thay đổi, cây virtual DOM được update

Giờ để xử lý, chúng ta tạo một component mới, đặt là Profession, để render thông tin description, chúng ta cần tổ chức lại để có cây virtual DOM như thế này

Thay vì <App /> nhận prop là profession, chuyển trách nhiệm này cho <Profession />

Kết quả sau khi tách component

shouldComponentUpdate và PureComponent
Các bài viết liên quan đến chủ đề performance, chắc chắn sẽ nhắc đến PureComponent, bài này cũng không ngoại lệ.
class MyComponent extends React.Component {}
class MyComponent extends React.PureComponent {}Điều gì bạn cần quan tâm khi khai báo một component là PureComponent? Đó là việc mất đi hàm shouldComponentUpdate. Về nguyên tắc nó chỉ render lại khi prop thay đổi
const Description = ({ description }) => (
<p>
<span className="faint">I am</span> a {description}
</p>
)Tác giả bài viết này còn chia nhỏ hơn nữa
const Description = ({ description }) => {
return (
<p>
<I />
<Am />
<A />
<Profession profession={description} />
</p>
);
};
Chúng ta cần nhớ lại 1 lần nữa: React định nghĩa thế nào là một PureComponent?
Hãy thử xem xét cách viết shouldComponentUpdate
class ShouldNotUpdate extends React.Component {
constructor(props) {
super(props);
this.counter = 0;
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.children !== nextProps.children;
}
render() {
return `I should be rendered only 1 time. actual times rendered: ${++this.counter}`;
}
}Kết quả trả về sẽ không phải counter = 1, nghĩa là hàm render thực sự sẽ chạy rất nhiều lần, tại sao lại vậy? bởi vì this.props.children !== nextProps.children sẽ luôn luôn trả về true, React sẽ tạo ra một instance mới, 1 ReactElement mới mỗi khi render
class ShouldNotUpdate extends React.PureComponent {
constructor(props) {
super(props);
this.counter = 0;
}
render() {
return `I should be rendered only 1 time. actual times rendered: ${++this.counter}`;
}
}Số lần render vẫn không giảm đi, tại sao? Đọc tiếp sẽ rõ.
Tránh việc đưa một object mới như là prop
Nó sẽ xảy ra tình huống là prop không thay đổi, nhưng React nghĩ là nó đã thay đổi, nên render lại. Ví dụ
class I extends PureComponent {
render() {
return <span className="faint">{this.props.i.value} </span>;
}
}
// đâu đó truyền vào
class Description extends Component {
render() {
const i = {
value: "i"
};
return (
<p>
<I i={i} />
<Am />
<A />
<Profession profession={this.props.description} />
</p>
);
}
}
Ngay cả khi <I /> là một PureComponent, nó vẫn render khi profession thay đổi.
Tại sao?
Khi <Description /> nhận một prop mới, hàm render được gọi
Khi đến đoạn này <I i={i} />, giá trị của i là một object hoàn toàn mới
Với PureComponent nó chỉ dùng shallow compare giữa prop cũ và mới, tức là string và number thì so sánh theo giá trị, còn object so sánh theo tham chiếu đến vùng nhớ
Giá trị của i không khác, nhưng thực sự nó đã tham chiếu đến vùng nhớ khác
Điều này cũng giải thích cho việc tại sao cách làm như thế này không được khuyến khích
Render()
<div onClick={() => { /*do something*/ }} />Function cũng là một object, bạn đang truyền vào một object mỗi lần render
// Do this, please
handleClick = () => {
}
render() {
<div onClick={this.handleClick}
}Production
Deploy thì luôn build bằng production nhỉ, ngoài ra ở phía server nên nén lại bằng Gzip. Nếu dùng Node/Express ở backend, cài thêm module compression và sử dụng như Express middleware
Code splitting
Cái này mình có hướng dẫn rồi, đọc lại ở đây
Thay đổi DOM quá nhiều lần
Có bao giờ bạn từng sử dụng một component nhiều lần trong app, cảm thấy app hơi lag? Animation cảm giác chạy không mượt?
Khi xây các component phức tạp, bạn sẽ phải xử lý DOM một chút, khả năng sẽ vướng vào 2 issue sau
Hãy chạy thử hiệu ứng đang làm cho một component Collapse với khoản vài chục cái instance, sau đó chọn 6x slowdown trên dev tool để thấy sự khác biệt, 6x slowdown là giá trị tương ứng với tốc độ khi xem trên điện thoại
Cải thiện performance của React App
Component Collapse, ta thường sẽ làm là thay đổi độ cao của nó
updateHeight(isOpen) {
if (isOpen) {
this.containerEl.style.height = `${this.contentEl.scrollHeight}px`;
} else {
this.containerEl.style.height = '0px';
}
}Có 2 điểm cần lưu ý
- Chúng ta thay đổi height, là chúng ta trigger chuyện sắp xếp lại Layout. Nếu chúng ta thay đổi
transform, chúng ta chỉ sẽ trigger Composite và nhìn nó sẽ smooth hơn. - Dòng
this.containerEl.style.height = ${this.contentEl.scrollHeight}px;là một ví dụ điển hình của Layout Thrashing, chúng ta đọc giá trị độ cao hiện tại, rồi lấy giá trị đó update cho một đối tượng DOM, nhân số lần này lên với số lượng component Collapse sẽ là một số lần đáng quan tâm. Sẽ tốt hơn nếu chúng ta chỉ đọc một lần rồi gán giá trị một lượt luôn.
updateHeight(isOpen) {
this.lastRAF && cancelAnimationFrame(this.lastRAF);
if (isOpen) {
this.lastRAF = requestAnimationFrame(() => {
// đọc
const height =`${this.contentEl.scrollHeight}px`;
this.lastRAF = requestAnimationFrame(() => {
this.lastRAF = requestAnimationFrame(() => {
// gán giá trị
this.containerEl.style.height = height;
this.lastRAF = null;
});
});
});
} else {
this.containerEl.style.height = '0px';
}
}Có thể sử dụng thư viện Fastdom thay vì tự viết
Callback
Khi attach một function vào bất kỳ event nào trong DOM, nếu có thêm debounced hoặc throttled sẽ tốt hơn, giảm tải số lần gọi đến function này đến mức thấp nhất.
Cách viết rất thường thấy
window.addEventListener('resize', _.throttle(callback))Nhưng tại sao không sử dụng nó trong component callback?
export default class UnleashedOne extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.onChange(e.target.value);
}
render () {
return (
<input onChange={this.onChange}/>
);
}
}Chúng ta đang lắng nghe tất tần tật mỗi khi có thay đổi trên input, như vậy thực sự có cần thiết không?
Để giải quyết vấn đề trên, có thể viết lại component
export default class LeashedOne extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onChangeDebounce = _.debounce(value => this.props.onChange(value), 300);
}
onChange(e) {
this.onChangeDebounce(e.target.value);
}
render () {
return (
<input onChange={this.onChange}/>
);
}
}Đợi user nhập xong đi rồi xử lý sự kiện, ở đây sử dụng _.debounce, _.throttle từ thư viện lodash, sự khác nhau của 2 thằng này thì đọc thêm trên docs của lodash.
Nếu bị nghiện performance, bạn có thể chia sẻ thêm một số tip với mình.
Tham khảo thêm
Tài liệu tham khảo




Initializing...