Closed7

よくある Frontend Code(悪い実践のみ)

天海 はら / ThaddeusJiang天海 はら / ThaddeusJiang

TypeScript Partial 🙅

type Person = {
  surname: string;
  middleName?: string;
  givenName: string;
};
type PartialPerson = Partial<Person>;

Partial<T> はオープンソースライブラリでよく見かけますが、実際のアプリケーション開発で使ったことがありません。
Partial はすべてのプロパティを一括でオプショナルにするため、any とほぼ変わらず、せっかく定義した型の意味が薄れてしまいます。

Action:

自分の関わるプロジェクトに Partial が使われていないか確認し、できれば削除することをおすすめします。

refs:

天海 はら / ThaddeusJiang天海 はら / ThaddeusJiang

Prop Drilling 🙅

const { control, register, setValue, formState: { errors } } = useForm()

// 第一層
<TopComponent
    control={control}
    register={register}    
    setValue={setValue}
    errors={errors}
    onSubmit={onSubmit}
    ...
/>

// 第二層
<ComplexComponent
    control={control}
    register={register}
    setValue={setValue}
    errors={errors}
    ...
/>

// 第三層
<RadioField
    control={control}
    register={register}
    error={errors[names.city]}
    name={names.city}
    required={required}
    ...
/>

// 第四層
<Controller<T>
    control={control}
    name={name}
    render={({ field }) => (
        <Radio prefix={name} items={items} defaultValue={field.value} onValueChange={field.onChange} />
    )}
/>

複雑になるにつれて、propsの受け渡しの階層が深くなって、本当に面倒だ。

正解: React Context

import { FormProvider, useForm, useFormContext } from "react-hook-form"

export const App = () => {
  const methods = useForm()

  return (
    <FormProvider {...methods}>
      <form>
        // 第一層
        <TopComponent />
      </form>
    </FormProvider>
  )
}

// 第二層
<ComplexComponent />

// 第三層
<RadioField />

// 第四層
export const DeepNest = () => (
  const { control } = useFormContext()

  return (
    <Controller<T>
        control={control}
        name={name}
        render={({ field }) => (
            <Radio prefix={name} items={items} defaultValue={field.value} onValueChange={field.onChange} />
        )}
    />
  )  
)

refs:

天海 はら / ThaddeusJiang天海 はら / ThaddeusJiang

React state で search params を二次処理する ❌

export const Page = () => {
  const [searchParams, setSearchParams] = useSearchParams()

  // 二重管理
  const [params, setParams] = useState<Params>({
    likeSearchText: searchParams.get('likeSearchText') ?? undefined,
    status: (searchParams.get('status') as StatusValue) ?? undefined,
  })

  // 二重管理
  const [page, setPage] = useState<number>(searchParams.get('page') ? Number(searchParams.get('page')) : 1)
  const [per, setPer] = useState<number>(searchParams.get('per') ? Number(searchParams.get('per')) : 10)

  const handleOnChangePer = (v: string) => {
    setPage(1) // 二重管理
    setPer(Number(v)) // 二重管理
    setSearchParams(...)
  }

  const handleChangePage = (page: number) => {
    setPage(page) // 二重管理
    setSearchParams(...)
  }

  const onSearch = (v) => {
    setParams(...) // 二重管理
    setSearchParams(...)
  }  

おすすめ

const [searchParams, setSearchParams] = useSearchParams()

const page = searchParams.get('page') ? Number(searchParams.get('page')) : 1
const per = searchParams.get('per') ? Number(searchParams.get('per')) : 20
const likeSearchText = searchParams.get('likeSearchText') ?? undefined
const status = (searchParams.get('campaignStatus') as StatusValue) ?? undefined

const handleOnChangePer = (v: string) => {    
    setSearchParams(...)
}
const handleChangePage = (page: number) => {
    setSearchParams(...)
}
const onSearch = (v) => {
    setSearchParams(...)
}  
天海 はら / ThaddeusJiang天海 はら / ThaddeusJiang

余計な useEffect ❌

export const Page: FC<Props> = ({ header, isActive }) => {
  const [value, setValue] = useState<string>('')

  useEffect(() => {
    setValue(isActive ? header : '')
  }, [isActive, header])

正解

export const Page: FC<Props> = ({ header, isActive }) => {

const [value, setValue] = useState<string>(isActive ? header : '')

天海 はら / ThaddeusJiang天海 はら / ThaddeusJiang

余計な useMemo ❌

  const {
    fieldState: { error: fromError },
  } = useController({
    control,
    name: from.name,
  })
  const fromErrorMessage = useMemo(() => {
    return fromError && 'message' in fromError ? fromError.message : undefined
  }, [fromError])

  const {
    fieldState: { error: toError },
  } = useController({
    control,
    name: to.name,
  })
  const toErrorMessage = useMemo(() => {
    return toError && 'message' in toError ? toError.message : undefined
  }, [toError])

return (<>
  <FormError errors={{ message: fromErrorMessage ?? '' }} />
  <FormError errors={{ message: toErrorMessage ?? '' }} />
</>)

正解

  const { errors } = useFormState({ control })

  return (<>
    <FormError errors={{ message: errors.from.message ?? '' }} />
    <FormError errors={{ message: errors.to.message ?? '' }} />
  </>)
天海 はら / ThaddeusJiang天海 はら / ThaddeusJiang

TypeScript 型定義の箇所に class を使ってる ❌

class IntegerItem { }
class StringItem { }
class DateItem { }

type Item = IntegerItem | DateItem | StringItem
class Account { }

function printAccountSummary(account: Account) { }

おすすめ:データはデータ、型は型

type IntegerItem 
type StringItem 
type DateItem 

type Item = IntegerItem | DateItem | StringItem
interface Account { }

function printAccountSummary(account: Account) { }
天海 はら / ThaddeusJiang天海 はら / ThaddeusJiang

TypeScript 配列専用の型を定義する 🙅

type SelectOption = { label: string; value: string }
export type SelectOptions = SelectOption[]

無用

export class ProductIdAndNameModel extends IdAndNameModel<
  'ProductIdAndNameModel',
  'ProductId',
  ProductId
> {}

export class ProductIdAndNameModels extends IdAndNameModels<
  'ProductIdAndNameModels',
  'ProductId',
  'ProductIdAndNameModel',
  ProductId,
  ProductIdAndNameModel
> {
  instantiate = (values: ProductIdAndNameModel[]): ProductIdAndNameModels => new ProductIdAndNameModels(values)
}

👆 ProductIdAndNameModels は複雑な定義されたが、実は ProductIdAndNameModel[]

このスクラップは25日前にクローズされました