1、一些项目基础配置
1、@ 别名 安装 npm install -D @craco/craco
在根目录创建一个 crao.config.js 文件,代码如下
const path = require('path');
module.exports = {
webpack: {
alias: {
// 路径别名
'@': path.resolve(__dirname, 'src'),
},
},
}
2、自动联想,在根目录创建 jsconfig.json 文件,代码如下
VSCode会自动读取 `jsconfig.json` 中的配置,让vscode知道@就是src目录
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
3、mock数据
安装 npm install -D json-server@0.17.3
准备一个json文件
在package.json 文件的scripts 中增加一项,以server/data.json 为数据源
"server": "json-server ./server/data.json --port 8888"
2、一些 React使用
2.1、useMemo
// 类似于 Vue 的计算属性 [] 中的是依赖的原始值
const monthGroup = useMemo(() => {
// lodash 中的分组方法
return _.groupBy(billList, item => dayjs(item.date).format('YYYY-MM'))
}, [billList])
const overview = useMemo(() => {
if (!currentMonthList) return { income: 0, pay: 0, total: 0 }
const income = currentMonthList.filter(item => item.type === 'income')
.reduce((a, c) => a + c.money, 0)
const pay = currentMonthList.filter(item => item.type === 'pay')
.reduce((a, c) => a + c.money, 0)
return {
income,
pay,
total: income + pay
}
}, [currentMonthList])
2.2、memo
React 默认的渲染机制是子组件跟着父组件一起渲染的,但是如果是,子组件,不需要着渲染,这时候就可以用上memo了,使用 demo 如下
// React.memo
import { memo, useState } from "react"
// 1. 验证默认的渲染机制 子跟着父一起渲染
// 2. memo进行缓存 只有props发生变化的时候才会重新渲染 (不考虑context)
const MemoSon = memo(function Son () {
console.log('我是子组件,我重新渲染了')
return <div>this is son</div>
})
function App () {
const [count, setCount] = useState(0)
return (
<div className="App">
<button onClick={() => setCount(count + 1)}>+{count}</button>
<MemoSon />
</div>
)
}
export default App
memo的比较机制
针对简单类型,是认为只要只没变那props就是没变的,子组件就只会渲染一次
针对引用类型,由于每次父组件都是重新执行,那引用类型数据,也会重新创建,会认为每次都是变的,子组件就是每次度渲染
如果引用类型props,当父组件改变的时候,想让子组件不改变,那就得想办法,让引用类型只初始化一次,也就是需要缓存,这时候就可以用到 useMemo了,例子如下
// React.memo props比较机制
// 1. 传递一个简单类型的prop prop变化时组件重新渲染
// 2. 传递一个引用类型的prop 比较的是新值和旧值的引用是否相等 当父组件的函数重新执行时,实际上形成的是新的数组引用
// 3. 保证引用稳定 -> useMemo 组件渲染的过程中缓存一个值
import { memo, useMemo, useState } from 'react'
const MemoSon = memo(function Son ({ list }) {
console.log('子组件重新渲染了')
return <div>this is Son {list}</div>
})
function App () {
const [count, setCount] = useState(0)
// const num = 100
const list = useMemo(() => {
return [1, 2, 3]
}, [])
return (
<div className="App">
<MemoSon list={list} />
<button onClick={() => setCount(count + 1)}>change Count</button>
</div>
)
}
export default App
2.3、useCallback
保持函数引用稳定,,不会随着父组件每次更新而改变
// useCallback
import { memo, useCallback, useState } from "react"
const Input = memo(function Input ({ onChange }) {
console.log('子组件重新渲染了')
return <input type="text" onChange={(e) => onChange(e.target.value)} />
})
function App () {
// 传给子组件的函数, 保持函数引用不变,结合子组件memo使用
const changeHandler = useCallback((value) => console.log(value), [])
// 触发父组件重新渲染的函数
const [count, setCount] = useState(0)
return (
<div className="App">
{/* 把函数作为prop传给子组件 */}
<Input onChange={changeHandler} />
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
)
}
export default App
2.4、forwardRef
获取子组件的dom元素
import { forwardRef, useRef } from "react"
const Son = forwardRef((props, ref) => {
return <input type="text" ref={ref} />
})
// 父组件
function App () {
const sonRef = useRef(null)
const showRef = () => {
console.log(sonRef)
sonRef.current.focus()
}
return (
<>
<Son ref={sonRef} />
<button onClick={showRef}>focus</button>
</>
)
}
export default App
2.5、useImperativeHandle
暴露方法给父组件使用
import { forwardRef, useImperativeHandle, useRef } from "react"
// 子组件
const Son = forwardRef((props, ref) => {
// 实现聚焦逻辑
const inputRef = useRef(null)
const focusHandler = () => {
inputRef.current.focus()
}
// 把聚焦方法暴露出去
useImperativeHandle(ref, () => {
return {
// 暴露的方法
focusHandler
}
})
return <input type="text" ref={inputRef} />
})
// 父组件
function App () {
const sonRef = useRef(null)
const focusHandler = () => {
console.log(sonRef.current)
sonRef.current.focusHandler()
}
return (
<>
<Son ref={sonRef} />
<button onClick={focusHandler}>focus</button>
</>
)
}
export default App
2.6、高阶组件
简单鉴权组件
3、打包配置相关
3.1、打包
npm run build
3.2、本地预览
npm i -g serve
serve -s ./build
// 访问http://localhost:3000
3.3、路由懒加载
点击那个页面加载对应的页面文件,减少首次加载项目,资源请求,点击那个页面就请求对应的文件
实现
* 使用 lazy 方法导入路由组件
* 使用内置的 Suspense 组件渲染路由组件
import { createBrowserRouter } from 'react-router-dom'
import { lazy, Suspense } from 'react'
import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import AuthRoute from '@/components/Auth'
const Publish = lazy(() => import('@/pages/Publish'))
const Article = lazy(() => import('@/pages/Article'))
const Home = lazy(() => import('@/pages/Article'))
const router = createBrowserRouter([
{
path: '/',
element: (
<AuthRoute>
<Layout />
</AuthRoute>
),
children: [
{
index: true,
element: (
<Suspense fallback={'加载中'}>
<Home />
</Suspense>
)
},
{
path: 'article',
element: (
<Suspense fallback={'加载中'}>
<Article />
</Suspense>
)
},
{
path: 'publish',
element: (
<Suspense fallback={'加载中'}>
<Publish />
</Suspense>
)
},
],
},
{
path: '/login',
element: <Login />,
},
])
export default router
3.4、打包体积分析
可视化分析工具
- 安装分析打包体积的包:npm i source-map-explorer
- 在 package.json 中的 scripts 标签中,添加分析打包体积的命令
- 对项目打包:npm run build(如果已经打过包,可省略这一步)
- 运行分析命令:npm run analyze
- 然后浏览器会自动打开下面页面,分析图表中的包体,就可以对一些大的包做优化处理了
3.5、优化配置-CDN
基于3.4的分析,可以看出 react、react-dom 是比较大的包,但是又不需要经常变的,我们就可以使用cdn的方式引入,让其不参与打包,减少打包后的体积
分析说明:通过 craco 来修改 webpack 配置,从而实现 CDN 优化
核心代码
craco.config.js
// 添加自定义对于webpack的配置
const path = require('path')
const { whenProd, getPlugin, pluginByName } = require('@craco/craco')
module.exports = {
// webpack 配置
webpack: {
// 配置别名
alias: {
// 约定:使用 @ 表示 src 文件所在路径
'@': path.resolve(__dirname, 'src')
},
// 配置webpack
// 配置CDN
configure: (webpackConfig) => {
let cdn = {
js:[]
}
whenProd(() => {
// key: 不参与打包的包(由dependencies依赖项中的key决定)
// value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下
webpackConfig.externals = {
react: 'React',
'react-dom': 'ReactDOM'
}
// 配置现成的cdn资源地址
// 实际开发的时候 用公司自己花钱买的cdn服务器
cdn = {
js: [
'https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js',
]
}
})
// 通过 htmlWebpackPlugin插件 在public/index.html注入cdn资源url
const { isFound, match } = getPlugin(
webpackConfig,
pluginByName('HtmlWebpackPlugin')
)
if (isFound) {
// 找到了HtmlWebpackPlugin的插件
match.options.files = cdn
}
return webpackConfig
}
}
}
public/index.html
<body>
<div id="root"></div>
<!-- 加载第三发包的 CDN 链接 -->
<% htmlWebpackPlugin.options.files.js.forEach(cdnURL => { %>
<script src="<%= cdnURL %>"></script>
<% }) %>
</body>
注意:上面两个得注意一致,否则会下面报这个下面这个错误
Template execution failed: TypeError: Cannot read properties of undefined (reading 'js')