假如只是需要展示一個 JSON Object ,那其實不需要特別設計,只需要
但我們現在需要它可以摺疊,所以需要特別為寫 JSON 寫樹狀遞迴 Component 。
TJson
這是 Component 的入口,他會把從後端傳過來的 JSON string parse 好後餵給 JsonValue
export function TJson({ node }: Props) {
return (
<div className="block">
<pre>
<JsonValue val={JSON.parse(node.props.value)} pad={0} />
</pre>
</div>
)
}
JsonValue
JsonValue
是一個 Factory ,他會根據傳進來的 Type 決定他要丟給以下哪個 Componet 處理:
function JsonValue({ val, pad }: { val: any, pad: number }) {
if (val === null) {
return JsonNull({ val })
}
if (Array.isArray(val)) {
return JsonList({ val, pad })
} else if (typeof val === 'object') {
return JsonDict({ val, pad })
} else if (typeof val === 'string') {
return JsonString({ val })
} else if (typeof val === 'boolean') {
return JsonBool({ val })
} else {
return JsonElement({ val })
}
}
JsonNull
, JsonString
, JsonElement
, JsonBool
對於字串、布林值、null 以及一般的基本型別,使用專屬的渲染方法。例如,字串會被顯示為綠色,而布林值和 null 則以棕色顯示,這有助於快速識別 JSON 的資料型態。
function JsonString({ val }: { val: string }) {
return <p style={{ display: 'inline', color: 'green' }} >"{val}"</p>
}
function JsonBool({ val }: { val: boolean }) {
if (val) {
return <p style={{ display: 'inline', color: 'brown' }} >true</p>
} else {
return <p style={{ display: 'inline', color: 'brown' }} >false</p>
}
}
function JsonNull({ val }: { val: null }) {
return <p style={{ display: 'inline', color: 'brown' }} >null</p>
}
function JsonElement({ val }: { val: any }) {
return <p style={{ display: 'inline', color: 'brown' }}>{val}</p>
}
JsonDict
這兩個是這 TJson
的關鍵,要可以摺疊。
我們使用 useState
來控制物件的展開與折疊狀態。當物件被展開時,它會遞迴地渲染每個 key-value 對,並且在每一層加上適當的縮排。
function JsonDict({ val, pad }: { val: any, pad: number }) {
const [open, setOpen] = useState(true)
if (!open) {
return <p style={{ display: 'inline' }}>
<span className="pseudolink" onClick={(e) => { setOpen(true) }}>
<b>{"{ ... }"}</b>
</span>
</p>
}
const kvs = []
for (const [key, value] of Object.entries(val)) {
kvs.push(<div>{' '.repeat(pad + 2)}"{key}": <JsonValue val={value} pad={pad + 2} />,</div>)
}
return (
<p style={{ display: 'inline' }}>
<span className="pseudolink" onClick={(e) => { setOpen(false) }}>
<b>{"{"}</b>
</span>
<div>{kvs}</div>
<span className="pseudolink" onClick={(e) => { setOpen(false) }}>
<b>{' '.repeat(pad)}{"}"}</b>
</span>
</p>
)
}
JsonList
類似 JsonDict
。
function JsonList({ val, pad }: { val: { [key: string]: any }, pad: number }) {
const [open, setOpen] = useState(true)
if (!open) {
return <p style={{ display: 'inline' }}>
<span className="pseudolink" onClick={(e) => { setOpen(true) }}>
<b>{"[ ... ]"}</b>
</span>
</p>
}
return (
<p style={{ display: 'inline' }}>
<span className="pseudolink" onClick={(e) => { setOpen(false) }}>
<b>{"["}</b>
</span>
<div>{
val.map((value: any) => (
<div>{' '.repeat(pad + 2)}<JsonValue val={value} pad={pad + 2} />,</div>
))
}</div>
<span className="pseudolink" onClick={(e) => { setOpen(false) }}>
<b>{' '.repeat(pad)}{"]"}</b>
</span>
</p>
)
}