React có giữ thứ tự cập nhật trạng thái không?


139

Tôi biết rằng React có thể thực hiện cập nhật trạng thái không đồng bộ và theo đợt để tối ưu hóa hiệu suất. Do đó, bạn không bao giờ có thể tin tưởng vào trạng thái được cập nhật sau khi đã gọi setState. Nhưng bạn có thể tin tưởng React để cập nhật trạng thái theo thứ tự như setStateđược gọi cho

  1. Thành phần giống nhau?
  2. thành phần khác nhau?

Xem xét nhấp vào nút trong các ví dụ sau:

1. Có bao giờ khả năng a sai và b đúng với:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Có bao giờ khả năng a sai và b đúng với:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Hãy nhớ rằng đây là những đơn giản hóa cực kỳ trong trường hợp sử dụng của tôi. Tôi nhận ra rằng tôi có thể làm điều này khác đi, ví dụ như cập nhật cả hai thông số trạng thái cùng lúc trong ví dụ 1, cũng như thực hiện cập nhật trạng thái thứ hai trong một cuộc gọi lại đến cập nhật trạng thái đầu tiên trong ví dụ 2. Tuy nhiên, đây không phải là câu hỏi của tôi, và tôi chỉ quan tâm nếu có một cách được xác định rõ rằng React thực hiện các cập nhật trạng thái này, không có gì khác.

Bất kỳ câu trả lời được hỗ trợ bởi tài liệu được đánh giá rất cao.



3
Nó không có vẻ là một câu hỏi vô nghĩa, bạn cũng có thể hỏi câu hỏi đó về các vấn đề github của trang phản ứng, dan abramov thường khá hữu ích ở đó. Khi tôi có những câu hỏi khó như vậy tôi sẽ hỏi và anh ấy sẽ trả lời. Tệ là những loại vấn đề đó không được chia sẻ công khai như trong các tài liệu chính thức (để những người khác cũng có thể truy cập dễ dàng). Tôi cũng cảm thấy các tài liệu chính thức của React thiếu phạm vi bao quát của một số chủ đề như chủ đề từ câu hỏi của bạn, v.v.
giorgim

Ví dụ: lấy cái này: github.com/facebook/react/issues/11793 , tôi tin rằng những thứ được thảo luận trong vấn đề đó sẽ hữu ích cho nhiều nhà phát triển nhưng những thứ đó không có trên tài liệu chính thức, bởi vì những người FB coi đó là nâng cao. Tương tự là về những thứ khác có thể. Tôi nghĩ rằng một bài viết chính thức có tiêu đề như 'quản lý nhà nước theo chiều sâu' hoặc 'cạm bẫy của quản lý nhà nước', nghiên cứu tất cả các trường hợp quản lý nhà nước như trong câu hỏi của bạn sẽ không tệ. có lẽ chúng ta có thể thúc đẩy các nhà phát triển FB mở rộng tài liệu bằng những thứ như vậy :)
giorgim

Thre là một liên kết đến một bài viết tuyệt vời về phương tiện trong câu hỏi của tôi. Nó sẽ bao gồm 95% các trường hợp sử dụng nhà nước. :)
Michal

2
@Michal nhưng bài báo đó vẫn không trả lời câu hỏi này IMHO
giorgim

Câu trả lời:


335

Tôi làm việc trên React.

TLDR:

Nhưng bạn có thể tin tưởng React để cập nhật trạng thái theo thứ tự như setState được gọi cho

  • Thành phần giống nhau?

Đúng.

  • thành phần khác nhau?

Đúng.

Thứ tự cập nhật luôn được tôn trọng. Việc bạn có thấy trạng thái trung gian "giữa" chúng hay không phụ thuộc vào việc bạn có ở trong một lô hay không.

Hiện tại (React 16 trở về trước), chỉ các bản cập nhật bên trong trình xử lý sự kiện React được bó theo mặc định . Có một API không ổn định để buộc hàng loạt bên ngoài các trình xử lý sự kiện cho các trường hợp hiếm khi bạn cần.

Trong các phiên bản trong tương lai (có thể là React 17 trở lên), React sẽ bó tất cả các bản cập nhật theo mặc định để bạn không phải suy nghĩ về điều này. Như mọi khi, chúng tôi sẽ thông báo bất kỳ thay đổi nào về điều này trên blog React và trong ghi chú phát hành.


Chìa khóa để hiểu điều này là cho dù có bao nhiêu setState()cuộc gọi trong bao nhiêu thành phần bạn thực hiện trong trình xử lý sự kiện React , chúng sẽ chỉ tạo ra một lần kết xuất lại vào cuối sự kiện . Điều này rất quan trọng để có hiệu suất tốt trong các ứng dụng lớn vì nếu ChildParentmỗi cuộc gọi setState()khi xử lý một sự kiện nhấp chuột, bạn không muốn kết xuất lại Childhai lần.

Trong cả hai ví dụ của bạn, setState()các cuộc gọi xảy ra bên trong trình xử lý sự kiện React. Do đó, chúng luôn được tuôn ra cùng nhau vào cuối sự kiện (và bạn không thấy trạng thái trung gian).

Các bản cập nhật luôn được hợp nhất một cách nông cạn theo thứ tự chúng xảy ra . Vì vậy, nếu bản cập nhật đầu tiên là {a: 10}, thứ hai là {b: 20}và thứ ba là {a: 30}, trạng thái kết xuất sẽ là {a: 30, b: 20}. Bản cập nhật gần đây hơn cho cùng một khóa trạng thái (ví dụ như atrong ví dụ của tôi) luôn "thắng".

Đối this.statetượng được cập nhật khi chúng ta kết xuất lại UI vào cuối đợt. Vì vậy, nếu bạn cần cập nhật trạng thái dựa trên trạng thái trước đó (chẳng hạn như tăng bộ đếm), bạn nên sử dụng setState(fn)phiên bản chức năng cung cấp cho bạn trạng thái trước đó, thay vì đọc từ đó this.state. Nếu bạn tò mò về lý do cho điều này, tôi đã giải thích sâu về nhận xét này .


Trong ví dụ của bạn, chúng tôi sẽ không thấy "trạng thái trung gian" vì chúng tôi đang ở trong trình xử lý sự kiện React nơi bật tính năng tạo khối (vì React "biết" khi chúng tôi thoát khỏi sự kiện đó).

Tuy nhiên, cả trong phiên bản React 16 và các phiên bản trước đó, vẫn chưa có đợt xử lý theo mặc định bên ngoài trình xử lý sự kiện React . Vì vậy, nếu trong ví dụ của bạn, chúng tôi có trình xử lý phản hồi AJAX thay vì handleClick, mỗi cái setState()sẽ được xử lý ngay khi nó xảy ra. Trong trường hợp này, có, bạn sẽ thấy trạng thái trung gian:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Chúng tôi nhận thấy điều bất tiện là hành vi đó khác nhau tùy thuộc vào việc bạn có ở trong xử lý sự kiện hay không . Điều này sẽ thay đổi trong phiên bản React trong tương lai, sẽ bó tất cả các bản cập nhật theo mặc định (và cung cấp API chọn tham gia để xóa các thay đổi một cách đồng bộ). Cho đến khi chúng tôi chuyển đổi hành vi mặc định (có khả năng trong Phản ứng 17), có một API bạn có thể sử dụng để buộc thực hiện theo đợt :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

Tất cả các trình xử lý sự kiện React Internal đều được gói gọn trong unstable_batchedUpdatesđó là lý do tại sao chúng được xử lý theo mặc định. Lưu ý rằng gói một bản cập nhật trong unstable_batchedUpdateshai lần không có hiệu lực. Các bản cập nhật được tuôn ra khi chúng ta thoát khỏi unstable_batchedUpdatescuộc gọi ngoài cùng .

API đó "không ổn định" theo nghĩa là chúng tôi sẽ xóa nó khi tính năng tạo khối đã được bật theo mặc định. Tuy nhiên, chúng tôi sẽ không xóa nó trong một phiên bản nhỏ, vì vậy bạn có thể dựa vào nó một cách an toàn cho đến khi React 17 nếu bạn cần buộc xử lý theo đợt trong một số trường hợp bên ngoài trình xử lý sự kiện React.


Tóm lại, đây là một chủ đề khó hiểu vì React chỉ có các lô bên trong trình xử lý sự kiện theo mặc định. Điều này sẽ thay đổi trong các phiên bản trong tương lai và hành vi sẽ đơn giản hơn sau đó. Nhưng giải pháp không phải là bó ít hơn , theo mặc định là bó nhiều hơn . Đó là những gì chúng ta sẽ làm.


1
Một cách để "luôn luôn có được thứ tự đúng" là tạo một đối tượng tạm thời, gán các giá trị khác nhau (ví dụ obj.a = true; obj.b = true) và cuối cùng chỉ cần làm this.setState(obj). Điều này là an toàn cho dù bạn có ở trong trình xử lý sự kiện hay không. Có thể là một mánh khóe gọn gàng nếu bạn thường thấy mình mắc lỗi khi đặt trạng thái nhiều lần bên ngoài các trình xử lý sự kiện.
Chris

Vì vậy, chúng tôi thực sự không thể dựa vào việc tạo khối được giới hạn chỉ trong một trình xử lý sự kiện - như bạn đã làm rõ, ít nhất là vì điều này sẽ không xảy ra sớm. Sau đó, chúng ta phải sử dụng setState với chức năng updater để có quyền truy cập vào trạng thái gần đây nhất, phải không? Nhưng điều gì sẽ xảy ra nếu tôi cần sử dụng một số state.filter để tạo XHR để đọc một số dữ liệu và sau đó đưa chúng vào trạng thái? Có vẻ như tôi sẽ phải đặt XHR với cuộc gọi lại bị trì hoãn (và do đó có tác dụng phụ) vào một trình cập nhật. Đó có được coi là một thực hành tốt nhất không?
Maksim Gumerov

1
Và bằng cách đó cũng có nghĩa là chúng ta không nên đọc từ this.state cả; cách hợp lý duy nhất để đọc một số trạng thái.X là đọc nó trong hàm updater, từ đối số của nó. Và, viết thư này.state cũng không an toàn. Vậy thì tại sao lại cho phép truy cập this.state? Những người đó có thể nên đưa ra những câu hỏi độc lập nhưng chủ yếu tôi chỉ đang cố gắng hiểu nếu tôi giải thích đúng.
Maksim Gumerov

10
câu trả lời này nên được thêm vào tài liệu Reacjs.org
Deen John

2
Bạn có thể vui lòng làm rõ trong bài đăng này nếu "Trình xử lý sự kiện React" bao gồm componentDidUpdatevà các cuộc gọi lại vòng đời khác kể từ React 16 không? Cảm ơn trước!
Ivan

6

Đây thực sự là một câu hỏi khá thú vị nhưng câu trả lời không quá phức tạp. Có bài viết tuyệt vời này trên phương tiện có câu trả lời.

1) Nếu bạn làm điều này

this.setState({ a: true });
this.setState({ b: true });

Tôi không nghĩ rằng sẽ có một tình huống mà asẽ truebsẽ falsedo Trạm trộn .

Tuy nhiên, nếu bphụ thuộc vàoa thì thực sự có thể có một tình huống mà bạn sẽ không có được trạng thái mong đợi.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

Sau khi tất cả các cuộc gọi trên được xử lý this.state.valuesẽ là 1, không phải 3 như bạn mong đợi.

Điều này được đề cập trong bài viết: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Điều này sẽ cho chúng ta this.state.value === 3


Điều gì nếu this.state.valueđược cập nhật cả trong xử lý sự kiện (nơi setStateđược trộn) và callbacks AJAX (nơi setStateđược không batched). Trong các trình xử lý sự kiện, tôi sẽ sử dụng updater functionđể luôn chắc chắn rằng tôi cập nhật trạng thái bằng trạng thái hiện được cập nhật do hàm cung cấp. Tôi có nên sử dụng setStatevới chức năng cập nhật bên trong mã của cuộc gọi lại AJAX ngay cả khi tôi biết rằng nó không được bó? Bạn có thể vui lòng làm rõ việc sử dụng setStatetrong một cuộc gọi lại AJAX có hoặc không sử dụng chức năng cập nhật? Cảm ơn bạn!
tonix

@Michal, hi Michal chỉ muốn hỏi câu hỏi đơn thuần, có đúng là nếu chúng ta có this.setState ({value: 0}); this .setState ({value: this.state.value + 1}); setState đầu tiên sẽ bị bỏ qua và chỉ setState thứ hai sẽ được thực thi?
Dickens

@Dickens Tôi tin rằng cả hai setStatesẽ bị xử tử nhưng người cuối cùng sẽ thắng.
Michal

3

Nhiều cuộc gọi trong cùng một chu kỳ có thể được gộp lại với nhau. Ví dụ: nếu bạn cố gắng tăng số lượng vật phẩm nhiều lần trong cùng một chu kỳ, điều đó sẽ dẫn đến tương đương:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html


3

như trong tài liệu

setState () enqueues thay đổi trạng thái thành phần và nói với React rằng thành phần này và các phần tử con của nó cần được kết xuất lại với trạng thái được cập nhật. Đây là phương pháp chính bạn sử dụng để cập nhật giao diện người dùng để đáp ứng với trình xử lý sự kiện và phản hồi của máy chủ.

nó sẽ tạo ra sự thay đổi như trong hàng đợi ( FIFO : First In First Out), cuộc gọi đầu tiên sẽ là lần đầu tiên tạo thành trước


chào Ali, chỉ muốn hỏi câu hỏi đơn thuần, có đúng là nếu chúng ta có this.setState ({value: 0}); this .setState ({value: this.state.value + 1}); setState đầu tiên sẽ bị bỏ qua và chỉ setState thứ hai sẽ được thực thi?
Dickens
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.