ReactJS: Tăng tốc độ đổ dữ liệu từ React-server
Nhắc đến React, chúng ta thường nghĩ đến một công cụ cho browser (đối với những người sử dụng React Native thì họ sẽ nghĩ đến một công cụ cho client). Nhưng số lượng sử dụng React render bên server ngày càng tăng (React-server) và hiện tại thì React khá là chậm trong khoản này nên làm ...
Nhắc đến React, chúng ta thường nghĩ đến một công cụ cho browser (đối với những người sử dụng React Native thì họ sẽ nghĩ đến một công cụ cho client). Nhưng số lượng sử dụng React render bên server ngày càng tăng (React-server) và hiện tại thì React khá là chậm trong khoản này nên làm sao tăng tốc độ gửi HTML cho người dùng để tăng trải nghiệm web cũng là một điều các dev đang quan tâm. Trong video demo của tác giả Sasha Aickin, ông có một ví dụ nhỏ để test benchmark (mục đích của bài viết chỉ là tổng kết lại những kết luận của tác giả mà không đi sâu vào test nên bạn chịu khó xem video để biết thêm chi tiết). Tác giả tạo một demo bắt React đổ khoảng 900 KBs ra server và gửi về client và sử dụng benchmarkjs để test. Ở lần thử đầu tiên thì tác giả thử với cmd:
1 2 3 |
node index.js |
và mất khoảng 1025ms để hoàn thành load HTML. Có 3 nguyên nhân chính dẫn đến việc giảm hiệu năng của React-server:
- renderToStrings (method trong React-server để đổ dữ liệu) chạy trên một thread (synchronous) nên nhiều CPU core không giúp cho việc chạy function này nhanh hơn.
- Client render và Server render giống nhau ( mặc dù điểm cộng là code sẽ dễ đọc hơn nhưng điểm trừ lớn là sự khác biệt giữa server và client side như React-server vẫn phải theo dõi virtual DOM và event trong khi việc này rất vô nghĩa và tốn dung lượng )
- Facebook không sử dụng cho production (=)) chắc tụi nó thấy chậm quá)
Sau đây là 6 mánh có thể giúp bạn cải thiện hiệu suất của React-server một cách tối đa.
1. PRODUCTION MODE
Đổi
1 2 3 |
node index.js |
thành
1 2 3 |
NODE_env = production node index.js |
Đơn giản như thế thôi. Bạn có thể thấy ở đây, tốc độ load về còn 263 ms, hiệu năng tăng khoảng 4 lần. Tại sao nhỉ?
Trong development mode, React rất chu đáo trong việc cung cấp cho bạn hệ thống báo lỗi khá tốt nhưng chính điều này làm chậm quá trình đổ dữ liệu nên khi chuyển sang production mode, toàn bộ hệ thống này sẽ không được sử dụng nên hiệu suất tăng là điều hiển nhiên.
2. Sử dụng bản min cho browser
Việc này đơn giản nhưng lại đem lại hiệu quả cũng tương đối (được ít nào hay ít đấy). Trong video, sau khi tác giả sử dụng bản thu gọn (min version) thì tốc độ load giảm khoảng 70ms ( trung bình trong các test khác của tác giả thì hiệu suất tăng khoảng 30%). Vì lý do gì đó mà node luôn kiểm tra xem app của chúng ta có chạy production mode(process.env.NODE_ENV) không và việc này rất tốn kém (khoảng 1/3 thời gian load). Rất nhiều người đã báo bug này lên React-server nên mong là trong thời gian không xa bug sẽ được fix. Lưu ý là sử dụng cách này cho production mode thôi nhé không dùng cho development mode, không là sẽ rất khó để debug.
3. Babels transforms
React bản 14. có thêm hai tính tăng quan trọng là Constant Elements và Inline Elements.
Constant Elements tìm component được khai báo dưới dạng const trong JSX, đẩy nó lên đầu tiên (hoist), gán vào biến khác và sử dụng luôn biến đó nên mỗi lần render React sẽ không phải tạo lại từ đầu.
Ví dụ trong trường hợp ta tạo một component như sau:
1 2 3 4 5 6 7 |
class myComponents extends React.Component{ render(){ return <Foo> bar </Foo> } } |
Constant Elements sẽ kéo nó ra ngoài như sau:
1 2 3 4 5 6 7 8 9 |
//var element = <Foo> bar </Foo>; bien nay do babel transform tao va se goi dau tien khi file jsx nay duoc goi nen React.component se khong tao them class nua class myComponents extends React.Component{ render(){ return <Foo> bar </Foo> } } |
Giờ khi chạy render, React Component sẽ không phải tạo lại một class mới nữa.
Inline Elements loại bỏ hoàn toàn việc gọi React.createElement và thay vào bằng literal object để giảm thiểu việc tạo lại element mỗi khi render. Nó thay thế object
1 2 3 |
<Baz foo = "bar"/> |
thành
1 2 3 4 5 6 7 8 |
({$$typeof: babelHelpers.typeofReactElemet, type: Baz, key: null, ref: null, props: babelHelpers.defaultProps (Baz.defaultProps, {foo:"baz"}), _owner:null }) |
(nhìn đã thấy hoa cả mắt nhưng may bạn không cần phải nhớ làm gì).
Chỉ việc:
1 2 3 |
npm install --save-dev babel-plugin-transform-react-constant-elements babel-plugin-transform-react-inline-elements |
và thêm vào Babel config:
1 2 3 |
"plugins": ["transform-react-constant-elements", "transform-react-inline-elements"] |
Voilà. Tốc độ load còn 173ms (thường giảm khoảng 10%).
4. Tránh sử dụng createClass
Component được tạo bởi React.createClass chậm hơn sử dụng ES6 class và stateless components rất nhiều.
Thay vì
1 2 3 4 5 6 7 8 9 10 |
const Component = React.createClass({ render:function(){ return <div onClick = {this.click}> Something </div> } click: function(){ alert("click roi nhe!"); } }) |
nên đổi thành
1 2 3 4 5 6 7 8 9 10 11 |
class Component extends React.Component{ render(){ return <div onClick = {()=> this.click()}> Something </div>; } click(){ alert("click roi nhe!"); } } //Lưu ý là vì React.createClass có chế độ autobind nên khi đổi sang dùng ES6 nên nhớ bind vào một anonymous function |
Trong demo, tốc độ load giảm còn 66ms. (Khả năng autobind của React.createClass có vấn đề)
5. Streaming
Việc browser xử lý càng ngày càng tốt data stream tạo điều kiện tốt hơn cho server khi truyền tải dữ liệu cũng như tạo trải nghiệm tốt hơn cho người dùng. Bạn có thể tìm hiểu thêm qua “Chunked Encoding” trong HTTP/1.1(1999).
React-dom chỉ có method renderToString trả về String sau khi đã render đầy đủ nên tác giả đã tạo react-dom-stream. Khi sử dụng react-dom-stream, thời gian gửi stream data sẽ như nhau kể cả khi nội dung dài hơn.
Chỉ cần đổi
1 2 3 |
import ReactDomServer from "react-dom/server" |
thành
1 2 3 |
import ReactDOMServer from "react-dom-stream/server" |
và đổi tất cả renderToString thành
1 2 3 4 5 |
app.get("/", (req,res)=>{ ReactDOMServer.renderToString(<Foo/>).pipe(res); }); |
Ở đây, byte đầu tiên được gửi chỉ sau 4ms, byte cuối được gửi sau 62ms. Lưu ý nhỏ ở đây là cách này mới đang trong giai đoạn beta, không nên sử dụng trong production mode (trừ khi bạn thích thì bạn có thể làm để tự tạo điểm nhấn thôi).
6. Cache Components
Các component trong React chỉ bao gồm props và state sau đó được đổ ra String nên việc cache ở server là hoàn toàn có thể.
Đầu tiên là install react-dom-stream@beta qua npm, thêm cache object vào renderToString
1 2 3 4 5 |
const cache = new LRURenderCache(); ReactDOMServer.renderToString(<Foo />, {cache: cache}).pipe(res); |
Thêm một method như sau:
1 2 3 4 5 |
componentCacheKey(){ return JSON.stringify(this.props); } |
Việc cache mà không có key rất nguy hiểm vì React sẽ không biết component nào để cache.
Sau demo, tốc độ load còn 5ms. Mặc dù đây là trường hợp tối ưu(best case scenario) tức là toàn bộ component là không đổi (immutable) nhưng ý tưởng này cũng khá hay. Điểm trừ là
- đây mới chỉ là alpha, chưa được thử trong production
- nếu bạn cache quá nhiều thứ khác nhau thì nó cũng chả khác với việc không cache là mấy
- cache mà nhầm 1 key thì data của bạn có thể đưa cho nhầm người (trong một lần backup lại server, steam đã gửi nhầm cache page có tài khoản ngân hàng của một khách hàng cho một khách hàng khác)
Tổng hợp
Production Mode: YES!
Minified React: YES!
Babel Trasnform: YES!
ES6 Classes & Stateless Components: YES!
Steaming: Tùy.
Cache: Thử thôi nhé.
Techtalk via Techmaster