26-React 파일 관리자 26장: 노드 감시자

그래서 우리는 아름다운 트리를 포함하도록 커널을 만들었습니다. 이제 트리의 변경 사항을 감시하도록 해야 새 노드가 추가되거나 제거될 때 트리를 업데이트할 수 있습니다.

디렉토리 생성



이제 createDirectory 작업으로 돌아가서 디렉토리가 생성되면 커널 트리를 업데이트해야 합니다.

// createDirectory.ts
import { toastLoading } from "design-system/components/Toast";
import Kernel from "../Kernel";
import fileManagerService from "../services/file-manager-service";

export default function createDirectory(kernel: Kernel) {
  return function create(
    directoryName: string,
    directoryPath: string = kernel.currentDirectoryNode?.path as string,
  ) {
    return new Promise((resolve, reject) => {
      const loader = toastLoading(
        "Creating directory...",
        "We are creating your directory, please wait a moment.",
      );

      fileManagerService
        .createDirectory(directoryName, directoryPath)
        .then(response => {
          loader.success("Success!", "Your directory has been created.");

          👉🏻 kernel.tree.setNode(response.data.node);

          resolve(response.data.node);
        })
        .catch(error => {
          loader.error("Error", error.response.data.error);
          reject(error);
        });
    });
  };
}


이제 우리는 커널 트리에게 이 노드가 속한 위치를 설정하도록 지시했습니다. 그러면 트리가 업데이트될 것입니다.

useNodeWatcher



이제 트리의 변경 사항을 감시하고 변경 사항이 감지되면 트리를 업데이트하는 후크를 생성해 보겠습니다.

// hooks/useNodeWatcher.ts
import { Node } from "app/file-manager/Kernel";
import { useEffect, useState } from "react";
import useKernel from "./useKernel";

export default function useWatchNodeChange(node?: Node) {
  const kernel = useKernel();

  // Store the node internally in a state
  const [internalNode, setNode] = useState<Node | undefined>(node);

  useEffect(() => {
    // watch for node change
    const event = kernel.on("nodeChange", (newNode: Node) => {
      // if the updated node is the same as the one we are watching
      // then update the internal node
      if (newNode.path === internalNode?.path) {
        setNode({ ...newNode });
      }
    });

    return () => event.unsubscribe();
  }, [internalNode, kernel]);

  useEffect(() => {
    setNode(node);
  }, [node]);

  return internalNode;
}


여기에는 설명할 것이 없습니다. 노드를 상태에 저장하고 커널의 변경 사항을 감시하고 있습니다. 업데이트된 노드가 우리가 보고 있는 노드와 동일하면 내부 노드를 업데이트합니다.

노드 목록 업데이트



이미 디렉터리 변경 이벤트를 수신하고 있으므로 노드 목록을 업데이트하기 위해 노드 자체도 감시해야 합니다.

// NodesList.tsx
import { Grid } from "@mantine/core";
import {
  useCurrentDirectoryNode,
  useNodeWatcher,
} from "app/file-manager/hooks";
import { DirectoryNode, FileNode } from "./ContentNode";

export default function NodesList() {
  const currentDirectoryNode = useCurrentDirectoryNode();

  const node = useNodeWatcher(currentDirectoryNode);

  return (
    <>
      <Grid>
        {node?.directories?.map(node => (
          <Grid.Col key={node.path} span={2}>
            <DirectoryNode node={node} />
          </Grid.Col>
        ))}
        {node?.files?.map(node => (
          <Grid.Col key={node.path} span={2}>
            <FileNode node={node} />
          </Grid.Col>
        ))}
      </Grid>
    </>
  );
}

prepareNode 메소드 덕분에 노드에서 파일과 디렉토리를 수집하는 메모를 제거했습니다.

그런 다음 노드의 변경 사항을 감시하기 위해 useNodeWatcher 후크를 추가했습니다.

사이드바 업데이트 중



루트도 노드로 간주되므로 루트 노드의 변경 사항을 감시하려면 사이드바를 업데이트해야 합니다.

// Sidebar.tsx
import {
  Card,
  ScrollArea,
  Skeleton,
  ThemeIcon,
  useMantineTheme,
} from "@mantine/core";
import { IconFolder, IconHome2 } from "@tabler/icons";
import { useKernel, useLoading } from "app/file-manager/hooks";
import useWatchNodeChange from "../../hooks/useNodeWatcher";
import { SidebarWrapper } from "./Sidebar.styles";
import SidebarNode from "./SidebarNode";

export default function Sidebar() {
  const isLoading = useLoading();

  const theme = useMantineTheme();

  // get the kernel
  const kernel = useKernel();

  // watch for the root node for change
  const rootNode = useWatchNodeChange(kernel.rootNode);

  if (isLoading) {
    return (
      <Card shadow={"sm"}>
        <SidebarWrapper>
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
        </SidebarWrapper>
      </Card>
    );
  }

  // if no root node yet, return null
  if (!rootNode) return null;

  return (
    <Card shadow="sm">
      <SidebarWrapper>
        <ScrollArea type="auto" style={{ height: "300px" }}>
          <SidebarNode
            node={rootNode}
            icon={
              <ThemeIcon variant="light" color={theme.colors.lime[1]}>
                <IconHome2 size={16} color={theme.colors.lime[9]} />
              </ThemeIcon>
            }
          />
          {rootNode.directories?.map(child => (
            <SidebarNode
              navProps={{
                pl: 25,
              }}
              key={child.path}
              icon={
                <ThemeIcon variant="light" color={theme.colors.blue[1]}>
                  <IconFolder size={16} color={theme.colors.blue[5]} />
                </ThemeIcon>
              }
              node={child}
            />
          ))}
        </ScrollArea>
      </SidebarWrapper>
    </Card>
  );
}


루트 노드의 변경 사항을 감시하기 위해 useWatchNodeChange 후크를 추가했습니다.

사이드바 스켈레톤을 별도의 구성 요소로 이동해 보겠습니다.

// SidebarSkeleton.tsx
import { Card, Skeleton } from "@mantine/core";

export default function SidebarSkeleton() {
  return (
    <Card shadow={"sm"}>
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
    </Card>
  );
}


이제 사이드바 구성 요소는 다음과 같습니다.

// Sidebar.tsx
import { Card, ScrollArea, ThemeIcon, useMantineTheme } from "@mantine/core";
import { IconFolder, IconHome2 } from "@tabler/icons";
import { useKernel, useLoading } from "app/file-manager/hooks";
import useWatchNodeChange from "../../hooks/useNodeWatcher";
import { SidebarWrapper } from "./Sidebar.styles";
import SidebarNode from "./SidebarNode";
import SidebarSkeleton from "./SidebarSkeleton";

export default function Sidebar() {
  const isLoading = useLoading();

  const theme = useMantineTheme();

  // get the kernel
  const kernel = useKernel();

  // watch for the root node for change
  const rootNode = useWatchNodeChange(kernel.rootNode);

  if (isLoading) return <SidebarSkeleton />;

  // if no root node yet, return null
  if (!rootNode) return null;

  return (
    <Card shadow="sm">
      <SidebarWrapper>
        <ScrollArea type="auto" style={{ height: "300px" }}>
          <SidebarNode
            node={rootNode}
            icon={
              <ThemeIcon variant="light" color={theme.colors.lime[1]}>
                <IconHome2 size={16} color={theme.colors.lime[9]} />
              </ThemeIcon>
            }
          />
          {rootNode.directories?.map(child => (
            <SidebarNode
              navProps={{
                pl: 25,
              }}
              key={child.path}
              icon={
                <ThemeIcon variant="light" color={theme.colors.blue[1]}>
                  <IconFolder size={16} color={theme.colors.blue[5]} />
                </ThemeIcon>
              }
              node={child}
            />
          ))}
        </ScrollArea>
      </SidebarWrapper>
    </Card>
  );
}


다음 장



다음 장에서는 노드 선택 알고리즘을 만들기 시작합니다.

기사 저장소



Github Repository에서 챕터 파일을 볼 수 있습니다.

Don't forget the main branch has the latest updated code.



지금 어디 있는지 말해줘



이 시리즈를 저와 함께 후속 조치하는 경우 현재 위치와 어려움을 겪고 있는 부분을 알려주시면 최대한 도와드리겠습니다.

살람.

좋은 웹페이지 즐겨찾기