react 的使用总结
【代码】react 的使用总结。
calc 失效
height: calc(~"100vh - 102px");
需要写成这种样式
Hooks只能在函数组件中使用
可以理解为听过Hooks 为函数组件钩入 class 组件的特性
Hooks 前后 , 组件开发模式的对比
- React v16.8 以前: class组件(提供状态) + 函数组件(展示内容)
- React v16.8 及其以后:
- class组件(提供状态) + 函数组件(展示内容)
- Hooks(提供状态) + 函数组件(展示内容)
- 混用以上两种方式: 部分功能用 class 组件 , 部分功能用 Hooks + 函数组件
📊 Hook vs 普通函数对比
| 特征 | React Hook | 普通函数 |
|---|---|---|
| 命名 | 以 use 开头 |
任意命名 |
| 状态管理 | 可以使用 useState、useEffect | 无状态 |
| 组件生命周期 | 可以访问组件生命周期 | 无法访问 |
| 调用位置 | 只能在 React 组件中调用(函数组件的最外层作用域调用) | 任何地方都可以调用 |
| 返回值 | 可以返回状态、函数等 | 返回普通数据 |
直接导入 STORE和通过 Context 获取 store区别
| 特性 | 直接导入 STORE | 通过 Context 获取 store |
|---|---|---|
| 响应式 | ❌ 非响应式 | ✅ 响应式 |
| 组件更新 | ❌ 不会触发组件更新 | ✅ 会触发组件更新 |
| 作用域 | ✅ 全局单例 | ✅ 组件作用域 |
| 测试性 | ❌ 难以测试 | ✅ 易于测试 |
| 性能 | ✅ 性能更好 | ❌ 可能影响性能 |
1. 响应式差异
直接导入 STORE(非响应式)
// ❌ 不会响应 store 状态变化
const commonState = STORE.getState(); // 获取当前快照
// 即使 store 状态变化,这个值也不会更新
通过 Context 获取(响应式)
// ✅ 响应 store 状态变化
let { store } = useContext(StoreContext);
const commonState = store.getState()?.common;
// 当 store 状态变化时,组件会重新渲染
2. 使用场景差异
适合直接导入 STORE 的场景
// ✅ 工具函数、纯计算逻辑
export const labelByValue = (key, value) => {
const commonState = STORE.getState(); // 只需要当前状态快照
return commonState?.common?.allOptions?.[key]?.find(t => t.value === value)?.label;
};
// ✅ 事件处理、非渲染逻辑
const handleClick = () => {
const state = STORE.getState(); // 不需要响应式更新
// 处理逻辑...
};
适合通过 Context 获取的场景
// ✅ React 组件、需要响应状态变化
const MyComponent = () => {
let { store } = useContext(StoreContext); // 需要响应式更新
const data = store.getState()?.data;
return <div>{data}</div>; // 数据变化时自动更新
};
// ✅ 自定义 Hook
export const useLabelByValue = (key, value) => {
let { store } = useContext(StoreContext);
const label = store.getState()?.common?.allOptions?.[key]?.find(t => t.value === value)?.label;
return label; // 返回响应式数据
};
store.subscribe作用
- 就是store.subscribe如果直接写在函数组件里,那么该函数组件每渲染一次,就会给store增加一个“订阅”
store.subscribe(() => {
console.log(femaleSex,num,222222222);
setRandom(Math.random());
});
useEffect
store.subscribe写在useEffect中 store.subscribe()因为传递给useEffect的第二个参数为一个空数组,所以回调只执行了一次
useEffect的回调函数里面的基本类型变量永远拿的是useEffect回调第一次执行 函数组件的基本类型变量(因为基本类型变量的赋值是值传递)
useEffect(() => {
store.subscribe(() => {
setRandom( v => {
console.log(femaleSex,v,222222222);
return Math.random()
});
// setFemaleSex(true)
// setFemaleSex((v) => {
// return !v
// })
});
}, []);//传递空数组 在初次渲染后才会执行 相当于只类组件的componentsDidMounted声明周期
类组件与函数组件对应
| 类组件 | 函数组件 |
|---|---|
| state | useState |
| 生命周期 | useEffect(依赖参数为[] = componentDidMount, 依赖项不写 = componentDidMount+ componentDidUpdate |
| ref / React.createRef | useRef |
vue与react对应
| vue | react | react |
|---|---|---|
| 类组件 | 函数组件 | |
| watch | 类组件的getDerivedStateFromProps | useEffect,需要传递依赖项, Effect 在 初始渲染后以及依赖项变更的重新渲染后 |
| computed | get ‘属性名’ | |
React中的flushSync与Vue中的nextTick
React中的flushSync与Vue中的nextTick是两种用于处理异步更新的机制。它们在React和Vue这两个流行的前端框架中起着重要的作用。
首先,让我们来看看flushSync。在React中,当需要更新UI时,React会将更新操作放入一个队列中,然后异步地执行这些更新操作。但是有时候,我们希望立即执行这些更新操作,而不是等待异步执行。这时就可以使用flushSync。flushSync会立即执行队列中的所有更新操作,确保更新是同步完成的。这对于某些特定的场景非常有用,比如在进行DOM测量之前,我们可能需要确保所有的更新已经完成。
而在Vue中,nextTick则是用来处理异步更新的机制。和React类似,当需要更新UI时,Vue也会将更新操作放入一个队列中,然后异步地执行这些更新操作。但是有时候,我们需要在DOM更新之后立即执行一些操作,比如获取更新后的DOM元素。这时就可以使用nextTick。nextTick会在DOM更新之后执行回调函数,确保我们可以在更新后立即访问到最新的DOM元素。
虽然flushSync和nextTick都是用于处理异步更新的机制,但它们的使用方式和场景略有不同。flushSync主要用于React中,用于确保更新是同步完成的;而nextTick则主要用于Vue中,用于在DOM更新之后执行回调函数。
总的来说,flushSync和nextTick都是非常有用的工具,它们可以帮助我们更好地处理异步更新,提升应用的性能和用户体验。无论是React还是Vue,都值得我们深入了解和使用。
传统生命周期(类组件)与 Hooks 对照表
| 类组件生命周期方法 | 函数组件 Hooks 替代方案 | 核心区别 |
|---|---|---|
componentDidMount |
useEffect(() => {}, []) |
依赖数组空表示仅挂载执行 |
componentDidUpdate |
useEffect(() => {}, [deps]) |
依赖变化时触发 |
componentWillUnmount |
useEffect(() => { return fn }, []) |
清理函数在卸载时执行 |
shouldComponentUpdate |
React.memo() + useMemo/useCallback |
浅比较或自定义比较函数 |
getDerivedStateFromProps |
useState + useEffect |
更明确的派生状态管理 |
getSnapshotBeforeUpdate |
无直接等效,需要自定义 Hook | 罕见需求,通常可以重构避免 |
React Hooks 的设计更加强调
"同步副作用到数据变化"的理念
状态(state)改变导致组件重新渲染
React 函数组件中的一个状态(state)改变,默认会导致整个函数组件重新执行(重新渲染),但这并不意味着一定会导致实际的DOM更新。
基本渲染行为
-
状态更新触发重新渲染:
- 当调用
setState或使用useReducer的 dispatch 时 - React 会安排一次新的渲染
- 整个函数组件会从头到尾重新执行一次
function Counter() { const [count, setCount] = useState(0); // 状态声明 console.log('组件重新渲染'); // 每次状态变化都会打印 return ( <button onClick={() => setCount(c => c + 1)}> 点击 {count} 次 </button> ); } - 当调用
关键细节说明
-
渲染 ≠ DOM更新:
- React 会进行 虚拟DOM对比(reconciliation)
- 只有发现真实DOM需要变化时才会更新
-
子组件的影响:
- 默认情况下,父组件重新渲染会导致所有子组件重新渲染
- 除非子组件使用
React.memo进行了优化
// 子组件默认会随父组件一起重新渲染 function Parent() { const [state, setState] = useState(); return <Child />; // Child每次都会重新渲染 }
性能优化方式
-
React.memo (针对props不变的情况):
const Child = React.memo(function Child({ text }) { console.log('只有props变化时才重新渲染'); return <div>{text}</div>; }); -
useMemo/useCallback (保持引用稳定):
function Parent() { const [count, setCount] = useState(0); const memoizedChild = useMemo(() => <Child />, []); const stableFn = useCallback(() => {}, []); return <div>{memoizedChild}</div>; } -
状态提升/下降 (隔离变化范围):
// 将频繁变化的状态下放到更小组件 function Parent() { return <Counter />; // 状态控制在Counter内部 }
特殊情况说明
-
相同的state值:
- 如果新state值与当前state相同(
Object.is比较) - React 会跳过渲染 (包括子组件)
const [value, setValue] = useState(0); // 下面的调用不会导致重新渲染: setValue(0); - 如果新state值与当前state相同(
-
批量更新 (React 18自动批处理):
function handleClick() { setCount(c => c + 1); // 不会立即触发渲染 setName('new'); // 不会立即触发渲染 // React会将这两个更新合并为一次渲染 }
为什么这样设计?
React选择默认重新执行整个函数组件的优点是:
- 避免手动管理shouldComponentUpdate的复杂性
- 保证状态和渲染的一致性
- 现代JavaScript引擎执行函数很快
- 配合虚拟DOM Diff算法效率足够
useEffect导致的死循环
🎯 useEffect 依赖数组详解
| 依赖数组 | 执行时机 | 使用场景 |
|---|---|---|
[] |
只在组件挂载时执行一次 | 初始化数据 |
[dep1, dep2] |
依赖变化时执行 | 响应状态变化 |
| 无依赖数组 | 每次渲染都执行 | 几乎不用(会导致死循环) |
使用 useEffect,依赖项传递是状态(useState)情况下,第一个回调函数参数是不是不能改变其他的状态,不然会导致死循环
const [ggg, setGgg] = useState([]);
const [aaa, setAaa] = useState([]);
useEffect(() => {
setGgg('ffff');
}, [aaa])
组件渲染-useEffect回调执行-改变状态(ggg)-触发组件重新渲染
📋 其他可能的死循环场景
1. 错误的依赖项
// ❌ 错误:依赖了会变化的状态
useEffect(() => {
setCount(count + 1); // 更新依赖的状态
}, [count]); // 依赖自身,死循环!
// ✅ 正确:使用函数式更新避免依赖
useEffect(() => {
setCount(prev => prev + 1); // 不依赖当前值
}, []); // 空依赖数组
2. 对象/数组依赖
// ❌ 错误:对象每次渲染都是新的
useEffect(() => {
fetchData(obj); // obj 每次渲染都是新对象
}, [obj]); // 死循环!
// ✅ 正确:使用基本类型或 useMemo
const objString = JSON.stringify(obj);
useEffect(() => {
fetchData(JSON.parse(objString));
}, [objString]); // 字符串是稳定的
3. 函数依赖
// ❌ 错误:函数每次渲染都是新的
useEffect(() => {
handleClick(); // 函数每次渲染都重新创建
}, [handleClick]); // 死循环!
// ✅ 正确:使用 useCallback
const handleClick = useCallback(() => {
// 函数逻辑
}, []); // 稳定的函数引用
useEffect(() => {
handleClick();
}, [handleClick]); // 不会死循环
避免死循环的5种正确方式
1. 添加条件判断
useEffect(() => {
if (count < 10) { // 添加终止条件
setCount(count + 1);
}
}, [count]);
2. 使用函数式更新
useEffect(() => {
setCount(prevCount => prevCount + 1); // ✅ 不直接依赖外部count
}, [/* 可省略count依赖 */]);
3. 移除非必要依赖
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api').then(res => {
// ✅ 不依赖data,只在mount时执行
setData(res.data);
});
}, []); // 空依赖数组
4. 拆分useEffect
const [filter, setFilter] = useState('');
const [results, setResults] = useState([]);
// 监听filter变化触发搜索
useEffect(() => {
searchAPI(filter).then(setResults);
}, [filter]);
// 独立的状态更新
const handleChange = (value) => {
setFilter(value);
};
5. 使用useReducer
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
function Component() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
useEffect(() => {
if (state.count < 5) {
dispatch({ type: 'INCREMENT' }); // ✅ 更安全的更新方式
}
}, [state.count]);
return <div>{state.count}</div>;
}
更多推荐
所有评论(0)