react 实践总结
常见问题
useEffect
useEffect 默认执行
- 默认执行两次
- 在 React 的开发模式下,useEffect 默认执行两次
<React.StrictMode>注释掉关闭
- 默认触发一次
useEffect(() => {},[a])a没有变化,也触发一次
useEffect测试
- 基础测试:第二个参数依赖,返回清除函数,以及打印顺序
查看代码
jsx
import { useEffect, useState } from 'react';
export default function Child1 () {
{/*
测试 1.1
useEffect 第二个参数依赖
测试 1.2
返回清除函数,以及打印顺序
*/}
const [state, setState] = useState(0);
console.log('render')
useEffect(() => {
console.log('useEffect');
});
useEffect(() => {
console.log('useEffect1', state);
}, [state]);
useEffect(() => {
console.log('useEffect2', state);
return () => {
console.log('useEffect2 return');
};
}, [state]);
return <>
<div>1.1 useEffect 第二个参数依赖</div>
<div>1.2 返回清除函数,以及打印顺序</div>
<button onClick={() => setState(state + 1)}>setState</button>
</>;
}- 第二个参数没有传入,打印
查看代码
jsx
import { useEffect, useState } from 'react';
export default function Child2 () {
{/*
测试
与 useCallback 对比下,即使useEffect第二个参数没有传入,打印的值都是新值
*/}
const [state, setState] = useState(0);
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(0);
useEffect(()=>{
// 这里打印是新值
console.log(state1)
setState1(() => {
// 这里打印是新值
console.log(state1)
return state1 + 1
})
setState2((value) => {
// 这里打印是新值
console.log(value)
return value + 1
})
},[state])
return <>
<div>2 useCallback 即使useEffect第二个参数没有传入,打印的值都是新值</div>
state:<button onClick={() => setState(state + 1)}>{state}</button>
state1:<button onClick={() => setState1(state1 + 1)}>{state1}</button>
state2:<button onClick={() => setState2(state2 + 1)}>{state2}</button>
</>
}useCallback
useCallback 中useState 取到的是旧值
useCallback(() => {console.log(b)},[a])不把 b 放到依赖数组时,取到的是旧值?- 因为 React 的函数组件里,闭包会捕获当时渲染时的变量值。
- useCallback 的函数体里用到的 count 定义时的那一份(旧值),不会随着 count 变化而更新。
查看代码
jsx
import { useState,useCallback } from 'react';
export default function Child() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
const callback = useCallback(()=>{
// 这里打印是旧值
// 但是当a更新时,再次调用callback则是新的b值
console.log("b in useCallback:", b);
setB((val)=>{
// 这里打印的是新值
console.log("setB 的参数", val)
return val + 1
})
},[a])
const callback2 = useCallback(()=>{
// 这里永远打印是旧值
console.log("b in useCallback:", b);
setB((val)=>{
// 这里打印的是新值
console.log("setB 的参数", val)
return val + 1
})
},[])
const callback3 = useCallback(()=>{
// 这里永远打印是新值
console.log("b in useCallback:", b);
setB((val)=>{
// 这里打印的是新值
console.log("setB 的参数", val)
return val + 1
})
},[b])
return (
<div>
<button onClick={() => setA(a + 1)}>add a</button>
<button onClick={() => setB(b + 1)}>add b</button>
<button onClick={callback}>callback</button>
<button onClick={callback2}>callback2</button>
<button onClick={callback3}>callback3</button>
</div>
);
}useContext
- UseContext 基本使用
查看代码
jsx
import React, { useState, useContext,useCallback } from 'react';
const MyContext = React.createContext('没路用的初始值');
function MyComponent() {
const [state, setState] = useState('state');
const handleClick = () => {
setState(val => val + "1");
};
const [show, setShow] = useState(false)
const showHandle = useCallback(()=>{
setShow((status) => !status)
},[])
return (
<MyContext.Provider value={{showHandle,state}}>
<ul>
<li>{show ? '显示':'隐藏'}</li>
<li><button onClick={handleClick}>更新state</button></li>
<MyChildComponent />
</ul>
</MyContext.Provider>
);
}
function MyChildComponent() {
return (
<MyGrandchildComponent />
);
}
function MyGrandchildComponent() {
const {state,showHandle} = useContext(MyContext);
// debugger
return (
<>
<li>{state}</li>
<li ><button onClick={showHandle}>更新show</button></li>
</>
);
}
export default MyComponent;- 弹窗提示 基于UseContext的实现
查看代码
tsx
'use client'
import React, {
createContext,
useContext,
useState,
useCallback,
ReactNode,
} from "react";
// Alert 类型
type AlertType = "success" | "error" | "info" | "warning";
interface Alert {
id: number;
message: string;
type: AlertType;
}
// Context 类型
interface AlertContextType {
show: (message: string, type?: AlertType, duration?: number) => void;
}
const AlertContext = createContext<AlertContextType | undefined>(undefined);
interface AlertProviderProps {
children: ReactNode;
}
export const AlertProvider: React.FC<AlertProviderProps> = ({ children }) => {
const [alerts, setAlerts] = useState<Alert[]>([]);
const show = useCallback(
(message: string, type: AlertType = "info", duration: number = 3000) => {
const id = Date.now(); // 唯一 id
setAlerts((prev) => [...prev, { id, message, type }]);
// 自动移除
setTimeout(() => {
setAlerts((prev) => prev.filter((a) => a.id !== id));
}, duration);
},
[]
);
const remove = useCallback((id: number) => {
setAlerts((prev) => prev.filter((a) => a.id !== id));
}, []);
return (
<>
<h2>AlertProvider</h2>
<AlertContext.Provider value={{ show }}>
{children}
<div className="fixed top-5 right-5 flex flex-col gap-2 z-50">
{alerts.map((alert) => (
<div
key={alert.id}
className={`alert ${
alert.type === "success"
? "alert-success"
: alert.type === "error"
? "alert-error"
: alert.type === "warning"
? "alert-warning"
: "alert-info"
}`}
>
<span>{alert.message}</span>
<button
className="btn btn-sm btn-ghost ml-2"
onClick={() => remove(alert.id)}
>
×
</button>
</div>
))}
</div>
</AlertContext.Provider>
</>
);
};
export const useAlert = (): AlertContextType => {
const context = useContext(AlertContext);
if (!context) {
throw new Error("useAlert 必须在 AlertProvider 内使用");
}
return context;
};jsx
import {useAlert} from "./Alert";
export default function Child(){
const {show} = useAlert()
const test = ()=>{
show("123","error")
}
return <button onClick={test}>alert</button>
}forwardRef
- React 19 以前,ref 并不能直接声明为一个组件的 prop,而是需要借助 forwardRef API
查看代码
jsx
import { forwardRef, useRef, useEffect } from 'react';
// React 19 以前,ref 并不能直接声明为一个组件的 prop,而是需要借助 forwardRef API
const NewCardInput = forwardRef(function NewCardInput (props, ref) {
const { title, onChange, onKeyDown } = props;
return (
<input type="text" value={title} ref={ref}
onChange={onChange} onKeyDown={onKeyDown} />
);
})
export default function KanbanNewCard () {
const inputElem = useRef(null);
useEffect(() => {
setTimeout(() => inputElem.current.focus(), 100);
}, []);
function handleChange () {
console.log("change");
}
function handleKeyDown () {
console.log("keydown");
}
return (
<div>
<h2>forwardRef</h2>
<NewCardInput ref={inputElem} onChange={handleChange} onKeyDown={handleKeyDown} />
</div>
);
}useMeno
- 基本使用
查看代码
jsx
import { useMemo, useState, useEffect } from "react"
export default function Meno () {
const [count, setCount] = useState(1)
const [count2, setCount2] = useState(1)
const memo = useMemo(() => {
console.log('useMemo')
return count + 10
}, [count])
useEffect(() => {
console.log('useMemo中 useEffect')
}, [count])
return (
<>
<h2>useMemo</h2>
<div>Meno</div>
<div>count:{count}</div>
<div>count2:{count2}</div>
<div>memo:{memo}</div>
<button onClick={() => setCount(count + 1)}>count++</button>
<button onClick={() => setCount2(count2 + 1)}>count2++</button>
</>
)
// 打印结果
// 首次渲染:useMemo 组件渲染 useEffect
// 点击 count++:useMemo 组件渲染 useEffect
// 点击 count2++:组件渲染
}React.Meno
- 基本使用
查看代码
jsx
import React, { useState } from 'react';
// 普通子组件,每次父组件渲染都会被重新渲染
const Child = ({ count, id }) => {
console.log('Child render', id);
return <div>Child count: {count} id: {id}</div>;
};
// 使用 React.memo 包裹后,props 相同不重新渲染
const MemoizedChild = React.memo(Child);
function App () {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div>
<h2>react.memo</h2>
<button onClick={() => setCount(count + 1)}>count + 1</button>
{/* 关键点:点击button,count2不会重新渲染MemoizedChild*/}
<button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
<MemoizedChild count={count} id={1} />
<Child count={count2} id={2} />
</div>
);
}
export default App;Props
- props各种传值
查看代码
jsx
function App() {
const todoList = [
{ title: '开发任务-1', status: '22-05-22 18:15' },
{ title: '开发任务-2', status: '22-05-22 18:15' },
{ title: '开发任务-3', status: '22-05-22 18:15' },
];
const KanbanCard = ({ title, status }) => {
return (
<li className="kanban-card">
<span className="card-title">{title} </span>
<span className="card-status">{status}</span>
</li>
);
};
const KanbanColumn = ({ title, children }) => {
return (
<section>
{title}
{children}
</section>
);
};
const handleAdd = () => {
console.log('handleAdd');
};
const showAdd = false;
const MyComponent = ({ prop1, prop2, booleanProp, onClick }) => (
<div>
<span>prop1文本:{prop1}, </span>
<span>prop2数字:{prop2},</span>
<span>booleanProp:{booleanProp ? 'true' : 'false'},</span>
<button onClick={onClick}>Click me</button>
</div>
)
return (<>
<h2>Props</h2>
<div className="App">
{/* title 传递一个组件 */}
<KanbanColumn title={
<>
待处理<button onClick={handleAdd}
disabled={showAdd}>⊕ 添加新卡片</button>
</>
}>
<ul>
{/* props 传递 一个对象*/}
{ todoList.map((props,index) => <KanbanCard {...props} key={index} />) }
</ul>
</KanbanColumn>
{/* props 传递各种类型的值 */}
<MyComponent prop1="我的" prop2={123} booleanProp={false}
onClick={(evt) => {console.log('clicked',evt)}} ></MyComponent>
</div></>
);
}
export default App;useRef
- useRef基本使用
查看代码
jsx
import { useRef } from 'react';
function Test1() {
const myRef = useRef(0);
return (<div>
<button onClick={() => {
myRef.current++;
// 不会触发组件重新渲染
console.log(myRef.current);
}}>设置</button>
</div>);
}
function Test2() {
// 通过 ref 操作 DOM
const myRef = useRef(null);
return (<div>
<div ref={myRef}>111</div>
<button onClick={() => {
myRef.current.innerText = "222";
}}>设置</button>
</div>);
}
export default function App () {
return (
<>
<h2>useRef</h2>
<Test1></Test1>
<Test2></Test2>
</>
);
};