hooks 之 useState
useState(initialState)
- useState 使函数组件可以拥有自己的状态
弱化了class组件,推崇函数式编程
- useState 使用时声明要在当前组件的作用域最顶层
- 初始化的时候可以传递参数,这个参数可以是一个值,也可以传一个函数进去,注意: 如果传递的是一个函数,这个函数只会执行一次
const [count, setCount] = useState(() => 2);
const [count, setCount] = useState(2);
- set function 在调用的时候不会立即执行,而是在react的下一次渲染阶段更新(commit render)
- 如果更新引用值的状态,我们必须传递一个新的引用
const [obj, setObj] = useState({})
//更新时必须传递一个新的引用,才会更新
setObj(() => { ...obj, age: 18 })
hooks 之 useEffect
useEffect(setup, dependencies?)
- useEffect 处理副作用的,比如: http请求,dom操作等
异步操作多数都会产生副作用
- 建议尽可能的把副作用放在useEffect中运行,因为副作用会产生意料以外的结果,为了便于追踪
- useEffect 接收两个参数
- setup: 函数, 如果需要自己清除副作用,可以返回一个函数,这个返回函数做副作用清除工作
- dependencies?: 依赖,必须是一个数组
- useEffect 执行时机
- 当使用useEffect注册了setup之后,React会在该组件每次挂载完毕到页面中都会执行这个setup函数,异步执行
- 当依赖项发生变化的,useEffect会重新执行setup函数
hooks 之 useCallback
useCallback(fn, dependencies)
- useCallback 维护某一个函数的引用,时空切片: 如果依赖项不变,会一直引用上一次的时空切片,如果这个函数需要用到变化的数据,而不是上次时空切片的数据,就需要重新生成一个引用(依赖发生变化会重新创建一个函数的引用)
- useCallback 需要两个参数
- 需要复制的函数体(函数声明)
- 依赖项,当依赖项发生变化,对应的函数引用会重新生成
- useCallback 需要两个参数
如果不给任何依赖,拿到的数据始终是首次渲染时的时空切片的数据
为性能考虑,函数最好使用useCallback包裹一下,如果不使用useCallback,每次组件渲染都会创建一个新的函数引用,会造成性能浪费
hooks 之 useMemo
- useMemo 缓存数据,当依赖发生变化才会重新执行,缓存引用数据,相当于Vue的computed
hooks 之 useRef
- useRef: 构建一个状态,这个状态脱离React控制,重新渲染组件不会被初始化
useRef创建的数据是可读可写的,在数据发生变化的时候不会造成组件重新渲染
const timerRef = useRef(null);
const startTime = useCallback(() => {
timerRef.current = setInterval(() => {
}, 1000)
}, [])
const stopTime = useCallback(() => {
if(timerRef.current) clearInterval(timerRef.current)
}, 100)
useRef 获取真实dom 如果使用状态获取,会造成组件不必要的重复渲染
const ref = useRef(null)
retrun <div ref={ref}></div>
高阶组件 fowardRef
- 函数组件无法使用useRef
- forwarRef是一个高阶组件: 接收一个组件,返回一个新的组件
- forwardRef给组件扩展了一个属性 ref
- fowardRef 将允许给函数挂载ref,同时给函数组件扩展了一个属性,作为函数组件的第二个参数(ref)存在,可以通过这个参数传递任何想要传递的数据
- forwardRef 一般都是和组件ref连用,不会单独使用
hooks 之 useImperativeHandle
useImperativeHandle(ref, createHandle, dependencies?)
- 通过ref向外暴露方法
- useImperativeHandle 内部修改了ref.current
const MyInput = forwardRef(function MyInput(props, ref) {
useImperativeHandle(ref, () => {
return {
// ... your methods ...
};
}, []);
return <input {...props} />;
});
hooks 之 useLayoutEffect
useLayoutEffect(setup, dependencies?)
- useLayoutEffect 是同步执行的hooks(慎用,会阻塞UI渲染,只在某些特殊场景下可用)
- useLayoutEffect 运行规则: 组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入到同步对列等待执行, 所以useLayoutEffect会完全阻塞后续的更新工作
hooks 之 useTransition
useTransition()
- 调整UI更新的优先级,不阻塞UI的情况下更新状态
- 如果某个操作非常耗费性能,阻塞了后续的执行产生卡顿等,但是后续的某个操作又需要立即执行,这时可以把这个操作放到startTransition,startTransition不会阻塞用户交互,也不会让页面丢失响应
const [isPending, startTransition] = useTransition()
startTransition(() => {
//
});
hooks 之 useSyncExternalStore
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
- 可以订阅外部store的react hook
- subscribe: 是外部仓库提供的 subscribe 方法;
- getSnapshot: 用于获取指定 store 状态的方法 用于客户端渲染
- getServerSnapshot 是用于获取指定 store 状态的方法,用于服务端渲染;返回值就是我们在组件中可用的 store 状态
import { useSyncExternalStore } from 'react';
export default function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
function getSnapshot() {
return navigator.onLine;
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
//hooks 自定义封装
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
注意: 如果getSnapshot返回值与上次不同,React将重新渲染组件。这就是为什么,如果你总是返回一个不同的值,你会进入一个无限循环并得到这个错误。
function getSnapshot() {
// 🔴 Do not return always different objects from getSnapshot
return {
todos: myStore.todos
};
}
只有在实际发生更改时,getSnapshot对象才应返回不同的对象。如果您的存储包含不可变的数据,则可以直接返回该数据
function getSnapshot() {
// ✅ You can return immutable data
return myStore.todos;
}
subscribe 订阅函数不应该放在组件内部,因为每次re-render会重新创建订阅函数, 或者,将subscribe包装到useCallback中,只在某些参数更改时重新订阅
// 只有在某个依赖发生变化时才重新更新订阅函数
const subscribe = useCallback(() => {
// ...
}, [userId]);