React + TypeScript 常用类型汇总(上)

简介: 本文适合对TypeScript感兴趣的小伙伴阅读~

一、前言


在React项目开发中,写出优雅的、更有意义的typescript代码,是我们一直追求的。本文广东靓仔带小伙伴们一起来看看React项目实际开发中用到的一些常用类型示例。


二、基本prop类型示例


常规的程序中使用的 TypeScript 类型列表:

type AppProps = {
  message: string;
  count: number;
  disabled: boolean;
 /** 一个类型的数组!*/
  names: string[];
  /** 用于指定精确字符串值的字符串文字,使用联合类型将它们连接在一起 */
  status: "waiting" | "success";
  /** 任何对象,只要你不使用它的属性(不常见,但用作占位符)*/
  obj: object;
  obj2: {}; // 和 `object` 差不多,和 `Object` 完全一样  
  /** 具有任意数量属性的对象 (PREFERRED) */
  obj3: {
    id: string;
    title: string;
  };
  /** 对象数组!(常见的) */
  objArr: {
    id: string;
    title: string;
  }[];
  /** 具有任意数量的相同类型属性的 dict 对象 */
  dict1: {
    [key: string]: MyTypeHere;
  };
  dict2: Record<string, MyTypeHere>; // 相当于 dict1   
  /** 任何函数,只要你不调用它(不推荐) */
  onSomething: Function;
   /** 不接受或不返回任何内容的函数(非常常见) */
  onClick: () => void;
   /** 带有命名props的函数(非常常见) */
  onChange: (id: number) => void;
   /** 接受事件的函数类型语法(非常常见) */
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  /** 接受事件的替代函数类型语法(非常常见) */
  onClick(event: React.MouseEvent<HTMLButtonElement>): void;
  /** 一个可选的props(非常常见!) */
  optional?: OptionalType;
};


三、有用的 React Prop 类型示例


export declare interface AppProps {
  children?: React.ReactNode; // 最好,接受 React 可以渲染的所有内容  
  childrenElement: JSX.Element; // 单个 React 元素  
  style?: React.CSSProperties; // 传递样式props 
  onChange?: React.FormEventHandler<HTMLInputElement>; // 形成事件!泛型参数是 event.target 的类型  
  props: Props & React.ComponentPropsWithoutRef<"button">; // 模拟按钮元素的所有 props 并明确不转发其 ref    
  props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // 模拟 MyButtonForwardedRef 的所有 props 并显式转发其 ref
}


type还是interface?

这是一个有用的经验法则:

在创作库或第 3 方环境类型定义时,始终用于公共 API 的定义,因为这允许使用者在缺少某些定义时通过声明合并来扩展它们。

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


四、函数组件


这些可以写成普通函数,接受一个props参数并返回一个 JSX 元素。

type AppProps = {
  message: string;
}; /* 如果导出使用 `interface` 以便消费者可以扩展 */ 
// 声明函数组件的最简单方法;推断返回类型。
const App = ({ message }: AppProps) => <div>{message}</div>;
// 您可以选择注释返回类型,这样如果您不小心返回了其他类型,则会引发错误
const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;
// 你也可以内联类型声明;消除了命名props类型,但看起来重复
const App = ({ message }: { message: string }) => <div>{message}</div>;


hook

useState类型推断对于简单值非常有效:

const [state, setState] = useState(false);
// `state` 被推断为布尔值
// `setState` 只接受布尔值


许多钩子都是用 null-ish 默认值初始化的,你可能想知道如何提供类型。


显式声明类型,并使用联合类型:

const [user, setUser] = useState<User | null>(null);
setUser(newUser);


如果状态在设置后不久初始化并且始终具有以下值,还可以使用类型断言:

const [user, setUser] = useState<User>({} as User);
setUser(newUser);


useReducer您可以将有区别的联合用于 reducer 操作。不要忘记定义reducer的返回类型,否则TypeScript会推断出来。

import { useReducer } from "react";
const initialState = { count: 0 };
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用于执行副作用并返回一个可选的清理函数,这意味着如果它们不处理返回值,则不需要类型。


使用 时useEffect,注意不要返回除函数 or 以外的任何东西undefined,否则 TypeScript 和 React 都会提示你。


这在使用箭头函数时可能很微妙:

function DelayedEffect(props: { timerMs: number }) {
  const { timerMs } = props;
  useEffect(
    () =>
      setTimeout(() => {
        /* do stuff */
      }, timerMs),
    [timerMs]
  );
  // 反面例子!setTimeout 隐式返回一个数字
  // 因为箭头函数体没有用大括号括起来
  return null;
}


useRef

在 TypeScript 中,返回一个只读或可变useRef的引用,取决于您的类型参数是否完全覆盖初始值。选择一个适合您的用例。


1、DOM 元素 ref 访问 DOM 元素:


仅提供元素类型作为参数,并null用作初始值。.current在这种情况下,返回的引用将具有由 React 管理的只读引用TypeScript 期望将此 ref 提供给元素的ref prop:


function Foo() {
  // - 如果可能,请尽可能具体。例如,HTMLDivElement
  // 比 HTMLElement 好,也比 Element 好得多。
  // - 从技术上讲,这会返回 RefObject<HTMLDivElement>
  const divRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    // 注意 ref.current 可能为空。这是意料之中的
    // 有条件地渲染被引用的元素,或者你可能忘记分配它
    if (!divRef.current) throw Error("divRef is not assigned");
    // 现在 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() {
  // 从技术上讲,这将返回 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 中返回一个数组,你会想要避免类型推断,因为 TypeScript 会推断一个联合类型(当你实际上想要在数组的每个位置使用不同的类型时)

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),因此希望为它提供(可选)prop 和 state 类型参数:

type MyProps = {
  // 使用 `interface` 也可以
  message: string;
};
type MyState = {
  count: number; // 像这样  
};
class App extends React.Component<MyProps, MyState> {
  state: MyState = {
    // 可选的第二个注解,用于更好的类型推断
    count: 0,
  };
  render() {
    return (
      <div>
        {this.props.message} {this.state.count}
      </div>
    );
  }
}


Tips: 可以导出/导入/扩展这些类型/接口以供重用。类方法:像往常一样做,要记住函数的任何参数也需要输入:

class App extends React.Component<{ message: string }, { count: number }> {
  state = { count: 0 };
  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

派生状态可以使用钩子来实现,这也可以帮助设置memoization。

以下是可以注释的几种方法getDerivedStateFromProps


1、如果已显式键入派生状态并希望确保 from 的返回值getDerivedStateFromProps符合它。

class Comp extends React.Component<Props, State> {
  static getDerivedStateFromProps(
    props: Props,
    state: State
  ): Partial<State> | null {
    //
  }
}


2、希望函数的返回值确定的状态时。

class Comp extends React.Component<
  Props,
  ReturnType<typeof Comp["getDerivedStateFromProps"]>
> {
  static getDerivedStateFromProps(props: Props) {}
}


3、想要具有其他状态字段和记忆的派生状态时

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<Props, State> {
  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 定义带有丰富的类型。输入要查找的内容,通常自动完成功能会为您提供帮助。onChange这是表单事件的样子


type State = {
  text: string;
};
class App extends React.Component<Props, State> {
  state = {
    text: "",
  };
   // 在 = 的右侧输入
  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键入参数和返回值void,您还可以将类型应用于事件处理程序本身


// 在 = 的左侧输入
  onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
    this.setState({text: e.currentTarget.value})
  }


键入 onSubmit,在表单中包含不受控制的组件

如果不太关心事件的类型,可以使用 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>


事件类型列表

image.png


七、Context


基本示例

import { createContext } from "react";
interface AppContextInterface {
  name: string;
  author: string;
  url: string;
}
const AppCtx = createContext<AppContextInterface | null>(null);
// 应用程序中的提供程序
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>
);
// 在你的应用中使用
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 {
  // 使用上下文设置你想要处理的状态类型,例如
  name: string | null;
}
// 设置一个空对象为默认状态
const Context = createContext({} as ContextState);
// 像在 JavaScript 中一样设置上下文提供程序


使用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>
  );
}


注意我们需要的显式类型参数,因为我们没有默认string值:

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


连同非空断言告诉 TypeScript currentUser肯定会在那里:

return <div>HELLO {currentUser!.toUpperCase()}!</div>;
//


这是不幸的,因为我们知道稍后在我们的应用程序中,a Provider将填充上下文。

有几个解决方案:


1、可以通过断言非空来解决这个问题:

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


2、我们可以编写一个名为的辅助函数createCtx来防止访问Context未提供值的 a。通过这样做,API 相反,我们不必提供默认值,也不必检查:

import { createContext, useContext } from "react";
/**
* 创建上下文和提供者的助手,没有预先的默认值,并且
* 无需一直检查未定义。
*/
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 [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结合这个想法。

import { createContext, useContext } from "react";
/**
* 创建上下文和提供者的助手,没有预先的默认值,并且
* 无需一直检查未定义。
*/
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>();  // 指定类型,但不需要预先指定值
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 上下文设置器:

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>
  );
}
相关文章
|
7天前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
14天前
|
JavaScript 开发者 索引
TypeScript接口与类型别名:深入解析与应用实践
【7月更文挑战第10天】TypeScript的接口和类型别名是定义类型的关键工具。接口描述对象结构,用于类、对象和函数参数的形状约束,支持可选、只读属性及继承。类型别名则为复杂类型提供新名称,便于重用和简化。接口适合面向对象场景,类型别名在类型重用和复杂类型简化时更有优势。选择时要考虑场景和灵活性。
|
14天前
|
JavaScript 前端开发 IDE
React 项目中有效地使用 TypeScript
React 项目中有效地使用 TypeScript
|
14天前
|
前端开发 JavaScript 开发者
React 和 TypeScript
React 和 TypeScript
|
22天前
|
JavaScript 前端开发 索引
TypeScript 的数组类型
TypeScript 的数组类型
21 1
|
18天前
|
JavaScript 前端开发 程序员
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
17 0
|
22天前
|
JavaScript 安全
TypeScript(十一)泛型工具类型
TypeScript(十一)泛型工具类型
19 0
|
22天前
|
JavaScript 前端开发 编译器
TypeScript(五)类型别名及类型符号
TypeScript(五)类型别名及类型符号
20 0
|
22天前
|
JavaScript 前端开发
TypeScript(二)基本类型和特殊类型
TypeScript(二)基本类型和特殊类型
14 0
|
26天前
|
JavaScript 安全 开发者
Vue3 中更好地利用 TypeScript 的类型系统来提高代码质量
Vue3 中更好地利用 TypeScript 的类型系统来提高代码质量