简体   繁体   English

如何在 useEffect 中使用更新的值

[英]How do I use an updated value in useEffect

I'm new to Javascript and I'm currently experimenting with the Demo application out of the Docker getting-started tutorial .我是 Javascript 新手,我目前正在 Docker入门教程中尝试使用 Demo 应用程序。 The application is a simple Todo list where you can add items and remove them.该应用程序是一个简单的待办事项列表,您可以在其中添加和删除项目。

I'm trying to update the list on every instance of the page without having to reload the page.我正在尝试更新页面的每个实例上的列表,而无需重新加载页面。 I've managed to edit the node express server so that it sends updates via Server-sent events.我设法编辑了节点快速服务器,以便它通过服务器发送的事件发送更新。

The problem: The frontend uses React.问题:前端使用 React。 The data of the currently displayer items is contained in the ìtems array.当前显示项的数据包含在ìtems数组中。 onNewItem adds items to that array. onNewItem将项目添加到该数组。 However when onNewItem is called from onmessage the items array is null even though it's not null when onNewItem is called from other React components.但是,当从onmessage调用onNewItem onmessage ,即使从其他 React 组件调用onNewItem时它不为 null,items 数组也是空的。 How can I access the initialized version of the items array?如何访问items数组的初始化版本? (It gets initialized by the 1. useEffect which fetches items from the server) (它由从服务器获取项目的 1.useEffect 初始化)

Below is a part of the code下面是部分代码

function TodoListCard() {
const [items, setItems] = React.useState(null);

const [ listening, setListening ] = React.useState(false);

React.useEffect(() => {
    fetch('/items')
        .then(r => r.json())
        .then(setItems);
}, []);

React.useEffect( () => {
    if (!listening) {
    const events = new EventSource('/events/subscribe');
    events.onmessage = (event) => {
        const parsedData = JSON.parse(event.data);
        switch (parsedData.type) {
            case "add":
                var newItem = {id: parsedData.id, name: parsedData.name, completed: parsedData.completed};
                onNewItem(newItem);
                break;
            default:
                break;
        }
    };

    setListening(true);
    }
}, [listening]);

const onNewItem = React.useCallback(
    newItem => {
        if (items.some(e => e.id === newItem.id)){return;}
        setItems([...items, newItem]);
    },
    [items],
);

Let's start of by why things are going wrong.让我们从为什么会出错开始。 The issue is that when you call onNewItem(newItem) you are using an outdated reference to the onNewItem .问题是,当您调用onNewItem(newItem)您使用的是对onNewItem的过时引用。 For this reason items within the function will still be set to the initial value.为此,函数内的items仍将设置为初始值。

You partially solved this by providing an dependency array to React.useCallback .您通过向React.useCallback提供依赖数组部分解决了这个问题。 This will update onNewItem when a new value of items is available.items的新值可用时,这将更新onNewItem However since React.useEffect does not list onNewItem as a dependency it keeps using the old version of onNewItem .然而,由于React.useEffect没有将onNewItem作为依赖项列出,所以它一直使用旧版本的onNewItem

With this being said you might consider adding onNewItem , to the dependency array of React.useEffect .随着这是说你可以考虑添加onNewItem ,要的依赖阵列React.useEffect Although this is the correct action, just adding this to dependency array is not enough.尽管这是正确的操作,但仅将其添加到依赖项数组是不够的。

What is the problem you get when you add onNewItem to the depency array of React.useEffect ?onNewItem添加到 React.useEffect 的依赖数组时会React.useEffect什么问题? There is no cleanup function, so you will subscribe to the channel multiple times with different onmessage handlers (different versions of onNewItem ).没有清理功能,因此您将使用不同的onmessage处理程序(不同版本的onNewItem )多次订阅频道。

So taking all the above into account a solution might look something like this:因此,考虑到以上所有内容,解决方案可能如下所示:

function TodoListCard() {
  const [items, setItems] = React.useState(null);
  const [events, setEvents] = React.useState(null);

  React.useEffect(() => {
    const pEvents = fetch('/items')
      .then(r => r.json())
      .then(setItems)
      .then(() => new EventSource('/events/subscribe'));

    pEvents.then(setEvents);

    return () => pEvents.then(events => events.close());
  }, []);

  React.useEffect(() => {
    if (!events) return;
    events.onmessage = (event) => {
      const parsedData = JSON.parse(event.data);
      switch (parsedData.type) {
        case "add":
          var newItem = {
            id: parsedData.id,
            name: parsedData.name,
            completed: parsedData.completed
          };
          onNewItem(newItem);
          break;
        default:
          break;
      }
    };
  }, [events, onNewItem]);

  const onNewItem = React.useCallback(newItem => {
    const isPresent = items.some(item => item.id === newItem.id);
    if (isPresent) return;
    setItems([...items, newItem]);
  }, [items]);

  return (
    // ...
  );
}

I've moved the EventSource creation inside the first React.useEffect since that only needs to happen once the component is mounted (and needs to close the connection on unmount).我已经在第一个React.useEffect移动了EventSource创建,因为只有在安装组件后才需要发生(并且需要在卸载时关闭连接)。 An empty dependency array will only call the function on mount, and calls the cleanup function on unmount.一个空的依赖数组只会在挂载时调用函数,在卸载时调用清理函数。

The second React.useEffect now has the dependency array [events, onNewItem] , because when events is set the onmessage handler needs to be attached.第二个React.useEffect现在有依赖数组[events, onNewItem] ,因为当设置events时需要附加onmessage处理程序。 And if the onNewItem callback updates to a new version you should attach it as the new onmessage handler (replacing the old handler).如果onNewItem回调更新为新版本,您应该将其附加为新的onmessage处理程序(替换旧的处理程序)。 This doesn't need a cleanup function anymore since, opening and closing events is already handled.这不再需要清理功能,因为已经处理了打开和关闭events

Although the above should do the job.虽然上面应该做的工作。 If managing a specific state is becoming more complicated it might be better to opt for useReducer instead of useState .如果管理特定状态变得越来越复杂,最好选择useReducer而不是useState

function reducer(items, action) {
  switch (action.type) {
    case "add":
      const isPresent = items.some(item => item.id == action.item.id);
      if (isPresent) return items;
      return [...items, action.item];

    case "replace all":
      return action.items;

    case "complete": // <- unused example case
      return items.map(item => {
        if (item.id != action.id) return item;
        return {...item, completed: true};
      });

    // ...

    default: // silently ignore unsupported operations
      return items;
  }
}

function TodoListCard() {
  const [items, dispatch] = React.useReducer(reducer, null);

  React.useEffect(() => {
    const pEvents = fetch('/items')
      .then(r => r.json())
      .then(items => dispatch({type: "replace all", items}))
      .then(() => new EventSource('/events/subscribe'));

    pEvents.then(events => {
      events.onmessage = (event) => {
        const {type, ...item} = JSON.parse(event.data);
        dispatch({type, item});
      };
    });

    return () => pEvents.then(events => events.close());
  }, []);

  // if you still need onNewItem for your render:
  const onNewItem = React.useCallback(item => {
    dispatch({type: "add", item});
  }, []);

  return (
    // ...
  );
}

The above extracts all the items management logic into a "reducer" function.以上将所有的items管理逻辑提取到一个“reducer”函数中。 The dispatch function returned by useReducer is guaranteed to be stable by React, so you can omit it from dependency arrays (but you don't have to). useReducer返回的dispatch函数被 React 保证是稳定的,所以你可以从依赖数组中省略它(但你不必)。

The error that you have done is you are not getting any data from your api.The setItems in your first useEffect won't work.你曾经做过的错误是你没有得到从api.The任何数据setItems在第一useEffect将无法正常工作。

Wrong Way:错误道:

React.useEffect(() => {
    fetch('/items')
        .then(r => r.json())
        .then(setItems);
}, []);

Right Way:正确的方法:

useEffect(() => {
      fetch('/items')
          .then(r => r.json())
          .then((result) => {
            setItems(result.items)
          });
  }, []);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM