Bài viết được sự cho phép của tác giả Lưu Bình An
Vấn đề
Bạn viết code, chạy thử trên localhost, yeah mọi thứ super nhanh
Nhưng khi chạy trên production thì mọi thứ sẽ trở nên như thế này
Sẽ có rất nhiều giải pháp để xử lý vấn đề này.
Tại sao chúng ta phải load một component mà user chưa sử dụng đến, thậm chí có khi trong suốt quá trình làm việc, user sẽ không đụng đến một số màn hình?
Giải pháp
Nếu bạn làm web nhiều thì chắc có nghe đến lazy load image, để giảm bớt số lượng request gởi đi và dữ liệu trả về cho lần load đầu tiên. Trong React cũng có khái niệm tương tự như vậy.
Tất cả những việc đó thực hiện được là nhờ sự thần thánh của webpack
Thông thường chúng ta import component như sau
import ScreenComponent from './ScreenComponent';
Vậy đã bao giờ bạn thử
import('./ScreenComponent');
Vâng, nó sẽ trả về 1 Promise!
Nếu import như thế, Webpack sẽ split file build ra và thực hiện việc load chỉ khi được gọi
Code ví dụ
import React, { Component } from 'react';
import Todo from './Todo';
constructor(props) {
super(props);
this.state = {
value: '',
todos: []
}
}
// ...
render() {
return (
<div className="App">
<header className="App-header">
Add todo
</header>
<input value={this.state.value} onChange={this.changeValue} />
<button onClick={this.submitValue}>Add</button>
<hr />
<Todo todos={this.state.todos} />
</div>
);
}
Nếu ở màn hình này, user chưa có gì trong state.todos
, chúng ta không thực sự cần import Todo from './Todo';
, chỉ khi user nhấn add để thêm một item vào trong state.todos
chúng ta mới cần load component <Todo />
Thêm state.LLTodo
để kiểm tra việc load component todo chưa, chúng ta đưa việc load này vào componentDidUpdate()
import React, { Component } from 'react';
//import Todo from './Todo'; // bỏ component import ở đây
constructor(props) {
super(props);
this.state = {
value: '',
todos: [],
LLTodo: null
}
}
componentDidUpdate(prevProps, prevState) {
if (!this.state.LLTodo) {
this.lazyLoadTodo();
}
}
lazyLoadTodo = () => {
const lazyLoad = import('./Todo');
debugger; // để xem nó trả về gì
}
Nó đã trả về cho chúng ta một Promise, sửa lại hàm này thành async
lazyLoadTodo = async () => {
const {default: Todo} = await import('./Todo');
this.setState({LLTodo: Todo});
}
Chúng ta kiểm tra việc render này theo state.LLTodo
render() {
const {LLTodo, todos} = this.state;
const lazyLoadTodo = LLTodo ? <LLTodo todos={todos} /> : null;
return (
<div className="App">
<header className="App-header">
Add todo
</header>
<input value={this.state.value} onChange={this.changeValue} />
<button onClick={this.submitValue}>Add</button>
<hr />
{lazyLoadTodo}
</div>
);
}
Để thực sự thấy được kết quả, chúng ta có thể build, hoặc trên mở tab network, khi nhập giá trị vào trong input, một file [tên].chunk.js
sẽ được load thêm vào.
File 1.chunk.js được load khi component được update
Mở file này ra để check xem sau
Đúng là file component của chúng ta được được tách ra 😀
Lazy load react-router
Giờ xem xét phần chúng ta setup cho cái route, đại khái nó sẽ như thế này
import Home from './Home'
import Blog from './Blog'
<Switch>
<Route exact path="/" component={HomeComponent} />
<Route path="/blog" component={BlogComponent} />
</Switch>
Áp dụng lazy load component với route, ở đây mình viết thêm một hàm asyncComponent
, nó sẽ nhận vào hàm import('path/to/file')
và return về component đó.
import React, {Component} from 'react';
export default function asyncComponent(getComponent) {
class AsyncComponent extends Component {
static Component = null;
state = {
// chổ này hơi tricky, trỏ về chính nó
Component: AsyncComponent.Component
};
componentDidMount(prevProps, prevState) {
// không re-load nếu đã có rồi
if (!this.state.Component) {
getComponent().then(Component => {
AsyncComponent.Component = Component;
this.setState({ Component })
})
}
}
render() {
const { Component } = this.state;
if (Component) {
return <Component {...this.props} />
}
return null;
}
}
return AsyncComponent;
}
Hàm asyncComponent()
này chúng ta sẽ truyền vào cho nó function là getComponent()
(chính là hàm import)
Chỉnh lại cách chúng ta import component cho từng route
// Dynamically imported components
const Home = asyncComponent(() =>
import('./Home').then(module => module.default)
)
const Blog = asyncComponent(() =>
import('./Blog').then(module => module.default)
)
<Switch>
<Route exact path="/" component={Home} />
<Route path="/blog" component={Blog} />
</Switch>
Tổng kết
Việc lazy load component không quá khó, không cần thêm bất kỳ thư viện, config gì cả nếu bạn đang init project bằng create-react-app
, nhưng nó sẽ là giải pháp phải làm khi file bundle của bạn đã quá lớn và ngày càng lớn hơn.
Bài viết gốc được đăng tải tại vuilaptrinh.com
Có thể bạn quan tâm:
- Tại sao bạn nên sử dụng lazyload image trong ứng dụng Reactjs
- Component là gì? Hiểu về Component trong React và Vue
- Bản chất của Lazy Loading Images
Xem thêm các việc làm react hấp dẫn tại TopDev