项目启动
-
新建项目
npx create-react-app project-name --template typescript npm install typescript-plugin-css-modules --save-dev若运行出错,可
npm config set registry https://registry.npmjs.org/如需将 TypeScript 添加到现有的 Create React App 项目中:
npm install --save typescript @types/node @types/react @types/react-dom @types/jest -
JSS 模块化
tsconfig.json下增加配置// compilerOptions 下增加 "noImplicitAny": false, "plugins": [ { "name": "typescript-plugin-css-modules" } ]新建文件
src/custom.d.ts文件declare module '*.css' { const css: { [key: string]: string } export default css }App.css文件改成App.module.css,并改变
App.tsx中引入方式import styles from './App.module.css' -
配置 eslint、 prettier 和 commitlint 规范工程
-
配置绝对路径
import logo from 'logo.svg' // src/logo.svgtsconfig.json下增加配置// compilerOptions 下增加 "baseUrl": "src", -
配置格式化
npm install prettier --save-dev新建文件
project-name/.prettierrc.json,可自行配置新建文件
project-name/.prettierignore
build coverage- 执行
npx mrm lint-staged,lint-staged是一个在git暂存文件上运行linters的工具。它将根据package.json依赖项中的代码质量工具来安装和配置husky和lint-staged,因此请确保在此之前安装(npm install --save-dev)并配置所有代码质量工具,如Prettier和ESlint。
"husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{js,css,md,ts,tsx,jsx}": "prettier --write" }- 安装依赖
npm install eslint-config-prettier --save-dev
src/package.json
"eslintConfig": { "extends": [ "react-app", "react-app/jest", "prettier" ] }, -
配置 commitlint
安装:npm install --save-dev @commitlint/config-conventional @commitlint/cli // 生成配置文件commitlint.config.js,当然也可以是 .commitlintrc.js echo "module.exports = {extends: ['@commitlint/config-conventional']};" > commitlint.config.js配置:在
husky的配置加入CommitlIint配置"husky": { "hooks": { "commit-msg": "commitlint -E $HUSKY_GIT_PARAMS" } },
-
引入 UI 框架 Ant Design
npm install antd @ant-design/icons --save
全局引用 Ant Design 样式文件
index.css中@import '~antd/dist/antd.css'按需引入引入 antd(具体用法见文档)
-
组件化思想(使用方便,易于独立)
src/components/index.ts:组件导出export * from './header' export * from './footer' ......src/components/footer:封装 Footer 组件src/components/footer/Footer.tsx:组件逻辑import React from 'react' import { Layout, Typography } from 'antd' export const Footer: React.FC = () => { return ( <Layout.Footer> <Typography.Title level={3} style={{ textAlign: 'center' }}> 版权所有 @ React 旅游网 </Typography.Title> </Layout.Footer> ) }src/components/footer/index.tsx:组件导出export * from './Footer'src/App.tsx:根组件import React from 'react'; import styles from './App.module.css'; import { Header, Footer } from './components' function App() { return ( <div className={styles.app}> <Header /> <Footer /> </div> ); } export default App;
引入路由系统
-
react-router-dom用于浏览器,处理Web App的路由- 安装
npm install react-router-dom --save-dev - 会自动安装
react-router核心框架 -
<Link />组件可以渲染出<a/>标签 -
<BrowserRouter/>组件利用H5 API实现路由切换 -
<HashRouter/>组件则利用原生JS中的window.location.hash来实现路由切换
- 安装
react-router-native用于React Native,处理手机app的路由react-router-redux提供了路由中间件,处理redux的集成react-router-config用来静态配置路由
react-router-dom
-
配置路由
import { BrowserRouter, Route } from 'react-router-dom' // 若匹配到多个路由,则UI显示为多组件共存 // exact:精准匹配 <div className={styles.app}> <BrowserRouter> // HomePage 中,可通过 props 获取到路由信息 <Route exact path="/" component={HomePage} /> <Route path="/login" render={() => <h1>登录</h1>} /> </BrowserRouter> </div> -
带参数路由匹配
// App.tsx import React from 'react' import { BrowserRouter, Route, Switch } from 'react-router-dom' import styles from './App.module.css' import { HomePage, LoginPage, RegisterPage, DetailPage } from './pages' function App() { return ( <div className={styles.app}> <BrowserRouter> <Switch> <Route exact path="/" component={HomePage} /> <Route path="/login" component={LoginPage} /> <Route path="/register" component={RegisterPage} /> <Route path="/detail/:id" component={DetailPage} /> </Switch> </BrowserRouter> </div> ) } export default App// Detail.tsx import React from 'react' import { RouteComponentProps } from 'react-router-dom' interface MatchParams { id: string } export const DetailPage: React.FC<RouteComponentProps<MatchParams>> = (props) => { return ( <> <h1>详情{props.match.params.id}</h1> </> ) } 路由跳转
-
withRouter
import { RouteComponentProps, withRouter } from 'react-router-dom' interface PropsType extends RouteComponentProps { id: number ...... } const ProductImageComponent: React.FC<PropsType> = ({ id, history, location, match }) => { return ( <div onClick={() => history.push(`detail/${id}`)}> ...... </div> ) } export const ProductImage = withRouter(ProductImageComponent) -
useHistory
import { useHistory } from 'react-router-dom' const history = useHistory() <Button onClick={() => history.push('/register')}>注册</Button> <Button onClick={() => history.push('/login')}>登录</Button> -
Link
import { Link } from 'react-router-dom' <Link to={`detail/${id}`}>......</Link>
redux 状态管理
详见 react-redux
常见 MOCK 方案
代码入侵:直接在代码中写死
Mock数据,或者请求本地的json文件请求拦截,如
Mock.js接口管理工具,如
rap、swagger、yapi-
本地
node服务,如json-server,推荐~~- 安装:
sudo npm install json-server -g - 配置:
project-name下新建文件db.json(RESTful 规范接口,如增删改查资源)、middleware.js(非 RESTful 规范接口,如登录、注册等) - 启动:
json-server json-server-mock/db.json --middlewares json-server-mock/middleware.js - 使用:
postman中使用restful规范操作,json联动 - 若在
package.json中配置"scripts": { ...... "json-server": "PORT=3000 json-server json-server-mock/db.json --middlewares json-server-mock/middleware.js --watch" },
- 安装:
-
React启动时自定义端口号
修改package.json中start一行// Linux or MacOS "start": "PORT=4003 react-scripts start" // or "start": "export PORT=3006 react-scripts start" // Windows "start": "set PORT=3006 && react-scripts start"
Ajax
npm install qs @types/qs --save
-
配置请求根路径,处理请求参数,必须为
REACT_APP_API_URL,否则会报错
project-name/.envREACT_APP_API_URL = http://online.comproject-name/.env.development
REACT_APP_API_URL = http://localhost:3000src/utils/index.js
// 是否虚值 export const isFalsy = (value: any): boolean => { return value === 0 ? true : !!value } // 在一个函数里,改变传入的对象本身是不好的 // 处理对象,去除空值 export const cleanObject = (object) => { const params = { ...object } Object.keys(params).forEach(key => { const value = params[key] if (!isFalsy(value)) { delete params[key] } }) return params }发送请求
import qs from 'qs' const apiUrl = process.env.REACT_APP_API_URL useEffect(() => { fetch(`${apiUrl}/projects?${qs.stringify(cleanObject(param))}`).then(async response => { if (response.ok) { setList(await response.json()) } }) }, [param]) -
custom hook
-
useMount
export const useMount = (callback: () => void) => { useEffect(() => { callback() }, []) } useMount(() => { fetch(`${apiUrl}/users`).then(async response => { if (response.ok) { setUsers(await response.json()) } }) }) -
useDebounce
export const useDebounce = <T>(value: T, delay = 300) => { const [ debounceValue, setDebounceValue ] = useState(value) useEffect(() => { // 每次在value 或 delay 变化后,设置一个定时器 const timeout = setTimeout(() => { setDebounceValue(value) }, delay) // 每次在上一个 useEffect 处理完以后再执行,最后一个 timeout 得以执行 return () => { clearTimeout(timeout) } }, [value, delay]) // 返回最后一次执行后的 value return debounceValue } const debounceParam = useDebounce(param, 300) useEffect(() => { fetch(`${apiUrl}/projects?${qs.stringify(cleanObject(debounceParam))}`).then(async response => { if (response.ok) { setList(await response.json()) } }) }, [debounceParam]) -
useArray
import React from 'react' export const useArray = <T>(list: T[]) => { const [ value, setValue ] = useState(list) return { value, clear: () => setValue([]), add: (item: T) => setValue([ ...value, item ]), removeIndex: (index: number) => { const array = [ ...value ] array.splice(index, 1) setValue(array) }, } } interface Person { name: string age: number } export const TsReactTest = () => { const persons: Person[] = [ { name: 'Jack', age: 25 }, { name: 'Marry', age: 22 }, ] const { value, clear, removeIndex, add } = useArray(persons) return ( <> <div> <button onClick={ () => add({ name: 'John', age: 20 }) }>add John</button> <button onClick={ () => removeIndex(0) }>remove 0</button> <button onClick={ () => clear() }>clear Array</button> </div> <div> { value.map((item: Person, index: number) => { return ( <div key={index}> <span>{index}</span> <span>{item.name}</span> <span>{item.age}</span> </div> ) }) } </div> </> ) }
-
-
完成 login
json-server-mock/middleware.jsmodule.exports = (req, res, next) => { const { method, path, body } = req // 登录 if (method === 'POST' && path === '/login') { if (body.username === 'jack' && body.password === '123') { return res.status(200).json({ user: { token: '123' } }) } else { return res.status(400).json({ message: '用户名或密码错误' }) } next() } }src/screens/login/index.tsx
import React, { FormEvent } from 'react' const apiUrl = process.env.REACT_APP_API_URL export const LoginScreen = () => { const handleSubmit = (event: FormEvent<HTMLFormElement>) => { event.preventDefault() const username = (event.currentTarget.elements[0] as HTMLInputElement).value const password = (event.currentTarget.elements[1] as HTMLInputElement).value login({username, password}) } const login = (param: { username: string; password: string }) => { fetch(`${apiUrl}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(param) }).then(async response => { if (response.ok) { const result = await response.json() console.log(result) } }) } return ( <div> <h3>登录</h3> <form onSubmit={ handleSubmit }> <div> <label htmlFor="username">用户名</label> <input type="text" id="username" /> </div> <div> <label htmlFor="password">密码</label> <input type="password" id="password" /> </div> <button type="submit">登录</button> </form> </div> ) }
npx imooc-jira-tool