Tăng tốc cho React
Bài đăng này đã không được cập nhật trong 3 năm
Về cơ bản React cung cấp kĩ thuật làm cho thời gian cập nhật lại UI tương ứng với mỗi thay đổi là nhỏ nhất. Hầu hết các ứng dụng thì React phản hồi rất nhanh đối với tương tác của người dùng khi ứng dụng không quá lớn hoặc phức tạp. Bên cạnh đó có một vài kĩ thuật để tăng tốc React
Tránh việc render lại ko cần thiết
Khi mà props hoặc state của component thay đổi. React sẽ quyết định việc có thay đổi DOM hay không bằng cách so sánh giá trị trả về hiện tại với giá trị trả về trước đó của phương thức render. Nếu chúng khác nhau React sẽ cập nhật lại DOM
Một vài trường hợp bạn cần tăng tốc độ phản hổi của trang, bạn có thể thay đổi việc một component có render lại không thông qua phương thức shouldComponentUpdate
, nó được gọi trước khi tiến trình render lại (re-rendering) được chạy. Mặc định phương thức này luôn trả về giá trị là true
shouldComponentUpdate(nextProps, nextState) {
return true;
}
Nếu bạn chắc chăn một số trường hợp đặc biệt thì comopent của bạn không cần thiết phải render lại, bạn có thể trả về false cho phương thức shouldComponentUpdate
để tránh việc component này render lại mất nhiều thời gian
shouldComponentUpdate(nextProps, nextState) {
if (someConditions) {return false}
return true
}
Trên đây là một ví dụ
Như bạn thấy
Component C1 là gốc, nếu component này thay đổi state hoặc props mặc định nó sẽ render lại kéo theo đó tất cả các component con của nó cũng render lại theo. Tuy nhiên do thay đổi này yêu cầu chỉ cần thay đổi component C3 và C6 là đủ. Như vậy khi cài đặt ta sẽ kiểm tra điều kiện sao cho phương thức shouldComponentUpdate
của coponent C2, C7, C8 trả về false và C1, C3, C6 trả về true. Có thể bạn sẽ thắc mắc với component C4, C5 thì sao? Bởi vì component C2 đã không thay đổi nên kéo theo C4 và C5 cũng sẽ không thay đổi gì và phương thức shouldComponentUpdate
của chúng cũng không được gọi.
Để hiểu chi tiết hơn ta có một ví dụ cụ thể như sau
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
Trong đoạn code này, phương thức shouldComponentUpdate
chỉ trả về true trong trường hợp props.color hay state.count thay đổi. Nếu chúng ko thay đổi thì component này cũng không cần cập nhật lại. Nếu component của bạn trở nên phức tạp, bạn có thể dùng dến một pattern khác gọi là shallow comparison
nó sẽ so sánh tất cả các thuộc tính của props
và state
để quyết định xem component này có cần update hay không. Rất may React cũng đã cung cấp cho bạn một component gọi là React.PureComponent
. Công việc rất đơn giản bạn chỉ cần kế thừa nó là được rồi. Ta có đoạn code tương ứng với code ở trên như sau
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
Hầu hết các trường hợp bạn có thể dùng React.PureComponent
thay thế cho việc viết thủ công shouldComponentUpdate
.
Đặc biệt chú ý là React.PureComponent
sẽ không hoạt động đúng nếu bạn thay đổi state hoặc props một cách trực tiếp (mutated).
Vấn đề này thường dễ mắc phải với trường hợp dữ liệu khá phức tạp hoặc có cấu trúc cây (nested). Ta có một ví dụ minh họa cho việc này như sau
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}
class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// This section is bad style and causes a bug
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}
Trong ví dụ trên bạn có thể thấy mỗi lần click vào button thì sẽ thêm một từ vào trong state.words
. Tuy nhiên kết quả ta thấy sau mỗi lần click thì phần hiển thị vẫn không hề thay đổi. Có thể giải thích như sau:
Khi component WordAdder
render lại trang thì gọi đến component ListOfWords
. Tuy nhiên ListOfWords
lại nhận props words là state.words
của component WordAdder
. Do chúng ta thay đổi gía trị state.words
một cách trực tiếp nên khi gọi lại component ListOfWords
với props words nó sẽ thấy props words này không có gì thay đổi so với lần trước bởi vì nó cùng là một đối tượng.
Sức mạnh của việc không thay đổi dữ liệu một cách trực tiếp
Để giải quyết vấn đề trên, bạn không nên thay đổi state một cách trực tiếp. Ta có thể sửa lại như sau
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
Trong ví dụ trên với array thì ta sẽ sử dụng phương thức concat
thay thế cho push
để không làm thay đổi gía trị của đối tượng.
Tương tự với trường hợp của object. Bạn có thể làm như sau
function updateColorMap(colormap) {
colormap.right = 'blue';
return colormap
}
Ta có thể sửa lại thành
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
Việc ko thay đổi dữ liệu trạng thái (state) một cách trực tiếp khiến cho dữ liệu của bạn trở nên rõ ràng hơn. Trạng thái cũ của đối tượng vẫn được giữ nguyên, như vậy ta có thể dễ dàng kiểm tra xem trạng thái mới có gì khác biệt so với trạng thái cũ, kiểm tra và bắt lỗi cũng dễ dàng hơn rất nhiều.
Cảm ơn bạn đã theo dõi bài viết !!!
Tham khảo
All rights reserved