数据结构在前端中的应用

lxf2023-03-16 10:13:01

1.Map

有两个数组,一个是用户发送的消息,另一个是用户列表。每条消息都有一个通过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>
));

2.Set

数据结构在前端中的应用

当用户选择一项时,我们通过将其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>
  );
}

3.stack

栈有两个特点:"先进后出"

  • 只能向栈顶添加元素
  • 只能删除栈顶元素
const stack = [];

// 进栈
stack.push('1');

// 出栈
stack.pop()

一个撤销删除功能:

每当用户从表中删除一行时,我们将其添加到历史数组()。当用户想要撤消删除时,我们从历史记录中获取最新一行并将其重新添加到表中。

数据结构在前端中的应用

注意:我们不能只使用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;

4.queue

队列的特点:“先进先出”

const queueu = [];

queueu.push('1');

queueu.shift()

常用的通知组件

数据结构在前端中的应用

5.tree

嵌套型

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} />;
}

reference:

codesandbox.io/s/javascrip…