Codride-bullectprof-react
구조
src
src
|
+-- assets フォルダには、画像やフォントなど、すべての静的ファイルを格納できます。
|
+-- components # アプリケーション全体で使用される共有コンポーネント
|
+-- config # すべてのグローバル設定、環境変数などは、ここからエクスポートされ、アプリで使用されます。
|
+-- features # 機能ベースのモジュール
|
+-- hooks # アプリケーション全体で使用される共有フック
|
+-- lib # アプリケーション用にあらかじめ設定されたさまざまなライブラリのファクトリ
|
+-- providers # アプリケーションの全プロバイダー
|
+-- routes # ルート設定
|
+-- stores # グローバルステートストア
|
+-- test # テスト用ユーティリティとモックサーバ
|
+-- types # アプリケーション全体で使用される基本型
|
+-- utils # 共有ユーティリティ関数
src/features
src/features/awesome-feature
|
+-- api # 特定の機能に関連するAPIリクエストの宣言とapiフックをエクスポート
|
+-- assets # assets フォルダには、特定の機能に関するすべての静的ファイルを格納することができます。
|
+-- components # 特定の機能にスコープされたコンポーネント
|
+-- hooks # 特定の機能に対応するフック
|
+-- routes # 特定のフィーチャーページのルートコンポーネント
|
+-- store # 特定の機能に関する状態ストア
|
+-- types # TS特定機能ドメイン用タイプスクリプトタイプ
|
+-- utils # 特定の機能に関するユーティリティ関数
|
+-- index.ts # 機能のエントリポイントであり、与えられた機能のパブリックAPIとして機能し、機能外で使用されるべきすべてのものをエクスポートする。
가장 간단하고 유지보수하기 쉬운 방법으로 프로그램을 확장하기 위해 대부분의 코드는features 폴더에 저장되며, 그 중에서 각종 기능 기반 코드를 포함한다.features 폴더에서 각 기능 기술역의 고유한 코드를 사용합니다.이렇게 하면 기능은 그 기능으로 한정되고 그 선언은 공유된 것과 혼합되지 않는다.이것은 많은 파일을 포함하는 평탄한 폴더 구조보다 유지하기 쉽다.기능 폴더는 다른 기능(부모 기능에서만 사용)을 포함하거나 분리할 수 있습니다.모든 기능은 해당 기능의 공개 API 작업의 index로 사용됩니다.ts 파일에서 내보내야 합니다.다른 기능에서만 가져오면 됩니다.
다음과 같이 사용한다
import {AwesomeComponent} from "@/features/awesome-feature"
이렇게 쓰면 안 돼요.
import {AwesomeComponent} from "@/features/awesome-feature/components/AwesomeComponent
https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md
설치 예
https://github.com/alan2207/bulletproof-react/tree/master/src/features/users
성능
코드 분할
https://github.com/alan2207/bulletproof-react/blob/master/src/routes/protected.tsx
import { Suspense } from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { Spinner } from '@/components/Elements';
import { MainLayout } from '@/components/Layout';
import { lazyImport } from '@/utils/lazyImport';
const { DiscussionsRoutes } = lazyImport(
() => import('@/features/discussions'),
'DiscussionsRoutes'
);
const { Dashboard } = lazyImport(() => import('@/features/misc'), 'Dashboard');
const { Profile } = lazyImport(() => import('@/features/users'), 'Profile');
const { Users } = lazyImport(() => import('@/features/users'), 'Users');
const App = () => {
return (
<MainLayout>
<Suspense
fallback={
<div className="h-full w-full flex items-center justify-center">
<Spinner size="xl" />
</div>
}
>
<Outlet />
</Suspense>
</MainLayout>
);
};
export const protectedRoutes = [
{
path: '/app',
element: <App />,
children: [
{ path: '/discussions/*', element: <DiscussionsRoutes /> },
{ path: '/users', element: <Users /> },
{ path: '/profile', element: <Profile /> },
{ path: '/', element: <Dashboard /> },
{ path: '*', element: <Navigate to="." /> },
],
},
];
https://github.com/alan2207/bulletproof-react/blob/master/src/utils/lazyImport.ts import * as React from 'react';
// named imports for React.lazy: https://github.com/facebook/react/issues/14603#issuecomment-726551598
export function lazyImport<
T extends React.ComponentType<any>,
I extends { [K2 in K]: T },
K extends keyof I
>(factory: () => Promise<I>, name: K): I {
return Object.create({
[name]: React.lazy(() => factory().then((module) => ({ default: module[name] }))),
});
}
// Usage
// const { Home } = lazyImport(() => import("./Home"), "Home");
읽으면 이 근원의 이해를 이해할 수 있을 것 같아요.이름 내보내기
React.lazy는 현재 기본 내보내기만 지원합니다.가져오려는 모듈의 이름을 사용하여 내보낼 경우 기본 방식으로 다시 내보낼 중간 모듈을 만들 수 있습니다.이렇게 하면 tree shaking이 역할을 발휘할 수 있으며 사용하지 않은 구성 요소를 가져올 필요가 없습니다.
나는 lazyImport 함수가 이름 가져오기 역할을 맡고 있다고 생각한다.
API 레이어
Use a single instance of the API client
프로그램이 RESTful API를 소모하든지GraphiQL API를 소모하든지 간에 우리는 사전에 설정하고 전체 응용 프로그램에서 다시 사용하는 API 클라이언트의 서명 실례를 준비할 것이다.예를 들어, 미리 설정된 single API 클라이언트 인스턴스가 있습니다.
설치 예
https://ja.reactjs.org/docs/code-splitting.html
Define and export request declarations
API 요청을 선언하는 대신 별도로 정의하여 내보냅니다.RESTful의 API인 경우 선언은 호출 끝점 추출 함수입니다.한편,GraphiQL API의 요청은react-query,apolo-client,urql 등 데이터로 라이브러리에서 사용할 수 있는 조회와 정음을 얻어 알립니다.따라서 정의된 단점과 응용 프로그램의 가용성을 추적하기 쉽다.또 데이터의 유형 안전성을 높이기 위해 응답을 분류해 추론할 수도 있다.또한 에서 적절한 API 후크를 정의하고 내보낼 수도 있습니다.
설치 예
https://github.com/alan2207/bulletproof-react/blob/master/src/lib/axios.ts
사용 예
https://github.com/alan2207/bulletproof-react/blob/master/src/features/discussions/api/getDiscussions.ts
자세 스테이션
추상화된 Form 구성 요소와 프로그램 라이브러리 기능을 랩하고 프로그램의 필요에 따라 모든 입력 필드 구성 요소를 제작합니다.이 구성 요소는 전체 응용 프로그램에서 다시 사용할 수 있습니다.
이 항목에서react-hook-form을 사용하는 예
창 구현 예
https://github.com/alan2207/bulletproof-react/blob/master/src/features/discussions/routes/Discussion.tsx#L14
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx';
import * as React from 'react';
import { useForm, UseFormReturn, SubmitHandler, UseFormProps } from 'react-hook-form';
import { ZodType, ZodTypeDef } from 'zod';
type FormProps<TFormValues, Schema> = {
className?: string;
onSubmit: SubmitHandler<TFormValues>;
children: (methods: UseFormReturn<TFormValues>) => React.ReactNode;
options?: UseFormProps<TFormValues>;
id?: string;
schema?: Schema;
};
// TFormValuesには{title: string, body: string} のような形でくる
// Schemaにはtypeof schemaの値がくる。
// const schema = z.object({
// title: z.string().min(1, 'Required'),
// body: z.string().min(1, 'Required'),
// });
// z.objectとはHTMLObjectElementとのことだが、そのtypeofの値が ZodType<unknown, ZodTypeDef, unknown> = ZodType<unknown, ZodTypeDef, unknown>になるとのことだがよくわからない。
export const Form = <
TFormValues extends Record<string, unknown> = Record<string, unknown>,
Schema extends ZodType<unknown, ZodTypeDef, unknown> = ZodType<unknown, ZodTypeDef, unknown>
>({
onSubmit,
children,
className,
options,
id,
schema,
}: FormProps<TFormValues, Schema>) => {
const methods = useForm<TFormValues>({ ...options, resolver: schema && zodResolver(schema) });
return (
<form
className={clsx('space-y-6', className)}
onSubmit={methods.handleSubmit(onSubmit)}
id={id}
>
{children(methods)}
</form>
);
};
입력 필드의 실현 예시https://github.com/alan2207/bulletproof-react/blob/master/src/components/Form/Form.tsx
사용 예
https://github.com/alan2207/bulletproof-react/blob/master/src/components/Form/InputField.tsx
import { PlusIcon } from '@heroicons/react/outline';
import * as z from 'zod';
import { Button } from '@/components/Elements';
import { Form, FormDrawer, InputField, TextAreaField } from '@/components/Form';
import { Authorization, ROLES } from '@/lib/authorization';
import { CreateDiscussionDTO, useCreateDiscussion } from '../api/createDiscussion';
// type CreateDiscussionDTO = {
// data: {
// title: string;
// body: string;
// };
// };
const schema = z.object({
title: z.string().min(1, 'Required'),
body: z.string().min(1, 'Required'),
});
export const CreateDiscussion = () => {
const createDiscussionMutation = useCreateDiscussion();
return (
<Authorization allowedRoles={[ROLES.ADMIN]}>
...
<Form<CreateDiscussionDTO['data'], typeof schema>
id="create-discussion"
onSubmit={async (values) => {
await createDiscussionMutation.mutateAsync({ data: values });
}}
schema={schema}
>
{({ register, formState }) => (
<>
<InputField
label="Title"
error={formState.errors['title']}
registration={register('title')}
/>
<TextAreaField
label="Body"
error={formState.errors['body']}
registration={register('body')}
/>
</>
)}
</Form>
</FormDrawer>
</Authorization>
);
};
@NOTE: 잘 모르겠어요.자신의 필기children: (methods: UseFormReturn<TFormValues>) => React.ReactNode;
Schema extends ZodType<unknown, ZodTypeDef, unknown> = ZodType<unknown, ZodTypeDef, unknown>
구성 요소
제3자의 구성 요소도 응용 프로그램의 수요에 따라 랩을 진행할 수 있다.앞으로 응용 프로그램의 기능에 영향을 주지 않는 상황에서 근본적인 변경이 쉬울 것이다.
https://github.com/alan2207/bulletproof-react/blob/master/src/features/discussions/components/CreateDiscussion.tsx
import clsx from 'clsx';
import { Link as RouterLink, LinkProps } from 'react-router-dom';
export const Link = ({ className, children, ...props }: LinkProps) => {
return (
<RouterLink className={clsx('text-indigo-600 hover:text-indigo-900', className)} {...props}>
{children}
</RouterLink>
);
};
대규모 프로젝트 중에는 모든 공유 구성 요소 주변에 추상화된 것을 구축하는 것이 가장 좋다.이렇게 하면 응용 프로그램이 일치성을 가지게 되고 유지보수도 쉬워진다.오류의 추상화를 피하기 위해 구성 요소를 만들기 전에 중복을 확인합니다이 프로젝트는 버튼, 다이얼로그, 드라워, 링크, 마크다운 프리뷰, 스핀너, 테이블을 공유 구성 요소로 구현한다.
버튼 설치 예
https://github.com/alan2207/bulletproof-react/blob/master/src/components/Elements/Link/Link.tsx
...
// buttonタグがもともと使用できるプロパティを引き継ぎながら、独自のプロパティを追加して型を定義。
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: keyof typeof variants;
size?: keyof typeof sizes;
isLoading?: boolean;
} & IconProps;
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
type = 'button',
className = '',
variant = 'primary',
size = 'md',
isLoading = false,
startIcon,
endIcon,
...props
},
ref
) => {
return (
<button
ref={ref}
type={type}
className={clsx(
'flex justify-center items-center border border-gray-300 disabled:opacity-70 disabled:cursor-not-allowed rounded-md shadow-sm font-medium focus:outline-none',
variants[variant],
sizes[size],
className
)}
{...props}
>
{isLoading && <Spinner size="sm" className="text-current" />}
{!isLoading && startIcon}
<span className="mx-2">{props.children}</span> {!isLoading && endIcon}
</button>
);
}
);
Button.displayName = 'Button';
React.forwardRef를 사용하면 버튼의 한쪽에서ref를 통해dom 노드에 접근할 수 있습니다.const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
引用: https://ja.reactjs.org/docs/forwarding-refs.html
이 항목의ref 사용 예시초점 맞추기
// src/components/Elements/ConfirmationDialog/ConfirmationDialog.tsx
...
const cancelButtonRef = React.useRef(null);
...
<Dialog isOpen={isOpen} onClose={close} initialFocus={cancelButtonRef}>
...
<Button
type="button"
variant="inverse"
className="w-full inline-flex justify-center rounded-md border focus:ring-1 focus:ring-offset-1 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm"
onClick={close}
ref={cancelButtonRef}
>
{cancelButtonText}
</Button>
...
// src/components/Elements/Dialog/Dialog.tsx
export const Dialog = ({ isOpen, onClose, children, initialFocus }: DialogProps) => {
return (
<>
<Transition.Root show={isOpen} as={React.Fragment}>
<UIDialog
as="div"
static
className="fixed z-10 inset-0 overflow-y-auto"
open={isOpen}
onClose={onClose}
initialFocus={initialFocus} // フォーカス
코드 스타일
아직 못 읽었어요.
Clean Code
https://github.com/alan2207/bulletproof-react/blob/master/src/components/Elements/Button/Button.tsx
https://github.com/ryanmcdermott/clean-code-javascript
Naming
https://github.com/mitsuruog/clean-code-javascript/
보안
sanitize
import createDOMPurify from 'dompurify';
import marked from 'marked';
const DOMPurify = createDOMPurify(window);
export type MDPreviewProps = {
value: string;
};
export const MDPreview = ({ value = '' }: MDPreviewProps) => {
console.log(value) // 1
console.log(marked(value)) // 2
console.log(DOMPurify.sanitize(marked(value))) // 3
return (
<div
className="p-2 w-full prose prose-indigo"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(marked(value)),
}}
/>
);
};
1 출력# ディスカッション
## サブタイトル
* item 1
* item 2
<script>alert('discussions')</script>
2 출력<h1 id="ディスカッション">ディスカッション</h1>
<h2 id="サブタイトル">サブタイトル</h2>
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
<script>alert('discussions')</script>
3의 출력<h1 id="ディスカッション">ディスカッション</h1>
<h2 id="サブタイトル">サブタイトル</h2>
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
인증
본 프로젝트의 예시에서 아래 모듈을 사용하다
https://github.com/kettanaito/naming-cheatsheet
react-query
다음 함수를 실현하여 인증 기능을 실현하다
react-query-auth
사용 예
https://github.com/alan2207/bulletproof-react/blob/master/src/lib/auth.tsx
export const AppRoutes = () => {
const auth = useAuth();
const commonRoutes = [{ path: '/', element: <Landing /> }];
const routes = auth.user ? protectedRoutes : publicRoutes;
const element = useRoutes([...routes, ...commonRoutes]);
return <>{element}</>;
};
승인
설치 예
https://github.com/alan2207/bulletproof-react/blob/master/src/routes/index.tsx
https://github.com/alan2207/bulletproof-react/blob/master/src/lib/authorization.tsx
https://github.com/alan2207/bulletproof-react/blob/master/src/features/discussions/components/CreateDiscussion.tsx
Role based access control (RBAC)
자원에 대한 권한 수여 역할을 정의하고 사용자가 자원에 접근할 수 있는 역할을 하는지 확인합니다.좋은 예는 USER와 ADMIN의 캐릭터다.사용자의 일부 내용을 제한하고 관리자의 접근을 허용합니다.
사용 예
<Authorization allowedRoles={[ROLES.ADMIN]}>
...
</Authorization>
Permission based access control (PBAC)
RBAC만으로는 부족한 경우도 있다.일부 작업은 자원의 소유자만 허용합니다.예를 들어, 사용자의 의견입니다.주석을 작성한 사람만 주석을 삭제할 수 있습니다.따라서 보다 유연한 PBAC를 사용하는 것이 좋습니다.
RBAC의 보호는 허용된 역할을 RBAC 구성 요소에 전달하여 사용할 수 있습니다.다른 한편, 더욱 엄격한 보호가 필요하다면 정책 검사를 제출할 수 있다.
사용 예
<Authorization policyCheck={POLICIES['comment:delete'](user as User, comment)}>
...
</Authorization>
Reference
이 문제에 관하여(Codride-bullectprof-react), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/ta_toshio/articles/54967a2990174d텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)