一、学习目标
- 掌握 ref、forwardRef、suspense、lazy 等特性
- 深入理解 React Hooks,能根据实际业务需求封装自定义 Hook
- 深入理解 React 样式体系方案:module css、css in js
- 掌握 React + Typescript 开发范式
- React 性能优化技巧:如代码拆分、懒加载、React.memo、useMemo 和 useCallback 等
- 了解 React19 新特性
二、补充资料
- Suspense 官方文档:https://zh-hans.react.dev/reference/react/Suspense
- 阿里 hooks 库:https://ahooks.js.org/zh-CN/
- React 生态:https://github.com/enaqx/awesome-react
- React 秘笈:https://react-typescript-cheatsheet.netlify.app/
- immer:https://github.com/immerjs/immer
- React + Typescript 类型工具小抄:https://github.com/typescript-cheatsheets/utilities
- 构建产物兼容支持等级,browserslist 方案:https://github.com/browserslist/browserslist
三、面试真题
1、说说你对 React Hooks 的理解
2、说说 React Suspense
四、React进阶特性
用vite快速创建一个react项目
1、ref
(1)、我们在开发中不再推荐使用DOM提供的api去获取和操作DOM元素了,如document.getElementById等
import React, { useRef, useEffect } from "react"
export const RefDemo = () => {
// 1、通过创建ref对象的方式获取dom实例
// const inputRef = React.createRef<HTMLInputElement>(null);
// 2、使用hooks方式获取dom实例
const inputRef = useRef<HTMLInputElement>(null);
// 只有在元素挂载完毕之后才能获取到该节点,可以等同于componentDidMount
useEffect(()=>{
if(inputRef.current){
inputRef.current.focus()
}
}, [])
return (
<div>
{/* form元素获取 dom 实例 */}
{/* canvas 元素获取 dom 实例 */}
<input ref={inputRef} />
</div>
)
}
(2)、用来存储值,但是不能是状态
之前
组件内部的数据一定要在组件内部定义,不要在组件外部定义
import React, { useRef, useEffect } from "react"
// 不要随意定义变量
// let timer = null
const Test = () => {
// 不要随意定义变量
let timer = null
useEffect(() => {
timer = setInterval(() => {
console.log('1')
}, 1000)
return () => {
clearTimeout(timer)
}
}, [])
return (
<div></div>
)
}
现在
const Test = () => {
const timerRef = useRef(null)
useEffect(() => {
timerRef.current = setInterval(() => {
console.log('1')
}, 1000)
return () => {
clearTimeout(timerRef.current)
}
}, [])
return (
<div></div>
)
}
对象的方式存储ref
export const Test = () => {
const inputsRef = useRef<Record<string, HTMLInputElement | null>>({})
useEffect(() => {
console.log(inputsRef.current)
console.log(inputsRef.current.input1.value)
}, [])
return (
<div>
{/* 回调函数直接赋值 */}
<input ref={(el) => inputsRef.current['input1'] = el} />
<input ref={(el) => inputsRef.current['input2'] = el} />
<input ref={(el) => inputsRef.current['input3'] = el} />
</div>
)
}
export const Test = () => {
const inputsRef = useRef({})
useEffect(() => {
console.log(inputsRef.current)
}, [])
return (
<div>
{
['input1', 'input2', 'input3', 'input4'].map((item) => (
<input ref={(el) => inputsRef.current[item] = el} />
))
}
</div>
)
}
2、suspense
(1)、动态加载组件,用Suspense来做过度过程的降级处理loading(异步组件)
代码拆分 = 动态加载异步组件 + suspense
Input.js
const Input = () => {
const inputRef = useRef(null)
useEffect(()=>{
if(inputRef.current){
inputRef.current.value = 666
}
}, [inputRef])
return (
<div>
<input type="text" ref={node=>inputRef.current = node} />
</div>
)
}
export default Input;
// 静态导入组件,性能不好,假如静态组件里面有个大体积图形,如果目前我不想渲染Input组件,那我还需要加载这个大体积图形的代码吗?
// import Input from './Input'
const Input = lazy(() => import('./Input')); //按需加载 默认导入
const Test = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<Input />
</Suspense>
)
}
(2)、动态加载数据,等待异步处理完之后,再来渲染内部的组件(带有异步操作组件)
import React, { useRef, useEffect, Suspense, use } from "react"
let cache = new Map();
const fetchDate = (url) => {
if (!cache.has(url)) {
cache.set(url, getData(url))
}
return cache.get(url)
}
async function getData(url){
if (url === "/the-beatles/albums") {
return await getAlbums();
} else if (url === "/the-beatles/bio") {
return await getBio();
}else{
throw new Error("url is not valid")
}
}
async function getBio(){
await new Promise(resolve=>{
setTimeout(resolve, 3000)
})
return 'Bio'
}
async function getAlbums(){
await new Promise(resolve=>{
setTimeout(resolve, 3000)
})
return [
{ id: 13, title: 'Let It Be', year: 1970},
{ id: 12, title: 'Abbey Road', year: 1969}
]
}
function Albums({ artistId, type }) {
const albums = use(fetchData(`/${artistId}/albums`));
return (
<ul>
{albums.map(album => (
<li key={album.id}>
{album.title} ({album.year})
</li>
))}
</ul>
);
}
const Test = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<Albums artistId="the-beatles"/>
</Suspense>
)
}
3、forwardRef(before React19)
父组件给子组件传递ref
React19,使用props传递ref
interface InputsProps {
value?: string;
ref?: Ref<HTMLInputElement>;
}
const Input = ({ ref }: InputsProps) => {
return (
<div>
<input ref={ref} />
</div>
)
}
const Test = () => {
const inputRef = useRef(null)
return (
<div>
<Input ref={inputRef} />
</div>
)
}
4、memo
优化子组件更新的hook
const Child = memo(({age, name}) => {
return (
<div>
<div>{age}</div>
<div>{name}</div>
</div>
)
}, (prevProps, nextProps)=>{// 判断props是否发生了变化,true表示props更新前后结果相等,不渲染
// console.log(prevProps, nextProps)
return prevProps.age === nextProps.age
})
const Father = () => {
const [age, setAge] = useState(0)
const [name, setName] = useState('pig')
return (
<div>
<Child name={name} age={age} />
<button onClick={()=>setAge(c=>c+1)}>age</button>
<button onClick={()=>setName("pitaya")}>name</button>
</div>
)
}
5、useLayoutEffect
与useEffect用法一样,但是它是在dom变更之后同步执行的,比useEffect先执行
const Test = () => {
const ref = useRef(null)
useEffect(() => {
if (ref.current) {
ref.current.style.backgroundColor = 'yellow';
}
console.log('useEffect')
}, [ref])
useLayoutEffect(() => {
if (ref.current) {
ref.current.style.backgroundColor = 'yellow';
}
console.log('useLayoutEffect')
}, [ref])
// useLayoutEffect
// useEffect
return <div ref={ref}>Layout Effect</div>;
}
6、useId
生成唯一的id,具体用法如下
const Test = () => {
const firstNameId = useId()
const lastNameId = useId()
useEffect(() => {
console.log(firstNameId, lastNameId)
}, [firstNameId, lastNameId])
return (
<div>
<label htmlFor={firstNameId}>firstName</label>
<input id={firstNameId} type="text" />
<label htmlFor={lastNameId}>lastName</label>
<input id={lastNameId} type="text" />
</div>
)
}
7、useTransition
处理ui过渡
8、自定义hook demo
状态和ui在一个组件里面,我们将它解耦
状态封装在hooks里面,ui封装在ui组件里面
const Input = () => {
const [value, setValue] = useState("");
const handleChange = (e: React.changeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}
return (
<div>
<input type="text" value={value} onChange={handleChange}/>
</div>
)
}
const Test = () => {
return (
<div>
<Input />
</div>
)
}
封装之后
const useControlValue = (initialValue: string) => {
const [value, setValue] = useState(initialValue);
const handleChange = (e: React.changeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}
return {
value,
handleChange
}
}
const Test = () => {
const {value, handleChange} = useControlValue
return (
<div>
<input type="text" value={value} onChange={handleChange}/>
</div>
)
}
9、样式方案
- 内联样式:简单直观,适合动态样式和小型项目,但复用性差,功能有限。
- CSS Modules:提供局部作用域,支持所有 CSS 功能,适合中大型项目,但配置复杂。
- CSS-in-JS:组件化样式,动态生成样式,功能强大,适合大型项目,但性能开销和学习成本较高。
- Tailwind CSS:实用优先,快速开发 UI,适合所有项目,但类名冗长,灵活性受限。
内联样式
- 样式复用差:无法轻易复用样式,导致样式代码重复。
- 缺乏伪类和伪元素支持:无法使用 :hover、:active 等伪类和伪元素。
- CSS 功能有限:许多 CSS 功能(如媒体查询、关键帧动画等)不能使用。
const divStyle = {
color: 'blue',
backgroundColor: "pink"
}
const InlineStyles = () => {
return (
<div style={divStyle}>hello, world</div>
)
}
CSS Modules
- 配置复杂:需要通过构建工具(如 Webpack)进行配置。
- 样式依赖文件系统:样式存储在独立的文件中,可能影响开发体验。
styles.module.css
.bluAndPink {
color: blue;
backgroundColor: "pink";
}
import styles from "./styles.module.css"
const CSSModules = () => {
return (
<div>
{/* CSS Modules */}
<h1 className={styles.bluAndPink}>+</h1>
</div>
)
}
CSS-in-JS
styled-components库&Emotion
- 性能开销:在运行时生成样式,可能会增加性能开销,特别是在大量组件中使用时。
- 学习成本:需要学习特定的库和语法。
- 打包体积:可能增加打包后的 JavaScript 体积。
- SSR 支持较困难
安装依赖
npm install styled-components
import styled from 'styled-components';
const Container = styled.div`
color: blue;
background-color: lightgray;
`;
function StyledComponent() {
return <Container>Hello, world!</Container>;
}
Tailwind CSS
- 类名冗长:大量的类名可能导致 JSX 代码不易读。
- 学习曲线:需要熟悉 Tailwind CSS 提供的类名和用法。
- 灵活性受限:在一些复杂场景下,可能需要编写自定义样式。
安装依赖
npm install tailwindcss
npx tailwindcss init
配置: 在 tailwind.config.js 中配置,并在 index.css 中引入 Tailwind 样式。
function StyledComponent() {
return <div className="text-blue-500 bg-pink-200">Hello, world!</div>;
}
10、React19
看不懂,回家再学
1094

被折叠的 条评论
为什么被折叠?



