0

我正在使用 React 构建一个简单的聊天室,并且希望能够通过在文本字段中输入并按下发送按钮来使用文本更新聊天窗口。我期待当保存上下文的 Store 调用 React.UseReducer 时,它会使用传递给 Dashboard 组件的文本值更新上下文。当我在 reducer 中记录文本时,它会显示应该添加到聊天窗口的文本字段中的值。但是,运行 useReducer 后,状态仍然只是 initState,不包括发送的文本。

我认为 allChats 将使用 Store 中的文本字段值进行更新,然后使用 React.useContext(CTX) 在 Dashboard.js 中访问。allChats 仅记录存储中的 initState。

如何更新它以将文本传递到上下文中,以便聊天窗口显示它?

仪表板.js


export default function Dashboard() {

  const classes = useStyles();
  //CTX store
  const {allChats, sendChatAction, user} = React.useContext(CTX);
  const topics = Object.keys(allChats);
  //local state
  const [activeTopic, changeActiveTopic] = React.useState(topics[0])
  const [textValue, changeTextValue] = React.useState('')


  return (
    <div className={classes.root}>
      <Paper className={classes.root}>
        <Typography variant="h4" component="h4">
          Chat Room
        </Typography>
        <Typography variant="h5" component="h5">
          {activeTopic}
        </Typography>
        <div className={classes.flex}>
          <div className={classes.topicsWindow}>
            <List>
              {topics.map((topic) => (
                <ListItem onClick={e=> changeActiveTopic(e.target.innerText)} key={topic} button>
                  <ListItemText primary={topic} />
                </ListItem>
              ))}
            </List>
          </div>
          <div className={classes.chatWindow}>
            { 
              allChats[activeTopic].map((chat, i) => (
              <div className={classes.flex} key={i}>
                <Chip label={chat.from} className={classes.chip} />
                <Typography variant='body1' gutterBottom>{chat.msg}</Typography>
              </div>
            ))
          }
          </div>
        </div>
        <div className={classes.flex}>
        <TextField
         label="Send a Chat"
         className={classes.chatBox}
         value={textValue}
         onChange={e => changeTextValue(e.target.value)}

         />

          <Button
            variant="contained"
            color="primary"
            className = {classes.button}
            onClick ={() => {
              sendChatAction({from: user, msg: textValue, topic: activeTopic});
              changeTextValue('');
            }}
          >
            Send
        </Button>
        </div>
      </Paper>
    </div>
  );
};

店铺


import React from 'react'
import io from 'socket.io-client'

export const CTX = React.createContext()


const initState = {
  general: [
    {from:'matt',msg:'yo'},
    {from:'jeff',msg:'yo'},
    {from:'steve',msg:'yo'},
  ],
  topic2:[
    {from:'bill',msg:'yo'},
    {from:'joe',msg:'yo'},
    {from:'dave',msg:'yo'},
  ]
}

function reducer(state, action){
   //payload logs as:
   //{from:'from',msg:'msg',topic:'topic'}
  const {from, msg, topic} = action.payload;
  switch(action.type){
      case 'RECEIVE_MESSAGE':
        return {
          ...state,
          [topic]:[
            ...state[topic],
            {from,msg}
          ]
        }
        default:
          return state
  }
}

let socket;

function sendChatAction(value){
  socket.emit('chat message', value);
}
export default function Store(props){
  const [allChats,dispatch] = React.useReducer(reducer, initState);
  if(!socket){
    socket = io(':3001');
    socket.on('chat message', function(msg){
       //msg logs with text field value that we need in 
       //chatWindow on Dashboard.
      dispatch({type:'RECEIVE_MESSAGE', payload: msg});
    });
  }
      //allChats logs only as initState, above.
const user = 'matt' + Math.random(100).toFixed(2);

return (
  <CTX.Provider value={{allChats,sendChatAction,user}}>
  {props.children}
  </CTX.Provider>
)

}

index.js

var app = require('express')()
var http = require('http').createServer(app);
var io = require('socket.io')(http)

app.get('/',function(req,res){
  res.send('<h1>hello world</h1>')
});

io.on('connection',function(socket){
  console.log('a user connected',socket);
  socket.on('chat message',function(msg){
    console.log('message: ' + JSON.stringify(msg))
    io.emit('chat message', msg)
  });
})

http.listen(3001,function(){
  console.log('listening on *:3001')
});

4

1 回答 1

1

我整理了一个工作示例,用一个简单的setTimeout.

可能最有趣的部分是我将sendChatAction函数移动到组件中,以便它可以使用dispatchreducer 的方法:

export default function Store(props) {
  const [allChats, dispatch] = React.useReducer(reducer, initState);

  function sendChatAction(value) {
    dispatch({
      type: "RECEIVE_MESSAGE",
      payload: value
    });
  }

  ...
}

一切似乎都奏效了。将您的代码与此进行比较:

商店.jsx

import React, { useEffect } from "react";

export const CTX = React.createContext();

const initState = {
  general: [
    { from: "matt", msg: "yo" },
    { from: "jeff", msg: "yo" },
    { from: "steve", msg: "yo" }
  ],
  topic2: [
    { from: "bill", msg: "yo" },
    { from: "joe", msg: "yo" },
    { from: "dave", msg: "yo" }
  ]
};

function reducer(state, action) {
  //payload logs as:
  //{from:'from',msg:'msg',topic:'topic'}
  const { from, msg, topic } = action.payload;
  switch (action.type) {
    case "RECEIVE_MESSAGE":
      return {
        ...state,
        [topic]: [...state[topic], { from, msg }]
      };
    default:
      return state;
  }
}

export default function Store(props) {
  const [allChats, dispatch] = React.useReducer(reducer, initState);

  function sendChatAction(value) {
    dispatch({
      type: "RECEIVE_MESSAGE",
      payload: value
    });
  }

  useEffect(() => {
    //msg logs with text field value that we need in
    //chatWindow on Dashboard.
    setTimeout(() => {
      sendChatAction({ from: "matt", msg: "hey", topic: "general" });
    }, 3000);
  }, []);

  //allChats logs only as initState, above.
  const user = "matt" + Math.random(100).toFixed(2);

  return (
    <CTX.Provider value={{ allChats, sendChatAction, user }}>
      {props.children}
    </CTX.Provider>
  );
}

仪表板.jsx

import {
  Button,
  Chip,
  List,
  ListItem,
  ListItemText,
  Paper,
  TextField,
  Typography
} from "@material-ui/core";
import React from "react";
import { CTX } from "./Store";

export default function Dashboard() {
  const classes = {};
  //CTX store
  const { allChats, sendChatAction, user } = React.useContext(CTX);
  const topics = Object.keys(allChats);
  //local state
  const [activeTopic, changeActiveTopic] = React.useState(topics[0]);
  const [textValue, changeTextValue] = React.useState("");

  return (
    <div className={classes.root}>
      <Paper className={classes.root}>
        <Typography variant="h4" component="h4">
          Chat Room
        </Typography>
        <Typography variant="h5" component="h5">
          {activeTopic}
        </Typography>
        <div className={classes.flex}>
          <div className={classes.topicsWindow}>
            <List>
              {topics.map((topic) => (
                <ListItem
                  onClick={(e) => changeActiveTopic(e.target.innerText)}
                  key={topic}
                  button
                >
                  <ListItemText primary={topic} />
                </ListItem>
              ))}
            </List>
          </div>
          <div className={classes.chatWindow}>
            {allChats[activeTopic].map((chat, i) => (
              <div className={classes.flex} key={i}>
                <Chip label={chat.from} className={classes.chip} />
                <Typography variant="body1" gutterBottom>
                  {chat.msg}
                </Typography>
              </div>
            ))}
          </div>
        </div>
        <div className={classes.flex}>
          <TextField
            label="Send a Chat"
            className={classes.chatBox}
            value={textValue}
            onChange={(e) => changeTextValue(e.target.value)}
          />

          <Button
            variant="contained"
            color="primary"
            className={classes.button}
            onClick={() => {
              sendChatAction({
                from: user,
                msg: textValue,
                topic: activeTopic
              });
              changeTextValue("");
            }}
          >
            Send
          </Button>
        </div>
      </Paper>
    </div>
  );
}

应用程序.js

import React from "react";
import Dashboard from "./Dashboard";
import Store from "./Store";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <Store>
        <Dashboard />
      </Store>
    </div>
  );
}
于 2020-11-06T01:24:03.490 回答