Thứ năm, 12/12/2019 | 00:00 GMT+7

6 Mẹo tối ưu hóa cho ứng dụng React

Trong vài năm gần đây, các khung công tác JavaScript đã thay đổi hoàn toàn cách ta xây dựng ứng dụng và React đã có phần công bằng trong quá trình chuyển đổi. Việc tối ưu hóa thời gian tải trang rất quan trọng vì thời gian tải trang tương quan trực tiếp với số trang không truy cập và tỷ lệ chuyển đổi. Trong hướng dẫn này, ta sẽ xem xét sáu lỗi phổ biến mà hầu hết các nhà phát triển mắc phải khi xây dựng ứng dụng với React. Ta cũng sẽ thảo luận về cách có thể tránh được những sai lầm này cũng như nêu bật các mẹo hữu ích để giữ thời gian tải trang web ở mức thấp nhất có thể.

Cách hoạt động của React

Hình ảnh trên giải thích những gì diễn ra mỗi khi bạn sử dụng ứng dụng React. Mọi ứng dụng React đều bắt đầu với một thành phần root - trong trường hợp của ta ở đây là App - và bao gồm các thành phần “con” - AddGrocery , GroceryListSearchBar . Các thành phần con này bao gồm các chức năng hiển thị giao diện user cho DOM dựa trên các thuộc tính và trạng thái có trong chúng.

User tương tác với giao diện user được hiển thị theo nhiều cách khác nhau - bằng cách điền vào biểu mẫu hoặc nhấp vào nút. Khi điều này xảy ra, các sự kiện được chuyển tiếp trở lại thành phần mẹ. Các sự kiện này gây ra sự thay đổi trong trạng thái của ứng dụng, do đó, khiến React phải kết xuất lại giao diện user trong DOM ảo của nó.

Đối với mọi sự kiện được gửi backup , công cụ của React phải so sánh DOM ảo với DOM thực và tính toán xem có cần thiết phải cập nhật DOM thực với dữ liệu tìm thấy mới này hay không. Bây giờ, mọi thứ trở nên lộn xộn nếu Ứng dụng của ta được cấu trúc theo cách mà mỗi lần nhấp và cuộn đều dẫn đến việc React liên tục so sánh và cập nhật các thay đổi giữa DOM ảo và thực. Điều này có thể dẫn đến một ứng dụng rất chậm và kém hiệu quả.

Những sai lầm phổ biến và cách tránh chúng

Dưới đây là ba thực hành kém mà các nhà phát triển thực hiện khi xây dựng ứng dụng. Những sai lầm này làm giảm hiệu quả và tăng thời gian tải trang nói chung, hãy tránh chúng càng nhiều càng tốt.

Nhập khẩu không cần thiết

Khi nào bạn nhập toàn bộ thư viện trong ứng dụng của bạn , nó sẽ bị hủy để truy cập vào module bạn cần. Một hoặc hai thư viện nhỏ có thể không gây hại gì nhưng khi bạn đang nhập nhiều thư viện và phần phụ thuộc, bạn chỉ nên nhập module bạn cần:

import assign from "101"; import Map from "immutable"; 

Đoạn mã trên nhập toàn bộ thư viện và bắt đầu cấu trúc lại nó để truy cập vào lệnh assignMap . Thay vì làm điều đó, hãy sử dụng một khái niệm được gọi là "hái anh đào" chỉ lấy những phần cần thiết của những gì ứng dụng của bạn cần:

import assign from "101/assign"; import Map from "immutable/src/map"; 

Nhúng các hàm trong JSX

JavaScript được biết đến nhiều như một ngôn ngữ được thu thập rác. Bộ thu gom rác của nó quản lý bộ nhớ bằng cách cố gắng lấy lại bộ nhớ không còn được sử dụng bởi các phần của ứng dụng. Việc xác định một hàm trong kết xuất sẽ tạo ra một thể hiện mới của hàm mỗi khi thành phần chứa được kết xuất lại. Khi thực tiễn này xảy ra trên khắp ứng dụng của bạn, nó cuối cùng sẽ trở thành vấn đề đối với bộ thu gom rác dưới dạng rò rỉ bộ nhớ - một tình huống mà bộ nhớ được sử dụng bởi các phần của ứng dụng không được giải phóng mặc dù nó không còn được sử dụng bởi các phần đó:

class GroceryList extends React.Component {     state = {         groceries: [],         selectedGroceryId: null     }      render(){         const { groceries } = this.state;         return (            groceries.map((grocery)=>{                return <Grocery onClick={(e)=>{                     this.setState({selectedGroceryId:grocery.groceryId})                }} grocery={grocery} key={grocery.id}/>            })          )     } } 

Điều đúng đắn sẽ là xác định một hàm mới onGroceryClick ngay trước khi render :

class GroceryList extends React.Component {     state = {         groceries: [],         selectedGroceryId: null     }      onGroceryClick = (groceryId)=>{         this.setState({selectedGroceryId:groceryId})     }      render(){         const { groceries } = this.state;         return (            groceries.map((grocery)=>{                return <Grocery onClick={this.onGroceryClick}                  grocery={grocery} key={grocery.id}/>            })          )     } } 

Sử dụng Toán tử Spread trong Phần tử DOM

Khi xây dựng ứng dụng, việc sử dụng toán tử lây lan theo cách không được bảo vệ là một thực tiễn kém. Điều này sẽ có các thuộc tính không xác định bay xung quanh và nó chỉ là vấn đề thời gian trước khi mọi thứ bắt đầu trở nên phức tạp. Đây là một ví dụ ngắn gọn:

const GroceriesLabel = props => {     return (       <div {...props}>         {props.text}       </div>     );   }; 

Khi ứng dụng bạn đang xây dựng trở nên lớn hơn . . .props có thể chứa bất cứ thứ gì. Một phương pháp tốt hơn và an toàn hơn là xác định bất kỳ giá trị nào bạn cần:

const GroceriesLabel = props => {     return (       <div particularValue={props.particularValue}>         {props.text}       </div>     ); }; 

Hack hiệu suất

Sử dụng Gzip để nén các gói của bạn

Điều này rất hiệu quả và có thể giảm kích thước file của bạn tới 65%. Hầu hết các file trong ứng dụng của bạn sẽ sử dụng nhiều văn bản và khoảng trắng lặp lại. Gzip xử lý điều này bằng cách nén các chuỗi lặp lại này, do đó rút ngắn đáng kể thời gian hiển thị đầu tiên của trang web . Gzip nén trước các file của bạn bằng cách sử dụng compressionPlugin - plugin local của Webpack để nén các file trong quá trình production , trước tiên hãy tạo một gói compressionPlugin bằng cách sử dụng compressionPlugin :

plugins: [     new CompressionPlugin({         asset: "[path].gz[query]",         algorithm: "gzip",         test: /.js$|.css$|.html$/,         threshold: 10240,         minRatio: 0.8     }) ] 

Khi file đã được nén, sau đó nó có thể được phân phát bằng phần mềm trung gian:

//this middleware serves all js files as gzip app.use(function(req, res, next) {     const originalPath = req.path;     if (!originalPath.endsWith(".js")) {         next();         return;     }     try {         const stats = fs.statSync(path.join("public", `${req.path}.gz`));         res.append('Content-Encoding', 'gzip');         res.setHeader('Vary', 'Accept-Encoding');         res.setHeader('Cache-Control', 'public, max-age=512000');         req.url = `${req.url}.gz`;          const type = mime.lookup(path.join("public", originalPath));         if (typeof type != 'undefined') {             const charset = mime.charsets.lookup(type);             res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));         }     } catch (e) {}     next(); }) 

Ghi nhớ các thành phần React

Khái niệm ghi nhớ không phải là mới - nó là một kỹ thuật lưu trữ các lệnh gọi hàm đắt tiền và trả về kết quả được lưu trong bộ nhớ cache khi cùng một đầu vào xuất hiện lại. Ghi nhớ trong React hoạt động bằng cách ghi nhớ tính toán dữ liệu để các thay đổi về trạng thái diễn ra nhanh nhất có thể. Để hiểu cách hoạt động của nó, hãy xem thành phần React bên dưới:

const GroceryDetails = ({grocery, onEdit}) => {     const {name, price, grocery_img} = grocery;     return (         <div className="grocery-detail-wrapper">             <img src={grocery_img} />             <h3>{name}</h3>             <p>{price}</p>         </div>     ) } 

Trong mẫu mã ở trên, tất cả các mã con trong GroceryDetails đều dựa trên các đạo cụ. Thay đổi đạo cụ sẽ khiến GroceryDetails kết xuất lại. Nếu GroceryDetails là một thành phần không có khả năng thay đổi, thì nó nên được ghi nhớ. User các version trước của React (<V16.6.0) sẽ sử dụng moize , một thư viện ghi nhớ cho JavaScript. Hãy xem cú pháp bên dưới:

import moize from 'moize/flow-typed';  const GroceryDetails = ({grocery, onEdit}) =>{     const {name, price, grocery_img} = grocery;      return (         <div className="grocery-detail-wrapper">             <img src={grocery_img} />             <h3>{name}</h3>             <p>{price}</p>         </div>     ) }  export default moize(GroceryDetails,{     isReact: true }); 

Trong khối mã ở trên, moize lưu trữ bất kỳ đạo cụ và ngữ cảnh đã chuyển nào trong GroceryDetails và sử dụng cả hai để phát hiện xem có cập nhật cho thành phần hay không. Miễn là đạo cụ và ngữ cảnh không thay đổi, các giá trị được lưu trong bộ nhớ cache sẽ được trả về. Điều này đảm bảo kết xuất siêu nhanh.

Đối với user làm việc với các version React mới hơn (lớn hơn V16.6.0), bạn có thể sử dụng React.memo thay vì moize:

const GroceryDetails = ({grocery, onEdit}) =>{     const {name, price, grocery_img} = grocery;      return (         <div className="grocery-detail-wrapper">             <img src={grocery_img} />             <h3>{name}</h3>             <p>{price}</p>         </div>     ) }  export default React.memo(GroceryDetails); 

Cân nhắc sử dụng kết xuất phía server

Kết xuất phía server (SSR) là khả năng của khung công tác front-end để hiển thị đánh dấu trong khi chạy trên hệ thống back-end.

Bạn nên tận dụng SSR với Ứng dụng trang đơn. Thay vì để user đợi các file JavaScript của bạn tải, user ứng dụng của bạn sẽ nhận được trang HTML được hiển thị đầy đủ ngay sau khi yêu cầu ban đầu được gửi trả lại phản hồi. Nói chung, các ứng dụng được hiển thị từ phía server cho phép user nhận nội dung nhanh hơn so với các ứng dụng do client kết xuất. Một số giải pháp để hiển thị phía server trong React bao gồm Next.js và Gatsby.

Kết luận

Đối với những người mới bắt đầu có thể không quen với những khái niệm này, tôi khuyên bạn nên xem khóa học này để bắt đầu với React vì nó bao gồm rất nhiều thứ - từ trạng thái xử lý đến tạo các thành phần. Không thể đánh giá thấp tầm quan trọng của việc tối ưu hóa thời gian tải trang trong ứng dụng React của bạn. Ứng dụng của bạn không chỉ giúp hỗ trợ SEO tốt hơn và đưa ra tỷ lệ chuyển đổi cao hơn, theo các phương pháp hay nhất, bạn có thể phát hiện lỗi dễ dàng hơn. Những khái niệm này có thể đòi hỏi nhiều nỗ lực nhưng chúng rất đáng để thử. Các bài đăng như Memoize React Components của Tony Quetano và Cách phục vụ file nén Webpack trong quá trình production của Selva Ganesh đã giúp viết bài này.


Tags:

Các tin liên quan