React 与 TypeScript 极客园移动端
一、基础环境创建
npm create vite@latest react-ts-pro -- --template react-ts
npm i
npm run dev
二、useState
1. 自动推导
通常 React 会根据传入 useState 的默认值来自动推导类型,不需要显式标注类型
const [value, toggle] = useState(false)
说明:
(1)value:类型为 boolean
(2)toggle: 参数类型为 boolean
// react + ts
// 根据初始值自动推断
// 场景:明确的初始值
import { useState } from 'react'function App() {const [value, toggle] = useState(false)const [list, setList] = useState([1, 2, 3])const changeValue = () => {toggle(true)}const changeList = () => {setList([4])}return <>this is app {list}</>
}export default App
2. 泛型参数
useState 本身是一个泛型函数,可以传入具体的自定义类型
type User = {name: stringage: number
}const [user, setUser] = useState<User>()
说明:
(1)限制 useState 函数参数的初始值必须满足类型为:User | () => User
(2)限制 useState 函数的参数必须满足类型为:User | () => User | undefined
(3)user 状态数据具备 User 类型相关的类型提示
// react + ts
import { useState } from 'react'type User = {name: stringage: number
}function App() {// 1. 限制初始值的类型// const [user, setUser] = useState<User>({// name: 'jack',// age: 18,// })// const [user, setUser] = useState<User>(() => {// return {// name: 'jack',// age: 18,// }// })const [user, setUser] = useState<User>({name: 'jack',age: 18,})const changeUser = () => {setUser(() => ({name: 'john',age: 28,}))}return <>this is app {user.name}</>
}export default App
3. 初始值为 null
当我们不知道状态的初始值是什么,将 useState 的初始值为 null 是一个常见的做法,可以通过具体类型联合 null 来做显式注解
type User = {name: stringage: number
}const [user, setUser] = useState<User | null>(null)
说明:
(1)限制 useState 函数参数的初始值可以是 User | null
(2)限制 setUser 函数的参数类型可以是 User | null
// react + ts
import { useState } from 'react'type User = {name: stringage: number
}function App() {const [user, setUser] = useState<User | null>(null)const changeUser = () => {setUser(null)setUser({name: 'jack',age: 18,})}// 为了类型安全 可选链做类型守卫// 只有user不为null(不为空值)的时候才进行点运算return <>this is app {user?.age}</>
}export default App
三、Props 与 TypeScript
1. 基础使用
为组件 prop 添加类型,本质是给函数的参数做类型注解,可以使用 type 对象类型或者 interface 接口来做注解
type Props = {className: string
}function Button(props: Props){const { className } = propsreturn <button className={className}>click me</button>
}
说明:Button 组件只能传入名称为 className 的 prop 参数,类型为 string,且为必填
// props + ts// type Props = {
// className: string
// }interface Props {className: stringtitle?: string
}function Button(props: Props) {const { className } = propsreturn <button className={className}>click me </button>
}function App() {return (<><Button className="test" title="this is title" /></>)
}export default App
2. 特殊的 children 属性
children 是一个比较特殊的 prop,支持多种不同类型数据的传入,需要通过一个内置的 ReactNode 类型来做注释
type Props = {className: stringchildren: React.ReactNode
}function Button(props: Props){const { className, children } = propsreturn <button className={className}>{children}</button>
}
说明:注解之后,children 可以是多种类型,包括:React.ReactElement、string、number、React.ReactFragment、React.ReactPortal、boolean、null、undefined
// props + ts
type Props = {className: stringchildren: React.ReactNode
}function Button(props: Props) {const { className, children } = propsreturn <button className={className}>{children} </button>
}function App() {return (<><Button className="test">click me!</Button><Button className="test"><span>this is span</span></Button></>)
}export default App
3. 为事件 prop 添加类型
组件经常执行类型为函数的 prop 实现子传父,这类 prop 重点在于函数参数类型的注解
说明:
(1)在组件内部调用时需要遵守类型的约束,参数传递需要满足要求
(2)绑定 prop 时如果绑定内联函数直接可以推断出参数类型,否则需要单独注解匹配的参数类型
// props + ts
type Props = {onGetMsg?: (msg: string) => void
}function Son(props: Props) {const { onGetMsg } = propsconst clickHandler = () => {onGetMsg?.('this is msg')}return <button onClick={clickHandler}>sendMsg</button>
}function App() {const getMsgHandler = (msg: string) => {console.log(msg)}return (<><Son onGetMsg={(msg) => console.log(msg)} /><Son onGetMsg={getMsgHandler} /></>)
}export default App
四、useRef 与 TypeScript
1. 获取 dom
获取 dom 的场景,可以直接把要获取的 dom 元素的类型当成泛型参数传递给 useRef, 可以推导出 .current 属性的类型
function App(){const domRef = useRef<HTMLInputElement>(null)useEffect(() => {domRef.current?.focus()}, [])return (<><input ref={domRef}/></>)
}
2. 引用稳定的存储器
把 useRef 当成引用稳定的存储器使用的场景可以通过泛型传入联合类型来做,比如定时器的场景:
function App(){const timerRef = useRef<number | undefined>(undefined)useEffect(() => {timerRef.current = setInterval(() => {console.log('1')}, 1000)return () => clearInterval(timerRef.current)}, [])return <>this is div</>
}
// useRef + ts
import { useEffect, useRef } from 'react'// 1. 获取dom
// 2. 稳定引用的存储器(定时器管理)function App() {const domRef = useRef<HTMLInputElement>(null)const timerId = useRef<number | undefined>(undefined)useEffect(() => {// 可选链 前面不为空值(null / undefined)执行点运算// 类型守卫 防止出现空值点运算错误domRef.current?.focus()timerId.current = setInterval(() => {console.log('123')}, 1000)return () => clearInterval(timerId.current)}, [])return (<><input ref={domRef} /></>)
}export default App
五、极客园移动端
1. 项目环境创建
基于 vite 创建开发环境
vite 是一个框架无关的前端工具链,可以快速的生成一个 React + TS 的开发环境,并且可以提供快速的开发体验
npm create vite@latest react-jike-mobile -- --template react-ts
说明:
(1)npm create vite@latest 固定写法 (使用最新版本 vite 初始化项目)
(2)react-ts-pro 项目名称 (可以自定义)
(3)-- --template react-ts 指定项目模版位 react + ts
2. 安装 Ant Design Mobile
Ant Design Mobile 是 Ant Design 家族里专门针对移动端的组件库
看文档!
3. 配置基础路由
初始化路由
React 的路由初始化,我们采用 react-router-dom 进行配置
4. 配置别名路径
场景:项目中各个模块之间的互相导入导出,可以通过 @ 别名路径做路径优化,经过配置 @ 相当于 src 目录,比如:
import Detail from '../pages/Detail'import Detail from '@/pages/Detail'
步骤:
(1)让 vite 做路径解析 (真实的路径转换)
(2)让 vscode 做智能路径提示 (开发者体验)
vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'// https://vitejs.dev/config/
export default defineConfig({plugins: [react()],resolve: {alias: {'@': path.resolve(_dirname, './src')},},})
安装 node 类型包
npm i @types/node -D
tsconfig.json
"compilerOptions": {..."baseUrl": ".","paths": {"@/*": ["src/*"]},
}
5. 安装 axios
场景:axios 作为最流行的请求插件,同样是类型友好的,基于 axios 做一些基础的封装
(1)安装 axios 到项目
(2)在 utils 中封装 http 模块,主要包括 接口基地址、超时时间、拦截器
(3)在 utils 中做统一导出
npm i axios
utils/http.ts
// axios的封装处理
import axios from "axios"const httpInstance = axios.create({baseURL: 'http://geek.itheima.net',timeout: 5000
})// 添加请求拦截器
httpInstance.interceptors.request.use((config) => {return config
}, (error) => {return Promise.reject(error)
})// 添加响应拦截器
httpInstance.interceptors.response.use((response) => {// 2xx 范围内的状态码都会触发该函数。// 对响应数据做点什么return response.data
}, (error) => {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error)
})export { httpInstance }
utils/index.js
// 统一中转工具模块函数
// import { httpInstance } from '@/utils'import { httpInstance as http } from './request'export {http,
}
6. 封装 API 模块
场景:axios 提供了 request 泛型方法,方便我们传入类型参数推导出接口返回值的类型