React 및 GraphQL에서 트리 뷰를 재귀적으로 렌더링
12189 단어 reacttypescript
재귀가 무엇인지 알아야 하는 경우 this link을 확인해야 합니다.
이 문서에서는 다음 패키지를 사용합니다.
패키지 설치
npm install @mui/lab @mui/material @mui/icons-material @apollo/client graphql
Apollo 클라이언트 설정
모든 앱에서 사용할 수 있도록 index.js에서 구성 요소를
ApolloProvider
로 래핑해야 합니다.import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
createHttpLink,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
// URI for graphql API on back4app
const httpLink = createHttpLink({
uri: "https://parseapi.back4app.com/graphql",
});
const headersLink = setContext((_, { headers }) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
// These keys are found when you create app on back4app
"X-Parse-Application-Id": "<YOUR_APPLICATION_ID>",
"X-Parse-Master-Key": "<YOUR_MASTER_KEY>",
"X-Parse-REST-API-Key": "<YOUR_REST_API_KEY>",
},
};
});
const client = new ApolloClient({
link: headersLink.concat(httpLink),
cache: new InMemoryCache(),
});
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>,
document.getElementById("root")
);
GraphQL 쿼리 준비
이제 사용 중인 API에 대한 쿼리를 준비해야 합니다. 나는 이 튜토리얼에 적절한 중첩을 제공할 back4app에서 ContinentsCountriesCities 데이터베이스를 사용할 것입니다.
따라서 대륙, 국가 및 도시에 대한 쿼리는 다음과 같습니다(쿼리 세부 정보에 대한 문서로 앱의 Graphql API 플레이그라운드를 확인할 수 있음).
import { gql } from "@apollo/client";
export const GET_CONTINENTS = gql`
query allContinents {
data: continentscountriescities_Continents {
count
results: edges {
node {
objectId
name
children: countries {
count
}
}
}
}
}
`;
export const GET_COUNTRIES = gql`
query allCountries($continentId: ID) {
data: continentscountriescities_Countries(
where: { continent: { have: { objectId: { equalTo: $continentId } } } }
) {
count
results: edges {
node {
objectId
name
children: cities {
count
}
}
}
}
}
`;
export const GET_CITIES = gql`
query allCities($countryId: ID) {
data: continentscountriescities_Cities(
where: { country: { have: { objectId: { equalTo: $countryId } } } }
) {
count
results: edges {
node {
objectId
name
}
}
}
}
`;
apollo 클라이언트에서 제공하는
gql
문자열 리터럴은 기본 스키마에 대한 쿼리 유효성 검사에 도움이 됩니다.트리 보기 UI
Material UI에서 기본 트리 보기를 사용할 수 있지만 TreeItem 클릭 시 데이터 가져오기를 처리하기 위해 사용자 지정 콘텐츠를 제공해야 합니다.
따라서 우리의
CustomTreeItem
는 다음과 같이 보일 것입니다.import React, { useEffect } from "react";
import clsx from "clsx";
import { CircularProgress, Typography } from "@mui/material";
import TreeItem, { useTreeItem } from "@mui/lab/TreeItem";
import { useLazyQuery } from "@apollo/client";
import { GET_COUNTRIES, GET_CITIES } from "../../utils/Queries";
const CustomContent = React.forwardRef(function CustomContent(
props,
ref
) {
// TreeItemContentProps + typename + appendNewData props
const {
classes,
className,
label,
nodeId,
icon: iconProp,
expansionIcon,
displayIcon,
typename,
appendNewData,
} = props;
// Extract last part from Typename key of node from graphql
// Ex: Continentscountriescities_Country => Country
const type: string = typename?.split("_")[1] || "";
let lazyQueryParams = {};
// Add lazyQueryParams according to type of node
switch (type) {
case "Continent":
lazyQueryParams = {
query: GET_COUNTRIES,
variableName: "continentId",
};
break;
case "Country":
lazyQueryParams = {
query: GET_CITIES,
variableName: "countryId",
};
break;
default:
lazyQueryParams = {
query: GET_COUNTRIES,
variableName: "continentId",
};
break;
}
// Lazy query for getting children of this node
const [getChildren, { loading, data }] = useLazyQuery(
lazyQueryParams?.query,
{
variables: { [lazyQueryParams?.variableName]: nodeId },
}
);
const { disabled, expanded, selected, focused, handleExpansion } =
useTreeItem(nodeId);
const icon = iconProp || expansionIcon || displayIcon;
// Append new children to node
useEffect(() => {
if (data?.data?.results && appendNewData) {
appendNewData(nodeId, data.data?.results || []);
}
}, [data]);
const handleExpansionClick = (event) => {
// Fetch data only once
if (!data) {
getChildren();
}
handleExpansion(event);
};
return (
<div
className={clsx(className, classes.root, {
[classes.expanded]: expanded,
[classes.selected]: selected,
[classes.focused]: focused,
[classes.disabled]: disabled,
})}
onClick={handleExpansionClick}
ref={ref}
>
<div className={classes.iconContainer}>{icon}</div>
<Typography component="div" className={classes.label}>
{label}
</Typography>
</div>
);
});
const CustomTreeItem = (props) => {
return (
<TreeItem
ContentComponent={CustomContent}
// These props will be sent from the parent
ContentProps={
{ typename: props.typename, appendNewData: props.appendNewData } as any
}
{...props}
/>
);
};
export default CustomTreeItem;
위에서 만든 쿼리와 apollo 클라이언트의
useLazyQuery
후크를 사용하여 구성 요소에서 필요할 때마다 호출할 메서드getChildren()
(또는 다른 이름)가 있습니다. 따라서 우리는 handleExpansionClick
메서드에서 이 메서드를 호출하고 데이터가 아직 가져오지 않았는지 확인합니다.그리고 계층에서 호출할 쿼리를 결정하기 위해 렌더링하는 노드의 유형을 전환하고 있습니다.
이제 트리를 렌더링하는 상위 구성 요소의 경우 첫 번째 렌더링에서 기본적으로 대륙 데이터를 렌더링하고 기본 배열에 가져온 새 하위 데이터를 추가하는 재귀 기능이 있습니다. 이를 위해서는 모든 쿼리가 위와 같이 고정된 구조를 가져야 합니다.
상위 구성 요소는 다음과 같습니다.
import React, { useEffect, useState } from "react";
import { useQuery } from "@apollo/client";
import TreeView from "@mui/lab/TreeView";
import { CircularProgress } from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import { GET_CONTINENTS } from "../../utils/Queries";
import CustomTreeItem from "../CustomTreeItem";
import { getModifiedData } from "../../utils/Shared";
const Tree = () => {
// Get all continents on first render
const { loading, data: allContinents } = useQuery(GET_CONTINENTS);
// Data to render all tree items from
const [treeItemsData, setTreeItemsData] = useState([]);
// Set treeItemsData with continents recieved
useEffect(() => {
if (allContinents?.data?.results) {
setTreeItemsData(allContinents?.data?.results);
}
}, [allContinents]);
// Add new data in its correct place in treeItemsData array
const appendNewData = (nodeId, data) => {
const treeItemsDataClone = JSON.parse(JSON.stringify(treeItemsData)); // Deep Copy
// getModifiedData is the recursive function (will be shown below alone)
const newData = getModifiedData(treeItemsDataClone, nodeId, data);
setTreeItemsData(newData); // set the rendered array with the modified array
};
// Render children items recursively
const renderChild = (node) => {
return (
<CustomTreeItem
key={node.objectId}
classes={{ content: styles.treeItemContent }}
typename={node.__typename}
appendNewData={appendNewData}
nodeId={node.objectId}
label={node.name}
>
{/* If children is an object with a count key > 0, render a dummy treeItem to show expand icon on parent node */}
{node.children &&
(node.children.count > 0 ? (
<CustomTreeItem nodeId="1" />
) : (
node.children.length &&
node.children.map((child: any) => renderChild(child.node)) // Recursively rendering children if array is found
))}
</CustomTreeItem>
);
};
// Show a loader until query resolve
if (loading) return <CircularProgress />;
else if (allContinents)
return (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: "auto" }}
>
{treeItemsData.map((continent: any) => {
return renderChild(continent.node);
})}
</TreeView>
);
else return <></>;
};
export default Tree;
이제 재귀 함수의 경우 원래 배열, 새 데이터를 찾아서 삽입할 노드 ID 및 삽입할 새 데이터와 같은 매개 변수를 사용합니다.
이 기능은 발견되었지만here 특정 요구 사항에 맞게 사용자 정의되었습니다.
/*
Original Answer: https://stackoverflow.com/a/15524326
@Description: Searches for a specific object in nested objects or arrays according to "objectId" key
@Params: originalData => The original array or object to search in
nodeId => the id to compare to objectId field
dataToBeAdded => new data to be added ad children to found node
@Returns: Modified original data
*/
export const getModifiedData = (
originalData: any,
nodeId: string,
dataToBeAdded: any
) => {
let result = null;
const originalDataCopy = JSON.parse(JSON.stringify(originalData)); // Deep copy
if (originalData instanceof Array) {
for (let i = 0; i < originalDataCopy.length; i++) {
result = getModifiedData(originalDataCopy[i], nodeId, dataToBeAdded);
if (result) {
originalDataCopy[i] = result;
}
}
} else {
for (let prop in originalDataCopy) {
if (prop === "objectId") {
if (originalDataCopy[prop] === nodeId) {
originalDataCopy.children = dataToBeAdded;
return originalDataCopy;
}
}
if (
originalDataCopy[prop] instanceof Object ||
originalDataCopy[prop] instanceof Array
) {
result = getModifiedData(originalDataCopy[prop], nodeId, dataToBeAdded);
if (result) {
originalDataCopy[prop] = result;
break;
}
}
}
}
return originalDataCopy;
};
상태에서 쉽게 설정할 수 있도록 수정된 배열을 반환합니다.
긴 코드 스니펫에 대해 죄송하지만 다소 복잡하고 모든 코드를 노출하고 싶었습니다. 반응에서 back4app 데이터베이스 및 graphql로 작업하는 것은 문서에서 명확하지 않았기 때문에 이러한 단계도 제공하고 싶었습니다.
이 기사가 유사한 기능을 구현하는 사람에게 도움이 되기를 바랍니다.
Reference
이 문제에 관하여(React 및 GraphQL에서 트리 뷰를 재귀적으로 렌더링), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/nouran96/render-tree-view-recursively-in-react-graphql-41gd텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)