一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

十九、项目搭建规范

1、项目开发规范和代码风格
* 文件夹、文件名称统一小写、多个单词以连接符(-)链接
* javascript变量名称采用小驼峰标识,常量全部使用大写字母,组件采用大驼峰
* css采用普通css和styled-component结合来编写(全局采用普通css、局部采用styled-component)
* 整个项目不再使用class组件,统一使用函数式组件,并且全面拥抱hooks
* 所有的函数式组件,为了避免不必要的渲染,全部使用memo进行包裹
* 组件内部的状态,使用useState、useReducer;业务数据全部放在redux中管理
* 函数组件内部基本按照如下顺序编写代码
    - 组件内部state管理
    - redux的hooks代码
    - 其他组件hooks代码
    - 其他逻辑代码
    - 返回jsx代码
* redux代码规范如下
    - 每个模块有自己独立的reducer,通过combineReducer进行合并
    - 异步请求代码使用redux-thunk,并且写在actionCreators中
    - redux直接采用redux hooks方式编写,不再使用connect
* 网络请求采用axios
    - 对axios进行二次封装
    - 所有的模块请求会放到一个请求文件中单独管理
* 项目使用antdesign
    - 项目中某些antdesign中的组件会被拿过来使用
    - 但是多部分组件还是自己进行编写
* 其他规范在项目中根据实际情况决定和编写
2、为什么看不到react devtools标记
// disable react-dev-tools for this project
if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === "object") {
  for (let [key, value] of Object.entries(window.__REACT_DEVTOOLS_GLOBAL_HOOK__)) {
    window.__REACT_DEVTOOLS_GLOBAL_HOOK__[key] = typeof value == "function" ? () => {} : null;
  }
}
3、react脚手架中craco修改端口号
* 配置package.json
########
{
  "scripts": {
    "start": "set PORT=3005 && craco start"
  }
}
########
4、redux的hooks使用方式
  • src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import "@/assets/css/index.css"
import App from './App';
import routes from "@/router"
import store from "@/store"
import {Provider} from "react-redux"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}>
  <App routes={routes}/>
</Provider>);
  • src/store/index.js
import {legacy_createStore, applyMiddleware, combineReducers, compose} from "redux"
// redux异步操作中间件
import thunkMiddleware from "redux-thunk"
import recommend from "./recommend"

const reducer = combineReducers({
  recommend
})
let composeEnhancers = compose
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
  composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true})
}
const storeEnhancer = applyMiddleware(thunkMiddleware)
const store = legacy_createStore(reducer, composeEnhancers(storeEnhancer))

export default store
  • src/store/recommend.js
import {getTopBanners} from "@/services/recommend";

const actionTypes = {
  CHANGE_TOP_BANNERS: "CHANGE_TOP_BANNERS"
}

const changeTopBannersAction = data => ({
  type: actionTypes.CHANGE_TOP_BANNERS,
  data
})

const defaultState = {
  topBanners: []
}

export default function reducer(state = defaultState, action) {
  switch (action.type) {
    case actionTypes.CHANGE_TOP_BANNERS:
      return {...state, topBanners: action.data};
    default:
      return state;
  }
}

export function getTopBannersAction() {
  return dispatch => {
    getTopBanners().then(res => {
      dispatch(changeTopBannersAction(res.banners))
    })
  }
}
  • 使用
import React, {memo, useEffect} from "react";
// react和redux结合
import {useSelector, useDispatch, shallowEqual} from "react-redux"
import {getTopBannersAction} from "@/store/recommend"

export default memo(function () {
  const {topBanners} = useSelector(state => ({
    topBanners: state.recommend.topBanners
  }), shallowEqual) // 浅层比较(默认是全等比较),用于性能优化
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(getTopBannersAction())
  }, [dispatch])

  return (<div>
    <ul>
      {
        topBanners.map(item => (<li key={item.imageUrl}>{item.typeTitle}</li>))
      }
    </ul>
  </div>);
});
5、redux结合immutable使用
  • src/store/index.js
import {legacy_createStore, applyMiddleware, compose} from "redux"
import thunkMiddleware from "redux-thunk"
import recommend from "./recommend"
// npm i redux-immutable
import {combineReducers} from "redux-immutable"

const reducer = combineReducers({
  recommend
})
let composeEnhancers = compose
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
  composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true})
}
const storeEnhancer = applyMiddleware(thunkMiddleware)
const store = legacy_createStore(reducer, composeEnhancers(storeEnhancer))

export default store
  • src/store/recommend.js
import {getTopBanners} from "@/services/recommend";
// npm i immutable
import {Map} from "immutable"

const actionTypes = {
  CHANGE_TOP_BANNERS: "CHANGE_TOP_BANNERS"
}

const changeTopBannersAction = data => ({
  type: actionTypes.CHANGE_TOP_BANNERS,
  data
})

const defaultState = Map({
  topBanners: []
})

export default function reducer(state = defaultState, action) {
  switch (action.type) {
    case actionTypes.CHANGE_TOP_BANNERS:
      return state.set("topBanners", action.data);
    default:
      return state;
  }
}

export function getTopBannersAction() {
  return dispatch => {
    getTopBanners().then(res => {
      dispatch(changeTopBannersAction(res.banners))
    })
  }
}
  • src/pages/discover/recommend/index.js
import React, {memo, useEffect} from "react";
import {useSelector, useDispatch, shallowEqual} from "react-redux"
import {getTopBannersAction} from "@/store/recommend"

export default memo(function () {
  const {topBanners} = useSelector(state => ({
    // 等价:topBanners: state.get("recommend").get("topBanners")
    topBanners: state.getIn(["recommend", "topBanners"])
  }), shallowEqual)
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(getTopBannersAction())
  }, [dispatch])

  return (<div>
    <ul>
      {
        topBanners.map(item => (<li key={item.imageUrl}>{item.typeTitle}</li>))
      }
    </ul>
  </div>);
});
6、redux中获取state
import {getSongDetail} from "@/services/player";
import {Map} from "immutable"

const defaultState = Map({
  currentSong: {}
})

const actionTypes = {
  CHANGE_CURRENT_SONG: "CHANGE_CURRENT_SONG"
}

const changeCurrentSongAction = data => ({
  type: actionTypes.CHANGE_CURRENT_SONG,
  data
})

export default function reducer(state = defaultState, action) {
  switch (action.type) {
    case actionTypes.CHANGE_CURRENT_SONG:
      return state.set("currentSong", action.data);
    default:
      return state;
  }
}

export function getCurrentSongAction(ids) {
  return (dispatch, getState) => {
    getSongDetail(ids).then(res => {
      dispatch(changeCurrentSongAction(res.songs[0]))
      // getState和immutable方式获取state值
      const currentSong = getState().getIn(["player", "currentSong"])
      console.log(currentSong)
    })
  }
}
7、react中的路由组件懒加载和Suspense使用
  • src/router/index.js
import React from "react";

// 异步加载不能在同步加载之后
const Discover = React.lazy(() => import("@/pages/discover"))
const Recommend = React.lazy(() => import("@/pages/discover/recommend"))
const Album = React.lazy(() => import("@/pages/discover/album"))
const Artist = React.lazy(() => import("@/pages/discover/artist"))
const Djradio = React.lazy(() => import("@/pages/discover/djradio"))
const Ranking = React.lazy(() => import("@/pages/discover/ranking"))
const Songs = React.lazy(() => import("@/pages/discover/songs"))
const Mine = React.lazy(() => import("@/pages/mine"))
const Friend = React.lazy(() => import("@/pages/friend"))

const routes = [{
  path: "/",
  redirect: "/discover"
}, {
  path: "/discover/*",
  component: Discover,
  children: [{
    path: "*",
    redirect: "recommend"
  }, {
    path: "recommend",
    component: Recommend
  }, {
    path: "album",
    component: Album
  }, {
    path: "artist",
    component: Artist
  }, {
    path: "djradio",
    component: Djradio
  }, {
    path: "ranking",
    component: Ranking
  }, {
    path: "songs",
    component: Songs
  }]
}, {
  path: "/mine",
  component: Mine
}, {
  path: "/friend",
  component: Friend
}]

export default routes
  • src/App.js
import React, {memo, Suspense} from "react";
import {HashRouter, Routes, Route, Navigate} from "react-router-dom"
import Header from "@/components/header"
import Footer from "@/components/footer"
import PlayerBar from "@/components/player-bar"

export default memo(function ({routes}) {
  return (<HashRouter>
    <Header/>
    <Routes>{
      routes.map(item => {
        return (<Route key={item.path}
                       path={item.path}
                       element={item.redirect ? <Navigate to={item.redirect}/> :
                         <Suspense fallback={<div>加载中...</div>}>
                           <item.component routes={item.children}/>
                         </Suspense>}/>)
      })
    }</Routes>
    <Footer/>
    <PlayerBar/>
  </HashRouter>);
});

二十、项目部署

1、react项目打包
* [hash].chunk.js:代表是所有依赖的第三方库,vendor(第三方库)的代码
* main.[hash].chunk.js:我们自己编写的应用程序代码
* runtime~main.[hash].js:webpack runtime逻辑的chunk;用于加载和运行你的应用程序
2、项目手动部署
* 配置nginx的代理
    - 设置nginx的权限为root:user nginx;改为user root;
    - 可以将配置文件进行分离:http {include /etc/nginx/conf.d/*.conf;}
      可以配置http下的属性,比如server {}
    - 单独配置conf.d文件夹下的配置文件
3、自动化部署流程
* jenkins服务器安装
    - 安装java环境
        ~ 运行jenkins需要依赖java环境
    - jenkins的安装
        ~ 我们使用jenkins来完成自动化打包、部署过程
        ~ 可以设置数据源后、通过yum工具安装
    - 安装git/svn
        ~ jenkins需要通过git或者svn从代码仓库中下载代码
        ~ 可以通过yum工具安装
    - node环境
        ~ web项目目前打包都需要依赖node
        ~ 我们在服务器进行自动化打包,必然需要有node环境
        ~ 提示:这里可以通过yum工具安装,之后通过工具n进行node版本升级
* 配置jenkins任务
    - 登录jenkins管理后台
    - 第一次会让我们安装插件(推荐插件直接安装即可),创建用户
    - 创建jenkins任务(根据视频内容演练即可)
* general配置
    - 丢弃旧的构建
    - 保持构建的天数(一般是7)
    - 保持构建的最大个数(一般是10)
* 源码管理配置
    - git(需安装jenkins的git插件)
    - repository url(仓库地址)、credentials(证书)、指定分支
* 轮训scm规则
    - 第一颗*表示分钟minute:取值0-59,第几分钟执行
    - 第二颗*表示小时hour:取值0-23,第几小时执行
    - 第三颗*表示日day:取值1-31,第几日执行
    - 第四颗*表示月month:取值1-12,第几月执行
    - 第五颗*表示星期week:取值0-7,每周第几天执行
########
#每半小时构建一次OR每半小时检查一次远程代码分支,有更新则构建
H/30 * * * *
#每两小时构建一次OR每两小时检查一次远程代码分支,有更新则构建
H H/2 * * *
#每天凌晨两点定时构建
H 2 * * *
#每月15号执行构建
H H 15 * *
#工作日,上午9点整执行
H 9 * * 1-5
#每周1,3,5,从8:30开始,截止19:30,每4小时30分构建一次
H/30 8-20/4 * * 1,3,5
########

二十一、react ssr开发

1、使用react ssr
* 使用react ssr主要有两种方式
    - 方式一:手动搭建一个ssr框架
    - 方式二:使用已经成熟的ssr框架:Next.js
* 安装Next.js框架的脚手架
    - npm i -g create-next-app
* 创建Next.js项目
    - create-next-app next-demo
########
Would you like to use TypeScript with this project? ... No
Would you like to use ESLint with this project? ... Yes
Would you like to use `src/` directory with this project? ... Yes
Would you like to use experimental `app/` directory with this project? ... No
What import alias would you like configured? ... @/*
########
* package.json
    - 开发使用dev
    - 发布需要先build,再start
########
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
########
2、路由
* 在nextjs中,页面根据其(pages目录下)文件名与路由相关联:
    - pages/index.js对应路由:/
    - pages/other/about.js对应路由:/other/about
3、前端渲染or服务端渲染
import Link from 'next/link'

export default function Home() {
  return (
    <div>
      home页面
      {/*服务端渲染*/}
      <a href="/about">a-关于</a>
      {/*前端渲染*/}
      <Link href="/about">link-关于</Link>
    </div>
  )
}
4、页面布局
  • src/pages/_document.js
import {Html, Head, Main, NextScript} from 'next/document'

export default function Document() {
  return (
    <Html lang="en">
      <Head/>
      <body>
      <div>document</div>
      {/*对应_app.js(id="__next")*/}
      <Main/>
      <NextScript/>
      </body>
    </Html>
  )
}
  • src/pages/_app.js
import '@/styles/globals.css'

export default function App({Component, pageProps}) {
  return (<>
    <div>app</div>
    {/*对应pages*/}
    <Component {...pageProps} />
  </>)
}
  • src/layout/index.js
import React, {memo} from "react";
import Head from "next/head";
import Link from "next/link";

export default memo(function ({children}) {
  return (<>
    <Head>
      <title>创建next app</title>
      <meta name="description" content="Generated by create next app"/>
      <meta name="viewport" content="width=device-width, initial-scale=1"/>
      <link rel="icon" href="/favicon.ico"/>
    </Head>
    <div className="header">
      <Link href="/">主页</Link> | <Link href="/about">关于</Link>
    </div>
    <div className="main">{children}</div>
    <div className="footer">
      备案信息
    </div>
  </>);
});
  • src/pages/index.js
import React, {memo} from "react";
import Layout from "@/layout"

export default memo(function Home() {
  return (<Layout>
    home页面
  </Layout>)
})
5、css支持
  • src/components/pages/name.js
import React, {memo} from "react";
/**
 * 1、css模块命名:名称.module.css
 * 2、实际的class:名称_class名__hash
 * 3、一个css模块文件,对应一个hash
 */
import styles from "./pages.module.css"

export default memo(function () {
  return (<div className={styles.danger}>
    姓名
  </div>);
});
  • src/components/pages/pages.module.css
.danger {
  color: green;
}
6、子路由
  • src/layout/index.js
import React, {memo} from "react";
import Head from "next/head";
import Link from "next/link";

export default memo(function ({children}) {
  return (<>
    <Head>
      <title>创建next app</title>
      <meta name="description" content="Generated by create next app"/>
      <meta name="viewport" content="width=device-width, initial-scale=1"/>
      <link rel="icon" href="/favicon.ico"/>
    </Head>
    <div className="header">
      <Link href="/">主页</Link> | <Link href="/about">关于</Link> | <Link href="/mine">我的</Link>
    </div>
    <hr/>
    <div className="main">{children}</div>
    <hr/>
    <div className="footer">
      备案信息
    </div>
  </>);
});
  • src/layout/sub-layout.js
import React, {memo} from "react";
import Layout from "./index"
import Link from "next/link";

export default memo(function ({children}) {
  return (<Layout>
    <div>
      <Link href="/mine">我的</Link> | <Link href="/mine/money">资产</Link>
    </div>
    <hr/>
    <div>
      {children}
    </div>
  </Layout>);
});
  • src/pages/mine/index.js
import React, {memo} from "react";
import SubLayout from "@/layout/sub-layout"

export default memo(function () {
  return (<SubLayout>
    我的信息
  </SubLayout>);
});
7、路由传参
  • src/layout/sub-layout.js
import React, {memo} from "react";
import Layout from "./index"
import Link from "next/link";

export default memo(function ({children}) {
  return (<Layout>
    <div>
      <Link href="/mine">我的</Link> | <Link href={`/mine/money?id=18`}>资产</Link>
    </div>
    <hr/>
    <div>
      {children}
    </div>
  </Layout>);
});
  • src/pages/mine/money.js
import React, {memo} from "react";
import SubLayout from "@/layout/sub-layout"
import {useRouter} from "next/router";

export default memo(function () {
  const router = useRouter()

  return (<SubLayout>
    我的资产:{router.query.id}
  </SubLayout>);
});
8、路由编程式跳转
import React, {memo} from "react";
import Layout from "./index"
import Link from "next/link";
import Router from "next/router";

export default memo(function ({children}) {
  const handleClick = () => {
    Router.push({
      pathname: "/mine/money",
      query: {
        id: 20
      }
    })
  }

  return (<Layout>
    <div>
      <Link href="/mine">我的</Link> | <button onClick={e => handleClick()}>资产</button>
    </div>
    <hr/>
    <div>{children}</div>
  </Layout>);
});
9、预渲染和数据获取
import React, {memo} from "react";
import SubLayout from "@/layout/sub-layout"

export default memo(function (props) {
  const {heroes} = props
  return (<SubLayout>
    {
      heroes?.map(item => {
        return <div key={item}>{item}</div>
      })
    }
  </SubLayout>);
})

/**
 * 1、getStaticProps和getServerSideProps
 *     - getStaticProps():一种方法,它告诉Next组件在构建时填充道具并呈现到静态HTML页面中
 *     - getServerSideProps():一种告诉Next组件填充道具并在运行时呈现到静态HTML页面中的方法
 * 2、getStaticProps和getServerSideProps使用的唯一区别是方法本身的名称
 */
export async function getServerSideProps(context) {
  return {
    props: {
      heroes: ["莱恩", "斯温", "希尔瓦娜斯"]
    }
  }
}
posted on 2023-01-31 01:01  一路繁花似锦绣前程  阅读(133)  评论(0编辑  收藏  举报