-9

MobX - Reactjs SetState: 3 lý do tôi ngưng sử dụng React.setState

Trong vài tháng trở lại đây, Tôi đã ngưng sử dụng React's setState trong tất cả các Component React mới của tôi.Đừng hiểu sai ý tôi. Tôi không dừng sử dụng local component state, tôi chỉ dừng sử dụng trong việc quản lý nó mà thôi. Và nó thực sự rất thú vị!

Sử dụng setState rất khó đối với người mới bắt đầu. Kể cả với những lập trình viên React đã có kinh nghiệm cũng gặp phải những bug khi sử dụng cơ chế state của React. ví như: https://cdn-images-1.medium.com/max/800/1*v2qbGqdV8wM1G4ixs7woEw.gif Tài liệu của React cũng chỉ ra những sai sót khi sử dụng với setState:

Note: 1. setState() không thay đổi ngay lập tức this.state nhưng chuyển sang một state trạng thái chờ. 2. Gọi this.state sau khi setState() có khả năng sẽ trả về giá trị hiện tại (trước khi thay đổi về giá trị) 3. Không có một đảm bảo nào về sự hoạt động đồng bộ (synchronous) với setState() và gọi đến nó có thể làm tăng hiệu suất thực hiện 4. setState() sẽ luôn luôn dẫn đến một cuộc gọi lại render() trừ khi hàm shouldComponentUpdate() trả về false. Nếu bạn thay đổi đối tượng đang sử dụng và tuỳ thuộc vào logic điều kiện render sẽ không thể thực hiện trong shouldComponentUpdate(), chỉ gọi setState() khi một state khác từ state trước để tránh render() thực hiện lại không cần thiết

Nói tóm lại. Có 3 vấn đề khi sử dụng setState().

1. setState là không đồng bộ (asynchronous)

Rất nhiều devs không nhận ra điều này lúc đầu, nhưng setState là không đồng bộ (nói nôm na theo ý hiểu của mình thì nó thực hiện song song với các tiến trình khác). Nếu bạn set gía trị một số state và sau đó hãy xem nó, bạn sẽ vẫn chỉ nhận được giá trị cũ. Đây cũng là phần khó khăn nhất khi sử dụng với setState(). setState gọi đến không có vẻ là không đồng bộ và khi gọi sẽ sinh ra một số bugs rất bí hiểm và khó hiểu và mình cũng đã gặp. Một minh chứng tiếp theo

class Select extends React.Component {
  constructor(props, context) {
    super(props, context)
    this.state = {
      selection: props.values[0]
    };
  }
  
  render() {
    return (
      <ul onKeyDown={this.onKeyDown} tabIndex={0}>
        {this.props.values.map(value =>
          <li
            className={value === this.state.selection ? 'selected' : ''}
            key={value}
            onClick={() => this.onSelect(value)}
          >
            {value}
          </li> 
        )}  
      </ul>
    )
  }
  
  onSelect(value) {
    this.setState({
      selection: value
    })
    this.fireOnSelect()
  }

  onKeyDown = (e) => {
    const {values} = this.props
    const idx = values.indexOf(this.state.selection)
    if (e.keyCode === 38 && idx > 0) { /* up */
      this.setState({
        selection: values[idx - 1]
      })
    } else if (e.keyCode === 40 && idx < values.length -1) { /* down */
      this.setState({
        selection: values[idx + 1]
      })  
    }
    this.fireOnSelect()
  }
   
  fireOnSelect() {
    if (typeof this.props.onSelect === "function")
      this.props.onSelect(this.state.selection) /* not what you expected..*/
  }
}

ReactDOM.render(
  <Select 
    values={["State.", "Should.", "Be.", "Synchronous."]} 
    onSelect={value => console.log(value)}
  />,
  document.getElementById("app")
)

Nhìn thoáng quá có vẻ ổn đấy. Gồm 2 sự kiện xử lý và 1 function sẽ gọi đến onSelect nếu được gọi. Tuy nhiên, Select component này có một bug cái mà chứng minh rõ nhất cho ảnh gift bên trên. onSelect luôn luôn gọi đến giá trị cũ với state.selection, vì setState không hoàn thành công việc của nó khi mà fireOnSelect helper được gọi. Tôi nghĩ rằng React mới nhất nên đặt lại tên của method là scheduleState hoặc yêu cầu một callback.

*** Bug này đã được sửa một cách dễ dàng, phần khó khăn là nhận ra nó ở đâu***

2. setState gây ra sự render không cần thiết.

Vấn đề thứ hai với setState đó là luôn gây ra một sự render lại. Thường những render lại này là không cần thiết. Bạn có thể sử dụng printWasted method từ React performance tools để có thể tìm ra những vấn đề xảy ra. Nhưng đại khái mà nói có một số lý do để trả lời cho câu hỏi tại sao lại có sự re-render không cần thiết như vậy: - state mới thực ra giống hệt state trước đó. Điều này thường có thế được giải quyết bằng cách thực hiện ở shouldComponentUpdate. Bạn có thể đã sử dụng một thư viện giải quyết cho bạn. - Đôi khi sự thay đổi state là thích hợp cho việc rendering, nhưng không phải là trong mọi trường hợp. Ví dụ khi một số dữ liệu chỉ điều kiện trông thấy. - Thứ ba là, như đã chỉ ra trong Aria Buckles' talk at React Europe 2015, đôi khi trường hợp state không liên quan cho tất cả sự rendering! Điều này thường được householding state liên quan đến việc quản lý sự kiện lắng nghe, timer, ....

3. setState là không đủ để nắm bắt tất cả các state trong component

Điểm cuối cùng, tất cả các component stae nên được lưu trữ và cập nhật trong sử dụng setState. Nhiều components phức tạp thông thường cần được quản lý như vòng đời tính toán quản lý timers, network requests hay events ... .Việc chăm chú sử dụng setState không những gây ra những việc render không cần thiết mà còn gây ra những lifecycle có liên quan được kích hoạt thêm một lần nữa gây ra những tình huống kì lạ.

Quản lý component state với MobX

(Ngạc nhiên chưa, ngạc nhiên chưa) Tại Mendix chúng ta sử dụng MobX để quản lý tất cả các lưu trữ của chúng ta. Tuy nhiên, chúng ta vẫn sử dụng cơ chế riêng của React's state cho component state. Gần đây, Tôi chuyển sang quản lý các component state với Mobx và nó rất ổn. Bạn xem:

import {observable} from "mobx"
import {observer} from "mobx-react"

@observer class Select extends React.Component {
  @observable selection = null; /* MobX managed instance state */

  constructor(props, context) {
    super(props, context)
    this.selection = props.values[0]
  }
  
  render() {
    return (
      <ul onKeyDown={this.onKeyDown} tabIndex={0}>
        {this.props.values.map(value =>
          <li
            className={value === this.selection ? 'selected' : ''}
            key={value}
            onClick={() => this.onSelect(value)}
          >
            {value}
          </li> 
        )}  
      </ul>
    )
  }
  
  onSelect(value) {
    this.selection = value
    this.fireOnSelect()
  }

  onKeyDown = (e) => {
    const {values} = this.props
    const idx = values.indexOf(this.selection)
    if (e.keyCode === 38 && idx > 0) { /* up */
      this.selection = values[idx - 1]
    } else if (e.keyCode === 40 && idx < values.length -1) { /* down */
      this.selection = values[idx + 1]
    }
    this.fireOnSelect()
  }
   
  fireOnSelect() {
    if (typeof this.props.onSelect === "function")
      this.props.onSelect(this.selection) /* solved! */
  }
}

Xem thành quả của chúng ta: https://cdn-images-1.medium.com/max/800/1*LPl8MGfkPyWGtRERQdw_3w.gif Đoạn code không chỉ ngắn gọn hơn mà MobX còn giải quyết tất cả vấn đề liên quan đế setState. Những thay đổi của state ngay lập tức phản ánh các local component state. Điều này làm cho logic đơn giản và dễ sử dụng. Bạn không phải quan tâm đến thực tế state có thể không chưa được cập nhật.

MobX xác định tại thời gian chạy cái mà observables liên quan đến rendering. Vì vậy observables tạm thời không thích hợp cho rendering sẽ không re-rendering. Chỉ cho đến khi chúng liên quan. Vì lý do này, cũng không có rendering penalties ( hoặc vấn đề về lifecycle) khi @observable đánh dấu rằng không có sự render lại tất cả.

Vì vậy, renderalbe và non-renderable state được xử lý đồng nhất. Thêm vào đố state được lưu trữ trong các component của chúng ta đang làm việc giống như state được lưu trữ trong bất kì kho lưu trữ nào của chúng ta. Điều này là cho nó trở lên dễ dàng trong refactor components và chuyển đổi local component state thành một separate store và ngược lại.

MobX hiệu quả biến components của bạn thành kho lưu trữ nhỏ.

Hơn nữa, Sai lầm mới nhất là thay đổi giá trị trực tiếp của đối tượng sate không được thực hiện nữa khi sử dụng observables cho state. Oh, đường lo lắng về việc thực hiện shouldComponentUpdate hay PureRenderMixi, MobX thực sự làm tốt điều đó.

Cuối cùng, bạn có thể tự hỏi, điều gì xảy ra neeys tôi chờ cho đến khi setState được hoàn thành? Vâng, Bạn vẫn có thể sử dụng componentDidUpdate lifcecly liên quan.

Nghe hay đấy. Vậy làm sao để bắt đầu với MobX? Khá đơn giản, hãy theo dõi 10 phút hướng dẫn hay xem video nói trên. Bạn có đơn giản thực hiện 1 component đơn giảm từ dòng code cơ bản của bạn, slap @observer vào và cho vào một số thuộc tính @obserable. Bạn thậm chí không cần phải thay thế cuộc gọi đến các setState hiện tại của bạn, chúng vẫn có thể tiếp tục trong khi sử dụng MobX. Mặc dù, trong vài phút bạn có thể tìm thấy chúng quá phúc tạp và bạn sẽ thay thế nó. Nếu bạn không thích trang trí không sao, nó làm việc tốt với ol' ES5.

Tôi đã dừng sử dụng quản lý state ở local component. Thay vào đó tôi sử dụng MobX. Bây giờ thì React thực sự "just the view". MobX hiện đang quản lý cả 2 trạng thái local component state và state trong stores. Nó súc tích, đồng bộ, hiệu quả và thống nhất. Từ kinh nghiệm tôi đã học được rằng MobX thậm chí còn dễ dàng hơn để giải thích cho người mới bắt đầu hơn chính setState của React. Nó giữ các thành phần của chúng ta sạch sẽ và đơn giản.

Tài liệu

Bài viết được dịch từ : https://medium.com/@mweststrate/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e#.vtu9s72e8


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí