+3

Tips cải thiện hiệu suất ReactJS

Giới thiệu

Xin chào, lang thang trên mạng và mình tìm được một bài viết khá hay và hữu ích và quyết định sẽ dịch lại để chia sẻ cho bạn đọc và cho chính mình, bài viết đã chỉ ra rất nhiều lỗi mà mình thường gặp phải khi làm với React.

Bài viết sẽ cung cấp cho bạn một vài cách để dễ dàng cải thiện hiệu suất cho React. Không nhất thiết là bạn phải áp dụng những cách dưới đây, nhưng sẽ tốt hơn nếu bạn biết đến nó và áp dụng vào trường hợp cần thiết.

Nội dung

1. Utilise render bail-out techniques

Mỗi khi component cha updates, component con cũng sẽ update theo bất kể props có thay đổi hay không. Nó đồng nghĩa với việc component con vẫn giữ props như trước, nó vẫn sẽ render lại. Điều này rõ dàng làm mất thời gian xử lý, đặc biệt là với component trees lớn, vì về cơ bản React áp dụng diffing algorithm để kiểm tra giá trị được thay đổi computed tree .

Bạn có thể dễ dàng tránh nó bằng cách sử dụng React.PureComponent, tận dụng shouldComponentUpdate hoặc gộp component trong memo (Higher-Order Component). Bằng cách này bạn có thể chắc chắn rằng component chỉ update khi props có sự thay đổi.

Nhưng cũng phải nhắc bạn rằng thực hiện cách này với component nhỏ thì sẽ không có lợi ích, có thể làm chậm ứng dụng đi một chút (vì react sẽ phải so sánh mỗi khi render component). Như vậy, Kỹ thuật này nên được sử dụng cho những component "nặng ký".

Tránh sử dụng cho component nhỏ, nếu cần thiết, hãy tách component lớn ra thành nhiều component nhỏ để gộp lại với memo().

// index.jsx
export default function ParentComponent(props) {
  return (
    <div>
      <SomeComponent someProp={props.somePropValue}
    <div>
      <AnotherComponent someOtherProp={props.someOtherPropValue} />
    </div>
   </div>
 )
}


// ./SomeComponent.jsx
export default function SomeComponent(props) {
  return (
    <div>{props.someProp}</div>  
  )
}

// --------------------------------------------

// ./AnotherComponent.jsx (1)
// Component này sẽ render mỗi khi `props.somePropValue` thay đổi
// Kể cả `props.someOtherPropValue` có thay đổi hay không
export default function AnotherComponent(props) {
  return (
    <div>{props.someOtherProp}</div>  
  )
}

// ./AnotherComponent.jsx (2)
// Component này sẽ chỉ render lại khi `props` thay đổi
export default memo((props) => {
  return (
    <div>{props.someOtherProp}</div>  
  )
});

// ./AnotherComponent.jsx (3)
// Component này cũng sẽ chỉ render lâij khi `props` thay đổi
class AnotherComponent extends React.PureComponent {
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}

// ./AnotherComponent.jsx (4)
// Giống bên trên
class AnotherComponent extends React.PureComponent {
  shouldComponentUpdate(nextProps) {
    return this.props !== nextProps
  }
  
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}

2. Avoid inline objects

Mỗi khi inline một object, React tạo lại một tham chiếu mới cho object này trên mỗi lần render. Điều này gây ra việc nhận object này coi như là một object mới. Như vậy, shallow equality trong props của component này sẽ trả về false với mỗi lần render.

Điều này gián tiếp tham chiếu tới inline styles mà nhiều người thường sử dụng, mình cũng vậy :v. Inlining styles props trong component sẽ force component và luôn render (trừ khi bạn sử dụng shouldComponentUpdate()), điều này có khả năng dẫn đến những vấn đề về hiệu suất, phụ thuộc vào component bao bọc các component con hoặc không.

Đây là một trick khá hay được sử dụng nếu phải có tham chiếu khác. Ví dụ, nó được tạo bên trong map(), bất kể khi nào thuộc tính của object là primitives (không phải hàm, đối tượng hoặc mảng) hoặc non-primitives với "fixed" references, bạn có thể truyền chúng như props thay thế cho truyền object có chưa chúng như prop đơn lẻ. Làm vậy sẽ cho phép component được hưởng lợi từ kỹ thuật rendering bail-out bằng cách so sánh prop mới và cũ.

Bạn không thể hưởng lợi từ React.PureComponent hoặc memo() nếu bạn sử dụng inline style (hoặc trong general objects). Trong tình huống nhất định, bạn có thể spread contents của object và truyền chúng như là props vào component.

// Không nên
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AnotherComponent style={{ margin: 0 }} aProp={aProp} />  
}

// Nên
const styles = { margin: 0 };
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AnotherComponent style={styles} {...aProp} />  
}

3. Avoid anonymous functions

Từ khi anonymous functions là một cách hay để truyền function prop (đặc biệt là cần invoke với props khác như tham số của nó), chúng tham chiếu sự khác biệt mỗi khi render. Nó giống với inline object nói ở phía trên. Để duy trì same reference tới hàm bạn truyền như là một props trong component, bạn có thể bạn có thể khai báo nó như là một class method (nếu bạn sử dụng class-based component) hoặc sử dụng useCallback hook để giữ cùng tham chiếu (nếu bạn sử dụng functional components). Đối với những lúc bạn cần một tham chiếu khác nhau cho từng bộ parameters mà hàm của bạn được gọi (chẳng hạn như hàm tính toán bên trong map()), bạn có thể check out rất nhiều chức năng memoize (như là memoize của lodash). Nó sẽ gọi function caching hoặc listener caching và giúp bạn sử tham chiếu với một dynamic number của anonymous functions ở cost của bộ nhớ trình duyệt.

Đương nhiên inline function là cách dễ nhất và không thực sự gây ra vấn đề về hiệu suất. Điều này có thể là bởi vì bạn sử dụng nó với component nhẹ hoặc component cha không render lại toàn bộ nội dung mỗi khi props thay đổi.

Điều quan trọng cuối cùng đó là anonymous function render props mặc định. Khi bạn sử dụng hàm như children của component, bạn có thể định nghĩa nó bên ngoài component chính, vì đó nó sẽ luôn được fixed refernce.

Thử và bind function props cho method hoặc sử dụng useCallback nhiều nhất có thể để hưởng lời từ kỹ thuật rendering bail-out.

// Không nên
function Component(props) {
  return <AnotherComponent onChange={() => props.callback(props.id)} />  
}

// Nên
function Component(props) {
  const handleChange = useCallback(() => props.callback(props.id), [props.id]);
  return <AnotherComponent onChange={handleChange} />  
}

// Nên
class Component extends React.Component {
  handleChange = () => {
   this.props.callback(this.props.id) 
  }
  
  render() {
    return <AnotherComponent onChange={this.handleChange} />
  }
}

4. Lazy load components that are not instantly needed

Có thể không liên quan tới article, nhưng càng ít component được mount thì chúng càng mount nhanh hơn. Vì vậy, nếu khởi tạo ban đầu của bạn cảm thấy khó khăn, bạn có thể giảm lượng clutter bằng cách tải các component khi cần thiết, sau khi initial đã mount xong. Đồng thời bạn sẽ giảm bundles và cho phép người dùng load nhanh hơn. Cuối cùng, bằng cách phân tách initial rendering, bạn đang phân chia khối lượng công việc thành các nhiệm vụ nhỏ hơn. Bạn có thể tìm hiểu React.Lazy cũng như React.Suspense.

Thử và lazy-load component không thực sự thấy rõ (hoặc cần thiết) cho người dùng tới khi họ tương tác với chúng.

// ./Tooltip.jsx
const MUITooltip = React.lazy(() => import('@material-ui/core/Tooltip'));
function Tooltip({ children, title }) {
  return (
    <React.Suspense fallback={children}>
      <MUITooltip title={title}>
        {children}
      </MUITooltip>
    </React.Suspense>
  );
}

// ./Component.jsx
function Component(props) {
  return (
    <Tooltip title={props.title}>
      <AnotherComponent />
    </Tooltip>
  )
}

5. Tweak CSS instead of forcing a component to mount & unmount

Render rất "tốn kém", đặc biệt khi DOM cần được thay đổi. Bất cứ khi nào bạn có một số loại accordion hoặc tab functionality -- tại đó bạn chỉ nhìn thấy một item tại một thời điểm --, bạn có thể muốn ngắt unmount component mà không thể nhìn thấy và gắn kết lại khi nó hiển thị.

Nếu việc component mounted / unmounted là khá nặng thì hành động này có thể tốn kém hơn mức cần thiết và dẫn đến lagginess, trong trường hợp này, bạn có thể ấn nó thông qua CSS, trong khi vẫn giữ content cho DOM. Nhận thấy rằng thỉnh thoảng nó là không thể vì bạn có thể gặp trường hợp các component đó được mounted có thể gặp lỗi, nhưng bạn nên chọn thực hiện cách này khi có thể.

Điều chỉnh opacity về 0 thì gần như là không tốn tài nguyên cho trình duyệt (vì nó không gây reflow) và nên ưu tiên visibilitydisplay bất cứ khi nào có thể.

Thay vì ẩn component thông qua unmouting, đôi lúc, nó có thể có ích khi ẩn qua CSS trong khi vẫn giữ component mounted. Điều này sẽ rất có lợi cho component nặng với thời giian mount/unmounting đáng kể.

// Tránh làm thế này với component quá nặng cho việc mount/unmount
// Hồi trước mình cũng mắc phải lỗi này
function Component(props) {
  const [view, setView] = useState('view1');
  return view === 'view1' ? <SomeComponent /> : <AnotherComponent />  
}

// Do this instead if you' re opting for speed & performance gains
// Thay vào đó hãy sử dụng cách này để tăng tốc độ và hiệu suất
const visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };
function Component(props) {
  const [view, setView] = useState('view1');
  return (
    <React.Fragment>
      <SomeComponent style={view === 'view1' ? visibleStyles : hiddenStyles}>
      <AnotherComponent style={view !== 'view1' ? visibleStyles : hiddenStyles}>
    </React.Fragment>
  )
}

6. Memoize expensive calculations

Có lúc việc render là không cần thiết, nhưng component lại là một functional, rendering xảy ra với bất kì phép tính nào có trong component. Các phép tính có giá trị không đổi với mỗi lần render có thể được memoized bằng cách sử dụng useMemo() hook. bạn có thể tìm hiều thêm qua link này.

Mục tiêu là giảm khối lượng công việc cho JS khi render, vì vậy main thread sẽ bị block trong thời gian ít hơn.

// không nên
function Component(props) {
  const someProp = heavyCalculation(props.item);
  return <AnotherComponent someProp={someProp} /> 
}
  
// Nên, `someProp` sẽ tính toán lại
// chỉ có `props.item` thay đổi
function Component(props) {
  const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
  return <AnotherComponent someProp={someProp} /> 
}

Tổng kết

Trên đây là một số Tip hữu ích cho bạn khi làm việc với ReactJs, cảm ơn đã đọc hết bài và đừng quên upvote hoặc clip bài viết ủng hộ mình nhé. Happy coding ❤️ !


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.