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;
复制代码
相关文章
相关标签/搜索