useEffect 中的 state 总是使用 React Hooks 引用初始状态

Posted

技术标签:

【中文标题】useEffect 中的 state 总是使用 React Hooks 引用初始状态【英文标题】:state inside useEffect refers the initial state always with 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>
    );
  

【问题讨论】:

【参考方案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 ; 

【讨论】:

以上是关于useEffect 中的 state 总是使用 React Hooks 引用初始状态的主要内容,如果未能解决你的问题,请参考以下文章

使用 useEffect 更新 useReducer 的“状态”

React中useEffect使用

为啥Android中的文件'/sys/power/state'总是'mem'?

React useEffect 无限循环获取数据 axios

ReactJs/NextJs useEffect() 总是被调用两次

useState和useEffect