保姆级别React综合案例构建

111 阅读2分钟

引言

最近小编对于React基础技能学习基本上已经学习完成,很多知识点在实战当中还根本不知如何使用,接下来小编希望屏幕前的你跟随小编的脚步,一步一个脚印,来将之前学习过的React知识点全部串起来。对于已经学过的知识点,进行一次全面的复习和学习,也希望这篇文章能帮到大家。

项目需求

react repos 项目开发

  • api.github.io/users/LouZhuKing/repos
  • 综合react开发全家桶、项目级别的、大型的、性能

1. 路由设计

    - react-router-demo
    - /users/:username/repos
    - /repos/:id
    懒加载
    hash/history
    (路由守卫)
    useParams :username

2.数据管理

   App 数据管理
   repos
   useContext + useReducer + hooks
   createContext + reducer + useRepos

3.api

        - api
        应用中的应用接口
    - main.jsx
        入口文件
        添加路由, SPA
        添加全局应用状态管理

4.RepoList 功能模块

 - params 解析
        - useParams 动态参数对象
        - 不要放到useEffect里面
        - 校验id
            不要相信用户的任何提交
        - navigate('/') -> useEffect中去(不要阻止页面的渲染)

5.组件开发模式

      - UI 组件 (JSX)
      - 自定义hooks useRepos 方便
      - 状态管理 应用全局 context 来管
          - repos loading error => context value
          - useReducer  reducer 函数

一、搭建api axios

Axios是一个基于Promise的HTTP客户端,用于浏览器和Node.js中进行AJAX请求,支持拦截请求和响应,取消请求,转换数据等特性。

pnpm i axios

在全局当中下载axios网络请求库,接下来在src文件当中使用它,创建api文件夹(应用中的应用接口请求)。

import axios from 'axios';   // http 请求库
// http 请求时候 所有接口地址的公共部分
const BASE_URL = 'https://api.github.com'; // 基础地址
// https://api.github.com/repos/shunwuyu/ai_lesson

export const getRepos =  (username) => {
  return axios.get(`${BASE_URL}/users/${username}/repos`)
}

export const getRepoDetail = async (username, repoName) => {
  return await axios.get(`${BASE_URL}/repos/${username}/${repoName}`)
}

  • 使用了 ES6 的模板字符串(Template Literal)语法:
    ${BASE_URL}/${username}/repos 会动态拼接成类似:
    https://api.github.com/users/octocat/repos
  • GitHub API 返回的将是这个用户的所有公开仓库的JSON数据。
  • axios.get()返回的是一个 Promise,调用者可以通过.then()async/await获取响应数据。

在项目当中调用getRepos函数使用它,正常情况下在useEffect函数当中调用该函数。在这里需要注意的是需要使用立即执行函数来包裹async/await函数,因为useEffect要么返回undefined,要么返回一个清理函数,如果你把 useEffect的回调写成 async函数,它会返回一个Promise,而不是undefined或一个函数。

import {
  useState,
  useEffect
} from 'react'
import {
  getRepos,
  getRepoDetail
} from './api/repos'
import './App.css'

function App() {
  useEffect(() => {
    (async () => {
      const repos = await getRepos('LouZhuKing');
      const repo = await getRepoDetail('LouZhuKing', 'lh_ai');
      console.log(repos, repo);
    })()
  }, [])

  return (
    <>
    </>
  )
}

export default App

image.png

写到这里将项目当中的axios网络请求就讲完了,现在需要将根组件当中的内容清空,接下来便是页面路由的搭建过程,现在大家跟随小编的脚步来到路由创建当中。


二、搭建路由

项目开发之前任何组件都应先搭建路由,页面级别组件由路由驱动

1.main.jsx中包裹路由

在以往当中引入路由代码经常会在根组件页面当中的头部进行路由引用,在大型项目当中,将Router放置在main.jsx当中编写,不用写在App组件当中。下面大家跟随小编来到主函数中! image.png

import {
  BrowserRouter as Router
} from 'react-router-dom'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

// 使用Router包裹入口文件
createRoot(document.getElementById('root')).render(
  <Router>
    <App />
  </Router>
)

  • 绝大多数情况下,推荐在main.jsx(入口文件)包裹Router,这样全局一致,易于维护。
  • 特殊需求(如微前端、嵌套应用)才考虑在App.jsx或更深层包裹Router

2.搭建GlobalProvider组件的模块化分离

创建GlobalContext组件页面,将需要使用的全局数据children渲染在页面当中,child是React组件的一个特殊属性(props),它代表了组件标签内部嵌套的内容。也就是说,children其实就是你在组件标签内部写的所有JSX内容。

import {
  createContext
} from 'react'

export const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
  return (
    <GlobalContext.Provider value="">
      {/* // 为什么会把传过来的children渲染在这里 */}
      {children}
    </GlobalContext.Provider>
  )
}

使用GlobalProvideruseContextuseReducer结合使用,为全局提供上下文数据传递。在函数组件的参数里写{children},其实是通过解构props,把props.children单独拿出来用。

image.png

接下来让我们来到react当中的App.jsx当中搭建页面路由跳转。在搭建的过程当中不要忘记使用之前咱们学过的路由懒加载进行页面加载性能优化。

import {
  useState,
  useEffect,
  Suspense,
  lazy
} from 'react'
import {
  Routes,
  Route,
  Navigate
} from 'react-router-dom'
import './App.css'
import Loading from './components/Loading'
const RepoList = lazy(() => import('./pages/RepoList'))
const RepoDetail = lazy(() => import('./pages/RepoDetail'))

function App() {

  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/users/:id/repos" element={<RepoList />} />
        <Route path="/users/:id/repos/:repoId" element={<RepoDetail />} />
        <Route path="*" element={<Navigate to="/users/LouZhuking/repos" />} />
      </Routes>
    </Suspense>
  )
}

export default App

image.png

三、组件开发

- 组件开发模式
      - UI 组件 (JSX)
      - 自定义hooks useRepos 方便
      - 状态管理 应用全局 context 来管
          - repos loading error => context value
          - useReducer  reducer 函数
      - 

1.RepoList组件模块

const RepoList = () => {
  const {id} = useParams();
  console.log(useparams());
  useEffect(() => {
    // 这里为何会显示报错情况
    //const { id } = useParams();
    //console.log(id);
  }, [])

现在大家仔细想想为何在这里代码会报错,因为useParams没有异步解决的需要,其应是放在组件顶层调用,而不是放在useEffect当中调用,应是同步代码当中进行的。
写到这里我们要思考一个问题,在这里不能100%能拿到用户的id,就要使其拿到登录id严谨一些。(不要相信用户的任何提交内容)

import {
  useParams,
  useNavigate
} from 'react-router-dom'
import {
  useEffect
} from 'react'

const RepoList = () => {
  const { id } = useParams();
  const navigate = useNavigate();

  useEffect(() => {
    if (!id.trim()) {
      navigate('/');
      return;
    }

  }, []);

  return (
    <>RepoList</>
  )
}

export default RepoList     

2. RepoList 功能模块(Prams)

    - params 解析
        - useParams 动态参数对象
        - 不要放到useEffect里面
        - 校验id
            不要相信用户的任何提交
        - navigate('/') -> useEffect中去 (不要阻止页面的渲染)
  // hooks
  const { repos, loading, error } = useRepos(id);

自定义hooks(useRepos),现在让我们来封装下useRepos这段代码的hooks,写好界面需要的功能内容,此时需要

import{
  useState,
  useEffect
} from 'react'

export const useRepos = (id) => {
  const repos = [];
  const loading = true;
  const error = null;

  return{
    repos,
    loading,
    error
  }
}
 

3. 引入repoReducer函数

通过GlobalContext.Provider,将statedispatch传递给所有子组件。通过这样子任何子组件都可以通过context访问和修改全局状态,实现全局状态管理。

import {
  createContext,
  useReducer
} from 'react'
import {
  repoReducer
} from '@/reducers/repoReducer'


const initialState = {
  repos: [],
  loading: false,
  error: null
}

export const GlobalContext = createContext()
export const GlobalProvider = ({ children }) => {
  const [state, dispatch] = useReducer(repoReducer, initialState)
  return (
    // state 应用状态
    <GlobalContext.Provider value={{ state, dispatch }}>
      {children}
    </GlobalContext.Provider >
  )
}     

使用reducer代码实现了一个典型的异步数据请求的三种状态(加载中、成功、失败)管理,保证了状态的正确性和可维护性。

// 保证状态的正确性
export const repoReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_START':
      return {
        ...state,
        loading: true,
        error: null
      }
    case 'FETCH_SUCCESS':
      return {
        ...state,
        loading: false,
        repos: action.payload,
        error: null
      }
    case 'FETCH_ERROR':
      return {
        ...state,
        loading: false,
        error: action.payload
      }
    default:
      return state
  }
}

4.更新useRepos函数

将响应式业务逻辑抽离到hooks中,把这些逻辑单独封装成一个自定义的 React Hook(useRepos),而不是直接写在组件内部。并且组件只负责渲染和交互,数据逻辑集中在Hook里,职责分明。

import{
  useState,
  useEffect,
  useContext
} from 'react'
import{
  GlobalContext
} from '@/context/GlobalContext'
import {
  getRepos
} from '@/api/repos'

// 将响应式业务逻辑抽离到hooks中
export const useRepos = (id) => {
  const { state, dispatch } = useContext(GlobalContext)
  useEffect(()=>{
    console.log('---------');
    (async ()=>{
      try {
        const res = await getRepos(id)
        dispatch({
          type: 'FETCH_SUCCESS',
          payload: res.data
        })
      }catch(err){
        dispatch({
          type: 'FETCH_ERROR',
          payload: err.message
        })
      }
    })()
    
  },[])

  return state
}
 

image.png

四、渲染至页面

根据路由参数 id(用户),获取并展示该用户的所有仓库列表,并支持点击跳转到具体仓库详情。如果 id 不合法会自动跳转回首页。

import {
  useParams,
  useNavigate,
  Link
} from 'react-router-dom'
import {
  useEffect
} from 'react'
import {
  useRepos
} from '@/hooks/useRepos'

const RepoList = () => {
  const { id } = useParams();
  const navigate = useNavigate();
  // hooks
  const { repos, loading, error } = useRepos(id);

  useEffect(() => {
    if (!id.trim()) {
      navigate('/');
      return;
    }

  }, []);

  if (loading) return (<>Loading...</>)
  if (error) return (<>Error: {error}</>)


  return (
    <>
      <h2>Repositories for {id}</h2>
      {
        repos.map((repo) => (
          <Link key={repo.id} to={`/users/${id}/repos/${repo.name}`}>
            {repo.name}
            <br />
          </Link>
        ))
      }
    </>
  )
}

export default RepoList     

image.png

五、总结

一个优秀的 React 项目结构,能够让团队成员快速上手,降低沟通和维护成本。建议在项目初期就制定好结构规范,并根据实际需求灵活调整。希望本文的总结能为你的 React 项目开发提供参考和帮助。