React: My Experience

Đợt vừa rồi lại làm dự án với React, note lại một số “trải nghiệm” hữu ích thêm ^^

Performance

Việc sử dụng state không đúng có thể dẫn đến component render rất nhiều lần. Một số Component “nặng” render cũng mất thời gian và công việc của bạn là cần tìm một công cụ để biết các component này, sau đó tối ưu

Profiler

React cũng cấp Profiler API cho phép bạn xem các lần render và “chi phí” (thời gian) cho mỗi lần render. Để sử dụng Profiler, bạn có thể xem trên DevTools hoặc thực hiện Coding

CustomStockChart.jsProfiler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { Profiler } from "react";
...
const CustomStockChart = props => {
// ...

return (
<Profiler id="StockChart" onRender={logTimes}>
<StockChart>
{/* ... */}
</StockChart>
</Profiler>
);
};
const logTimes = (id, phase, actualTime, baseTime, startTime, commitTime) => {
// Phase này sẽ là mount khi mount vào DOM hoặc update cho mỗi lần render Componnet, thường
// sẽ nhièu update, và tính tổng thời gian render ta sẽ cộng thời gian lại
console.log(`${id}'s ${phase} phase:`);
console.log(`Actual time: ${actualTime}`);
console.log(`Base time: ${baseTime}`);
console.log(`Start time: ${startTime}`);
console.log(`Commit time: ${commitTime}`);
};

export default CustomStockChart;

Gộp State

Khi dùng nhiều biến State, bạn có thể gộp chúng lại để giảm thiểu số lần render. Ví dụ thay vì nhiều state

1
2
3
4
5
function ExampleWithManyStates() {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

Bạn có thể gộp cung chung lại thành một state
1
2
3
4
5
6
7
function ExampleWithManyStates() {
// Declare multiple state variables!
const [manyState, setManyState] = useState({
age: 42,
fruit: 'banana',
todos: { text: 'Learn Hooks' }]
});

Tuy nhiên một khi đã gộp state, mỗi lần cập nhật state, bạn phải merge các giá trị trước đó nữa một cách thủ công

1
2
3
4
5
6
7
8
9
10
11
// ...
useEffect(() => {
function handleWindowMouseMove(e) {
// "...state" để đảm bảo không "mất" giá trị width và height
setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
}
// Lưu ý: phần này viết đơn giản nhất có thể
window.addEventListener('mousemove', handleWindowMouseMove);
return () => window.removeEventListener('mousemove', handleWindowMouseMove);
}, []);
// ...

Cách merge toàn bộ state về một object chưa phải là tối ưu hẳn. Bạn có thể sử dụng đan xem việc tách nhiều state và gộp lại với nhau trong một Component để hài hoà. Chúng ta chỉ nên gộp state nếu các state có logic liên quan đến nhau.

Chi tiết xem thêm tại Một số câu hỏi thường gặp với Hook.

Memo, useCallback

React memo là một HOC giúp tối ưu các việc render không cần thiết

This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.

Code-Splitting

Đây là cách tối ưu source code đi liền với việc cấu trúc thư mục của bạn. Một hướng dẫn hữu ích bạn nên đọc qua.

Đóng Gói (Bundling)

Cách đóng gói code, giảm các tab và space khi deploy trên môi trường production

Code Splitting

Đóng gói hẵn rất tuyệt vời, nhưng khi ứng dụng của bạn trở nên lớn hơn, file đóng gói của bạn cũng sẽ lớn theo. Đặc biệt khi bạn sử dụng third-party library (thư viện bên thứ 3) lớn. Bạn cần phải cẩn thận với những đoạn code bạn đang include vào bundle của mình, bằng cách đó bạn sẽ không vô tình làm nó trở nên quá lớn khiến ứng dụng mất nhiều thời gian để tải.

Để tránh việc nhận được một bundle lớn, tốt nhất nên bắt đầu “splitting (chia nhỏ)” gói bundle của bạn. Code-Splitting là một feature hỗ trợ bởi bundler như Webpack, Rollup và Browserify (via factor-bundle) nó có thể tạo ra nhiều bundle nhỏ có thể được load một cách tự động tại thời điểm runtime.

Phân chia code cho ứng dụng giúp “lazy-load” chỉ những phần người dùng đang cần, tăng đáng kể hiệu suất mà không cần phải giảm số lượng code trong ứng dụng, bạn đã tránh phải tải những đoạn code người dùng có thể sẽ không bao giờ cần đến, và giảm số lượng code cần tải lên trong lần đầu tiên.

Đoạn này hiểu là chia code thành các folder và import các file, component thực sự cần thiết :D

import()

Phương pháp tốt nhất để sử dụng code-splitting trong ứng dụng là thông qua cú pháp import() động.

Trước

1
2
3
import { add } from './math';

console.log(add(16, 26));

Sau
1
2
3
import("./math").then(math => {
console.log(math.add(16, 26));
});

React.lazy

Chú ý:

React.lazy và Suspense chưa có sẵn cho server-side rendering. Nếu bạn muốn phân chia code ở những ứng dụng render tại server, chúng tôi xin giới thiệu Loadable Components. Nó có hướng dẫn phân chia code với server-side rendering.

Error boundaries

Nếu OtherComponent không thể tải lên (Ví dụ, lỗi mạng), nó sẽ kích hoạt lỗi. Bạn có thể điều khiển những lỗi đó để hiển thị một trải nghiệm người dùng tốt hơn và quản lý phục hồi với Error Boundaries. Một khi bạn đã tạo Error Boundary, bạn có thể sử dụng nó bất kỳ nơi nào bên trên lazy components của bạn để hiển thị thông báo lỗi khi có sự cố về mạng.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);

Performance Tab

https://reactjs.org/docs/optimizing-performance.html

Theme + Global Style

Lỗi import vòng

Nếu bạn bị trang trắng và các lỗi liên quan đến gọi phương thức của một object null (Uncaught TypeError), có thể bạn đang bị lỗi này

1
Uncaught TypeError: Super expression must either be null or a function, not undefined

Hãy theo dõi trace của console để tìm ra chỗ bị import vòng

Debug Tool

Cách share data

  • Redux
  • Context

Others

Render Props

ref ,forwardRef

Config env

Để thêm biến env cho React, bạn chỉ cần tạo file .env và đặt tên biến với tiền tố REACT_APP_

1
2
#.env
REACT_APP_NAME="Ming"

và sử dụng
1
console.log(process.env.REACT_APP_NAME)

Ngoài ra thứ tự load file env có thể xem tại đây

Proxy

Sử dụng khi điều hướng qua Proxy

QA (cần đọc hết)

Portals

Cánh cổng giúp “dịch chuyển” thẻ con đến ngoài thẻ cha

Uncontrolled Components

Trong hầu hết các trường hợp, chúng tôi khuyên bạn nên sử dụng controlled components để triển khai forms. Trong controlled component, dữ liệu trong form sẽ được quản lí hoàn toàn bởi React component. Trái ngược với điều đó, uncontrolled component, dữ liệu sẽ được quản lí trực tiếp bởi chính DOM.

Để tạo uncontrolled component, thay vì việc xử lí sự kiện mỗi khi state được update, bạn có thể sử dụng ref để lấy dữ liệu từ DOM.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}

handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

Vì uncontrolled component chỉ dựa trên DOM, điều này khiến việc tương tác với code React và non-React khá dễ dàng. Nếu bạn muốn code nhanh và bẩn, uncontrolled components có thể giúp bạn code ít code hơn. Nếu không, bạn nên sử dụng controlled components.

Author

Ming

Posted on

2022-07-24

Updated on

2022-08-01

Licensed under

Comments