从0开始搭建react项目(函数组件)

6 篇文章 0 订阅

目录

项目搭建步骤

本地开发环境

脚手架构建项目

关联Git仓库

strictMode 严格模式

路由配置

路由传参

路由守卫

数据绑定

生命周期

父子组件通信

redux持久化

安装Sass

安装postcss-pxtorem(移动端项目)

安装axios

环境变量

本地代理

项目部署

nginx配置

jenkins自动化部署

自由风格配置

流水线配置

灰度部署方案


项目搭建步骤

本地开发环境

安装nodejs,node版本建议大于16.0.0

Download | Node.js

脚手架构建项目

官网:快速入门 – React

npx create-react-app my-app

关联Git仓库

初始化后的项目关联远程git仓库

strictMode 严格模式

index.js 中包裹了一层 React.strictMode,开发环境严格模式,用于校验页面方法,在开发模式会调用两次页面方法,建议删除

root.render(
  // <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  // </React.StrictMode>
)

路由配置

1、安装react-router-dom

npm i react-router-dom -S

2、新增 / src / router / index.js,可配置路由懒加载

import { lazy } from 'react'
import { Navigate } from 'react-router'

// 非懒加载的路由,组件及引入的css会打包到全局
import Index from '../views/index'
import Login from '../views/login'
// 以下为懒加载路由,访问到路由才会加载页面代码,组件及引入的css也是访问时才加载生效
const My = lazy(() => import('../views/my'))
const Profile = lazy(() => import('../views/my/profile'))
const Order = lazy(() => import('../views/my/order'))

//创建路由
const routes = [
  {
    path: '/',
    meta: {
      title: '首页',
    },
    exact: true,
    element: <Index />,
  },
  {
    path: '/login',
    element: <Login />,
    meta: {
      title: '登录',
    },
  },
  {
    path: '/my',
    element: <My />,
    exact: true,
    meta: {
      title: '我的',
    },
    children: [
      {
        path: '/my/profile',
        element: <Profile />,
        meta: {
          title: '我的-主页',
        },
      },
      {
        path: '/my/order',
        element: <Order />,
        meta: {
          title: '我的-订单',
        },
      }
    ]
  },
  {
    // 未匹配到全部跳转首页
    path: '*',
    element: <Navigate to="/" />,
  },
]

export default routes

3、修改index.js

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

root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
)

4、修改App.js

import {useRoutes} from "react-router-dom"
import router from "./router/index"
function App() {
  return useRoutes(router)
}
export default App;

路由传参

1、search方式

// 传参页面
<Link to={`/my/order?id=123`}>订单</Link>

// 接收参数页面
import { useSearchParams } from "react-router-dom"
export default function Order() {
  // 获取params参数
  const [search] = useSearchParams()
  const id = search.get('id')
  return (
    <div>订单id为:{ id }</div>
  )
}

2、动态路由方式

// 路由配置 /router/index.js
path: '/my/order/:id'

// 传参页面
<Link to={`/my/order/123`}>订单</Link>

// 接收参数页面
import { useParams } from "react-router"
export default function Order() {
  // 获取params参数
  const params = useParams()
  const id = params.id
  return (
    <div>订单id为:{ id }</div>
  )
}

路由守卫

1、新建 / src / router / beforeEnter.js

import { useLocation, useNavigate, useRoutes } from 'react-router-dom'
import { useEffect } from 'react'
const BeforeEnter = ({ routers }) => {
  // 1.在路由数组中找当前页面路由的对应路由项
  const fineRouter = (routes, path) => {
    for (let item of routes) {
      if (item.path === path) return item
      if (item.children) {
        // 如果有子路由,查找子路由
        // 注意:1)因为传入的path是当前完整路径,子路由的path也需要设置完整的路径,例如:/my/order,而不是 order
        //      2)不可配置动态参数路由,例如:/product/:id
        return fineRouter(item.children, path)
      }
    }
    return null // 没有找到录音配置,返回null,由judgeRouter方法跳转404页面
  }
  // 2.路由守卫判断
  const judgeRouter = (location, navigate) => {
    const { pathname } = location
    // 2.1路由数组找路由项
    const findRoute = fineRouter(routers, pathname)
    // 2.2没找到,说明没有这个路由,直接404
    if (!findRoute) {
      navigate('/404')
      return
    }
    // 2.3更新页面标题
    if (findRoute?.meta?.title) {
      document.title = findRoute.meta.title
    }
    if (findRoute.auth) {
      // 用户未登陆,挑战登陆页面
      if (!localStorage.getItem('user')) navigate('/login')
    }
  }
  // 3.基于useEffect监听页面路由改变。然后组件重新加载,又重新校验权限。
  const navigate = useNavigate()
  const location = useLocation()
  const router = useRoutes(routers)
  useEffect(() => {
    // 路由守卫判断
    judgeRouter(location, navigate)
  })
  return router
}
export default BeforeEnter

2、修改App.js

import BeforeEnter from "./router/beforeEnter"
import router from "./router/index"

function App() {
  return <BeforeEnter routers={router} />
}
export default App;

数据绑定

1、表单数据

import { useNavigate } from 'react-router-dom'
export default function Login() {
  const navigate = useNavigate()
  // 声明账号、密码变量
  let account, password
  // form提交
  function loginSubmit(e) {
    // 阻止form提交事件
    if (e) e.preventDefault()
    // 通过ref方法获取到输入框内容
    if (account.value === 'xxx' && password.value === 'xxx') {
      // 登录成功本地存储登录状态
      localStorage.setItem('isLogin', true)
      // 登录成功跳转我的页面
      navigate('/my', {
        replace: true
      })
    }
  }
  return (
    <div>
      <h4>登录页面</h4>
      <form onSubmit={loginSubmit}>
        <p>账号</p>
        <input type="text" ref={(input) => (account = input)} />
        <p>密码</p>
        <input type="password" ref={(input) => (password = input)} />
        <button type="submit">提交</button>
      </form>
    </div>
  )
}

2、页面渲染

通过useState方法声明变量,变量更新时才能同步渲染到页面

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

export default function ProductList() {
  const navigate = useNavigate()
  // 声明商品列表数据
  const [productList, setProductList] = useState([])
  //  添加商品
  // 注意:不可以对原变量productList做push操作
  const addProduct = () => {
    const list = productList.concat([
      {
        id: 1,
        name: '新商品'
      }
    ])
    setProductList(list)
  }
  // 删除商品
  // 注意:不可以对原变量productList做splice操作
  const deleteItem = (product) => {
    setProductList(productList.filter((item) => item.id !== product.id))
  }
  // 跳转商品详情
  const goDetail = (item) => {
    navigate('/productDetail?id=' + item.id)
  }
  return (
    <div>
      <h4>商品列表页</h4>
      <button onClick={() => addProduct()}>添加商品</button>
      <ul>
        {productList.map((item, index) => {
          return (
            <li key={index}>
              <h5>商品名称:{item.name}</h5>
              <button onClick={() => goDetail(item)}>跳转详情页</button>
              <button onClick={() => deleteItem(item)}>删除</button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

生命周期

使用useEffect方法,第一个参数传入一个方法,第二个可选参数传入[],如果不写第二个参数,则页面更新就会执行

import React, { useState, useEffect } from 'react'

export default function ProductList() {
  const [productList, setProductList] = useState([]);
  // 获取商品列表接口
  const getList = async () => {
    const res = await React.$http({
      url: '/m1/2235670-0-default/article/list',
    })
    setProductList(res.productList)
  }
  // useEffect hook,可作为生命周期方法使用
  // 第二个可选参数传入[],进入页面只执行一次
  useEffect(()=>{
    console.log('进入列表页')
    getList()
  }, [])
  return (
    <div>...</div>
  )
}

父子组件通信

1、父组件

import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
// 引入子组件
import ProductItem from '../components/productItem'

export default function ProductList() {
  const navigate = useNavigate()
  const [productList, setProductList] = useState([]);
  // 声明删除商品方法,注册子组件时传入
  const deleteItem = (product) => {
    setProductList(productList.filter(item => item.id !== product.id))
  }
  // 声明跳转方法,注册子组件时传入
  const goDetail = (item) => {
    navigate('/productDetail?id='+item.id)
  }
  return (
    <div>
      <h4>商品列表页</h4>
      <ul>
        {
          productList.map((item, index) => {
            return (
              <li key={index}>
                <ProductItem item={ item } goDetail={ goDetail } deleteItem={ deleteItem } />
              </li>
            )
          })
        }
      </ul>
    </div>
  )
}

2、子组件

// 接收父组件传入的变量和方法,可直接调用
export default function ProductItem({ item, goDetail, deleteItem }) {
  return (
    <div className="flex product-item">
      <div>
        <h5>{ item.name }</h5>
        <button onClick={() => goDetail(item)}>查看详情</button>
        <button onClick={() => deleteItem(item)}>删除</button>
      </div>
    </div>
  )
}

redux持久化

1、新建 src / store / reducer.js,配置state数据

// 定义state数据
const defaultState = {
  inputValue: 0, // 输入框内容
  isLogin: false // 登录状态
}
function reducer(state = defaultState, action) {
  const newState = JSON.parse(JSON.stringify(state)) // 深度拷贝state
  switch (action.type) {
    case 'changeInput':
      newState.inputValue = action.value
      break
    case 'changeIsLogin':
      newState.isLogin = action.value
      break
    default:
  }
  return newState
}
export default reducer

2、新建 src / store / index.js,配置state数据

// 因为 redux已经不推荐使用 createStore()创建数据仓库了,如果我们继续使用,需要引入具有标识的 legacy_createStore
import { legacy_createStore as createStore } from 'redux';
// 持久化插件
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import reducer from './reducer';

// 配置持久化插件
const persistConfig = {
  key: 'root',
  storage,
  // blacklist: ['inputValue'], // 黑名单,例:inputValue不会被持久化
  whitelist: ['isLogin'] // 白名单,例:只有isLogin会被持久化
}

const persistedReducer = persistReducer(persistConfig, reducer)

// 创建数据存储仓库
const store = createStore(persistedReducer,
  // 浏览器redux devTools添加以下配置
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

const persistor = persistStore(store);

export {
  store,
  persistor
}

3、修改index.js,全局配置redux

import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';

// redux
import { store, persistor } from './store'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      {/* 持久化redux,在路由懒加载时,无法获取数据,所以添加Suspense Loading */}
      <Suspense fallback={<h2>Loading..</h2>}>
        {/* history模式路由 */}
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </Suspense>
    </PersistGate>
  </Provider>
);

4、页面使用

import { useDispatch, useSelector } from "react-redux"

export default function Pdp() {
  // 声明输入框
  let textValue

  // 获取redux中的inputValue数据
  const inputValue = useSelector(state => state.inputValue)
  
  // 修改redux数据
  const dispatch = useDispatch()
  const changeInputFun = () => {
    dispatch({ type: 'changeInput', value: textValue.value })
  }
  return (
    <div>
      <span>redux值为:{inputValue}</span>
      <input ref={(input) => (textValue = input)} />
      <button onClick={() => changeInputFun()}>修改</button>
    </div>
  )
}

安装Sass

npm i sass -D

使用:将css文件换为scss文件即可

安装postcss-pxtorem(移动端项目)

移动端适配,兼容不同分辨率的手机

npm i @craco/craco postcss-pxtorem -D
npm i amfe-flexible -S

使用craco启动服务,项目根目录新增 craco.config.js

module.exports = {
  style: {
    postcss: {
      mode: 'extends',
      loaderOptions: {
        postcssOptions: {
          ident: 'postcss',
          plugins: [
            [
              'postcss-pxtorem',
              {
                rootValue: 75, // 根元素字体大小
                propList: ['*'],
                minPixelValue: 2 // 最小px为2,如果设置1px则不转rem
              }
            ]
          ]
        }
      }
    }
  }
}

在index.js引入amfe-flexible

import 'amfe-flexible'

修改package.json的script命令,将 react-scripts 替换为 craco

"scripts": {
  "start": "craco start",
  "build": "craco build",
  "test": "craco test",
  "eject": "craco eject"
}

安装axios

官网:axios

npm i axios -S

封装axios方法,新建/ src / utils / request.js文件

import axios from 'axios'

const service = axios.create({
  // process.env.NODE_ENV 判断是否为本地环境
  baseURL: process.env.NODE_ENV === 'development' ? '/api' : 'https://www.baidu.com',
  timeout: 30000
})

/* 请求拦截器 */
service.interceptors.request.use(
  (config) => {
    // 登录状态鉴权
    // if (token) {
    //   config.headers.Authorization = `Bearer ${token}`;
    // }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

/* 响应拦截器 */
service.interceptors.response.use(
  (response) => {
    const { code, message, data } = response.data
    // 根据自定义错误码判断请求是否成功
    if (code === '0') {
      // 将组件用的数据返回
      return data
    } else {
      // 处理业务错误。
      return Promise.reject(new Error(message))
    }
  },
  (error) => {
    // 处理 HTTP 网络错误
    let message = ''
    // HTTP 状态码
    const status = error.response?.status
    switch (status) {
      case 401:
        message = 'token 失效,请重新登录'
        // 这里可以触发退出的 action
        break
      case 403:
        message = '拒绝访问'
        break
      case 404:
        message = '请求地址错误'
        break
      case 500:
        message = '服务器故障'
        break
      default:
        message = '网络连接故障'
    }
    console.log(message)
    return Promise.reject(error)
  }
)

/* 导出封装的请求方法 */
export const http = (options) => {
  return new Promise((resolve, reject) => {
    // 方法一:get和delete请求,将请求体转换为queryString形式拼接在url
    // if (!options.method || options.method === 'get' || options.method === 'delete') {
    //   if (options.data) {
    //     options.url = options.url + '?' + new URLSearchParams(options.data).toString()
    //   }
    // }

    // 方法二:get和delete请求,使用params参数传参,post和put使用data参数
    const serviceOpt = {}
    if (!options.method || options.method === 'get' || options.method === 'delete') {
      serviceOpt.params = options.data
    } else {
      serviceOpt.data = options.data
    }
    service({
      method: options.method || 'get',
      url: options.url,
      ...serviceOpt
    })
      .then((res) => {
        resolve(res)
      })
      .catch((err) => {
        reject(err)
      })
  })
}

使用

import { http } from '@/utils/request'

const res = await http({
  url: '/get/product/list',
  // method: 'get',
  data: {
    id: 1
  }
})
console.log(res)

环境变量

1、获取系统环境变量

const NODE_ENV = process.env.NODE_ENV

2、获取自定义环境变量

在create-react-app中获取npm自定义变量,可以使用process.env对象。在package.json中设置变量后,可以在代码中使用process.env.VARIABLE_NAME来获取变量的值。

"scripts": {
  "start-test": "REACT_APP_ENV=test react-scripts start",
  "start-dev": "REACT_APP_ENV=dev react-scripts start"
}

在业务代码中获取,比如:/ src / index.js

const ENV = process.env.REACT_APP_ENV;

 注意:变量名必须以REACT_APP_开头,这是create-react-app的约定

本地代理

新建 / src / setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports = function (app) {
  app.use(
    createProxyMiddleware('/api', {
      // 使用 /api 前缀代理配置
      target: 'https://mock.apifox.cn', // 跨域地址
      changeOrigin: true, // 控制服务器收到的请求头中host的值
      pathRewrite: {
        '^/api': '',
      },
    })
  )
}

项目部署

nginx配置

Apache,Nginx部署vue/react项目_apached发布 react_不求甚解bc的博客-CSDN博客

jenkins自动化部署

自由风格配置

jenkins部署vue/react项目_jenkins部署vue项目_不求甚解bc的博客-CSDN博客

jenkins分环境部署vue/react项目_不求甚解bc的博客-CSDN博客

流水线配置

jenkins流水线部署H5项目_jenkins部署h5_不求甚解bc的博客-CSDN博客

灰度部署方案

web前端灰度部署_不求甚解bc的博客-CSDN博客

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 为了搭建一个React项目,首先要确保你的开发环境已经配置好了。你需要安装Node.js和npm(Node包管理器)。 2. 使用命令行工具进入你想要保存项目的文件夹,并执行以下命令来创建一个新的React应用: ```shell npx create-react-app my-app ``` 这将使用create-react-app脚手架工具创建一个新的React应用,并在my-app文件夹中初始化项目结构。 3. 进入my-app文件夹,并启动开发服务器: ```shell cd my-app npm start ``` 这将启动一个本地开发服务器,并在浏览器中打开http://localhost:3000以查看你的React应用。 4. 现在你可以开始编写你的React组件了。你可以创建函数组件或类组件来定义你的UI。函数组件是一个纯函数,接收props对象并返回一个React元素;而类组件需要继承React.Component,并实现一个render函数来返回React元素。 5. 如果你需要在组件之间进行通信,可以使用Redux这样的状态管理库。Redux可以帮助你管理全局的状态,并使不同组件之间共享数据变得更加容易。你可以使用npm来安装Redux和相关的库: ```shell npm install redux react-redux ``` 然后,你可以在你的应用中创建Redux的store,并通过Provider组件将它提供给整个应用。你可以使用connect函数来连接你的组件,使其能够访问Redux中的状态。 6. 最后,你可以使用第三方UI库来加快你的开发速度。Ant Design是一个流行的React UI框架,它提供了丰富的组件和样式,可以帮助你快速构建漂亮的用户界面。你可以使用npm来安装Ant Design: ```shell npm install antd ``` 然后,你可以在你的组件中引入Ant Design的组件,并使用它们来构建你的UI。Ant Design的文档提供了详细的使用说明和示例代码,可以帮助你更好地了解和使用该框架。 以上是从0到1搭建一个React项目的一般步骤。你可以根据实际需求和项目要求进行相应的调整和扩展。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [REACT实战项目从0到1搭建(仅供参考)](https://blog.csdn.net/qq_31851435/article/details/121740400)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [react-从0到1新建react项目](https://blog.csdn.net/weixin_40220492/article/details/127145343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值