类型推导和泛型
请描述如何在 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
是一个接收name
和info
属性的组件。我们使用withExtraInfo
来创建一个新的组件EnhancedMyComponent
,它除了接收MyComponent的所有属性外,还接收一个额外
的info属性。
TypeScript 中的装饰器
请解释 TypeScript 装饰器的概念,并展示如何在 React 组件中使用它们。
装饰器的常见类型
- 类装饰器:应用于类构造函数,用于观察、修改或替换类定义。
- 方法装饰器:应用于方法的属性描述符,可以用来监视、修改或替换方法定义。
- 访问器装饰器:应用于访问器的属性描述符。
- 属性装饰器:应用于属性的特性。
- 参数装饰器:应用于类构造函数或方法的参数。
题解
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 的示例。
题解
- 定义 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' }
- 创建 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
}
}
- 创建 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>
)
}
- 在应用中使用
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 策略。
题解
- 设置测试环境:使用测试框架,如
Jest
。Jest 支持 TypeScript,可以轻松集成到 React 项目中。 - 编写测试用例:创建测试文件(例如
Component.test.tsx
),并在其中编写针对组件的测试用例。 - Mock 依赖项:当组件依赖外部模块或服务时,可以使用 Jest 的
jest.mock()
函数来模拟这些依赖项。这有助于隔离测试,确保它们不依赖于外部系统的状态。 - 类型安全的 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>
)
}