有两个数组,一个是用户发送的消息,另一个是用户列表。每条消息都有一个通过userId字段指向作者。
const messages = [
{
id: "message-1",
text: "Hey folks!",
userId: "user-1"
},
{
id: "message-2",
text: "Hi",
userId: "user-2"
},
...
];
const users = [
{
id: "user-1",
name: "Paul"
},
{
id: "user-2",
name: "Lisa"
},
...
];
当我们渲染信息的时候,需要正确的找到用户。以React为例,我们一般会这么写:
messages.map(({ id, text, userId }) => {
const name = users.find((user) => user.id === userId).name;
return (
<div key={id}>
<div>{text}</div>
<div>{name}</div>
</div>
)
}
但是这些写性能并不理想。因为每条信息都会循环用户数组。怎样才能避免呢?如果有个map形式的数据结构,我们只需要从map中取值就行,不需要再循环用户数组。像这样:
const names = users.reduce((preUser, user) => ({...preUser, [user.id]: user.name}), {})
messages.map(({ id, text, userId }) => {
return (
<div key={id}>
<div>{text}</div>
<div>{names[userId]}</div>
</div>
)
}
使用Map代替reduce:
const names = new Map(users.map(({ id, name }) => [id, name]));
messages.map(({ id, text, userId }) => (
<div key={id}>
<div>{text}</div>
<div>{names.get(userId)}</div>
</div>
));
当用户选择一项时,我们通过将其id添加到一个集合中。当用户取消选择时,我们再次将其从集合中删除。此时使用Set非常方便。
import { useState } from "react";
const rows = [
{
id: "id-1",
name: "Row 1"
},
{
id: "id-2",
name: "Row 2"
},
{
id: "id-3",
name: "Row 3"
},
{
id: "id-4",
name: "Row 4"
},
{
id: "id-5",
name: "Row 5"
},
{
id: "id-6",
name: "Row 6"
}
];
function TableUsingSet() {
const [selectedIds, setSelectedIds] = useState(new Set());
const handleOnChange = (id) => {
const updatedIdToSelected = new Set(selectedIds);
if (updatedIdToSelected.has(id)) {
updatedIdToSelected.delete(id);
} else {
updatedIdToSelected.add(id);
}
setSelectedIds(updatedIdToSelected);
};
return (
<table>
<tbody>
{rows.map(({ id, name }) => (
<tr key={id}>
<td>
<input
type="checkbox"
checked={selectedIds.has(id)}
onChange={() => handleOnChange(id)}
/>
</td>
<td>{id}</td>
<td>{name}</td>
</tr>
))}
</tbody>
</table>
);
}
栈有两个特点:"先进后出"
- 只能向栈顶添加元素
- 只能删除栈顶元素
const stack = [];
// 进栈
stack.push('1');
// 出栈
stack.pop()
一个撤销删除功能:
每当用户从表中删除一行时,我们将其添加到历史数组(栈)。当用户想要撤消删除时,我们从历史记录中获取最新一行并将其重新添加到表中。
:::info
注意:我们不能只使用Array.push()和Array.pop(),因为React需要不可变数据。我们使用Array.concat()和Array.slice(),因为它们都返回新的数组。
:::
import { useReducer } from "react";
const rows = [
{
id: "id-1",
name: "Row 1"
},
{
id: "id-2",
name: "Row 2"
},
{
id: "id-3",
name: "Row 3"
},
{
id: "id-4",
name: "Row 4"
},
{
id: "id-5",
name: "Row 5"
},
{
id: "id-6",
name: "Row 6"
}
];
function removeRow(state, action) {
return state.rows.filter(({ id }) => id !== action.id)
}
function addRowAtOriginalIndex(state) {
const undo = state.history[state.history.length - 1];
return [
...state.rows.slice(0, undo.action.index),
undo.row,
...state.rows.slice(undo.action.index)
]
}
const initialState = { rows, history: [] };
function reducer(state, action) {
switch (action.type) {
case "remove":
return {
rows: removeRow(state, action),
history: state.history.concat({
action,
row: state.rows[action.index]
})
};
case "undo":
return {
rows: addRowAtOriginalIndex(state),
history: state.history.slice(0, -1)
};
default:
throw new Error();
}
}
function TableUsingStack() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<button
onClick={() => dispatch({ type: "undo" })}
disabled={state.history.length === 0}
>
Undo Last Action
</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
{state.rows.map(({ id, name }, index) => (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>
<button onClick={() => dispatch({ type: "remove", id, index })}>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
</>
);
}
export default TableUsingStack;
队列的特点:“先进先出”
const queueu = [];
queueu.push('1');
queueu.shift()
const menuItems = [
{
text: "Menu 1",
children: [
{
text: "Menu 1 1",
href: "#11",
},
{
text: "Menu 1 2",
href: "#12",
},
],
},
{
text: "Menu 2",
href: "#2",
},
{
text: "Menu 3",
children: [
{
text: "Menu 3 1",
children: [
{
id: "311",
text: "Menu 3 1 1",
href: "#311",
},
],
},
],
},
];
function Menu({ items }) {
return (
<ul>
{items.map((item, index) => (
<MenuItem key={index} {...item} />
))}
</ul>
);
}
function MenuItem({ text, href, children }) {
if (!children) {
return (
<li>
<a href={href}>{text}</a>
</li>
);
}
return (
<li>
{text}
<Menu items={children} />
</li>
);
}
// 根组件
function NestedMenu() {
return <Menu items={menuItems} />;
}
嵌套型的树结构,虽然代码容易阅读,但是想要更新数据时,就会很麻烦。后端处理起来比较麻烦,他们一般返回给前端是扁平的数组。
const menuItems = [
{
id: "1",
text: "Menu 1",
children: ["11", "12"],
isRoot: true,
},
{
id: "11",
text: "Menu 1 1",
href: "#11",
},
{
id: "12",
text: "Menu 1 2",
href: "#12",
},
{
id: "2",
text: "Menu 2",
href: "#2",
isRoot: true,
},
{
id: "3",
text: "Menu 3",
children: ["31"],
isRoot: true,
},
{
id: "31",
text: "Menu 3 1",
children: ["311"],
},
{
id: "311",
text: "Menu 3 1 1",
href: "#311",
},
];
function Menu({ itemIds, itemsById }) {
return (
<ul>
{itemIds.map((id) => (
<MenuItem key={id} itemId={id} itemsById={itemsById} />
))}
</ul>
);
}
function MenuItem({ itemId, itemsById }) {
const item = itemsById[itemId];
if (!item.children) {
return (
<li>
<a href={item.href}>{item.text}</a>
</li>
);
}
return (
<li>
{item.text}
<Menu itemIds={item.children} itemsById={itemsById} />
</li>
);
}
function NestedMenu() {
const itemsById = menuItems.reduce(
(prev, item) => ({ ...prev, [item.id]: item }),
{}
);
const rootIds = menuItems.filter(({ isRoot }) => isRoot).map(({ id }) => id);
return <Menu itemIds={rootIds} itemsById={itemsById} />;
}
https://codesandbox.io/s/javascript-data-structures-forked-v7mco5?file=/src/MapExample.jsx