hooks中的ts类型

2,223 阅读4分钟

React中的hooks

学习这些内容需要一些前置知识,typescript的类型定义和react的hooks知识。 在看hooks的类型声明时建议先看_。

  1. useState
  2. useMemo
  3. useCallback
  4. useRef
  5. useReducer
  6. useContext

useState

useState提供函数组件也可以动态修改,相当于类组件的state; 给useState设置类型的时候通过范型传递进去:state的类型就被定义了,并且给了一个初始值

interface IProps {
  name: string;
}
// React.FunctionComponent -> React.FC 支持缩写
const State: React.FunctionComponent<IProps> = (props: IProps) => {
  let [count, setCount] = useState<number>(0);
  return (<>
    <div>
      <h1>{count}</h1>
      <h2>{props.name}</h2>
    </div>
  </>)
}

如果没有初始值那么可以使用联合属性将初始值设置为null,例如<number|null>,但是在这里需要注意,后续使用state的时候需要进行空值判断,通常使用可选链来进行访问 a?.b -> a && a.b

interface IObject {
  num: number;
}
...
let [count, setCount] = useState<IObject | null>(null);
...
<h1>{count?.num}</h1>

useMemo

useMemo是渲染组件时的优化项,传递范型规定返回值(不过一般在使用中都不会去定义)

const memoized = useMemo<string>(() => {
  return name;
}, [name])

useCallback

useRef

useRef相当于创建一个ref,有两个用途:

  1. 可以用于保存一个state的最新值,在异步处理state的时候也可以获取到最新的state值;
  2. 与标签关联起来,可以通过.current来获取到DOM元素。

保存state 属于一个MutableRefObject类型

const Ref: React.FC = () => {
  let [count, setCount] = useState<number>(0);
  let divRef = useRef<number>(0);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => {
        setCount(count + 10);
        divRef.current = count + 10;
      }}>+10</button>
      <button onClick={() => {
        setTimeout(() => {
          // 这里如果不做处理,那么延迟处理会拿不到最新的值,可以借助useRef的current属性来暂存值,这个一定拿到的最新值
          setCount(divRef.current - 10);
          divRef.current -= 10;
        }, 1000)
      }}>-10</button>
    </div>
  )
}

关联DOM,对于ref的类型选择在不知道的时候可以使用ts的类型判断,然后再确定。

const Ref: React.FC = () => {
  let divRef = useRef<HTMLHeadingElement>(null);
  return (
    <div>
      <h1 ref={divRef}>ref</h1>
      <button onClick={(event: React.MouseEvent)=>{
        console.log('button', event.target);
        console.log('h1', divRef.current);
      }}>获取DOM</button>
    </div>
  )
}

useReducer

在state比较复杂使用多个useState的时候可以采用useReducer来进行集中管控。

enum ActionType {
  INCREMENT_COUNT = 'INCREMENT_COUNT',
  DECREMENT_COUNT = 'DECREMENT_COUNT'
}
interface IState {
  count: number;
}
interface IAction {
  type: ActionType,
  value: number;
}
const initializeState = {
  count: 0
}
const reducer: React.Reducer<IState, IAction> = (state: IState, action: IAction) => {
  switch (action.type) {
    case 'INCREMENT_COUNT':
      return {
        ...state,
        count: state.count + action.value
      }
    case 'DECREMENT_COUNT':
      return {
        ...state,
        count: state.count - action.value
      }
    default:
      return {
        ...state
      }
  }
}

const Reducer: React.FC = () => {
  let [state, dispatch] = useReducer<React.Reducer<IState, IAction>>(reducer, initializeState);
  return (
    <div className="reducer">
      <h1>{state.count}</h1>
      <button onClick={() => {
        dispatch({
          type: ActionType.INCREMENT_COUNT,
          value: 10
        })
      }}>+10</button>
      <button onClick={() => {
        dispatch({
          type: ActionType.DECREMENT_COUNT,
          value: 5
        })
      }}>-5</button>
    </div>
  )
}

useContext

通过useContext可以将数据通过上下文的形式进行传递

interface IArticle {
  id: number;
  title: string;
}
interface IContext {
  articles: IArticle[];
  style?: React.CSSProperties;
}

type IProps = {
  name: string;
  style: React.CSSProperties,
  // 这里children可以写也可以省略,因为React.FunctionComponent已经内置了一个
  // children: React.ReactNode[];
  // children: React.ReactChild[];
  // children: JSX.Element[];
}

const initlizeArticles = [
  {
    id: 1,
    title: 'first'
  },
  {
    id: 2,
    title: 'second'
  }
]
const ArticleContext = createContext<IContext>({} as IContext);

const ArticleProvider: FC<IProps> = ({ children, style }) => {
  let [articles] = useState<IArticle[]>(initlizeArticles);
  let context = { articles, style };
  return (
    <ArticleContext.Provider value={context}>
      {children}
    </ArticleContext.Provider>
  )
}

const ShowArticles: FC = () => {
  let { articles = [], style } = useContext<IContext>(ArticleContext);

  return (
    <div style={style}>
      {
        articles.map((article: IArticle) => <p key={article.id}>{article.title}</p>)
      }
      <input type="text" onChange={(e: React.ChangeEvent) => {
        console.log('a', e);
      }} value={'a'} />
    </div>
  )
}

const Article: FC = () => {
  return (
    <ArticleProvider name={'11'} style={{ color: 'red' }}>
      <h1>My Title</h1>
      <ShowArticles />
    </ArticleProvider>
  )
}

对于代码中提到的React.FunctionComponent内置了children属性,可以通过声明文件寻找到踪迹

// 在这里的props已经有了一个PropsWithChildren类型,内置了children
interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
}
type PropsWithChildren<P> = P & { children?: ReactNode | undefined };

hooks中props中的类型声明

  1. 一般的组件参数:
type IProps = {
  message: string;
  count: number;
  disabled: boolean;
  /** string类型的数组 两种方式 */
  // names: string[];
  names: Array<string>;
  /** 字面量方式的精确控制,并且可以采用联合类型,必须为两者中的一个 */
  status: "waiting" | "success";
  /** 占位用,不能知道里面的属性,在不操作对象属性时可以使用,因为访问会报错,提示没有这个属性 */
  obj: object;
  /** 和object差不多的功能,但是这个属于精确捕捉,必须为 {} */
  obj2: {}; 
  /** 常用的对象声明,包含对象拥有的属性和对应属性值的类型 */
  obj3: {
    id: string;
    title: string;
  };
  /** 对象类型的数组 */
  objArr: {
    id: string;
    title: string;
  }[];
  objAry: Array<{
    id: string;
    title: string;
  }>
  /** 一个可扩展的对象,属性和属性值需要符合类型要求 */
  dict1: {
    [key: string]: MyTypeHere;
  };
  dict2: Record<string, MyTypeHere>; // 和dict1一样
  /** 不做任何操作时可以这样定义函数 */
  onSomething: Function;
  /** 没有参数和返回值的函数 */
  onClick: () => void;
  /** 有参数id为number且没有返回值的函数 */
  onChange: (id: number) => void;
  /** button按钮传递一个鼠标点击事件,没有返回值 */
  onClick(event: React.MouseEvent<HTMLButtonElement>): void;
  /** 可选参数 */
  optional?: OptionalType;
};
  1. 一些特殊的组件参数

总结:使用React.ReactNode来定义children

export interface AppProps {
  children1: JSX.Element// 接收一个jsx结构,只能有一个child
  children2: JSX.Element | JSX.Element[]; // 可以接收多个child
  children3: React.ReactChildren; // 尽管有这个属性,但是不实用
  children4: React.ReactChild[]; // React.ReactChild = React.ReactElement|React.ReactText -> React.ReactText=string|number
  children: React.ReactNode; // 在声明children中最好使用这个,包含了ReactChild | ReactFragment | ReactPortal | boolean | null | undefined
  functionChildren: (name: string) => React.ReactNode; // 接收一个函数返回ReactNode
  style?: React.CSSProperties; // 接收style属性
  onChange?: React.FormEventHandler<HTMLInputElement>; // onChange事件的触发
  //  更多参考: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring
  // TODO 暂时没明白如何完美的使用
  props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
  props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
}
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;