<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React + TypeScript 常用類型匯總

          共 45419字,需瀏覽 91分鐘

           ·

          2022-06-30 15:05

          本文適合對TypeScript感興趣的小伙伴閱讀~

          歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~

          作者:廣東靚仔

          一、前言

          在React項目開發(fā)中,寫出優(yōu)雅的、更有意義的typescript代碼,是我們一直追求的。本文廣東靚仔帶小伙伴們一起來看看React項目實際開發(fā)中用到的一些常用類型示例。
          目錄搶先看:
          • 基本prop類型示例
          • 有用的 React Prop 類型示例
          • 函數(shù)組件
          • 類組件
          • form和event
          • Context
          • forwardRef/createRef
          • 有用的hooks
          • HOC
          • Linting

          二、基本prop類型示例

          常規(guī)的程序中使用的 TypeScript 類型列表:

          type AppProps = {
            message: string;
            count: number;
            disabled: boolean;
           /** 一個類型的數(shù)組!*/
            names: string[];
            /** 用于指定精確字符串值的字符串文字,使用聯(lián)合類型將它們連接在一起 */
            status: "waiting" | "success";
            /** 任何對象,只要你不使用它的屬性(不常見,但用作占位符)*/
            obj: object;
            obj2: {}; // 和 `object` 差不多,和 `Object` 完全一樣  
            /** 具有任意數(shù)量屬性的對象 (PREFERRED) */
            obj3: {
              id: string;
              title: string;
            };
            /** 對象數(shù)組!(常見的) */
            objArr: {
              id: string;
              title: string;
            }[];
            /** 具有任意數(shù)量的相同類型屬性的 dict 對象 */
            dict1: {
              [key: string]: MyTypeHere;
            };
            dict2: Record<string, MyTypeHere>; // 相當于 dict1   
            /** 任何函數(shù),只要你不調(diào)用它(不推薦) */
            onSomething: Function;
             /** 不接受或不返回任何內(nèi)容的函數(shù)(非常常見) */
            onClick: () => void;
             /** 帶有命名props的函數(shù)(非常常見) */
            onChange: (id: number) => void;
             /** 接受事件的函數(shù)類型語法(非常常見) */
            onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
            /** 接受事件的替代函數(shù)類型語法(非常常見) */
            onClick(event: React.MouseEvent<HTMLButtonElement>): void;
            /** 一個可選的props(非常常見!) */
            optional?: OptionalType;
          };

          三、有用的 React Prop 類型示例

          export declare interface AppProps {
            children?: React.ReactNode; // 最好,接受 React 可以渲染的所有內(nèi)容  
            childrenElement: JSX.Element; // 單個 React 元素  
            style?: React.CSSProperties; // 傳遞樣式props 
            onChange?: React.FormEventHandler<HTMLInputElement>; // 形成事件!泛型參數(shù)是 event.target 的類型  
            props: Props & React.ComponentPropsWithoutRef<"button">; // 模擬按鈕元素的所有 props 并明確不轉(zhuǎn)發(fā)其 ref    
            props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // 模擬 MyButtonForwardedRef 的所有 props 并顯式轉(zhuǎn)發(fā)其 ref
          }


          type還是interface?

          這是一個有用的經(jīng)驗法則:

          在創(chuàng)作庫或第 3 方環(huán)境類型定義時,始終用于公共 API 的定義,因為這允許使用者在缺少某些定義時通過聲明合并來擴展它們。

          考慮為您的 React 組件 Props 和 State 使用,以保持一致性并且因為它受到更多限制。

          四、函數(shù)組件

          這些可以寫成普通函數(shù),接受一個props參數(shù)并返回一個 JSX 元素。
          type AppProps = {
            message: string;
          }; /* 如果導(dǎo)出使用 `interface` 以便消費者可以擴展 */ 
          // 聲明函數(shù)組件的最簡單方法;推斷返回類型。
          const App = ({ message }: AppProps) => <div>{message}</div>;
          // 您可以選擇注釋返回類型,這樣如果您不小心返回了其他類型,則會引發(fā)錯誤
          const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;
          // 你也可以內(nèi)聯(lián)類型聲明;消除了命名props類型,但看起來重復(fù)
          const App = ({ message }: { message: string }) => <div>{message}</div>;

          hook

          useState
          類型推斷對于簡單值非常有效:
          const [state, setState] = useState(false);
          // `state` 被推斷為布爾值
          // `setState` 只接受布爾值

          許多鉤子都是用 null-ish 默認值初始化的,你可能想知道如何提供類型。

          顯式聲明類型,并使用聯(lián)合類型:

          const [user, setUser] = useState<User | null>(null);
          setUser(newUser);
          如果狀態(tài)在設(shè)置后不久初始化并且始終具有以下值,還可以使用類型斷言:
          const [user, setUser] = useState<User>({} as User);
          setUser(newUser);
          useReducer
          您可以將有區(qū)別的聯(lián)合用于 reducer 操作。不要忘記定義reducer的返回類型,否則TypeScript會推斷出來。
          import { useReducer } from "react";

          const initialState = { count0 };

          type ACTIONTYPE =
            | { type"increment"; payload: number }
            | { type"decrement"; payload: string };

          function reducer(state: typeof initialState, action: ACTIONTYPE{
            switch (action.type) {
              case "increment":
                return { count: state.count + action.payload };
              case "decrement":
                return { count: state.count - Number(action.payload) };
              default:
                throw new Error();
            }
          }

          function Counter() {
            const [state, dispatch] = useReducer(reducer, initialState);
            return (
              <>
                Count: {state.count}
                <button onClick={() => dispatch({ type: "decrement", payload: "5" })}>
                  -
                </button>
                <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
                  +
                </button>
              </>

            );
          }

          useEffect / useLayoutEffect

          useEffect和都useLayoutEffect用于執(zhí)行副作用并返回一個可選的清理函數(shù),這意味著如果它們不處理返回值,則不需要類型。

          使用 時useEffect,注意不要返回除函數(shù) or 以外的任何東西undefined,否則 TypeScript 和 React 都會提示你。

          這在使用箭頭函數(shù)時可能很微妙:

          function DelayedEffect(props: { timerMs: number }{
            const { timerMs } = props;

            useEffect(
              () =>
                setTimeout(() => {
                  /* do stuff */
                }, timerMs),
              [timerMs]
            );
            // 反面例子!setTimeout 隱式返回一個數(shù)字
            // 因為箭頭函數(shù)體沒有用大括號括起來
            return null;
          }

          useRef

          在 TypeScript 中,返回一個只讀或可變useRef的引用,取決于您的類型參數(shù)是否完全覆蓋初始值。選擇一個適合您的用例。

          1、DOM 元素 ref 
          訪問 DOM 元素:

          僅提供元素類型作為參數(shù),并null用作初始值。.current在這種情況下,返回的引用將具有由 React 管理的只讀引用TypeScript 期望將此 ref 提供給元素的ref prop:

          function Foo() {
            // - 如果可能,請盡可能具體。例如,HTMLDivElement
            // 比 HTMLElement 好,也比 Element 好得多。
            // - 從技術(shù)上講,這會返回 RefObject<HTMLDivElement>
            const divRef = useRef<HTMLDivElement>(null);

            useEffect(() => {
              // 注意 ref.current 可能為空。這是意料之中的
              // 有條件地渲染被引用的元素,或者你可能忘記分配它
              if (!divRef.current) throw Error("divRef is not assigned");

              // 現(xiàn)在 divRef.current 肯定是 HTMLDivElement
              doSomethingWith(divRef.current);
            });
             // 將 ref 賦予一個元素,以便 React 可以管理它
            return <div ref={divRef}>etc</div>;
          }
          如果確定divRef.current永遠不會為空,也可以使用非空斷言運算符!:
          const divRef = useRef<HTMLDivElement>(null!);
          // 無需檢查是否為空
          doSomethingWith(divRef.current);
          2、可變值 ref 
          要具有可變值:提供您想要的類型,并確保初始值完全屬于該類型:
          function Foo() {
            // 從技術(shù)上講,這將返回 MutableRefObject<number | 空>
            const intervalRef = useRef<number | null>(null);

            // 你自己管理 ref(這就是為什么它被稱為 MutableRefObject!)
            useEffect(() => {
              intervalRef.current = setInterval(...);
              return () => clearInterval(intervalRef.current);
            }, []);

            // ref 不會傳遞給任何元素的 "ref" 屬性
            return <button onClick={/* clearInterval the ref */}>Cancel timer</button>;
          }

          自定義hook
          如果你在自定義 Hook 中返回一個數(shù)組,你會想要避免類型推斷,因為 TypeScript 會推斷一個聯(lián)合類型(當你實際上想要在數(shù)組的每個位置使用不同的類型時)
          import { useState } from "react";
          export function useLoading() {
            const [isLoading, setState] = useState(false);
            const load = (aPromise: Promise<any>) => {
              setState(true);
              return aPromise.finally(() => setState(false));
            };
            return [isLoading, load] as const;  // 推斷 [boolean, typeof load] 而不是 (boolean | typeof load)[]
          }

          五、類組件

          在TypeScript 中,React.Component是一個泛型類型(aka React.Component<PropType, StateType>),因此希望為它提供(可選)prop 和 state 類型參數(shù):
          type MyProps = {
            // 使用 `interface` 也可以
            message: string;
          };

          type MyState = {
            count: number; // 像這樣  
          };

          class App extends React.Component<MyPropsMyState{
            state: MyState = {
              // 可選的第二個注解,用于更好的類型推斷
              count0,
            };
            render() {
              return (
                <div>
                  {this.props.message} {this.state.count}
                </div>

              );
            }

          }
          Tips: 可以導(dǎo)出/導(dǎo)入/擴展這些類型/接口以供重用。
          類方法:像往常一樣做,要記住函數(shù)的任何參數(shù)也需要輸入:
          class App extends React.Component<{ message: string }, { count: number }> {
            state = { count0 };
            render() {
              return (
                <div onClick={() => this.increment(1)}>
                  {this.props.message} {this.state.count}
                </div>

              );
            }
            increment = (amt: number) => {
              this.setState((state) => ({
                count: state.count + amt,
              }));
            };
          }
          類屬性:如果需要聲明類屬性以供以后使用,只需將其聲明為state,但無需賦值:
          class App extends React.Component<{
            message: string;
          }> {
            pointer: number; // 像這樣
            componentDidMount() {
              this.pointer = 3;
            }
            render() {
              return (
                <div>
                  {this.props.message} and {this.pointer}
                </div>

              );
            }
          }

          getDerivedStateFromProps

          派生狀態(tài)可以使用鉤子來實現(xiàn),這也可以幫助設(shè)置memoization。

          以下是可以注釋的幾種方法getDerivedStateFromProps
          1、如果已顯式鍵入派生狀態(tài)并希望確保 from 的返回值getDerivedStateFromProps符合它。
          class Comp extends React.Component<PropsState{
            static getDerivedStateFromProps(
              props: Props,
              state: State
            ): Partial<State> | null {
              //
            }
          }
          2、希望函數(shù)的返回值確定的狀態(tài)時。
          class Comp extends React.Component<
            Props,
            ReturnType<typeof Comp["getDerivedStateFromProps"]>

          {
            static getDerivedStateFromProps(props: Props) {}

          }
          3、想要具有其他狀態(tài)字段和記憶的派生狀態(tài)時
          type CustomValue = any;
          interface Props {
            propA: CustomValue;
          }
          interface DefinedState {
            otherStateField: string;
          }
          type State = DefinedState & ReturnType<typeof transformPropsToState>;
          function transformPropsToState(props: Props{
            return {
              savedPropA: props.propA, // 保存以備memoization
              derivedState: props.propA,
            };
          }
          class Comp extends React.PureComponent<PropsState{
            constructor(props: Props) {
              super(props);
              this.state = {
                otherStateField"123",
                ...transformPropsToState(props),
              };
            }
            static getDerivedStateFromProps(props: Props, state: State) {
              if (isEqual(props.propA, state.savedPropA)) return null;
              return transformPropsToState(props);
            }
          }

          六、form和event

          如果需要單獨定義事件處理程序,IDE 工具在這里真的很方便,因為 @type 定義帶有豐富的類型。輸入要查找的內(nèi)容,通常自動完成功能會為您提供幫助。onChange這是表單事件的樣子

          type State = {
            text: string;
          };

          class App extends React.Component<PropsState{
            state = {
              text"",
            };

             // 在 = 的右側(cè)輸入
            onChange = (e: React.FormEvent<HTMLInputElement>): void => {
              this.setState({ text: e.currentTarget.value });
            };
            render() {
              return (
                <div>
                  <input type="text" value={this.state.text} onChange={this.onChange} />
                </div>

              );
            }
          }
          React.FormEvent<>除了使用and鍵入?yún)?shù)和返回值void,您還可以將類型應(yīng)用于事件處理程序本身
          // 在 = 的左側(cè)輸入
            onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
              this.setState({text: e.currentTarget.value})
            }

          鍵入 onSubmit,在表單中包含不受控制的組件

          如果不太關(guān)心事件的類型,可以使用 React.SyntheticEvent。

          如果目標表單具有想要訪問的自定義命名輸入,可以使用類型斷言:

          <form
            ref={formRef}
            onSubmit={(e: React.SyntheticEvent) => {
              e.preventDefault();
              const target = e.target as typeof e.target & {
                email: { value: string };
                password: { value: string };
              };
              const email = target.email.value; // 類型檢查! 
              const password = target.password.value; // 類型檢查! 
              // ...
            }}
          >
            <div>
              <label>
                Email:
                <input type="email" name="email" />
              </label>
            </div>

            <div>
              <label>
                Password:
                <input type="password" name="password" />
              </label>
            </div>

            <div>
              <input type="submit" value="Log in" />
            </div>

          </form>

          事件類型列表

          七、Context

          基本示例
          import { createContext } from "react";

          interface AppContextInterface {
            name: string;
            author: string;
            url: string;
          }

          const AppCtx = createContext<AppContextInterface | null>(null);

          // 應(yīng)用程序中的提供程序

          const sampleAppContext: AppContextInterface = {
            name"Using React Context in a Typescript App",
            author"thehappybug",
            url"http://www.example.com",
          };

          export const App = () => (
            <AppCtx.Provider value={sampleAppContext}>...</AppCtx.Provider>
          );

          // 在你的應(yīng)用中使用
          import { useContext } from "react";

          export const PostInfo = () => {
            const appContext = useContext(AppCtx);
            return (
              <div>
                Name: {appContext.name}, Author: {appContext.author}, Url:{" "}
                {appContext.url}
              </div>

            );
          };
          擴展示例
          使用createContext空對象作為默認值
          interface ContextState {
            // 使用上下文設(shè)置你想要處理的狀態(tài)類型,例如
            name: string | null;
          }
          // 設(shè)置一個空對象為默認狀態(tài)
          const Context = createContext({} as ContextState);
          // 像在 JavaScript 中一樣設(shè)置上下文提供程序
          使用createContext 和 context getters來制作 a createCtx with no ,但無需檢查:
          import { createContext, useContext } from "react";

          const currentUserContext = createContext<string | undefined>(undefined);

          function EnthusasticGreeting() {
            const currentUser = useContext(currentUserContext);
            return <div>HELLO {currentUser!.toUpperCase()}!</div>;
          }

          function App() {
            return (
              <currentUserContext.Provider value="Anders">
                <EnthusasticGreeting />
              </currentUserContext.Provider>

            );
          }
          注意我們需要的顯式類型參數(shù),因為我們沒有默認string值:
          const currentUserContext = createContext<string | undefined>(undefined);
          //                                             ^^^^^^^^^^^^^^^^^^
          連同非空斷言告訴 TypeScript currentUser肯定會在那里:
          return <div>HELLO {currentUser!.toUpperCase()}!</div>;
          //      

          這是不幸的,因為我們知道稍后在我們的應(yīng)用程序中,a Provider將填充上下文。

          有幾個解決方案:

          1、可以通過斷言非空來解決這個問題:

          const currentUserContext = createContext<string>(undefined!);    
          2、我們可以編寫一個名為的輔助函數(shù)createCtx來防止訪問Context未提供值的 a。通過這樣做,API 相反,我們不必提供默認值,也不必檢查:
          import { createContext, useContext } from "react";

          /**
          * 創(chuàng)建上下文和提供者的助手,沒有預(yù)先的默認值,并且
          * 無需一直檢查未定義。
          */

          function createCtx<A extends {} | null>() {
            const ctx = createContext<A | undefined>(undefined);
            function useCtx() {
              const c = useContext(ctx);
              if (c === undefined)
                throw new Error("useCtx must be inside a Provider with a value");
              return c;
            }
            return [useCtx, ctx.Provider] as const// 'as const' 使 TypeScript 推斷出一個元組 
          }

          // 用法:
          // 我們?nèi)匀恍枰付ㄒ粋€類型,但沒有默認值!
          export const [useCurrentUserName, CurrentUserProvider] = createCtx<string>();

          function EnthusasticGreeting() {
            const currentUser = useCurrentUserName();
            return <div>HELLO {currentUser.toUpperCase()}!</div>;
          }

          function App() {
            return (
              <CurrentUserProvider value="Anders">
                <EnthusasticGreeting />
              </CurrentUserProvider>

            );
          }   
          3、可以更進一步,使用createContext和context getters結(jié)合這個想法。
          import { createContext, useContext } from "react";

          /**
          * 創(chuàng)建上下文和提供者的助手,沒有預(yù)先的默認值,并且
          * 無需一直檢查未定義。
          */

          function createCtx<A extends {} | null>() {
            const ctx = createContext<A | undefined>(undefined);
            function useCtx() {
              const c = useContext(ctx);
              if (c === undefined)
                throw new Error("useCtx must be inside a Provider with a value");
              return c;
            }
            return [useCtx, ctx.Provider] as const// 'as const' 使 TypeScript 推斷出一個元組
          }

          // 用法

          export const [useCtx, SettingProvider] = createCtx<string>();  // 指定類型,但不需要預(yù)先指定值
          export function App() {
            const key = useCustomHook("key"); // 從鉤子中獲取值,必須在組件中
            return (
              <SettingProvider value={key}>
                <Component />
              </SettingProvider>

            );
          }
          export function Component() {
            const key = useCtx(); // 仍然可以在沒有空檢查的情況下使用!
            return <div>{key}</div>;
          }
          4、使用createContext and useContext制作一個createCtx with  unstated-like 上下文設(shè)置器:
          import {
            createContext,
            Dispatch,
            PropsWithChildren,
            SetStateAction,
            useState,
          from "react";

          export function createCtx<A>(defaultValue: A{
            type UpdateType = Dispatch<SetStateAction<typeof defaultValue>>;
            const defaultUpdate: UpdateType = () => defaultValue;
            const ctx = createContext({
              state: defaultValue,
              update: defaultUpdate,
            });

            function Provider(props: PropsWithChildren<{}>{
              const [state, update] = useState(defaultValue);
              return <ctx.Provider value={{ state, update }} {...props} />;
            }
            return [ctx, Provider] as const;  // 或者,[typeof ctx, typeof Provider]
          }

          // 用法
          import { useContext } from "react";

          const [ctx, TextProvider] = createCtx("someText");
          export const TextContext = ctx;
          export function App() {
            return (
              <TextProvider>
                <Component />
              </TextProvider>

            );
          }
          export function Component() {
            const { state, update } = useContext(TextContext);
            return (
              <label>
                {state}
                <input type="text" onChange={(e) => update(e.target.value)} />
              </label>

            );
          }

          八、forwardRef/createRef

          檢查Hooks 部分的useRef.

          createRef:

          import { createRef, PureComponent } from "react";

          class CssThemeProvider extends PureComponent<Props{
            private rootRef = createRef<HTMLDivElement>(); // 像這樣 
            render() {
              return <div ref={this.rootRef}>{this.props.children}</div>;
            }
          }
          forwardRef:
          import { forwardRef, ReactNode } from "react";

          interface Props {
            children?: ReactNode;
            type: "submit" | "button";
          }
          export type Ref = HTMLButtonElement;

          export const FancyButton = forwardRef<Ref, Props>((props, ref) => (
            <button ref={ref} className="MyClassName" type={props.type}>
              {props.children}
            </button>

          ));
          通用 forwardRefs
          1 - Wrapper component
          type ClickableListProps<T> = {
            items: T[];
            onSelect: (item: T) => void;
            mRef?: React.Ref<HTMLUListElement> | null;
          };

          export function ClickableList<T>(props: ClickableListProps<T>{
            return (
              <ul ref={props.mRef}>
                {props.items.map((item, i) => (
                  <li key={i}>
                    <button onClick={(el) => props.onSelect(item)}>Select</button>
                    {item}
                  </li>
                ))}
              </ul>

            );
          }
          2 - Redeclare forwardRef
          // 重新聲明 forwardRef
          declare module "react" {
            function forwardRef<TP = {}>(
              render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
            ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
          }

          // 只需像以前一樣編寫組件!
          import { forwardRef, ForwardedRef } from "react";

          interface ClickableListProps<T> {
            items: T[];
            onSelect: (item: T) => void;
          }

          function ClickableListInner<T>(
            props: ClickableListProps<T>,
            ref: ForwardedRef<HTMLUListElement>
          {
            return (
              <ul ref={ref}>
                {props.items.map((item, i) => (
                  <li key={i}>
                    <button onClick={(el) => props.onSelect(item)}>Select</button>
                    {item}
                  </li>
                ))}
              </ul>

            );
          }

          export const ClickableList = forwardRef(ClickableListInner);

          九、有用的hooks

          useLocalStorage
          import { useState } from "react";

          // 用法
          function App() {
            // 類似于 useState 但第一個 arg 是本地存儲中值的鍵。
            const [name, setName] = useLocalStorage<string>("name""Bob");

            return (
              <div>
                <input
                  type="text"
                  placeholder="Enter your name"
                  value={name}
                  onChange={(e) =>
           setName(e.target.value)}
                />
              </div>

            );
          }

          // Hook
          function useLocalStorage<T>(
            key: string,
            initialValue: T
          ): [T, (value: T | ((val: T) => T)) => void
          {
            // 狀態(tài)來存儲我們的值
            // 將初始狀態(tài)函數(shù)傳遞給 useState,因此邏輯只執(zhí)行一次
            const [storedValue, setStoredValue] = useState<T>(() => {
              try {
                // 按鍵從本地存儲中獲取
                const item = window.localStorage.getItem(key);
                // 解析存儲的 json 或者如果沒有則返回 initialValue
                return item ? JSON.parse(item) : initialValue;
              } catch (error) {
                // 如果錯誤也返回initialValue
                console.log(error);
                return initialValue;
              }
            });

            // 返回 useState 的 setter 函數(shù)的包裝版本,它...
            // ... 將新值保存到 localStorage。
            const setValue = (value: T | ((val: T) => T)) => {
              try {
                // 允許 value 是一個函數(shù),所以我們有與 useState 相同的 API
                const valueToStore =
                  value instanceof Function ? value(storedValue) : value;
                // 保存狀態(tài)
                setStoredValue(valueToStore);
                // 保存到本地存儲
                window.localStorage.setItem(key, JSON.stringify(valueToStore));
              } catch (error) {
                // 更高級的實現(xiàn)將處理錯誤情況
                console.log(error);
              }
            };

            return [storedValue, setValue];
          }
          useMedia
          import { useState, useEffect } from 'react';

          function App() {
            const columnCount = useMedia<number>(
              // 媒體查詢
              ['(min-width: 1500px)''(min-width: 1000px)''(min-width: 600px)'],
              // 列數(shù)(與上述按數(shù)組索引的媒體查詢有關(guān))
              [543],
              // 默認列數(shù)
              2
            );

            // 創(chuàng)建列高數(shù)組(從 0 開始)
            let columnHeights = new Array(columnCount).fill(0);

            // 創(chuàng)建包含每列項目的數(shù)組數(shù)組
            let columns = new Array(columnCount).fill().map(() => []) as Array<DataProps[]>;

            (data as DataProps[]).forEach(item => {
              // 獲取最短列的索引
              const shortColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
              // 添加項目
              columns[shortColumnIndex].push(item);
              // 更新高度
              columnHeights[shortColumnIndex] += item.height;
            });

            // 渲染列和項目
            return (
              <div className="App">
                <div className="columns is-mobile">
                  {columns.map(column => (
                    <div className="column">
                      {column.map(item => (
                        <div
                          className="image-container"
                          style={{
                            // 將圖像容器大小調(diào)整為圖像的縱橫比
                            paddingTop: (item.height / item.width) * 100 + '%'
                          }}
                        >

                          <img src={item.image} alt="" />
                        </div>
                      ))}
                    </div>
                  ))}
                </div>
              </div>

            );
          }

          // Hook
          const useMedia = <T>(queries: string[], values: T[], defaultValue: T) => {
             // 包含每個查詢的媒體查詢列表的數(shù)組
            const mediaQueryLists = queries.map(q => window.matchMedia(q));

            // 根據(jù)匹配的媒體查詢獲取值的函數(shù)
            const getValue = () => {
              // 獲取第一個匹配的媒體查詢的索引
              const index = mediaQueryLists.findIndex(mql => mql.matches);
              // 返回相關(guān)值,如果沒有則返回默認值
              return values?.[index] || defaultValue;
            };

            // 匹配值的狀態(tài)和設(shè)置器
            const [value, setValue] = useState<T>(getValue);

            useEffect(
              () => {
                // 事件監(jiān)聽回調(diào)
                // 注意:通過在 useEffect 之外定義 getValue,我們確保它具有 ...
                // ... 鉤子參數(shù)的當前值(因為這個鉤子回調(diào)在掛載時創(chuàng)建一次)。

                const handler = () => setValue(getValue);
                // 使用上述處理程序為每個媒體查詢設(shè)置一個偵聽器作為回調(diào)。
                mediaQueryLists.forEach(mql => mql.addListener(handler));
                // 在清理時移除監(jiān)聽器
                return () => mediaQueryLists.forEach(mql => mql.removeListener(handler));
              },
              [] // 空數(shù)組確保效果僅在掛載和卸載時運行
            );

            return value;
          }
          useAsyncTask
          // 用法
          const task = useAsyncTask(async (data: any) => await myApiRequest(data));
          task.run(data);
          useEffect(() => {
            console.log(task.status); // 'IDLE' | 'PROCESSING' | 'ERROR' | 'SUCCESS';
          }, [task.status]);

          // 執(zhí)行

          import { useCallback, useState } from "react";

          type TStatus = "IDLE" | "PROCESSING" | "ERROR" | "SUCCESS";

          function useAsyncTask<T extends any[], R = any>(
            task: (...args: T
          ) => Promise<R>
          {
            const [status, setStatus] = useState<TStatus>("IDLE");
            const [message, setMessage] = useState("");

            const run = useCallback(async (...arg: T) => {
              setStatus("PROCESSING");
              try {
                const resp: R = await task(...arg);
                setStatus("SUCCESS");
                return resp;
              } catch (error) {
                let message = error?.response?.data?.error?.message || error.message;
                setMessage(message);
                setStatus("ERROR");
                throw error;
              }
            }, []);

            const reset = useCallback(() => {
              setMessage("");
              setStatus("IDLE");
            }, []);

            return {
              run,
              status,
              message,
              reset,
            };
          }

          export default useAsyncTask;
          useFetch
          export function useFetch(request: RequestInfo, init?: RequestInit{
            const [response, setResponse] = useState<null | Response>(null);
            const [error, setError] = useState<Error | null>();
            const [isLoading, setIsLoading] = useState(true);

            useEffect(() => {
              const abortController = new AbortController();
              setIsLoading(true);
              (async () => {
                try {
                  const response = await fetch(request, {
                    ...init,
                    signal: abortController.signal,
                  });
                  setResponse(await response?.json());
                  setIsLoading(false);
                } catch (error) {
                  if (isAbortError(error)) {
                    return;
                  }
                  setError(error as any);
                  setIsLoading(false);
                }
              })();
              return () => {
                abortController.abort();
              };
            }, [init, request]);

            return { response, error, isLoading };
          }

          // type guards
          function isAbortError(error: any): error is DOMException {
            if (error && error.name === "AbortError") {
              return true;
            }
            return false;
          }

          十、HOC

          一個 HOC 示例

          注入props

          interface WithThemeProps {
            primaryColor: string;
          }

          在組件中的使用

          在組件的接口上提供可用的props,但在包裝在 HoC 中時為組件的消費者減去。

          interface Props extends WithThemeProps {
            children?: React.ReactNode;
          }

          class MyButton extends React.Component<Props{
            public render() {
              // 使用主題和其他props渲染元素。
            }

            private someInternalMethod() {
              // 主題值也可在此處作為props使用。
            }
          }

          export default withTheme(MyButton);

          使用組件

          現(xiàn)在,在使用組件時,可以省略primaryColor props或覆蓋通過上下文提供的props。

          <MyButton>Hello button</MyButton> // 有效

          <MyButton primaryColor="#333">Hello Button</MyButton> // 同樣有效

          聲明 HoC

          實際的 HoC。

          export function withTheme<T extends WithThemeProps = WithThemeProps>(
            WrappedComponent: React.ComponentType<T>
          {
             // 嘗試為 React 開發(fā)工具創(chuàng)建一個不錯的 displayName。
            const displayName =
              WrappedComponent.displayName || WrappedComponent.name || "Component";

            // 創(chuàng)建內(nèi)部組件。這里計算出來的 Props 類型是魔法發(fā)生的地方。
            const ComponentWithTheme = (props: Omit<T, keyof WithThemeProps>) => {
              // 獲取要注入的props。這可以通過上下文來完成。
              const themeProps = useTheme();

              // props隨后出現(xiàn),因此可以覆蓋默認值。
              return <WrappedComponent {...themeProps} {...(props as T)} />;
            };

            ComponentWithTheme.displayName = `withTheme(${displayName})`;

            return ComponentWithTheme;
          }
          這是一個更高級的動態(tài)高階組件示例,它的一些參數(shù)基于傳入的組件的 props:
          // 向組件注入靜態(tài)值,以便始終提供它們
          export function inject<TPropsTInjectedKeys extends keyof TProps>(
            Component: React.JSXElementConstructor<TProps>,
            injector: Pick<TProps, TInjectedKeys>
          {
            return function Injected(props: Omit<TProps, TInjectedKeys>{
              return <Component {...(props as TProps)} {...injector} />;
            };
          }
          使用forwardRef
          對于“真正的”可重用性,還應(yīng)該考慮為 HOC 公開一個 ref。

          十一、Linting

          yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint
          將lint腳本添加到您的package.json:
          "scripts": {
              "lint""eslint 'src/**/*.ts'"
            },
          一個合適的.eslintrc.js
          module.exports = {
            env: {
              es6true,
              nodetrue,
              jesttrue,
            },
            extends"eslint:recommended",
            parser"@typescript-eslint/parser",
            plugins: ["@typescript-eslint"],
            parserOptions: {
              ecmaVersion2017,
              sourceType"module",
            },
            rules: {
              indent: ["error"2],
              "linebreak-style": ["error""unix"],
              quotes: ["error""single"],
              "no-console""warn",
              "no-unused-vars""off",
              "@typescript-eslint/no-unused-vars": [
                "error",
                { vars"all"args"after-used"ignoreRestSiblingsfalse },
              ],
              "@typescript-eslint/explicit-function-return-type""warn"// 考慮對對象字面量和函數(shù)返回類型使用顯式注釋,即使它們可以被推斷出來。  
              "no-empty""warn",
            },

          };
          更多.eslintrc.json選項需要考慮,可能需要更多應(yīng)用選項:
          {
            "extends": [
              "airbnb",
              "prettier",
              "prettier/react",
              "plugin:prettier/recommended",
              "plugin:jest/recommended",
              "plugin:unicorn/recommended"
            ],
            "plugins": ["prettier""jest""unicorn"],
            "parserOptions": {
              "sourceType""module",
              "ecmaFeatures": {
                "jsx"true
              }
            },
            "env": {
              "es6"true,
              "browser"true,
              "jest"true
            },
            "settings": {
              "import/resolver": {
                "node": {
                  "extensions": [".js"".jsx"".ts"".tsx"]
                }
              }
            },
            "overrides": [
              {
                "files": ["**/*.ts""**/*.tsx"],
                "parser""typescript-eslint-parser",
                "rules": {
                  "no-undef""off"
                }
              }
            ]
          }

          面試題庫推薦


            百度某部門面試原題
            某中型公司面試原題
            【精品】前端知識梳理

          十二、最后

          在我們閱讀完官方文檔后,我們一定會進行更深層次的學(xué)習,比如看下框架底層是如何運行的,以及源碼的閱讀。
              這里廣東靚仔給下一些小建議:
          • 在看源碼前,我們先去官方文檔復(fù)習下框架設(shè)計理念、源碼分層設(shè)計
          • 閱讀下框架官方開發(fā)人員寫的相關(guān)文章
          • 借助框架的調(diào)用棧來進行源碼的閱讀,通過這個執(zhí)行流程,我們就完整的對源碼進行了一個初步的了解
          • 接下來再對源碼執(zhí)行過程中涉及的所有函數(shù)邏輯梳理一遍

          關(guān)注我,一起攜手進階

          歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~

          瀏覽 249
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本色视网 | 国产精品v欧美精品v日韩 | 中文字幕AV网站 | 操逼男人的天堂 | 操B视频动漫免费网站 |