到目前为止,这里的答案很好。只是想补充一点,您保存数据的哪里 应该是基于数据的类型 的决定。 James Nelson 有我经常提到的an excellent article on this topic。
对于你的情况,让我们谈谈前 3 种状态:
- 数据
- 通讯状态
- 控制状态
数据
您的 WebSocket 连接是通用的,从技术上讲可以返回任何内容,但您收到的消息很可能是数据。例如,假设您正在构建一个聊天应用程序。然后,所有已发送和接收的消息的日志将是数据。您应该使用messages reducer 将此数据存储在 redux 中:
export default function messages(state = [], action) {
switch (action.type) {
case 'SEND_MESSAGE':
case 'RECEIVE_MESSAGE': {
return [ ...state, action.message ];
}
default: return state;
}
}
我们不必(也不应该)在我们的 reducer 中包含任何 WebSocket 逻辑,因为它们是通用的并且不关心数据来自哪里。
另外,请注意,这个 reducer 能够以完全相同的方式处理发送和接收。这是因为网络通信是由我们的通信状态 reducer 单独处理的。
通信状态
由于您使用的是 WebSockets,因此您要跟踪的通信状态类型可能与我的示例不同。在使用标准 API 的应用中,我会跟踪请求何时加载、失败或成功。
在我们的聊天应用示例中,您可能希望在发送消息时跟踪这些请求状态,但您也可能希望将其他事物归类为通信状态。
我们的network reducer 可以使用与messages reducer 相同的操作:
export default function network(state = {}, action) {
switch (action.type) {
case 'SEND_MESSAGE': {
// I'm using Id as a placeholder here. You'll want some way
// to tie your requests with success/failure receipt.
return {
...state,
[action.id]: { loading: true }
};
} case 'SEND_MESSAGE_SUCCESS': {
return {
...state,
[action.id]: { loading: false, success: true }
};
} case 'SEND_MESSAGE_FAILURE': {
return {
...state,
[action.id]: { loading: false, success: false }
};
}
default: return state;
}
}
这样,我们可以很容易地找到我们的请求的状态,并且我们不必为我们的组件中的加载/成功/失败而烦恼。
但是,由于您使用的是 WebSocket,因此您可能并不关心任何给定请求的成功/失败。在这种情况下,您的通信状态可能只是您的套接字是否已连接。如果这听起来更好,那么只需编写一个 connection reducer 来响应打开/关闭操作。
控制状态
我们还需要一些东西来启动消息的发送。在聊天应用示例中,这可能是一个提交按钮,用于发送输入字段中的任何文本。我不会演示整个组件,因为我们将使用controlled component。
这里的要点是控制状态是消息发送之前。在我们的例子中,有趣的代码是在handleSubmit 中做什么:
class ChatForm extends Component {
// ...
handleSubmit() {
this.props.sendMessage(this.state.message);
// also clear the form input
}
// ...
}
const mapDispatchToProps = (dispatch) => ({
// here, the `sendMessage` that we're dispatching comes
// from our chat actions. We'll get to that next.
sendMessage: (message) => dispatch(sendMessage(message))
});
export default connect(null, mapDispatchToProps)(ChatForm);
所以,这解决了我们所有状态的何处。我们创建了一个通用应用程序,该应用程序可以使用操作调用 fetch 以获得标准 API、从数据库或任意数量的其他来源获取数据。在您的情况下,您想使用WebSockets。所以,这个逻辑应该存在于你的行动中。
动作
在这里,您将创建所有处理程序:onOpen、onMessage、onError 等。这些仍然可以相当通用,因为您已经单独设置了 WebSocket 实用程序。
function onMessage(e) {
return dispatch => {
// you may want to use an action creator function
// instead of creating the object inline here
dispatch({
type: 'RECEIVE_MESSAGE',
message: e.data
});
};
}
我在这里使用thunk 进行异步操作。对于这个特定的示例,这可能不是必需的,但您可能会遇到这样的情况,您希望发送消息然后处理成功/失败并从单个 sendMessage 操作中将多个操作发送到您的减速器。 Thunk 非常适合这种情况。
全部连接在一起
最后,我们已经完成了所有设置。我们现在要做的就是初始化 WebSocket 并设置适当的侦听器。我喜欢 Vladimir 建议的模式——在构造函数中设置套接字——但我会参数化你的回调,以便你可以提交你的操作。然后你的 WebSocket 类就可以设置所有的监听器了。
通过创建 WebSocket 类 a singleton,您可以从您的操作内部发送消息,而无需管理对活动套接字的引用。您还可以避免污染全局命名空间。
通过使用单例设置,只要您第一次调用new WebSocket(),您的连接就会建立。因此,如果您需要在应用程序启动后立即打开连接,我会在App 的componentDidMount 中设置它。如果惰性连接没问题,那么您可以等到您的组件尝试发送消息。该操作将创建一个新的 WebSocket 并建立连接。