ReactJs、TypeScript、MobX - 无法在商店数据更新时获得 MobX 刷新屏幕
ReactJs, TypeScript, MobX - Unable to get MobX refreshing screen on store data update
我在使用 mobx 时遇到问题。事实上,我有一个简单的聊天室,它使用了我自己定义的 WebSocketService。虽然此服务抽象了与我的 API 的交流,但聊天本身处理消息的存储。
main.tsx(入口点)
import React from 'react';
import ReactDOM from 'react-dom';
import {App} from './app';
import {createBrowserHistory} from 'history';
import {createStores} from "app/stores";
import {Provider} from "mobx-react";
import {LoggedUserModel} from "app/models";
import {newUUID} from "app/utils/UUID";
// prepare MobX stores
const history = createBrowserHistory();
const rootStore = createStores(history, new LoggedUserModel(newUUID(), 'User'));
ReactDOM.render(
<Provider {...rootStore}>
<App history={history}/>
</Provider>,
document.getElementById('root')
);
index.tsx(应用程序)
import * as React from 'react';
import {Router, Route, Switch} from 'react-router';
import { hot } from 'react-hot-loader/root';
import { Chat } from "app/containers/Chat";
// render react DOM
export const App = hot(({history}) => {
return (
<Router history={history}>
<Switch>
<Route path="/" component={Chat}/>
</Switch>
</Router>
);
});
Chat.tsx
import * as React from 'react';
import style from './style.css'
import {RouteComponentProps} from 'react-router';
import ChatChannelMessagesList from "app/components/Chat/ChatChannelMessagesListComponent";
import ChatChannelInput from "app/components/Chat/ChatChannelInputComponent";
import {inject, observer} from "mobx-react";
import {STORE_LOGGED_USER, STORE_MESSAGES} from "app/constants";
import {LoggedUserStore, MessagesStore} from "app/stores";
import {newUUID} from "app/utils/UUID";
import {MessageModel} from "app/models";
import WebSocketService from "app/services/webSocketService";
export interface ChatProps extends RouteComponentProps<any> {
[STORE_LOGGED_USER]: LoggedUserStore;
[STORE_MESSAGES]: MessagesStore;
}
export interface ChatState {
WebSocketService: WebSocketService
}
@inject(STORE_LOGGED_USER, STORE_MESSAGES)
@observer
export class Chat extends React.Component<ChatProps, ChatState> {
constructor(props: ChatProps, context: any) {
super(props, context);
this.state = {
WebSocketService: new WebSocketService(event => {
// add the new message to state
this.onMessage(event)
})
}
}
componentDidMount() {
this.state.WebSocketService.connect(this.props[STORE_LOGGED_USER].getLoggedUser.id)
}
componentWillUnmount() {
this.state.WebSocketService.disconnect(this.props[STORE_LOGGED_USER].getLoggedUser.id)
}
addMessageToScreen(message: string) {
// add the new message to state
this.props[STORE_MESSAGES].addMessage(new MessageModel(newUUID(), message));
console.log(this.getMessages())
}
onMessage(event) {
console.log(event.data);
this.addMessageToScreen(event.data)
}
sendMessage(input: string) {
this.addMessageToScreen(input);
this.state.WebSocketService.message(this.props[STORE_LOGGED_USER].getLoggedUser.id, input)
}
getMessages() {
return this.props[STORE_MESSAGES].getMessages
}
render() {
return (
<div id={"chat"} className={style.container}>
<div className="rox">
<div className="row-offset-11 row-11 row-sm-11 row-md-11 row-lg-11 row-xl-11">
<ChatChannelMessagesList messages={this.getMessages()}/>
</div>
<div className="row-1 row-sm-1 row-md-1 row-lg-1 row-xl-1">
<ChatChannelInput onSendClicked={(input: string) => this.sendMessage(input)}/>
</div>
</div>
</div>
);
}
}
export default Chat
我没有错误,一切运行顺利,但更新不会刷新屏幕,我不明白为什么..
messageStore
import { observable, computed, action } from 'mobx';
import { MessageModel } from 'app/models';
export class MessagesStore {
constructor(items: Array<MessageModel>) {
this.Messages = items;
}
@observable public Messages: Array<MessageModel>;
@computed
get getMessages() {
return this.Messages;
}
@action
addMessage = (item: MessageModel): void => {
this.Messages.push(item);
};
@action
setMessages = (items: Array<MessageModel>): void => {
this.Messages = items
};
@action
deleteMessage = (id: string): void => {
this.Messages = this.Messages.filter((Message) => Message.id !== id);
};
}
export default MessagesStore;
我想我已经把所有的东西都放上了,请随时询问更多细节,感谢您的帮助!
Disclaimer: First time using MobX
有两个问题:
在您的 MessagesStore
中,您没有正确使用 @observable。
@observable 使您的字段的初始值可观察。但是在 MessageStore
的构造函数中,您立即将此值替换为常规的、不可观察的数组。所以 MessageStore.Messages
永远不会被观察到,也不会触发任何观察者组件的重新渲染。
你应该做的是用一个数组初始化 MessageStore.Messages
字段,这将是可观察的,永远不要为这个字段分配一个不可观察的数组。
如果你需要用另一个数组替换这个数组,就像你在 MessageStore
的构造函数和 MessageStore.deleteMessage
中所做的那样,你应该使用 MobX 的可观察数组的 .replace
方法,它将替换可观察数组的内容与另一个数组的内容。
export class MessagesStore {
constructor(items: Array<MessageModel>) {
this.Messages.replace(items);
}
@observable public Messages = new Array<MessageModel>();
...
@action
deleteMessage = (id: string): void => {
this.Messages.replace(this.Messages.filter((Message) => Message.id !== id));
};
}
这样,MessageStore.Messages
将是可见的,观察其变化的组件将按预期重新呈现。
第二个问题是您的 Chat
组件没有观察到 MessageStore.Messages
的变化,所以所说的变化永远不会触发 Chat
的重新渲染。原因如下(来自MobX's documentation):
MobX can do a lot, but it cannot make primitive values observable (although it can wrap them in an object see boxed observables). So not the values that are observable, but the properties of an object. This means that @observer actually reacts to the fact that you dereference a value.
<ChatChannelMessagesList messages={this.getMessages()}/>
在这里,您只是将 MessageStore.Messages
作为值传递给 ChatChannelMessagesList
。您没有取消引用它(这意味着访问它的任何属性,例如迭代数组),所以 Chat
,即使标记为 @observer,也不会对 MessageStore.Messages
的内容更改做出反应.
将对这些更改做出反应的组件是第一个实际访问数组内容的组件,可能是 ChatChannelMessagesList
。我的猜测是该组件未标记为@observer,并且从不对消息更新做出反应,这就是您看不到任何重新呈现的原因。
这个问题你有两种解决方案:
- 在 Chat
中取消引用(访问 MessageStore.Messages
的内容),因此当有新消息时它会重新呈现
- 制作 ChatChannelMessagesList
(或第一个取消引用 MessageStore.Messages
的组件)@observer,以便它对更改做出反应。
第二种解决方案更好,因为您应该始终尽可能晚地取消引用您的可观察对象,以避免不依赖于这些更改的父组件的无用重新渲染。在你的情况下,当 MessageStore.Messages
的内容发生变化时重新渲染 Chat
是没有用的,因为 Chat
无论如何都会渲染同样的东西。
总结:
- 不要用新数组重新分配包含可观察数组的字段,而是更改可观察数组的内容。
- @observer 组件在您访问可观察对象的 属性 时重新呈现,而不是它们的值。 @observer 组件应该是那些访问这些属性的组件。
我在使用 mobx 时遇到问题。事实上,我有一个简单的聊天室,它使用了我自己定义的 WebSocketService。虽然此服务抽象了与我的 API 的交流,但聊天本身处理消息的存储。
main.tsx(入口点)
import React from 'react';
import ReactDOM from 'react-dom';
import {App} from './app';
import {createBrowserHistory} from 'history';
import {createStores} from "app/stores";
import {Provider} from "mobx-react";
import {LoggedUserModel} from "app/models";
import {newUUID} from "app/utils/UUID";
// prepare MobX stores
const history = createBrowserHistory();
const rootStore = createStores(history, new LoggedUserModel(newUUID(), 'User'));
ReactDOM.render(
<Provider {...rootStore}>
<App history={history}/>
</Provider>,
document.getElementById('root')
);
index.tsx(应用程序)
import * as React from 'react';
import {Router, Route, Switch} from 'react-router';
import { hot } from 'react-hot-loader/root';
import { Chat } from "app/containers/Chat";
// render react DOM
export const App = hot(({history}) => {
return (
<Router history={history}>
<Switch>
<Route path="/" component={Chat}/>
</Switch>
</Router>
);
});
Chat.tsx
import * as React from 'react';
import style from './style.css'
import {RouteComponentProps} from 'react-router';
import ChatChannelMessagesList from "app/components/Chat/ChatChannelMessagesListComponent";
import ChatChannelInput from "app/components/Chat/ChatChannelInputComponent";
import {inject, observer} from "mobx-react";
import {STORE_LOGGED_USER, STORE_MESSAGES} from "app/constants";
import {LoggedUserStore, MessagesStore} from "app/stores";
import {newUUID} from "app/utils/UUID";
import {MessageModel} from "app/models";
import WebSocketService from "app/services/webSocketService";
export interface ChatProps extends RouteComponentProps<any> {
[STORE_LOGGED_USER]: LoggedUserStore;
[STORE_MESSAGES]: MessagesStore;
}
export interface ChatState {
WebSocketService: WebSocketService
}
@inject(STORE_LOGGED_USER, STORE_MESSAGES)
@observer
export class Chat extends React.Component<ChatProps, ChatState> {
constructor(props: ChatProps, context: any) {
super(props, context);
this.state = {
WebSocketService: new WebSocketService(event => {
// add the new message to state
this.onMessage(event)
})
}
}
componentDidMount() {
this.state.WebSocketService.connect(this.props[STORE_LOGGED_USER].getLoggedUser.id)
}
componentWillUnmount() {
this.state.WebSocketService.disconnect(this.props[STORE_LOGGED_USER].getLoggedUser.id)
}
addMessageToScreen(message: string) {
// add the new message to state
this.props[STORE_MESSAGES].addMessage(new MessageModel(newUUID(), message));
console.log(this.getMessages())
}
onMessage(event) {
console.log(event.data);
this.addMessageToScreen(event.data)
}
sendMessage(input: string) {
this.addMessageToScreen(input);
this.state.WebSocketService.message(this.props[STORE_LOGGED_USER].getLoggedUser.id, input)
}
getMessages() {
return this.props[STORE_MESSAGES].getMessages
}
render() {
return (
<div id={"chat"} className={style.container}>
<div className="rox">
<div className="row-offset-11 row-11 row-sm-11 row-md-11 row-lg-11 row-xl-11">
<ChatChannelMessagesList messages={this.getMessages()}/>
</div>
<div className="row-1 row-sm-1 row-md-1 row-lg-1 row-xl-1">
<ChatChannelInput onSendClicked={(input: string) => this.sendMessage(input)}/>
</div>
</div>
</div>
);
}
}
export default Chat
我没有错误,一切运行顺利,但更新不会刷新屏幕,我不明白为什么..
messageStore
import { observable, computed, action } from 'mobx';
import { MessageModel } from 'app/models';
export class MessagesStore {
constructor(items: Array<MessageModel>) {
this.Messages = items;
}
@observable public Messages: Array<MessageModel>;
@computed
get getMessages() {
return this.Messages;
}
@action
addMessage = (item: MessageModel): void => {
this.Messages.push(item);
};
@action
setMessages = (items: Array<MessageModel>): void => {
this.Messages = items
};
@action
deleteMessage = (id: string): void => {
this.Messages = this.Messages.filter((Message) => Message.id !== id);
};
}
export default MessagesStore;
我想我已经把所有的东西都放上了,请随时询问更多细节,感谢您的帮助!
Disclaimer: First time using MobX
有两个问题:
在您的 MessagesStore
中,您没有正确使用 @observable。
@observable 使您的字段的初始值可观察。但是在 MessageStore
的构造函数中,您立即将此值替换为常规的、不可观察的数组。所以 MessageStore.Messages
永远不会被观察到,也不会触发任何观察者组件的重新渲染。
你应该做的是用一个数组初始化 MessageStore.Messages
字段,这将是可观察的,永远不要为这个字段分配一个不可观察的数组。
如果你需要用另一个数组替换这个数组,就像你在 MessageStore
的构造函数和 MessageStore.deleteMessage
中所做的那样,你应该使用 MobX 的可观察数组的 .replace
方法,它将替换可观察数组的内容与另一个数组的内容。
export class MessagesStore {
constructor(items: Array<MessageModel>) {
this.Messages.replace(items);
}
@observable public Messages = new Array<MessageModel>();
...
@action
deleteMessage = (id: string): void => {
this.Messages.replace(this.Messages.filter((Message) => Message.id !== id));
};
}
这样,MessageStore.Messages
将是可见的,观察其变化的组件将按预期重新呈现。
第二个问题是您的 Chat
组件没有观察到 MessageStore.Messages
的变化,所以所说的变化永远不会触发 Chat
的重新渲染。原因如下(来自MobX's documentation):
MobX can do a lot, but it cannot make primitive values observable (although it can wrap them in an object see boxed observables). So not the values that are observable, but the properties of an object. This means that @observer actually reacts to the fact that you dereference a value.
<ChatChannelMessagesList messages={this.getMessages()}/>
在这里,您只是将 MessageStore.Messages
作为值传递给 ChatChannelMessagesList
。您没有取消引用它(这意味着访问它的任何属性,例如迭代数组),所以 Chat
,即使标记为 @observer,也不会对 MessageStore.Messages
的内容更改做出反应.
将对这些更改做出反应的组件是第一个实际访问数组内容的组件,可能是 ChatChannelMessagesList
。我的猜测是该组件未标记为@observer,并且从不对消息更新做出反应,这就是您看不到任何重新呈现的原因。
这个问题你有两种解决方案:
- 在 Chat
中取消引用(访问 MessageStore.Messages
的内容),因此当有新消息时它会重新呈现
- 制作 ChatChannelMessagesList
(或第一个取消引用 MessageStore.Messages
的组件)@observer,以便它对更改做出反应。
第二种解决方案更好,因为您应该始终尽可能晚地取消引用您的可观察对象,以避免不依赖于这些更改的父组件的无用重新渲染。在你的情况下,当 MessageStore.Messages
的内容发生变化时重新渲染 Chat
是没有用的,因为 Chat
无论如何都会渲染同样的东西。
总结:
- 不要用新数组重新分配包含可观察数组的字段,而是更改可观察数组的内容。
- @observer 组件在您访问可观察对象的 属性 时重新呈现,而不是它们的值。 @observer 组件应该是那些访问这些属性的组件。