【问题标题】:state inside useEffect refers the initial state always with React HooksuseEffect 中的 state 总是使用 React Hooks 引用初始状态
【发布时间】:2019-07-07 14:26:15
【问题描述】:

每次我从另一个组件发出消息时,我都无法获得完整的消息列表。这是钩子和视图组件:

export function useChat() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = openSocket("http://localhost:3003");
    socket.on("chat message", msg => {
      const newState = update(messages, { $push: [msg] });
      setMessages(newState);
    });
  }, []);

  return { messages };
}

不幸的是,状态不会持续存在并且总是显示最后一条消息:

export const HookSockets = () => {
  const { messages } = useChat();
  return (
    <div>
      {messages.map((message, index) => (
        <div key={index}>{message}</div>
      ))}
    </div>
  );
};

如果我以常规方式执行此操作,一切都会按预期进行:

export class ClassSockets extends Component {
  state = {
    socket: openSocket("http://localhost:3003"),
    messages: [],
    message: ""
  };

  componentDidMount() {
    this.state.socket.on("chat message", msg => {
      const newState = update(this.state, {
        messages: { $push: [msg] }
      });
      this.setState(newState);
    });
  }

  handleClick = () => {
    this.state.socket.emit("chat message", this.state.message);
    this.setState({ message: "" });
  };

  handleChange = event => {
    this.setState({ message: event.target.value });
  };

  render() {
    return (
      <div>
        <div>Sockets</div>
        <div>{this.state.messages}</div>
        <input
          type="text"
          value={this.state.message}
          onChange={this.handleChange}
        />
        <button onClick={this.handleClick}>Send Message</button>
      </div>
    );
  }
}

【问题讨论】:

    标签: reactjs websocket socket.io immutability react-hooks


    【解决方案1】:

    由于您已经编写了 useEffect 以在组件的初始挂载时执行,它会创建一个引用 messages 初始值的闭包,即使消息更新,它仍然会在后续调用中引用相同的值

    您应该将 useEffect 配置为在初始挂载和消息更改时运行

    export function useChat() {
      const [messages, setMessages] = useState([]);
    
      useEffect(() => {
        const socket = openSocket("http://localhost:3003");
        socket.on("chat message", msg => {
          const newState = update(messages, { $push: [msg] });
          setMessages(newState);
        });
      }, [messages]);
    
      return { messages };
    } 
    

    否则你可以使用回调模式来更新状态

    export function useChat() {
      const [messages, setMessages] = useState([]);
    
      useEffect(() => {
        const socket = openSocket("http://localhost:3003");
        socket.on("chat message", msg => {
          setMessages(prevMessages => update(prevMessages, { $push: [msg] }););
        });
      }, []);
    
      return { messages };
    }
    

    【讨论】:

      【解决方案2】:

      当您在useEffect() 中使用空数组编写套接字处理程序时,因此此效果仅在您的组件第一次安装时运行一次。 socket.on() 函数(或闭包)将记住消息的初始值,即使消息发生更改,socket.on() 闭包仍将引用其初始值。这个问题的解决方案是将我们的消息注册到效果的依赖数组中。

      export function useChat() {   
      const [messages, setMessages] = 
      useState([]);
      
      useEffect(() => {
      const socket = openSocket("http://localhost:3003");
      socket.on("chat message", msg => {
        const newState = update(messages, { $push: [msg] });
        setMessages(newState);
      });   }, [messages]);
      return { messages }; 
      }
      

      您会遇到一个新问题,即每次更改消息时都会创建一个带有“聊天消息”处理程序的新套接字,这可能会导致意外和添加代码多次运行。要解决该问题,您必须取消注册较早的处理程序。我建议你只创建一次套接字(例如在 App.js 中)并将其作为道具传递。

      export function useChat(socket) {   
      const [messages, setMessages] = useState([]);
      
      useEffect(() => { 
      socket.on("chat message", msg => {
        const newState = update(messages, { $push: [msg] });
        setMessages(newState);
      });   
      //De-register old handler   
       return function(){
      socket.off("chat message")   } }, [messages]);
      
      return { messages }; }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-07-20
        • 1970-01-01
        • 2020-11-02
        • 1970-01-01
        • 1970-01-01
        • 2019-11-01
        • 2019-05-28
        • 2020-11-28
        相关资源
        最近更新 更多