Как обрабатывать повторяющиеся похожие типы в TypeScript

#reactjs #typescript #types #context-api

Вопрос:

В упрощенной версии моего приложения я получаю комментарии с сервера, и каждый комментарий имеет массив ответов в качестве свойства. Я хочу нормализовать свое состояние, чтобы у меня были комментарии без ответов как одна часть состояния и ответы как вторая часть состояния.

Когда я создаю комментарий, я отправляю только author и. body Когда я получаю комментарий от сервера, я получаю дополнительные свойства вместе с replies массивом и в состоянии, в котором я хочу иметь комментарии без ответов.

Потому что, если это в моем types.ts файле, у меня есть 4 разных интерфейса. Один предназначен для нового комментария, другой-для комментария без ответов, который переходит в состояние, третий-для всего комментария, полученного с сервера, и четвертый-только для ответа.

 export interface NewComment {
  author: string,
  body: string
}

export interface CommentWithoutReplies {
  id: string,
  author: string,
  body: string,
  postedAt: number,
  replies_count: number,
}

export interface Comment {
  id: string,
  author: string,
  body: string,
  postedAt: number,
  replies_count: number,
  replies: Reply[]
}

export interface Reply {
  id: string
  comment_id: string,
  author: string,
  body: string,
  postedAt: number,
}
 

Я использую создание файла контекста комментария, который теперь выглядит так и проходит проверку:

 import { createContext, useCallback, useState, FC } from "react";
import {Comment, CommentWithoutReplies, CommentContextState, CommentContextDispatch} from "../types/types"

const defaultStateValue: CommentContextState = {
  comments: [],
};
const defaultDispatchValue: CommentContextDispatch = {
  getComments: () => undefined,
  addComment: () => undefined,
};

export const CommentStateContext = createContext<CommentContextState>(defaultStateValue);
export const CommentDispatchContext = createContext<CommentContextDispatch>(defaultDispatchValue);

const CommentProvider: FC = ({children}) => {
  const [comments, setComments] = useState<CommentWithoutReplies[]>(defaultStateValue.comments);

  const getComments = useCallback(
    (data: Comment[]) => {
      setComments(() => {
        return data.map(comment => {
          const {replies, ...commentWithoutReplies} = comment;
          return commentWithoutReplies
        })
      });
    },
    [setComments]
  );

  const addComment = useCallback(
    (data: CommentWithoutReplies) => {
      setComments((currentComments: CommentWithoutReplies[]) => {
        return [...currentComments, data];
      });
    },
    [setComments]
  );

  return (
    <CommentDispatchContext.Provider
      value={{
        getComments,
        addComment,
      }}
    >
      <CommentStateContext.Provider
        value={{
          comments,
        }}
      >
        {children}
      </CommentStateContext.Provider>
    </CommentDispatchContext.Provider>
  );
};

export default CommentProvider;
 

Есть ли более изящный способ обработки этих типов, чем иметь 4 разных интерфейса?

Ответ №1:

То, что вы сейчас делаете, кажется мне вполне разумным — вы используете несколько отдельных форм данных, поэтому в большинстве случаев вам нужно где-то определить каждую из этих форм. Тем не менее, вы можете сделать вещи менее повторяющимися, не отмечая типы, когда в этом нет необходимости, например, это

 setComments((currentComments: CommentWithoutReplies[]) => {
 

может быть

 setComments((currentComments) => {
 

и вы можете определить типы более кратко, начав с базового типа, который содержит все общие свойства — id , author , postedAt .

 type BaseComment {
    id: string,
    author: string,
    postedAt: number,
}
export type CommentWithoutReplies = BaseComment amp; {
    body: string,
    replies_count: number,
}
export type Comment = BaseComment amp; {
    body: string,
    replies_count: number,
    replies: Reply[]
}
export type Reply = BaseComment amp; {
    comment_id: string,
    body: string,
}
 

Не знаю, какой у вас есть другой код, но если типы здесь не импортируются в другое место, вы также можете определить их в самом файле контекста вместо того, чтобы импортировать их.