Những lý do ảnh hưởng đến ReactNative performance của bạn

3396

Đối với các bạn mới bắt đầu làm quen React Native thì việc tối ưu performance là điều không cần thiết. Các bạn nên dành thời gian để tìm hiểu xem props/state là gì, tạo style thế nào, tạo component ra sao, blabla…

Nhưng khi đã thành thạo những kiến thức cơ bản và bắt đầu làm 1 sản phẩm hoàn chỉnh thì việc quan tâm đến performance là điều cực kỳ quan trọng, đặc biệt trong những view phức tạp hoặc có dữ liệu lớn.

Nguyên tắc chung khi lập trình giao diện là: render càng nhiều, performance càng thấp (app càng lag). Vậy nên để cải thiện performance, việc đầu tiên chúng ta phải xem xét là có xử lý nào gây ra những render không cần thiết hay không?

Tuy nhiên, có những thói quen lập trình tưởng chửng rất đơn giản nhưng lại là nguyên nhân của những render không mong muốn. Cùng thử xem các lý do ảnh hưởng đến ReactNative performance của bạn để khắc phục nhé!

Sử dụng literal object làm props

Literal object là gì?

Nó đơn giản chỉ là các JS object như {a:1, b:2} hoặc [1, 2, 3]…

Ví dụ:

<MyView style={{flex: 1}} /> // <== literal object for style props

Vậy tại sao lại có vấn đề với cách viết super basic này? Thậm chí trên trang chủ của facebook cũng có sample kiểu như vậy!!!

Tất cả đều do cách làm việc của Shallow Compare của React.

Quay lại phân tích ví dụ trên có thể thấy mỗi lần được Component cha của MyView được render, nó sẽ tạo ra 1 literal object {flex: 1} và gán vào props style của MyView => props style bị thay đổi!

oldProps.style = {flex: 1}
newProps.style = {flex: 1}
// Nhìn thì giống nhau, nhưng thực tế lại khác nhau!!!
shallowEqual(oldProps, newProps) // = false

Nếu component MyView không phải kế thừa từ React. Component và implement phương thức shouldComponentUpdate để lờ đi sự thay đổi của props này, hoặc sử dụng Function Component mà không có biện pháp xử lý phù hợp thì chắc chắn MyView sẽ bị re-render!

Cách đơn giản nhất để giải quyết vấn đề này đó là:

Không sử dụng literal object làm props

class ParentView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: ["a", "b", props.name],
    };
  }
  render() {
    return <MyView style={styles.myView} data={this.state.data} />
  }
}

const styles = {
  myView: { flex: 1 }
}

Nếu ParentView là 1 function component thì sao? Câu trả lời là Hooks

const ParentView = (props) => {
  // useMemo sẽ tạo ra 1 object data
  // và chỉ tạo object khác khi nào props.name thay đổi
  const data = useMemo(() => ["a", "b", props.name], [props.name])
  
  return <MyView style={styles.myView} data={data} />
}

const styles = {
  myView: { flex: 1 }
}

Sử dụng arrow function làm props

Thói quen phổ biến thứ 2 gây ra những re-render vô nghĩa là sử dụng arrow function làm props.

<MyView onUpdate={(data) => OtherModule.update(data, this.props.name)} />

hoặc

<TextInput
  value={this.state.name}
  onValueChange={(name) => this.setState({name})}
/>

Khi sử dụng cách viết code này, props của component có sử dụng arrow function sẽ luôn được gán 1 function mới (do arrow function sinh ra) mỗi lần component cha của nó re-render. Có props mới truyền vào, component này sẽ bị re-render theo đúng cơ chế giống như literal object ở trường hợp đầu tiên!

Vì vậy, hãy tử bỏ ngay thói quen sử dụng arrow function làm props.

class ParentView extends React.Component {
  updateData = (data) => OtherModule.update(data, this.props.name); 

  render() {
    return <MyView onUpdate={this.updateData} />
  }
}

Nếu ParentView là function component, hãy nghĩ ngay đến Hooks.

const ParentView = (props) => {

  // useCallback sẽ tạo ra 1 function updateData
  // và chỉ tạo function khác khi nào props.name thay đổi
  const updateData = useCallback((data) => {
    OtherModule.update(data, props.name);
  }, [props.name]);

  return <MyView onUpdate={updateData} />
}

Sử dụng hàm bind() khi tạo props

Về cơ bản, hàm bind hoạt động giống với arrow function khi nó luôn tạo ra 1 function mới mỗi khi được gọi.

<MyView onUpdate={this.update.bind(this)} />

Chính vì sự tương đồng với arrow function nên cách giải quyết nó cũng giống với arrow function như đã trình bày ở phần 2.

Có thể bạn chưa biết, 1 arrow function đã tự động bind con trỏ this trong scope mà function đó được sinh ra, nên trong nhiều trường hợp, việc bind là không cần thiết.

class ParentView extends Component {
  
  update = (data) => this.updateData(data);
  
  render() {
    // không cần bind this nữa vì đã được auto bind từ arrow function rồi
    return <MyView onUpdate={this.update} /> 
  }
}

Nếu thực sự phải bind (do không muốn sửa code hiện tại để tránh sinh ra nhiều different khi commit code chẳng hạn), thì có thể bind trước ở construtor:

class ParentView extends Component {
  constructor(props) {
    ..
    this.update = this.update.bind(this)
  }

  function update(data) {
    this.updateData(data);
  }

  render() {
    // this.update ở đây là kết quả của hàm bind ở constructor,
    // không phải là "function update(data) {...}" ban đầu
    return <MyView onUpdate={this.update} /> 
  }
}

Lời kết

Hãy luôn luôn nhớ rằng, render càng nhiều, performance càng thấp. Vì vậy, khi cảm thấy ứng dụng của bạn chạy không mượt mà, hãy điều tra ngay xem có xử lý nào gây nên tình trạng re-render ngoài ý muốn hay không.

Tối ưu performance là công việc quan trọng, đòi hỏi nhiều thời gian và kinh nghiệm. Chúng tôi sẽ tiếp tục đề cập đến vấn đề này trong những bài viết tiếp theo. Nếu có ý kiến đóng góp cho bài viết, vui lòng để lại bình luận cho chúng tôi.

Thanks for reading!

Xem thêm việc làm React Native hấp dẫn nhất trên TopDev

Người viết: Nguyen Duc Binh

TopDev via blog.icts.vn

Bài viết liên quan:

  9 ứng dụng tuyệt vời được viết bằng React Native

  React Native là gì? Kiến thức tổng quan về React Native

  Tạo slider component trong React Native bằng PanResponder