前端笔试题

2023-08-31 | 28分钟 | yrobot | FE, 前端, 笔试题

用户中心实现 UserCenter + hooks

考察:ts 基础 + react hooks + async/await + class + 解决业务问题能力

题面

要实现一个 React 聊天页面,页面很多地方(聊天列表、chat room navbar、对话列表)等都需要用到用户信息展示的场景。用 ts 实现一个 UserCenter 的 class,他提供根据 user id 返回 user info 的方法。用户的数据通过 API 请求获取,API 接口支持 传入 id 列表。UserCenter 可以对用户数据 进行 统一获取、缓存和更新。

一些信息:

// type schema
interface User {
  id: string;
  name: string;
  avatar: string;
}

// API
const fetchUsers = async (ids: string[]): Promise<User[]> => [];

需要实现:

// 1. UserCenter class:
//   - 避免过度请求(避免重复请求同一 id 的数据,避免300ms内发起多个请求)
class UserCenter {
  constructor() {}
  async getUser(id: string): Promise<User> {}
  // ...
}
// 2. react 组件内使用
//   - const {user,loading} = useUser(id)
function useUser(id: string): {
  user: User | null;
  loading: boolean;
};

参考答案

interface User {
  id: string;
  name: string;
  avatar: string;
}

const fetchUsers = async (ids: string[]): Promise<User[]> => [];
class UserCenter {
  userCache = new Map<string, User>();
  waitList: string[] = [];
  fetchingList: string[] = [];
  callBacks: Map<string, Function[]> = new Map();

  private async fetchList() {
    if (this.waitList.length === 0) return;
    this.fetchingList = [...this.fetchingList, ...this.waitList];
    fetchUsers(this.waitList).then((users) => {
      users.forEach((user) => {
        this.userCache.set(user.id, user);
        this.callBacks.get(user.id)?.forEach((cb) => cb(user));
        this.callBacks.delete(user.id);
      });
      this.fetchingList = this.fetchingList.filter(
        (id) => !this.userCache.has(id)
      );
    });
    this.waitList = [];
  }

  private async fetchUser(id: string): Promise<User> {
    if (!this.waitList.includes(id) && !this.fetchingList.includes(id))
      this.waitList.push(id);
    return new Promise((resolve) => {
      this.callBacks.set(id, [...(this.callBacks.get(id) || []), resolve]);
    });
  }

  constructor() {
    setInterval(this.fetchList, 300);
  }

  async getUser(id: string) {
    if (this.userCache.has(id)) return this.userCache.get(id);
    return this.fetchUser(id);
  }
}

const userCenter = new UserCenter();

const useUser = (
  id: string
): {
  user: User | null;
  loading: boolean;
} => {
  const [user, setUser] = useState<User>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    setLoading(true);
    userCenter.getUser(id).then((user) => {
      setUser(user);
      setLoading(false);
    });
  }, [id]);

  return {
    user,
    loading,
  };
};