React Hook 一些用法

一、useState/useReducer

useState

const [isLoading, setIsLoading] = useState<boolean>(false);
複製代碼

若是有複雜數據結構的,可使用 useImmer ,解決深層數據修改,視圖不更新的問題react

簡單的可使用 數組map 方法返回一個新數組,或者深拷貝一下,再設置就好了ios

import { useImmer } from 'use-immer';
export interface TreeDataItemProps {
  label: string;
  value: string;
  children?: TreeDataItemProps[];
}

// ...
  const [treeData, setTreeData] = useImmer<TreeDataItemProps>({
    label: '',
    value: '',
    children: []
  });
// ...
複製代碼

useReducer

若是 useState 不少,能夠把相關的 state 改爲一個 對象的 useReducer 寫法git

import React, { useReducer } from 'react';

export interface CountStateProps {
  count: number;
}

export interface CountActionProps {
  type: 'increment' | 'decrement' | 'set';
  value?: number;
}

const countReducer = (state: CountStateProps, { type, value }: CountActionProps) => {
  switch (type) {
    case 'increment':
      return {
        count: state.count + 1,
      };
    case 'decrement':
      return {
        count: state.count - 1,
      };
    case 'set':
      return {
        count: value || state.count,
      };
    default:
      return state;
  }
};
const initialCountState: CountStateProps = { count: 0 };

const Count: React.FC = props => {
  const [countState, countDispatch] = useReducer(countReducer, initialCountState);

  return (
    <div>
      <p>{countState.count}</p>
      <button type="button" onClick={() => countDispatch({ type: 'increment' })}>
        increment
      </button>
      <button type="button" onClick={() => countDispatch({ type: 'decrement' })}>
        decrement
      </button>
      <button type="button" onClick={() => countDispatch({ type: 'set', value: 1 })}>
        set
      </button>
    </div>
  );
};

export default Count;
複製代碼

二、useEffect

useEffect(() => fn, [deps]);
複製代碼
  • fn:回調函數github

    回調函數內返回一個函數,且依賴項爲空數組 [] 時,這個函數會在當前組件卸載時執行typescript

    好比一些 事件監聽/定時器 能夠這裏取消npm

  • deps:依賴項axios

    • 不傳:useState 每次變化都會執行 fn
    • []:fn 只會在當前頂層函數 mount 後執行一次
    • [deps]: deps 任意項變化後,都會執行 fn
useEffect(() => {
  console.log('mount 時會打印');

  return () => {
    console.log('unmount 時會打印');
  };
}, []);

useEffect(() => {
  console.log('每次 State 變化都會打印');
});

useEffect(() => {
  console.log('Mount 後打印一次');
}, []);

useEffect(() => {
  console.log('deps 任意項變化後都會打印');
}, [deps]);
複製代碼

三、useMemo

監聽依賴的變化,執行回調函數,回調函數的返回值 做爲 useMemo 的返回值,能夠緩存結果,相似 Vue 計算屬性 computedapi

const memorized =  useMemo(() => {
  console.log('deps changed');
  return 'newValue';
}, [deps]);
複製代碼

四、useCallback

監聽依賴的變化,執行新的回調函數;依賴不變化則不會執行數組

const fn = useCallback(() => {
  console.log('deps changed');
}, [deps]);
複製代碼

五、useRef

useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化爲傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命週期內保持不變。緩存

訪問子組件

const InputRef = useRef(null);
InputRef.focus();

<input ref={inputRef} /> 複製代碼

存儲變量

useRef 能夠經過 *.current 來存儲/獲取變量值,改變不會觸發頁面更新;

能夠看 自定義 Hook usePrevState

const value = useRef(null);

value.current = newVal;
複製代碼

六、useImperativeHandle

函數組件沒有 ref;若是要在父組件經過 ref,須要使用 useImperativeHandle + React.forwardRef 實現;useImperativeHandle 回調函數的返回值,能夠被父組件經過 ref 調用

無論是 createRef 仍是 useRef 都不是動態的;即便被引用的 子組件更新了,也不會從新獲取新的 ref

子組件

import React, { useState, useImperativeHandle } from 'react';

export interface CountRefProps {
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
}

const Count: React.FC = (props, ref) => {
  const [count, setCount] = useState(0);

  useImperativeHandle(ref, () => ({
    count,
    setCount,
  }));

  return <div>{count}</div>;
};

export default React.forwardRef(Count);
複製代碼

父組件

import React, { useRef } from 'react';
import Count, { CountRefProps } from './Count';

const Counter: React.FC = () => {
  const countRef = useRef<CountRefProps>(null);

  const onClick = () => {
    console.log(countRef.current!.count); // 0

    // 調用 Count 的 setCount 方法,使 Count 視圖更新
    countRef.current!.setCount(1); 

    // 子組件更新了,可是這裏仍是一開始的 ref,不會自動更新的
    console.log(countRef.current!.count); // 0
  };

  return (
    <div>
      <Count ref={countRef} />
      <button type="button" onClick={onClick}>
        setCount
      </button>
    </div>
  );
};

export default Counter;
複製代碼

自定義 Hook

實現幾個經常使用的 自定義 Hook

usePrevState

這個其實 React 官網有說過,後期可能會成爲官方api,這裏只是簡單實現

import React, { useRef, useEffect, useState } from 'react';

function usePrevState<T>(state: T) {
  const countRef = useRef<any>(null);
  const [_state, setState] = useState<T>(state);

  useEffect(() => {
    countRef.current = _state;
    setState(state);
  }, [state]);

  // prevState
  return countRef.current;
}

export default usePrevState;
複製代碼

使用:

import React, { useState } from 'react';
import usePrevState from './usePrevState';

const Count2: React.FC = props => {
  const [count, setCount] = useState<number>(0);
  const prevCount = usePrevState<number>(count);

  return (
    <div>
      <p>prevCount: {prevCount}</p>
      <p>count: {count}</p>
      <button type="button" onClick={() => setCount(prev => prev + 1)}>
        increment
      </button>
      <button type="button" onClick={() => setCount(prev => prev - 1)}>
        decrement
      </button>
    </div>
  );
};

export default Count2;
複製代碼

useFetchData

這個是以前看一位大佬的 文章 05,裏面分享的另外一篇國外的 文章,而後本身根據實際使用改的

項目使用的是 UmiJS 框架,自帶的 request,

使用 axios 的話也是差很少的,把 fetchFn 類型改成
fetchFn: () => Promise<AxiosResponse>; 而後,請求函數改成 axios 相應的寫法就能夠了

說明:

  • fetchFn: 請求函數
  • deps: 更新依賴,從新執行 fetchFn
  • isReady: fetchFn 執行條件
import { useState, useEffect } from 'react';
import { RequestResponse } from 'umi-request';

export interface UseFetchDataProps {
  fetchFn: () => Promise<RequestResponse>;
  deps?: any[];
  isReady?: boolean;
}

/** * 自定義 Hook: 獲取數據 * @param fetchFn {*} 使用 request 封裝的請求函數 * @param deps 更新依賴,從新執行 * @param isReady 能夠獲取數據標誌,默認直接獲取數據 */
export default function useFetchData({ fetchFn, deps = [], isReady }: UseFetchDataProps) {
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [isError, setIsError] = useState<boolean>(false);
  const [resData, setResData] = useState<any>();

  useEffect(() => {
    let isDestroyed = false;

    const getData = async () => {
      try {
        setIsLoading(true);
        const res = await fetchFn();
        if (!isDestroyed) {
          setResData(res);
          setIsLoading(false);
        }
      } catch (err) {
        console.error(err);
        setIsError(true);
      }
    };

    // 默認(undefined)直接獲取數據
    // 有條件時 isReady === true 再獲取
    if (isReady === undefined || isReady) {
      getData();
    }

    return () => {
      isDestroyed = true;
    };
  }, deps);

  return {
    isLoading,
    isError,
    resData,
  };
}
複製代碼

使用:

const { isLoading, resData } = useFetchData({
  fetchFn: () => getAccountList(searchParams),
  deps: [searchParams],
  isReady: Boolean(searchParams.companyId),
});


// getAccountList 是這樣的:
export function getAccountList(params: AccountListRequestProps) {
  return request('/accountList', {
    params,
  });
}
複製代碼

與 Hook 相關的狀態管理

hox

定義 Model

import { createModel } from 'hox';
import { useState, useEffect } from 'react';
import { getCompanyList } from '@/api';

const useCompanyModel = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [companyList, setCompanyList] = useState([]);

  useEffect(() => {
    if (!companyList.length) getData();
  }, [companyList]);

  const getData = async () => {
    setIsLoading(true);
    const res = await getCompanyList({ userId: '11' });
    setCompanyList(res);
    setIsLoading(false);
  };

  return {
    isLoading,
    companyList,
  };
};

export default createModel(useCompanyModel);
複製代碼

使用 Model

import React from 'react';
import useCompanyModel from '@/models/useCompanyModel';

export interface CompanyListProps {
  onChange: (id: string) => void;
}

const CompanyList: React.FC<CompanyListProps> = ({ onChange }) => {
  const { isLoading, companyList } = useCompanyModel();
  //...
}

export default CompanyList;
複製代碼
相關文章
相關標籤/搜索