Skip to content

类型推导和泛型

请描述如何在 React 组件中使用泛型,并给出一个示例,展示如何为具有不同 prop 类型的多个组件创建一个高阶组件。

题解

  • 步骤 1:定义组件和它们的 prop 类型
tsx
import React from 'react'

interface PropsA {
  message: string
}

const ComponentA: React.FC<PropsA> = ({ message }) => <div>{message}</div>

interface PropsB {
  count: number
}

const ComponentB: React.FC<PropsB> = ({ count }) => <div>{count}</div>
import React from 'react'

interface PropsA {
  message: string
}

const ComponentA: React.FC<PropsA> = ({ message }) => <div>{message}</div>

interface PropsB {
  count: number
}

const ComponentB: React.FC<PropsB> = ({ count }) => <div>{count}</div>
  • 步骤 2:创建泛型高阶组件
tsx
function withHOC<P>(WrappedComponent: React.ComponentType<P>) {
  return class extends React.Component<P> {
    render() {
      return <WrappedComponent {...this.props} />
    }
  }
}
function withHOC<P>(WrappedComponent: React.ComponentType<P>) {
  return class extends React.Component<P> {
    render() {
      return <WrappedComponent {...this.props} />
    }
  }
}
  • 步骤 3:使用高阶组件
tsx
const EnhancedComponentA = withHOC(ComponentA)
const EnhancedComponentB = withHOC(ComponentB)
const EnhancedComponentA = withHOC(ComponentA)
const EnhancedComponentB = withHOC(ComponentB)
  • 步骤 4:在应用中使用
tsx
function App() {
  return (
    <div>
      <EnhancedComponentA message="Hello from A" />
      <EnhancedComponentB count={10} />
    </div>
  )
}
function App() {
  return (
    <div>
      <EnhancedComponentA message="Hello from A" />
      <EnhancedComponentB count={10} />
    </div>
  )
}

使用 TypeScript 的高阶组件(HOCs)

如何使用 TypeScript 为高阶组件(HOC)编写类型定义?请提供一个示例,展示如何为增强 React 组件的 HOC 添加类型注解。

题解

  • 步骤一:类型定义
tsx
import React from 'react'

// 定义传入组件的额外属性
interface WithExtraInfoProps {
  info: string
}

// 高阶组件的类型定义
function withExtraInfo<P extends object>(
  WrappedComponent: React.ComponentType<P>
): React.ComponentType<P & WithExtraInfoProps> {
  // 高阶组件的实现
  const ComponentWithExtraInfo = (props: P & WithExtraInfoProps) => (
    <WrappedComponent {...props} />
  )

  return ComponentWithExtraInfo
}
import React from 'react'

// 定义传入组件的额外属性
interface WithExtraInfoProps {
  info: string
}

// 高阶组件的类型定义
function withExtraInfo<P extends object>(
  WrappedComponent: React.ComponentType<P>
): React.ComponentType<P & WithExtraInfoProps> {
  // 高阶组件的实现
  const ComponentWithExtraInfo = (props: P & WithExtraInfoProps) => (
    <WrappedComponent {...props} />
  )

  return ComponentWithExtraInfo
}
  • 步骤二:使用高阶组件
tsx
interface MyComponentProps {
  name: string
}

const MyComponent: React.FC<MyComponentProps> = ({ name, info }) => (
  <div>
    <p>Name: {name}</p>
    <p>Info: {info}</p>
  </div>
)

// 使用高阶组件增强 MyComponent
const EnhancedMyComponent = withExtraInfo(MyComponent)

// 在应用中使用 EnhancedMyComponent
const App = () => <EnhancedMyComponent name="Alice" info="Extra Information" />
interface MyComponentProps {
  name: string
}

const MyComponent: React.FC<MyComponentProps> = ({ name, info }) => (
  <div>
    <p>Name: {name}</p>
    <p>Info: {info}</p>
  </div>
)

// 使用高阶组件增强 MyComponent
const EnhancedMyComponent = withExtraInfo(MyComponent)

// 在应用中使用 EnhancedMyComponent
const App = () => <EnhancedMyComponent name="Alice" info="Extra Information" />
  • 在这个例子中,MyComponent是一个接收nameinfo属性的组件。我们使用withExtraInfo 来创建一个新的组件EnhancedMyComponent,它除了接收MyComponent的所有属性外,还接收一个额外的info属性。

TypeScript 中的装饰器

请解释 TypeScript 装饰器的概念,并展示如何在 React 组件中使用它们。

装饰器的常见类型

  1. 类装饰器:应用于类构造函数,用于观察、修改或替换类定义。
  2. 方法装饰器:应用于方法的属性描述符,可以用来监视、修改或替换方法定义。
  3. 访问器装饰器:应用于访问器的属性描述符。
  4. 属性装饰器:应用于属性的特性。
  5. 参数装饰器:应用于类构造函数或方法的参数。

题解

tsx
// 定义一个简单的装饰器
function withLogging<T extends { new (...args: any[]): {} }>(Component: T) {
  return class extends Component {
    componentDidMount() {
      console.log(`Component ${Component.name} is mounted.`)
    }

    componentWillUnmount() {
      console.log(`Component ${Component.name} is unmounting.`)
    }
  }
}

// 应用装饰器
@withLogging
class MyComponent extends React.Component {
  render() {
    return <div>Hello, World!</div>
  }
}

export default MyComponent
// 定义一个简单的装饰器
function withLogging<T extends { new (...args: any[]): {} }>(Component: T) {
  return class extends Component {
    componentDidMount() {
      console.log(`Component ${Component.name} is mounted.`)
    }

    componentWillUnmount() {
      console.log(`Component ${Component.name} is unmounting.`)
    }
  }
}

// 应用装饰器
@withLogging
class MyComponent extends React.Component {
  render() {
    return <div>Hello, World!</div>
  }
}

export default MyComponent

复杂状态管理

在使用 TypeScript 的 React 项目中,如何有效地管理复杂状态?请提供一个使用 Context API 和 useReducer 的示例。

题解

  1. 定义 State 类型和 Action 类型
ts
// 定义状态类型
type StateType = {
  count: number
}

// 定义动作类型
type ActionType =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }
// 定义状态类型
type StateType = {
  count: number
}

// 定义动作类型
type ActionType =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }
  1. 创建 Reducer 函数
ts
const reducer = (state: StateType, action: ActionType): StateType => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'reset':
      return { count: 0 }
    default:
      return state
  }
}
const reducer = (state: StateType, action: ActionType): StateType => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'reset':
      return { count: 0 }
    default:
      return state
  }
}
  1. 创建 Context 和 Provider
tsx
import React, { createContext, useReducer, useContext } from 'react'

// 创建 Context
const StateContext = createContext<
  [StateType, React.Dispatch<ActionType>] | undefined
>(undefined)

// 创建 Provider 组件
type StateProviderProps = { children: React.ReactNode }

export const StateProvider: React.FC<StateProviderProps> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, { count: 0 })

  return (
    <StateContext.Provider value={[state, dispatch]}>
      {children}
    </StateContext.Provider>
  )
}
import React, { createContext, useReducer, useContext } from 'react'

// 创建 Context
const StateContext = createContext<
  [StateType, React.Dispatch<ActionType>] | undefined
>(undefined)

// 创建 Provider 组件
type StateProviderProps = { children: React.ReactNode }

export const StateProvider: React.FC<StateProviderProps> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, { count: 0 })

  return (
    <StateContext.Provider value={[state, dispatch]}>
      {children}
    </StateContext.Provider>
  )
}
  1. 在应用中使用
tsx
const CounterComponent = () => {
  const context = useContext(StateContext)

  if (!context)
    throw new Error('useContext must be inside a Provider with a value')

  const [state, dispatch] = context

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  )
}

// 在应用中使用
const App = () => (
  <StateProvider>
    <CounterComponent />
  </StateProvider>
)
const CounterComponent = () => {
  const context = useContext(StateContext)

  if (!context)
    throw new Error('useContext must be inside a Provider with a value')

  const [state, dispatch] = context

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  )
}

// 在应用中使用
const App = () => (
  <StateProvider>
    <CounterComponent />
  </StateProvider>
)

性能优化和 TypeScript

请讨论使用 TypeScript 编写的 React 应用中的性能优化技巧。特别是,如何利用 TypeScript 的类型系统来减少运行时错误和提高渲染效率?

题解

利用类型系统减少运行时错误

  • 强类型接口和组件属性: 使用 TypeScript 的强类型特性为组件定义明确的接口。这样可以在编译时捕捃可能导致运行时错误的问题。
  • 避免使用 any 类型: 尽量避免使用 any 类型,因为它会绕过类型检查。更准确的类型定义可以帮助发现潜在的错误。
  • 利用类型守卫: 使用类型守卫(Type Guards)来确保传递给组件的数据符合期望的类型。
tsx
interface UserProps {
  name: string
  age: number
  isActive?: boolean
}

const UserComponent: React.FC<UserProps> = ({
  name,
  age,
  isActive = false
}) => {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      {isActive && <p>Active User</p>}
    </div>
  )
}
interface UserProps {
  name: string
  age: number
  isActive?: boolean
}

const UserComponent: React.FC<UserProps> = ({
  name,
  age,
  isActive = false
}) => {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      {isActive && <p>Active User</p>}
    </div>
  )
}

优化渲染性能

  • 使用 React.memo: 使用 React.memo 来避免不必要的渲染。React.memo 会缓存组件的渲染结果,并在下一次渲染时进行比较。如果组件的 props 没有发生变化,那么就会直接使用缓存的结果,从而避免不必要的渲染。
  • 使用 useCallback: 使用 useCallback 来缓存函数,避免不必要的函数创建。
  • 使用 useMemo: 使用 useMemo 来缓存计算结果,避免不必要的计算。
tsx
import React, { useState, useCallback, useMemo } from 'react'

const UserComponent: React.FC = () => {
  const [name, setName] = useState('')
  const [age, setAge] = useState(0)

  const handleNameChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setName(e.target.value)
    },
    []
  )

  const handleAgeChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setAge(Number(e.target.value))
    },
    []
  )

  const isAdult = useMemo(() => age >= 18, [age])

  return (
    <div>
      <h2>User</h2>
      <p>
        <label>Name: </label>
        <input type="text" value={name} onChange={handleNameChange} />
      </p>
      <p>
        <label>Age: </label>
        <input type="number" value={age} onChange={handleAgeChange} />
      </p>
      <p>Is Adult: {isAdult ? 'Yes' : 'No'}</p>
    </div>
  )
}
import React, { useState, useCallback, useMemo } from 'react'

const UserComponent: React.FC = () => {
  const [name, setName] = useState('')
  const [age, setAge] = useState(0)

  const handleNameChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setName(e.target.value)
    },
    []
  )

  const handleAgeChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setAge(Number(e.target.value))
    },
    []
  )

  const isAdult = useMemo(() => age >= 18, [age])

  return (
    <div>
      <h2>User</h2>
      <p>
        <label>Name: </label>
        <input type="text" value={name} onChange={handleNameChange} />
      </p>
      <p>
        <label>Age: </label>
        <input type="number" value={age} onChange={handleAgeChange} />
      </p>
      <p>Is Adult: {isAdult ? 'Yes' : 'No'}</p>
    </div>
  )
}

代码分割和懒加载

  • 动态导入组件: 使用 React.lazy 和 import() 来动态加载组件,这有助于减小初始包的大小,并提高应用加载速度。
  • 类型化的异步加载: 确保异步加载的组件和数据具有正确的类型,这可以减少因类型不匹配引起的渲染问题。
tsx
const LazyLoadedComponent = React.lazy(() => import('./LazyLoadedComponent'))

const App: React.FC = () => {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <LazyLoadedComponent />
    </React.Suspense>
  )
}
const LazyLoadedComponent = React.lazy(() => import('./LazyLoadedComponent'))

const App: React.FC = () => {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <LazyLoadedComponent />
    </React.Suspense>
  )
}

利用类型系统进行静态分析

  • 集成静态代码分析工具: 结合 TypeScript 和诸如 ESLint 的工具,可以在编译阶段就发现潜在的性能问题。
  • 自定义类型辅助性能分析: 利用 TypeScript 的高级类型功能,如条件类型和映射类型,可以创建辅助工具来分析和优化渲染性能。
json
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": ["plugin:@typescript-eslint/recommended"]
}
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": ["plugin:@typescript-eslint/recommended"]
}

性能测试和监控

  • 类型化的性能监控: 利用 TypeScript 的类型系统来定义性能监控的数据结构,确保在收集和分析性能数据时的准确性和一致性。
ts
interface PerformanceData {
  componentLoadTime: number
  apiResponseTime: number
  // 其他性能指标...
}

const monitorPerformance = (data: PerformanceData) => {
  // 发送性能数据到监控系统
}

// 示例用法
monitorPerformance({
  componentLoadTime: 123,
  apiResponseTime: 456
})
interface PerformanceData {
  componentLoadTime: number
  apiResponseTime: number
  // 其他性能指标...
}

const monitorPerformance = (data: PerformanceData) => {
  // 发送性能数据到监控系统
}

// 示例用法
monitorPerformance({
  componentLoadTime: 123,
  apiResponseTime: 456
})

测试和 Mocking

在 TypeScript 环境中,如何为 React 组件编写单元测试?请展示如何 mock 依赖项,并解释类型安全的 mocking 策略。

题解

  1. 设置测试环境:使用测试框架,如 Jest。Jest 支持 TypeScript,可以轻松集成到 React 项目中。
  2. 编写测试用例:创建测试文件(例如Component.test.tsx),并在其中编写针对组件的测试用例。
  3. Mock 依赖项:当组件依赖外部模块或服务时,可以使用 Jest 的 jest.mock() 函数来模拟这些依赖项。这有助于隔离测试,确保它们不依赖于外部系统的状态。
  4. 类型安全的 Mocking:在 TypeScript 中,确保 mock 对象遵循正确的类型非常重要。这通常通过定义具有相同接口的 mock 函数或对象来实现。
tsx
import React from 'react'
import { someService } from './someService'

const MyComponent = () => {
  const [data, setData] = React.useState<string>('')

  React.useEffect(() => {
    someService.getData().then(setData)
  }, [])

  return <div>{data}</div>
}

export default MyComponent
import React from 'react'
import { someService } from './someService'

const MyComponent = () => {
  const [data, setData] = React.useState<string>('')

  React.useEffect(() => {
    someService.getData().then(setData)
  }, [])

  return <div>{data}</div>
}

export default MyComponent
ts
export const someService = {
  getData: async () => {
    // 实际获取数据的逻辑
  }
}
export const someService = {
  getData: async () => {
    // 实际获取数据的逻辑
  }
}
tsx
import React from 'react'
import { render, waitFor } from '@testing-library/react'
import MyComponent from './MyComponent'
import { someService } from './someService'

jest.mock('./someService', () => ({
  someService: {
    getData: jest.fn()
  }
}))

describe('MyComponent', () => {
  it('should display data from someService', async () => {
    // 设置 mock 返回值
    ;(someService.getData as jest.Mock).mockResolvedValue('Mocked Data')

    const { getByText } = render(<MyComponent />)

    await waitFor(() => {
      expect(getByText('Mocked Data')).toBeInTheDocument()
    })
  })
})
import React from 'react'
import { render, waitFor } from '@testing-library/react'
import MyComponent from './MyComponent'
import { someService } from './someService'

jest.mock('./someService', () => ({
  someService: {
    getData: jest.fn()
  }
}))

describe('MyComponent', () => {
  it('should display data from someService', async () => {
    // 设置 mock 返回值
    ;(someService.getData as jest.Mock).mockResolvedValue('Mocked Data')

    const { getByText } = render(<MyComponent />)

    await waitFor(() => {
      expect(getByText('Mocked Data')).toBeInTheDocument()
    })
  })
})

类型安全的事件处理

在 React 中,如何处理事件并确保类型安全?请提供一个示例,说明如何为不同类型的事件(如鼠标事件、键盘事件)编写类型安全的事件处理函数。

题解

在React中处理事件并确保类型安全通常涉及到使用TypeScript以及React的事件类型。TypeScript可以帮助你确保事件处理函数接收正确类型的事件对象。
下面是一个示例,展示了如何为鼠标事件和键盘事件编写类型安全的事件处理函数:

tsx
import React from 'react'

const App: React.FC = () => {
  // 类型安全的鼠标事件处理函数
  const handleMouseClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    console.log(`Mouse clicked at X: ${event.clientX}, Y: ${event.clientY}`)
  }

  // 类型安全的键盘事件处理函数
  const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
    console.log(`Key pressed: ${event.key}`)
  }

  return (
    <div>
      <button onClick={handleMouseClick}>Click me</button>
      <input onKeyPress={handleKeyPress} placeholder="Type something" />
    </div>
  )
}

export default App
import React from 'react'

const App: React.FC = () => {
  // 类型安全的鼠标事件处理函数
  const handleMouseClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    console.log(`Mouse clicked at X: ${event.clientX}, Y: ${event.clientY}`)
  }

  // 类型安全的键盘事件处理函数
  const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
    console.log(`Key pressed: ${event.key}`)
  }

  return (
    <div>
      <button onClick={handleMouseClick}>Click me</button>
      <input onKeyPress={handleKeyPress} placeholder="Type something" />
    </div>
  )
}

export default App
tsx
import React, { useState, ChangeEvent, FormEvent } from 'react'

const App: React.FC = () => {
  const [inputValue, setInputValue] = useState('')

  // 类型安全的表单更改事件处理函数
  const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value)
  }

  // 类型安全的表单提交事件处理函数
  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    console.log(`Form submitted with input: ${inputValue}`)
  }

  //它可以接收任意类型的参数。这种方式使得函数非常灵活,可以处理多种类型的自定义事件。
  const handleCustomEvent = <T,>(data: T) => {
    console.log('Handling custom event with data:', data)
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={inputValue}
          onChange={handleInputChange}
          placeholder="Enter something"
        />
        <button type="submit">Submit</button>
      </form>

      {/* 触发自定义事件处理函数 */}
      <button onClick={() => handleCustomEvent<string>('Custom String')}>
        Trigger Custom Event
      </button>
      <button onClick={() => handleCustomEvent<number>(123)}>
        Trigger Custom Event with Number
      </button>
    </div>
  )
}

export default App
import React, { useState, ChangeEvent, FormEvent } from 'react'

const App: React.FC = () => {
  const [inputValue, setInputValue] = useState('')

  // 类型安全的表单更改事件处理函数
  const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value)
  }

  // 类型安全的表单提交事件处理函数
  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    console.log(`Form submitted with input: ${inputValue}`)
  }

  //它可以接收任意类型的参数。这种方式使得函数非常灵活,可以处理多种类型的自定义事件。
  const handleCustomEvent = <T,>(data: T) => {
    console.log('Handling custom event with data:', data)
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={inputValue}
          onChange={handleInputChange}
          placeholder="Enter something"
        />
        <button type="submit">Submit</button>
      </form>

      {/* 触发自定义事件处理函数 */}
      <button onClick={() => handleCustomEvent<string>('Custom String')}>
        Trigger Custom Event
      </button>
      <button onClick={() => handleCustomEvent<number>(123)}>
        Trigger Custom Event with Number
      </button>
    </div>
  )
}

export default App

自定义 Hooks 与 TypeScript

如何在 TypeScript 中编写自定义 React Hooks,并确保类型的正确性?请提供一个自定义 Hook 的示例,并解释其类型注解的过程。

题解

创建一个名为 useForm 的 Hook,它用于处理表单的输入、验证和提交

ts
interface FormOptions<TValues> {
  initialValues: TValues
  onSubmit: (values: TValues) => void
  validate?: (values: TValues) => Partial<Record<keyof TValues, string>>
}

interface FormReturn<TValues> {
  values: TValues
  handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void
  handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void
  errors: Partial<Record<keyof TValues, string>>
}
interface FormOptions<TValues> {
  initialValues: TValues
  onSubmit: (values: TValues) => void
  validate?: (values: TValues) => Partial<Record<keyof TValues, string>>
}

interface FormReturn<TValues> {
  values: TValues
  handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void
  handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void
  errors: Partial<Record<keyof TValues, string>>
}
ts
import { useState } from 'react'

function useForm<TValues>(options: FormOptions<TValues>): FormReturn<TValues> {
  const [values, setValues] = useState<TValues>(options.initialValues)
  const [errors, setErrors] = useState<Partial<Record<keyof TValues, string>>>(
    {}
  )

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValues({
      ...values,
      [event.target.name]: event.target.value
    })
  }

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    if (options.validate) {
      const newErrors = options.validate(values)
      setErrors(newErrors)
      if (Object.keys(newErrors).length > 0) return
    }
    options.onSubmit(values)
  }

  return { values, handleChange, handleSubmit, errors }
}
import { useState } from 'react'

function useForm<TValues>(options: FormOptions<TValues>): FormReturn<TValues> {
  const [values, setValues] = useState<TValues>(options.initialValues)
  const [errors, setErrors] = useState<Partial<Record<keyof TValues, string>>>(
    {}
  )

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValues({
      ...values,
      [event.target.name]: event.target.value
    })
  }

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    if (options.validate) {
      const newErrors = options.validate(values)
      setErrors(newErrors)
      if (Object.keys(newErrors).length > 0) return
    }
    options.onSubmit(values)
  }

  return { values, handleChange, handleSubmit, errors }
}
tsx
import React from 'react'
import { useForm } from './useForm'

interface FormValues {
  email: string
  password: string
}

const MyForm = () => {
  const form = useForm<FormValues>({
    initialValues: { email: '', password: '' },
    onSubmit: (values) => console.log(values),
    validate: (values) => {
      let errors: Partial<Record<keyof FormValues, string>> = {}
      if (!values.email) errors.email = 'Email required'
      if (!values.password) errors.password = 'Password required'
      return errors
    }
  })

  return (
    <form onSubmit={form.handleSubmit}>
      <input
        name="email"
        value={form.values.email}
        onChange={form.handleChange}
      />
      {form.errors.email && <p>{form.errors.email}</p>}
      <input
        name="password"
        type="password"
        value={form.values.password}
        onChange={form.handleChange}
      />
      {form.errors.password && <p>{form.errors.password}</p>}
      <button type="submit">Submit</button>
    </form>
  )
}
import React from 'react'
import { useForm } from './useForm'

interface FormValues {
  email: string
  password: string
}

const MyForm = () => {
  const form = useForm<FormValues>({
    initialValues: { email: '', password: '' },
    onSubmit: (values) => console.log(values),
    validate: (values) => {
      let errors: Partial<Record<keyof FormValues, string>> = {}
      if (!values.email) errors.email = 'Email required'
      if (!values.password) errors.password = 'Password required'
      return errors
    }
  })

  return (
    <form onSubmit={form.handleSubmit}>
      <input
        name="email"
        value={form.values.email}
        onChange={form.handleChange}
      />
      {form.errors.email && <p>{form.errors.email}</p>}
      <input
        name="password"
        type="password"
        value={form.values.password}
        onChange={form.handleChange}
      />
      {form.errors.password && <p>{form.errors.password}</p>}
      <button type="submit">Submit</button>
    </form>
  )
}