我使用 React 构建的 Chatkit 应用程序遇到了一些奇怪的活动。本质上,我是在不同房间对两个不同的用户进行测试。当我从一个房间的用户发送消息时。其他用户可以看到该消息,尽管他们不在同一个房间。这是正在发生的事情的屏幕截图。
只有当用户至少在同一个房间里出现过一次时,才会出现这种情况。
我可以判断消息正在正确创建,因为我在 ChatKit API 的正确位置看到了它们。此外,如果我重新渲染组件,消息最终会出现在正确的位置。但是跨房间消息传递错误仍然存在。
我的印象是它肯定与 MessageList 组件的状态有关。我确保每次进入新房间时都会更新组件状态,但我想真正的问题是应用程序的其他实例是否甚至关心不同实例的组件状态变化。
所以事不宜迟,这是我的代码:
ChatScreen(主应用程序)
import React from "react"
import Chatkit from "@pusher/chatkit"
import MessageList from "./MessageList"
import SendMessageForm from "./SendMessageForm"
import WhosOnlineList from "./WhosOnlineList"
import RoomList from "./RoomList"
import NewRoomForm from "./NewRoomForm"
import { getCurrentRoom } from "../../actions/chatkitActions"
import { connect } from "react-redux"
class ChatScreen extends React.Component{
constructor(props){
super(props)
this.state = {
messages: [],
currentRoom: {},
currentUser: {},
usersWhoAreTyping: [],
joinableRooms: [],
joinedRooms: [],
errors: {}
}
this.sendMessage = this.sendMessage.bind(this)
this.sendTypingEvent = this.sendTypingEvent.bind(this)
this.subscribeToRoom = this.subscribeToRoom.bind(this)
this.getRooms = this.getRooms.bind(this)
this.createRoom = this.createRoom.bind(this)
}
componentDidMount(){
//setup Chatkit
let tokenUrl
let instanceLocator = "somecode"
if(process.env.NODE_ENV === "production"){
tokenUrl = "somenedpoint"
} else {
tokenUrl = "http://localhost:3000/api/channels/authenticate"
}
const chatManager = new Chatkit.ChatManager({
instanceLocator: instanceLocator,
userId: this.props.chatUser.name,
connectionTimeout: 120000,
tokenProvider: new Chatkit.TokenProvider({
url: tokenUrl
})
})
//initiate Chatkit
chatManager.connect()
.then((currentUser) => {
this.setState({
currentUser: currentUser
})
//get all rooms
this.getRooms()
// if the user is returning to the chat, direct them to the room they last visited
if(this.props.chatkit.currentRoom.id > 0){
this.subscribeToRoom(this.props.chatkit.currentRoom.id)
}
})
}
sendMessage = (text) => {
this.state.currentUser.sendMessage({
roomId: this.state.currentRoom.id,
text: text
})
}
sendTypingEvent = () => {
this.state.currentUser
.isTypingIn({
roomId: this.state.currentRoom.id
})
.catch((errors) => {
this.setState({
errors: errors
})
})
}
getRooms = () => {
this.state.currentUser.getJoinableRooms()
.then((joinableRooms) => {
this.setState({
joinableRooms: joinableRooms,
joinedRooms: this.state.currentUser.rooms
})
})
.catch((errors) => {
this.setState({
errors: { error: "could not retrieve rooms"}
})
})
}
subscribeToRoom = (roomId) => {
this.setState({
messages: []
})
this.state.currentUser.subscribeToRoom({
roomId: roomId,
hooks: {
onNewMessage: (message) => {
this.setState({
messages: [...this.state.messages, message]
})
},
onUserStartedTyping: (currentUser) => {
this.setState({
usersWhoAreTyping: [...this.state.usersWhoAreTyping, currentUser.name]
})
},
onUserStoppedTyping: (currentUser) => {
this.setState({
usersWhoAreTyping: this.state.usersWhoAreTyping.filter((user) => {
return user !== currentUser.name
})
})
},
onUserCameOnline: () => this.forceUpdate(),
onUserWentOffline: () => this.forceUpdate(),
onUserJoined: () => this.forceUpdate()
}
})
.then((currentRoom) => {
this.setState({
currentRoom: currentRoom
})
this.getRooms()
//store currentRoom in redux state
this.props.getCurrentRoom(currentRoom)
})
.catch((errors) => {
this.setState({
errors: errors
})
})
}
createRoom = (roomName) => {
this.state.currentUser.createRoom({
name: roomName
})
.then((newRoom) => {
this.subscribeToRoom(newRoom.id)
})
.catch((errors) => {
this.setState({
errors: { error: "could not create room" }
})
})
}
render(){
const username = this.props.chatUser.name
return(
<div className="container" style={{ display: "flex", fontFamily: "Montserrat", height: "100vh"}}>
<div
className="col-md-3 bg-dark mr-2 p-0"
style={{display: "flex", flexDirection: "column", maxHeight: "80vh", padding: "24px 24px 0px"}}
>
<div style={{flex: "1"}} className="p-4">
<WhosOnlineList users={this.state.currentRoom.users}/>
<RoomList
roomId={this.state.currentRoom.id}
rooms={[...this.state.joinedRooms, ...this.state.joinableRooms]}
subscribeToRoom={this.subscribeToRoom}
/>
</div>
<NewRoomForm createRoom={this.createRoom} user={this.state.currentUser}/>
</div>
<div
className="col-md-9 border p-0"
style={{display: "flex", flexDirection: "column", maxHeight: "80vh"}}
>
<div className="mb-3">
{ this.state.currentRoom.name ? (
<h4
className="bg-black text-light m-0"
style={{padding: "1.0rem 1.2rem"}}
>
{this.state.currentRoom.name}
</h4>
) : (
this.props.chatkit.currentRoom.id > 0 ) ? (
<h3 className="text-dark p-4">Returning to room...</h3>
) : (
<h3 className="text-dark p-4">← Join a Room!</h3>
)}
</div>
<div style={{flex: "1"}}>
<MessageList messages={this.state.messages} room={this.state.currentRoom.id} usersWhoAreTyping={this.state.usersWhoAreTyping}/>
</div>
<SendMessageForm
sendMessage={this.sendMessage}
userTyping={this.sendTypingEvent}
currentRoom={this.state.currentRoom}
/>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return{
chatkit: state.chatkit
}
}
const mapDispatchToProps = (dispatch) => {
return{
getCurrentRoom: (currentRoom) => {
dispatch(getCurrentRoom(currentRoom))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatScreen)
消息列表(组件)
import React from "react"
import ReactDOM from "react-dom"
import TypingIndicator from "./TypingIndicator"
class MessageList extends React.Component{
constructor(props){
super(props)
this.state = {
currentRoom: {}
}
}
componentWillReceiveProps(nextProps){
if(nextProps.room){
console.log(nextProps.room)
this.setState({
currentRoom: nextProps.room
})
}
}
componentWillUpdate(){
const node = ReactDOM.findDOMNode(this)
//scrollTop is the distance from the top. clientHeight is the visible height. scrollHeight is the height on the component
this.shouldScrollToBottom = node.scrollTop + node.clientHeight + 100 >= node.scrollHeight
}
componentDidUpdate(){
//scroll to the bottom if we are close to the bottom of the component
if(this.shouldScrollToBottom){
const node = ReactDOM.findDOMNode(this)
node.scrollTop = node.scrollHeight
}
}
render(){
const messages = this.props.messages
let updatedMessages = []
for(var i = 0; i < messages.length; i++){
let previous = {}
if(i > 0){
previous = messages[i - 1]
}
if(messages[i].senderId === previous.senderId){
updatedMessages.push({...messages[i], senderId: ""})
} else{
updatedMessages.push(messages[i])
}
}
return(
<div>
{this.props.room && (
<div style={{overflow: "scroll", overflowX: "hidden", maxHeight: "65vh"}}>
<ul style={{listStyle: "none"}} className="p-3">
{updatedMessages.map((message, index) => {
return (
<li className="mb-1" key={index}>
<div>
{message.senderId && (
<span
className="text-dark d-block font-weight-bold mt-3"
>
{message.senderId}
</span>
)}
<span
className="bg-info text-light rounded d-inline-block"
style={{padding:".25rem .5rem"}}
>
{message.text}
</span>
</div>
</li>
)
})}
</ul>
<TypingIndicator usersWhoAreTyping={this.props.usersWhoAreTyping}/>
</div>
)}
</div>
)
}
}
export default MessageList
RoomList(组件)
import React from "react"
class RoomList extends React.Component{
render(){
const orderedRooms = [...this.props.rooms].sort((a, b) => {
return a.id - b.id
})
return(
<div>
{ this.props.rooms.length > 0 ? (
<div>
<div className="d-flex justify-content-between text-light mb-2">
<h6 className="font-weight-bold">Channels</h6><i className="fa fa-gamepad"></i>
</div>
<ul style={{listStyle: "none", overflow: "scroll", overflowX: "hidden", maxHeight: "27vh"}} className="p-2">
{orderedRooms.map((room, index) => {
return(
<li key={index} className="font-weight-bold mb-2">
<a
onClick={() => {
this.props.subscribeToRoom(room.id)
}}
href="#"
className={room.id === this.props.roomId ? "text-success": "text-info"}
style={{textDecoration: "none"}}
>
<span className="mr-2">#</span>{room.name}
</a>
</li>
)
})}
</ul>
</div>
) : (
<p className="text-muted p-2">Loading...</p>
)}
</div>
)
}
}
这是渲染 ChatScreen 的组件 (ChannelsContainer)
import React from "react"
import UsernameForm from "./UsernameForm"
import ChatScreen from "./ChatScreen"
import { connect } from "react-redux"
class ChannelsContainer extends React.Component{
constructor(props){
super(props)
this.state = {
chatScreen: false
}
}
componentWillMount(){
if(this.props.chatkit.chatInitialized){
this.setState({
chatScreen: true
})
}
}
componentWillReceiveProps(nextProps){
if(nextProps.chatkit.chatInitialized){
this.setState({
chatScreen: true
})
}
}
render(){
let chatStage
if(this.state.chatScreen){
chatStage = <ChatScreen chatUser={this.props.chatkit.chatUser}/>
} else{
chatStage = <UsernameForm/>
}
return(
<div style={{minHeight: "90vh"}}>
{chatStage}
</div>
)
}
}
const mapStateToProps = (state) => {
return{
chatkit: state.chatkit
}
}
export default connect(mapStateToProps)(ChannelsContainer)
请让我知道你们的想法。