В документации React говорится следующее о setState :
If you need to set the state based on the previous state, read about the updater argument below
,
Кроме следующего предложения, которое я не понимаю:
Если используются изменяемые объекты и логика условного рендеринга не может быть реализована в shouldComponentUpdate () , вызов setState () только тогда, когда новое состояние отличается от предыдущего состояния, будетизбегайте ненужных повторных рендеров.
Они говорят:
Первый аргумент - функция Updater с подписью (state, props) => stateChange ... состояние является ссылкой на состояние компонента во время применения изменения.
И приведем пример:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Говоря:
Оба состояния и реквизитов , полученных функцией обновления, гарантированно будут актуальными.Вывод средства обновления поверхностно объединен с состоянием.
Что они означают под гарантированно актуальными и что мы должны знать при принятии решения, если мыследует использовать setState
с функцией обновления (state, props) => stateChange
или напрямую с объектом в качестве первого параметра?
Давайте предположим, что сценарий реального мира.Предположим, у нас есть необычное приложение для чата, в котором:
- Состояние чата представлено
this.state = { messages: [] }
; - Загружаются предыдущие сообщения с запросом AJAX и добавляются к
messages
в данный момент в состоянии; - Если другие пользователи (не текущий пользователь) отправляют сообщение текущему пользователю, новые сообщения поступают текущему пользователю из соединения WebSocket в реальном времени и добавляются к
messages
в настоящее времяв состоянии; - Если отправителем сообщения является текущий пользователь, сообщение добавляется в
messages
состояния, как в пункте 3, как только запрос AJAX сработал, когда сообщениеотправлено завершено;
Давайте представим, что это наш FancyChat
компонент:
import React from 'react'
export default class FancyChat extends React.Component {
constructor(props) {
super(props)
this.state = {
messages: []
}
this.API_URL = 'http://...'
this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
this.handleNewMessageFromOtherUser = this.handleNewMessageFromOtherUser.bind(this)
this.handleNewMessageFromCurrentUser = this.handleNewMessageFromCurrentUser.bind(this)
}
componentDidMount() {
// Assume this is a valid WebSocket connection which lets you add hooks:
this.webSocket = new FancyChatWebSocketConnection()
this.webSocket.addHook('newMessageFromOtherUsers', this.handleNewMessageFromOtherUser)
}
handleLoadPreviousChatMessages() {
// Assume `AJAX` lets you do AJAX requests to a server.
AJAX(this.API_URL, {
action: 'loadPreviousChatMessages',
// Load a previous chunk of messages below the oldest message
// which the client currently has or (`null`, initially) load the last chunk of messages.
below_id: (this.state.messages && this.state.messages[0].id) || null
}).then(json => {
// Need to prepend messages to messages here.
const messages = json.messages
// Should we directly use an updater object:
this.setState({
messages: messages.concat(this.state.messages)
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: messages.concat(state.messages)
.sort(this.sortByTimestampComparator)
}
})
// What if while the user is loading the previous messages, it also receives a new message
// from the WebSocket channel?
})
}
handleNewMessageFromOtherUser(data) {
// `message` comes from other user thanks to the WebSocket connection.
const { message } = data
// Need to append message to messages here.
// Should we directly use an updater object:
this.setState({
messages: this.state.messages.concat([message])
// Assume `sentTimestamp` is a centralized Unix timestamp computed on the server.
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: state.messages.concat([message])
.sort(this.sortByTimestampComparator)
}
})
}
handleNewMessageFromCurrentUser(messageToSend) {
AJAX(this.API_URL, {
action: 'newMessageFromCurrentUser',
message: messageToSend
}).then(json => {
// Need to append message to messages here (message has the server timestamp).
const message = json.message
// Should we directly use an updater object:
this.setState({
messages: this.state.messages.concat([message])
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: state.messages.concat([message])
.sort(this.sortByTimestampComparator)
}
})
// What if while the current user is sending a message it also receives a new one from other users?
})
}
sortByTimestampComparator(messageA, messageB) {
return messageA.sentTimestamp - messageB.sentTimestamp
}
render() {
const {
messages
} = this.state
// Here, `messages` are somehow rendered together with an input field for the current user,
// as well as the above event handlers are passed further down to the respective components.
return (
<div>
{/* ... */}
</div>
)
}
}
С таким количеством асинхронных операций, как я могу быть уверен, что this.state.messages
всегда будетв соответствии с данными на сервере и как бы я использовал setState
для каждого случая?Какие соображения я должен сделать?Должен ли я всегда использовать updater
функцию setState
(почему?) Или безопасно передать объект в качестве параметра updater
(почему?)?
Спасибо за внимание!