目录
十九、项目搭建规范
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: ["莱恩", "斯温", "希尔瓦娜斯"]
}
}
}