Tại sao các đạo cụ JSX không nên sử dụng các hàm mũi tên hoặc liên kết?


103

Tôi đang chạy lint với ứng dụng React của mình và tôi nhận được lỗi này:

error    JSX props should not use arrow functions        react/jsx-no-bind

Và đây là nơi tôi đang chạy hàm mũi tên (bên trong onClick):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

Đây có phải là một thực hành xấu nên tránh? Và cách tốt nhất để làm điều đó là gì?

Câu trả lời:


170

Tại sao bạn không nên sử dụng các hàm mũi tên nội tuyến trong các đạo cụ JSX

Sử dụng các hàm mũi tên hoặc liên kết trong JSX là một phương pháp không tốt làm ảnh hưởng đến hiệu suất, vì hàm được tạo lại trên mỗi lần hiển thị.

  1. Bất cứ khi nào một hàm được tạo, hàm trước đó sẽ được thu gom. Việc kết xuất nhiều phần tử có thể tạo ra sự xáo trộn trong hoạt ảnh.

  2. Việc sử dụng hàm mũi tên nội tuyến sẽ khiến PureComponents và các thành phần sử dụng shallowComparetrong shouldComponentUpdatephương thức vẫn hiển thị. Vì hỗ trợ hàm mũi tên được tạo lại mỗi lần, so sánh nông sẽ xác định nó là một thay đổi đối với hỗ trợ và thành phần sẽ hiển thị.

Như bạn có thể thấy trong 2 ví dụ sau - khi chúng tôi sử dụng hàm mũi tên nội tuyến, <Button>thành phần được hiển thị mỗi lần (bảng điều khiển hiển thị văn bản 'nút kết xuất').

Ví dụ 1 - PureComponent không có trình xử lý nội tuyến

Ví dụ 2 - PureComponent với trình xử lý nội tuyến

Các phương thức ràng buộc thiskhông có hàm mũi tên nội tuyến

  1. Ràng buộc phương thức theo cách thủ công trong hàm tạo:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
  2. Ràng buộc một phương thức bằng cách sử dụng các trường -lớp-đề xuất với một hàm mũi tên. Vì đây là đề xuất ở giai đoạn 3, bạn sẽ cần thêm cài đặt trước Giai đoạn 3 hoặc biến đổi thuộc tính Lớp vào cấu hình babel của mình.

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }

Các thành phần chức năng với các lệnh gọi lại bên trong

Khi chúng ta tạo một hàm bên trong (ví dụ: trình xử lý sự kiện) bên trong một thành phần hàm, hàm sẽ được tạo lại mỗi khi thành phần đó được hiển thị. Nếu hàm được chuyển dưới dạng đạo cụ (hoặc thông qua ngữ cảnh) cho một thành phần con ( Buttontrong trường hợp này), thì thành phần con đó cũng sẽ hiển thị lại.

Ví dụ 1 - Thành phần hàm có lệnh gọi lại bên trong:

Để giải quyết vấn đề này, chúng ta có thể bao bọc cuộc gọi lại bằng useCallback()hook và đặt các phụ thuộc thành một mảng trống.

Lưu ý: các useStatechức năng được tạo ra chấp nhận một chức năng cập nhật, cung cấp tình trạng hiện thời. Bằng cách này, chúng ta không cần đặt trạng thái hiện tại làm phụ thuộc useCallback.

Ví dụ 2 - Thành phần hàm với một lệnh gọi lại bên trong được bao bọc bởi useCallback:


3
Làm thế nào để bạn đạt được điều này trên các thành phần không trạng thái?
lux

4
Các thành phần không trạng thái (hàm) không có this, vì vậy không có gì để ràng buộc. Thông thường, các phương thức được cung cấp bởi một thành phần thông minh của trình bao bọc.
Ori Drori

39
@OriDrori: Điều đó hoạt động như thế nào khi bạn cần chuyển dữ liệu trong lệnh gọi lại? onClick={() => { onTodoClick(todo.id) }
adam-beck

4
@ adam-beck - thêm nó vào bên trong định nghĩa phương thức gọi lại trong lớp cb() { onTodoClick(this.props.todo.id); }.
Ori Drori

2
@ adam-beck Tôi nghĩ đây là cách sử dụng useCallbackvới giá trị động. stackoverflow.com/questions/55006061/…
Shota Tamura,

9

Điều này là do một hàm mũi tên rõ ràng sẽ tạo một phiên bản mới của hàm trên mỗi lần hiển thị nếu được sử dụng trong thuộc tính JSX. Điều này có thể tạo ra một áp lực lớn cho bộ thu gom rác và cũng sẽ cản trở trình duyệt tối ưu hóa bất kỳ "đường dẫn nóng" nào vì các chức năng sẽ bị loại bỏ thay vì sử dụng lại.

Bạn có thể xem toàn bộ giải thích và một số thông tin khác tại https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md


Không chỉ thế. Tạo các thể hiện chức năng mới mỗi lần có nghĩa là trạng thái được sửa đổi và khi trạng thái của thành phần được sửa đổi, nó sẽ được hiển thị lại. Vì một trong những lý do chính để sử dụng React là chỉ hiển thị các phần tử thay đổi, việc sử dụng bindhoặc các chức năng mũi tên ở đây là tự bắn vào chân bạn. Người ta không cũng ghi nhận tuy nhiên, đặc biệt là trong trường hợp làm việc với mapmảng ping trong danh sách, vv
hippietrail

"Tạo các thể hiện chức năng mới mỗi lần có nghĩa là trạng thái được sửa đổi" ý bạn là gì? Không có trạng thái nào trong câu hỏi
apecesofbart Ngày

4

Để tránh tạo các hàm mới với các đối số giống nhau, bạn có thể ghi nhớ kết quả ràng buộc hàm, đây là một tiện ích đơn giản có tên memobindđể thực hiện điều đó: https://github.com/supnate/memobind


4

Sử dụng các hàm nội tuyến như thế này là hoàn toàn tốt. Quy tắc linting đã lỗi thời.

Quy tắc này có từ thời khi các hàm mũi tên không phổ biến và mọi người sử dụng .bind (this), quy tắc này từng chậm. Sự cố hiệu suất đã được khắc phục trong Chrome 49.

Hãy chú ý rằng bạn không chuyển các hàm nội tuyến làm đạo cụ cho một thành phần con.

Ryan Florence, tác giả của React Router, đã viết một đoạn tuyệt vời về điều này:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578


Bạn có thể vui lòng chỉ cách viết một bài kiểm tra đơn vị trên các thành phần có chức năng mũi tên nội tuyến được không?
krankuba

1
@krankuba Đây không phải là câu hỏi này. Bạn vẫn có thể chuyển các hàm ẩn danh không được xác định nội tuyến nhưng vẫn không thể kiểm tra được.
sbaechler

-1

Bạn có thể sử dụng các hàm mũi tên bằng cách sử dụng thư viện xử lý bộ nhớ đệm phản ứng , không cần phải lo lắng về hiệu suất kết xuất:

Lưu ý: Bên trong nó lưu trữ các chức năng mũi tên của bạn bằng phím được chỉ định, không cần phải lo lắng về việc kết xuất!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

Các tính năng khác:

  • Trình xử lý được đặt tên
  • Xử lý sự kiện bằng các hàm mũi tên
  • Quyền truy cập vào khóa, đối số tùy chỉnh và sự kiện ban đầu
  • Hiệu suất kết xuất thành phần
  • Ngữ cảnh tùy chỉnh cho trình xử lý

Câu hỏi đặt ra là tại sao chúng ta không thể sử dụng nó. Không phải làm thế nào để sử dụng nó với một số hack khác.
kapil

-1

Tại sao các đạo cụ JSX không nên sử dụng các hàm mũi tên hoặc liên kết?

Chủ yếu là do các hàm nội tuyến có thể phá vỡ khả năng ghi nhớ của các thành phần được tối ưu hóa:

Theo truyền thống, các mối quan tâm về hiệu suất xung quanh các hàm nội tuyến trong React có liên quan đến cách chuyển các lệnh gọi lại mới trên mỗi kết xuất sẽ phá vỡ shouldComponentUpdatetối ưu hóa trong các thành phần con. ( tài liệu )

Đó là ít hơn về chi phí tạo chức năng bổ sung:

Các vấn đề về hiệu suất với Function.prototype.bind đã được khắc phục ở đây và các hàm mũi tên là một thứ nguyên bản hoặc được chuyển bằng babel thành các hàm đơn giản; trong cả hai trường hợp, chúng tôi có thể cho rằng nó không chậm. ( Đào tạo React )

Tôi tin rằng những người tuyên bố rằng việc tạo hàm là tốn kém luôn bị cung cấp thông tin sai (nhóm React chưa bao giờ nói điều này). ( Tweet )

Khi nào react/jsx-no-bindquy tắc hữu ích?

Bạn muốn đảm bảo rằng các thành phần được ghi nhớ hoạt động như dự định:

  • React.memo (đối với các thành phần chức năng)
  • PureComponenthoặc tùy chỉnh shouldComponentUpdate(cho các thành phần lớp)

Bằng cách tuân theo quy tắc này, các tham chiếu đối tượng hàm ổn định sẽ được chuyển. Vì vậy, các thành phần trên có thể tối ưu hóa hiệu suất bằng cách ngăn hiển thị lại, khi các đạo cụ trước đó không thay đổi.

Làm thế nào để giải quyết lỗi ESLint?

Lớp: Định nghĩa trình xử lý là phương thức hoặc thuộc tính lớp để thisràng buộc.
Móc: Sử dụng useCallback.

Middleground

Trong nhiều trường hợp, các hàm nội tuyến rất thuận tiện để sử dụng và hoàn toàn tốt về mặt hiệu suất. Thật không may, quy tắc này không thể giới hạn ở chỉ các loại thành phần được ghi nhớ. Nếu bạn vẫn muốn sử dụng nó trên toàn bộ bảng, bạn có thể tắt nó cho các nút DOM đơn giản:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.