25- React 파일 관리자 25장: 커널 트리
42935 단어 typescriptmongezreactjavascript
하지만 그 전에 코드를 조금 정리합시다.
코드 청소
주요 구성 요소 함수
FileManager.tsx
로 이동하고 다음 코드로 업데이트합니다.import { Grid } from "@mantine/core";
import Content from "app/file-manager/components/Content";
import LoadingProgressBar from "app/file-manager/components/LoadingProgressBar";
import Sidebar from "app/file-manager/components/Sidebar";
import Toolbar from "app/file-manager/components/Toolbar";
import { KernelContext } from "app/file-manager/contexts";
import Kernel from "app/file-manager/Kernel";
import { useEffect, useRef } from "react";
import { BodyWrapper } from "./FileManager.styles";
import { FileManagerProps } from "./FileManager.types";
export default function FileManager({ rootPath }: FileManagerProps) {
const { current: kernel } = useRef(new Kernel(rootPath as string));
// load root directory
useEffect(() => {
if (!rootPath) return;
kernel.load(rootPath);
}, [rootPath, kernel]);
return (
<KernelContext.Provider value={kernel}>
<LoadingProgressBar />
<Toolbar />
<BodyWrapper>
<Grid>
<Grid.Col span={3}>
<Sidebar />
</Grid.Col>
<Grid.Col span={9}>
<Content />
</Grid.Col>
</Grid>
</BodyWrapper>
</KernelContext.Provider>
);
}
FileManager.defaultProps = {
rootPath: "/",
};
여기서 우리가 한 것은 정의된 모든
states
이 여기에서 더 이상 필요하지 않기 때문에 제거한 것이며 모든 상태는 이제 Kernel
클래스 안에 있습니다.또한
rootPath
를 커널 클래스에 전달하고 useEffect
후크 내부의 루트 디렉토리를 로드합니다.커널 트리
이제 생성자 메서드를 추가하여
rootPath
를 수락하고 그것으로 tree
를 초기화하겠습니다.// app/file-manager/Kernel/Kernel.ts
import { Node } from "app/file-manager/Kernel/Node";
...
/**
* Constructor
*/
public constructor(rootPath: string) {
this.rootPath = rootPath;
}
이제
KernelTree
클래스 옆에 우리의 Kernel
클래스도 정의해 보겠습니다.// app/file-manager/Kernel/KernelTree.ts
import Kernel from "./Kernel";
import { Node } from "./Kernel.types";
export default class KernelTree {
/**
* Root node
*/
public root?: Node;
/**
* Constructor
*/
constructor(public kernel: Kernel) {}
}
생성자에 커널을 주입하여 커널에서 직접 모든 메서드/속성을 사용할 수 있습니다.
이제
KernelTree
클래스를 사용하도록 커널 클래스를 업데이트하겠습니다.// app/file-manager/Kernel/Kernel.ts
import events, { EventSubscription } from "@mongez/events";
import { createDirectory } from "../actions";
import fileManagerService from "../services/file-manager-service";
import { KernelEvents, Node } from "./Kernel.types";
import KernelTree from "./KernelTree";
export default class Kernel {
...
/**
* Kernel nodes tree
*/
public tree: KernelTree;
/**
* Root node
*/
public rootNode?: Node;
/**
* Constructor
*/
public constructor(rootPath: string) {
this.rootPath = rootPath;
this.tree = new KernelTree(this);
}
...
}
커널 트리의 개념
커널 트리의 개념은 모든 노드를 내부에 주입하는 것이므로 모든 노드에 쉽게 직접 액세스하고 업데이트, 삭제 또는 하위 목록을 업데이트할 수 있습니다.
따라서 우리의
KernelTree
클래스에는 다음과 같은 기능이 있습니다.이제
KernelTree
클래스 구현을 시작하겠습니다.// app/file-manager/Kernel/KernelTree.ts
import Kernel from "./Kernel";
import { Node } from "./Kernel.types";
export default class KernelTree {
/**
* Root node
*/
public root?: Node;
/**
* Constructor
*/
constructor(public kernel: Kernel) {}
/**
* Set root node
*/
public setRootNode(root: Node) {
this.root = root;
this.kernel.trigger("nodeChange", this.root);
this.prepareNode(this.root);
}
}
여기에
setRootNode
를 추가하여 내부에 모든 것을 포함할 최상위 노드를 정의하고 prepareNode
를 호출하여 노드 자식을 디렉토리 및 파일로 분할하고 알파벳순으로 자식을 이름으로 정렬하는 두 가지 작업을 수행합니다.노드 준비 중
이전에 언급한 것처럼 노드 자식을 디렉토리와 파일로 분할하고 알파벳순으로 이름별로 자식을 정렬합니다.
// app/file-manager/Kernel/KernelTree.ts
...
/**
* Prepare the given node
*/
public prepareNode(node: Node) {
if (!node.children) return;
this.reorderChildren(node);
// set children directories
node.directories = node.children.filter(child => child.isDirectory);
// set children files
node.files = node.children.filter(child => !child.isDirectory);
}
/**
* Reorder node children by child name
*/
public reorderChildren(node: Node) {
node.children?.sort((a, b) => {
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) return 1;
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) return -1;
return 0;
});
}
하지만
Node
유형에는 directories
및 files
속성이 없으므로 추가해 보겠습니다.// app/file-manager/Kernel/Kernel.types.tsx
/**
* File Manager node is the primary data structure for the File Manager.
* It can be a directory or a file.
* It contains the following properties:
*/
export type Node = {
/**
* Node Name
*/
name: string;
/**
* Node full path to root
*/
path: string;
/**
* Node size in bits
*/
size: number;
/**
* Is node directory
*/
isDirectory: boolean;
/**
* Node children
* This should be present (event with empty array) if the node is directory
*/
children?: Node[];
/**
* Get children directories
*/
👉🏻 directories?: Node[];
/**
* Get children files
*/
👉🏻 files?: Node[];
};
커널 트리 업데이트
이제 노드가 서버에서 로드되면 트리를 업데이트해야 하므로
load
메서드로 이동하겠습니다. /**
* Load the given path
*/
public load(path: string): Promise<Node> {
// trigger loading event
this.trigger("loading");
return new Promise((resolve, reject) => {
fileManagerService
.list(path)
.then(response => {
this.currentDirectoryPath = path;
if (response.data.node.path === this.rootPath) {
👉🏻 this.tree.setRootNode(response.data.node);
this.rootNode = response.data.node;
} else {
👉🏻 this.tree.setNode(response.data.node);
}
// trigger load event as the directory has been loaded successfully.
this.trigger("load", response.data.node);
// if the current directory is not as the same loaded directory path,
// then we'll trigger directory changed event.
if (response.data.node.path !== this.currentDirectoryNode?.path) {
this.trigger("directoryChange", response.data.node);
}
this.currentDirectoryNode = response.data.node;
resolve(this.currentDirectoryNode as Node);
})
.catch(reject);
});
}
이제 트리 노드 내부에 노드를 전달하면서 트리 노드를 업데이트할 수 있습니다. 따라서 노드가 로드되면 루트 노드인지 확인한 다음 루트 노드를 업데이트하고 그렇지 않으면 로드된 노드만 업데이트합니다.
setNode
클래스 내부에 KernelTree
메서드를 생성해 보겠습니다.// app/file-manager/Kernel/KernelTree.ts
/**
* Add the given node to the tree
*/
public setNode(node: Node) {
// first find the parent node
let parentNode = this.parentNode(node);
// if it has no parent, which should not happen, then mark the root as parent
if (!parentNode) {
parentNode = this.root;
}
// if there is no parent, then do nothing and just return
if (!parentNode) return;
// a flag to determine if the given node was already existing but has been changed
let nodeHasChanged = false;
// a flag to determine if the parent node is changed
let parentHasChanged = false;
// now check if the node already exists in its parent
if (this.parentHas(parentNode, node)) {
// if it exists, replace it
parentNode.children = parentNode.children?.map(child => {
if (child.path === node.path) {
if (this.nodeHasChanged(child, node)) {
nodeHasChanged = true;
parentHasChanged = true;
}
return node;
}
return child;
});
} else {
// it means the node does not exist in the parent, then push it to the parent's children
parentNode?.children?.push(node);
this.kernel.trigger("newNode", node);
parentHasChanged = true;
// prepare the node
this.prepareNode(node);
}
// this will be only triggered if the node has changed
if (nodeHasChanged) {
this.prepareNode(node);
this.kernel.trigger("nodeChange", node);
}
// this will be only triggered if the parent node has changed
if (parentHasChanged) {
this.prepareNode(parentNode);
// as the parent node has changed thus the root node will be marked as changed as well
// we may later make it recursive to mark all the parent nodes as changed
this.prepareNode(this.root as Node);
this.kernel.trigger("nodeChange", parentNode);
this.kernel.trigger("nodeChange", this.root);
}
}
코드는 자명합니다. 노드가 부모에 이미 있는지 확인하고, 있으면 교체하고, 그렇지 않으면 부모의 자식으로 푸시합니다.
다음 메서드도 추가해 보겠습니다.
parentNode
주어진 노드parentHas
상위 노드에 지정된 노드가 있는지 확인nodeHasChanged
지정된 노드가 변경되었는지 확인// app/file-manager/Kernel/KernelTree.ts
/**
* Check if the given parent has the given node
*/
public parentHas(parent: Node, node: Node): boolean {
return parent.children?.some(child => child.path === node.path) ?? false;
}
/**
* Get parent node
*/
public parentNode(node: Node): Node | undefined {
return this.findNode(this.getParentPath(node.path));
}
/**
* Find node for the given path recursively in the tree
*/
public findNode(path: string): Node | undefined {
// loop starting from the tree root
const currentNode = this.root;
const findNode = (node?: Node): Node | undefined => {
if (node?.path === path) {
return node;
}
if (!node?.children) return undefined;
for (const child of node.children) {
const foundNode = findNode(child);
if (foundNode) return foundNode;
}
};
return findNode(currentNode);
}
/**
* Check if the given node has been changed
*/
public nodeHasChanged(oldNode: Node, newNode: Node): boolean {
return JSON.stringify(oldNode) !== JSON.stringify(newNode);
}
/**
* Get the parent path of the given path
*/
protected getParentPath(path: string): string {
if (!path) return "/";
// get the parent path by splitting the path and removing the last item
return path.split("/").slice(0, -1).join("/");
}
마지막으로 할 일은 커널 이벤트를 업데이트하는 것입니다.
// Kernel.types
/**
* Kernel events
*/
export type KernelEvents =
| "loading"
| "load"
| "directoryChange"
| "nodeChange"
| "nodeDestroy"
| "newNode";
노드가 변경될 때 트리거되는
nodeChange
, 노드가 파괴될 때 트리거되는 nodeDestroy
, 새 노드가 추가될 때 트리거되는 newNode
를 추가했습니다.다음 장
다음 장에서는 만들기 디렉터리, 사이드바 및 노드 변경 사항을 감시할 콘텐츠를 향상시킬 것입니다.
기사 저장소
Github Repository에서 챕터 파일을 볼 수 있습니다.
Don't forget the
main
branch has the latest updated code.
지금 어디 있는지 말해줘
이 시리즈를 저와 함께 후속 조치하는 경우 현재 위치와 어려움을 겪고 있는 부분을 알려주시면 최대한 도와드리겠습니다.
살람.
Reference
이 문제에 관하여(25- React 파일 관리자 25장: 커널 트리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/hassanzohdy/25-react-file-manager-chapter-xxv-kernel-tree-78b텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)