Open ArthurWangCN opened 4 months ago
优点:
缺点:
怎么避免 hooks 的常见问题:
useState: 用于定义组件状态, 需要注意的是该方法在更新状态时会进行浅比较, 如果待更新状态值和当前状态值一致, 则不会进行更新, 不会引起组件的重新渲染
useEffect: 让函数型组件拥有处理 副作⽤ 的能⼒, 每次依赖项改变, 都会触发回调函数的执行, 通过它可模拟类似 类组件 中的部分⽣命周期
useLayoutEffect: 与 useEffect 相同, 但它会在所有的 DOM 变更之后同步调用
useInsertionEffect: 在任何 DOM 突变之前触发, 主要是解决 CSS-in-JS 在渲染中注入样式的性能问题
useMemo: 可以监测某个值的变化, 根据变化值计算新值, useMemo 会缓存计算结果, 如果监测值没有发⽣变化, 即使组件重新渲染, 也不会重新计算
useRef: 获取 DOM 元素对象、记录非状态数据、获取子组件实例对象
useCallback: 可让您在重新渲染之间缓存函数定义, 使组件重新渲染时得到相同的函数实例
useImperativeHandle 用于绑定 ref
useReducer: 使用简易版 Redux
useContext: 使用 context
useDebugValue: 可以在 React DevTools 中向自定义 Hook 添加一个标签, 方便追踪数据
useId: 生成唯一 ID, 是 hook 所以只能在组件的顶层或您自己的 Hook 中调用它, 您不能在循环或条件内调用它、不应该用于生成列表中的键
useDeferredValue: 用于推迟更新部分 UI
useSyncExternalStore: 使用外部 store
useTransition: 允许在不阻塞 UI 的情况下更新状态
在类组件的时代时代, 为了性能优化我们经常会选择使用 PureComponent, 组件每次默认会对 props 进行一次 浅比较, 只有当 props 发生变更, 才会触发 render
class MyComponent extends PureComponent {
render () {}
}
当然在类组件中, 我们除了使用 PureComponent 还可以在 shouldComponentUpdate 生命周期中, 对 props 进行比较, 进行更深层次的控制;
补充: shouldComponentUpdate 当收到新的 props 或 state 时, 在渲染之前都会被调用 这里的比较可以是浅比较、也可以是深比较, 主要看代码实现 当 shouldComponentUpdate 返回为 true 的时候, 当前组件进行 render, 如果返回的是 false 则不进行 render
class MyComponent extends Component {
shouldComponentUpdate(){
if (需要 Render) {
// 会进行渲染
return true
}
// 不会进行渲染
return false
}
render () {}
}
在函数组件中, 我们是无法使用上面两种方式来限制 render 的, 但是 React 贴心的提供了 React.memo 这个 HOC(高阶组件), 它的作用和 PureComponent 很相似, 只是它是专门为函数组件设计的
// 组件
function MyComponent(props) {}
// 比较方法
function areEqual(prevProps, nextProps) {
if (需要 Render) {
// 会进行渲染
return false
}
// 不会进行渲染
return true
}
export default React.memo(MyComponent, areEqual);
作用: 性能优化, 如果本组件中的数据没有发⽣变化, 阻⽌组件更新, 类似类组件中的 PureComponent 和 shouldComponentUpdate
总结: React.memo 是一个高阶组件,用于优化 React 函数组件的性能。它通过浅层比较(shallow comparison)来决定是否重新渲染组件,从而避免不必要的渲染,提升应用性能。 将一个函数组件传递给 React.memo,它会返回一个新组件,该组件在 props 没有变化时不会重新渲染。
Hooks 只能在函数组件的顶层使用, 或者在自定义 hooks 中使用, 不能在循环、条件或嵌套函数中使用 hooks
原因: React 需要利用 调用顺序 来正确更新相应的状态, 以及调用相应的钩子函数, 一旦在循环或条件分支语句中调用 Hooks, 就容易导致调用顺序的不一致性, 从而产生难以预料到的后果
这里拿 useState 来举例:
export default () => {
const [name, setName] = useState('1');
if (!name) {
return null;
}
const [age, setAge] = useState();
const handler = useCallback(() => {
setName(null);
}, []);
return (
<div onClick={handler}>
点击我会报错
</div>
);
};
总结:
hooks 是将 state 原子化, 使用类似索引的方式来记录状态值, 当连续创建状态 A B, 就会有索引 0 对应着 A, 索引 1 对应这 B, 如果使用在循环、条件、嵌套函数内使用 Hook 就很容易造成索引错乱
useState 要返回两个值, 一个是当前状态, 另一个则是修改状态的方法, 那么这里它就有两种方式可以返回这两个值: 数组、对象
那么问题就回到, 数组和对象解构赋值的区别了:
数组的元素是按次序排列的, 数组解构时变量的取值由数组元素的位置决定, 变量名可以任意命名, 如下:
const [name, setName] = useState()
const [age, setAge] = useState()
对象的属性没有次序, 解构时变量名必须与属性同名才能取到正确的值, 假设 useState 返回的是一个对象, 那么就得这么使用:
const { state: name, setState: setName } = useState()
const { state: age, setState: setAge} = useState()
上面例子可以得出结果, useState 返回数组相比于对象会更灵活、解构起来也会更简洁、方便。
当然最终 useState 返回的是啥, 还是由具体实现决定, 如果 useState 返回的是对象, 也不是不行
// 一、实现useState
const { render } = require("react-dom");
let memoriedStates = [];
let lastIndex = 0;
function useState(initialState) {
memoriedStates[lastIndex] = memoriedStates[lastIndex] || initialState;
function setState(newState) {
memoriedStates[lastIndex] = newState;
// 状态更新完毕,调用render函数。重新更新视图
render();
}
// 返回最新状态和更新函数,注意 index 要前进
return [memoriedStates[lastIndex++], setState];
}
// 二、实现useEffect
let lastDendencies; // 存放依赖项的数组
function useEffect(callback, dependencies) {
if (lastDendencies) {
// 判断传入的依赖项是不是都没有变化,只要有以一项改变,就需要执行callback
const isChange = dependencies && dependencies.some((dep, index) => dep !== lastDendencies[index]);
if (isChange) {
// 一开始没有值,需要更新一次(相当于componentDidMount)
typeof callback === 'function' && callback();
// 更新依赖项
lastDendencies = dependencies;
}
} else {
// 一开始没有值,需要更新一次(相当于componentDidMount)
typeof callback === 'function' && callback();
// 更新依赖项
lastDendencies = dependencies;
}
}
// 三、实现useCallback
let lastCallback; // 最新的回调函数
let lastCallbackDependencies = []; // 回调函数的依赖项
function useCallback(callback, dependencies = []) {
if (lastCallback) {
const isChange = dependencies && dependencies.some((dep, index) = dep !== lastCallbackDependencies[index]);
if (isChange) {
// 只要有一个依赖项改变了,就更新回调(重新创建)
lastCallback = callback;
lastCallbackDependencies = dependencies;
}
} else {
lastCallback = callback;
lastCallbackDependencies = dependencies;
}
// 最后需要返回最新的函数
return lastCallback;
}
// 四、实现useRef
let lastRef;
function useRef(initialValue = null){
lastRef = lastRef != undefined ? lastRef : initialValue;
// 本质上就是返回一个对象,对象种有一个current属性,值为初始化传入的值,如果没有传入初始值,则默认为null
return {
current: lastRef
}
}
// 五、实现useContext
function useContext(context){
// 很简单,就是返回context的_currentValue值
return context._currentValue;
}
// 六、实现useReducer
let lastState;
function useReducer(reducer, initialState){
lastState = lastState !== undefined ? lastState : initialState;
// dispatch一个action,内部就是自动调用reducer来计算新的值返回
function dispatch(action){
lastState = reducer(lastState, action);
// 更新完毕后,需要重新渲染视图
render();
}
// 最后返回一个的状态值和派发action的方法
return [lastState, dispatch];
}
useCallback 是「useMemo 的返回值为函数」时的特殊情况, 是 React 提供的便捷方式。在 React Server Hooks 代码 中, useCallback 就是基于 useMemo 实现的, 尽管 React Client Hooks 没有使用同一份代码, 但 useCallback 的代码逻辑和 useMemo 的代码逻辑仍是一样的