React 实战(旅游网站)

项目启动

  1. 新建项目

    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

  2. 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'

  3. 配置 eslint、 prettier 和 commitlint 规范工程

    • 配置绝对路径

      import logo from 'logo.svg' // src/logo.svg
      

      tsconfig.json 下增加配置

      // compilerOptions 下增加
      "baseUrl": "src",
      
    • 配置格式化 npm install prettier --save-dev

      1. 新建文件 project-name/.prettierrc.json,可自行配置

      2. 新建文件 project-name/.prettierignore

      build
      coverage
      
      1. 执行 npx mrm lint-stagedlint-staged 是一个在 git 暂存文件上运行 linters 的工具。它将根据 package.json 依赖项中的代码质量工具来安装和配置 huskylint-staged,因此请确保在此之前安装(npm install --save-dev)并配置所有代码质量工具,如 PrettierESlint
      "husky": {
        "hooks": {
          "pre-commit": "lint-staged"
        }
      },
      "lint-staged": {
        "*.{js,css,md,ts,tsx,jsx}": "prettier --write"
      }
      
      1. 安装依赖 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

  1. 全局引用 Ant Design 样式文件
    index.css@import '~antd/dist/antd.css'

  2. 按需引入引入 antd(具体用法见文档)

  3. 组件化思想(使用方便,易于独立)
    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 (
            
                
                版权所有 @ React 旅游网
                
            
        )
    }
    

    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 (
            
    ); } export default App;

引入路由系统

  1. react-router-dom 用于浏览器,处理 Web App 的路由

  2. react-router-native 用于 React Native,处理手机 app 的路由

  3. react-router-redux 提供了路由中间件,处理 redux 的集成

  4. react-router-config 用来静态配置路由

react-router-dom

  1. 配置路由

    import { BrowserRouter, Route } from 'react-router-dom'
    
    // 若匹配到多个路由,则UI显示为多组件共存
    // exact:精准匹配
    
    // HomePage 中,可通过 props 获取到路由信息

    登录

    } />
  2. 带参数路由匹配

    // 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 (
            
    ) } export default App
    // Detail.tsx
    import React from 'react'
    import { RouteComponentProps } from 'react-router-dom'
    
    interface MatchParams {
        id: string
    }
    
    export const DetailPage: React.FC> = (props) => {
        return (
            <>
                

    详情{props.match.params.id}

    ) }
  3. 路由跳转

  • withRouter

    import { RouteComponentProps, withRouter } from 'react-router-dom'
    
    interface PropsType extends   RouteComponentProps {
       id: number
       ......
    }
    
    const ProductImageComponent: React.FC = ({ id, history, location, match }) => {
       return (
           
    history.push(`detail/${id}`)}> ......
    ) } export const ProductImage = withRouter(ProductImageComponent)
  • useHistory

    import { useHistory } from 'react-router-dom'
    
    const history = useHistory()
    
    
    
    
    
  • Link

    import { Link } from 'react-router-dom'
    
    ......
    

redux 状态管理

详见 react-redux

常见 MOCK 方案

  1. 代码入侵:直接在代码中写死 Mock 数据,或者请求本地的 json 文件

  2. 请求拦截,如 Mock.js

  3. 接口管理工具,如 rapswaggeryapi

  4. 本地 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"
      },
      
  5. React 启动时自定义端口号
    修改 package.jsonstart 一行

    // 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

  1. 配置请求根路径,处理请求参数,必须为 REACT_APP_API_URL,否则会报错
    project-name/.env

    REACT_APP_API_URL = http://online.com
    

    project-name/.env.development

    REACT_APP_API_URL = http://localhost:3000
    

    src/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])
    
  2. 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 = (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 = (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 (
              <>
                  
      { value.map((item: Person, index: number) => { return (
      {index} {item.name} {item.age}
      ) }) }
      ) }
  3. 完成 login
    json-server-mock/middleware.js

    module.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) => {
            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 (
            

    登录

    ) }

npx imooc-jira-tool

你可能感兴趣的:(React 实战(旅游网站))