javascript - 如何在useEffect中使用更新的值

标签 javascript reactjs docker use-effect

我是Java语言的新手,目前正在Docker getting-started tutorial中尝试使用Demo应用程序。该应用程序是一个简单的待办事项列表,您可以在其中添加和删除项目。
我试图在页面的每个实例上更新列表,而不必重新加载页面。
我已经设法编辑节点 express 服务器,以便它通过服务器发送的事件发送更新。
问题:
前端使用React。当前显示项目的数据包含在ìtems数组中。onNewItem将项目添加到该数组。但是,当从onNewItem调用onmessage时,即使从其他React组件调用onNewItem时,items数组也为null。如何访问items数组的初始化版本? (它由1.useEffect初始化,它从服务器获取项目。)
下面是代码的一部分

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],
);

最佳答案

让我们从出现问题的原因开始。问题是,当您调用onNewItem(newItem)时,您使用的是对onNewItem的过时引用。因此,函数内的items仍将设置为初始值。
您通过为React.useCallback提供依赖项数组来部分解决此问题。当onNewItem的新值可用时,这将更新items。但是,由于React.useEffect并未将onNewItem列为依赖项,因此它继续使用旧版本的onNewItem
如此说来,您可以考虑将onNewItem添加到React.useEffect的依赖项数组中。尽管这是正确的操作,但仅将其添加到依赖项数组是不够的。
onNewItem添加到React.useEffect的依赖数组中会遇到什么问题?没有清除功能,因此您将使用不同的onmessage处理程序(不同版本的onNewItem)多次订阅该 channel 。
因此,考虑到以上所有因素,解决方案可能看起来像这样:

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 (
    // ...
  );
}
我将EventSource的创建移到了第一个React.useEffect内,因为仅在组件安装后才需要发生(并且在卸载时需要关闭连接)。空的依赖项数组只会在安装时调用该函数,而在卸载时调用清除函数。
现在,第二个React.useEffect具有依赖项数组[events, onNewItem],因为设置events时需要附加onmessage处理程序。并且,如果onNewItem回调更新到新版本,则应将其附加为新的onmessage处理程序(替换旧的处理程序)。由于已经处理了打开和关闭events的操作,因此不再需要清理功能。
虽然上面应该做的。如果管理特定状态变得越来越复杂,则最好选择 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 (
    // ...
  );
}
上面的代码将所有items管理逻辑提取到“reducer”函数中。 React保证dispatch返回的useReducer函数是稳定的,因此您可以从依赖项数组中忽略它(但不必这样做)。

关于javascript - 如何在useEffect中使用更新的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64376266/

相关文章:

javascript - Native Base如何获取List dataArray的ListItem索引?

node.js - 如何将 Dockerfile 转换为 docker compose 镜像?

javascript - 当我尝试使用英特尔 XDK 发送 Ajax 查询时,为什么会出现错误?

node.js - 无法在使用 React 构建的简单博客中添加新文章

javascript - 如何使用 TypeScript 根据另一个属性将 React 组件的两个属性设置为可选?

docker-compose 更新容器路径

Docker 注册表垃圾收集

javascript - 如何按名称计算 JavaScript 对象中嵌套的数组数量

javascript - Firefox 未调用扩展安装回调

javascript - 使用 JavaScript 更改元素上的 CSS?