跳转至

1-Hooks

意义

旨在分离视图逻辑与业务逻辑

对照类组件

  • useState:
    • 类组件state的一个属性
    • 所有返回的set方法都是调用类组件的setState方法更新state的一个属性
  • useEffect:
    • componentDidMount是effect函数
    • componentDidUpdate是deps更新时执行的effect函数
    • componentWillUnmount是useEffect的cleanup函数
  • useRef:把一个值直接作为组件的属性
    • 因此不像state一样会随着值更新而重新渲染

Hook

useState

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]

type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);

- 不会自动合并属性或者元素
- 返回的第二个元素是一个函数,俗称updater函数,这个函数是稳定的,一个纯函数,因此不需要把对应状态加入根据依赖变化的函数依赖中

useEffect

function useEffect(effect: EffectCallback, deps?: DependencyList): void;

type EffectCallback = () => void | Destructor;
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
declare const UNDEFINED_VOID_ONLY: unique symbol;
type DependencyList = readonly unknown[];
  • effect:通常忽视{ [UNDEFINED_VOID_ONLY]: never }即可,只需要知道cleanup函数的类型是() => void即可
  • deps
    • 值是空或者deps元素引用改变时,就会重新渲染,即componentDidUpdate
    • 而值是空数组当然就是表示仅在组件挂载时(componentDidMount)执行

体验cleanup函数的重要性

useEffect(() => {
  let ignore = false;
  setBio(null);
  fetchBio(person).then(result => {
    if (!ignore) {
      setBio(result);
    }
  });
  return () => {
    ignore = true;
  };
}, [person]);
  • 这个例子不仅展示useEffect用法,还说明了返回cleanup函数的重要性

  • 如果person连续更新,那么会发送多个请求,但是请求的响应不会按照请求的序列排序

  • 导致状态bio不是以请求的序列被使用,可能先用后面的请求的结果,再用前面请求的结果
  • 其实要是不这么写,就是没把网络请求当作一个资源,网络请求在useEffect当然也是一个资源,需要释放

  • fetch请求的取消方法

  • 就像上面一样,用一个bool标记

  • signal:这个需要注意,这么做的话就必须得在then中处理响应

useRef

function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T = undefined>(initialValue?: undefined): MutableRefObject<T | undefined>;
interface MutableRefObject<T> {
    current: T;
}

function useRef<T>(initialValue: T | null): RefObject<T>;
interface RefObject<T> {
    readonly current: T | null;
}

自定义自己写的组件的Ref

//T ref接口,P组件属性
function forwardRef<T, P = {}>(
    render: ForwardRefRenderFunction<T, PropsWithoutRef<P>>,
): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;


forwardRef<RefInterface, Props>((props, ref)=>{});


//ref和useCallback等一样可以有依赖
function useImperativeHandle<T, R extends T>(
ref: Ref<T> | undefined, 
init: () => R, deps?: DependencyList
): void;
useImperativeHandle(ref, ()=>({}), []);

useMemo

function useMemo<T>(factory: () => T, deps: DependencyList): T;

- memo就是Memoized
- 缓存工厂函数返回的值
- 注意
- React规范:useMemo仅用于缓存值,而用useCallback缓存函数
- 如果缓存的值反复变化,也就没了缓存的意义,还额外消耗了性能,如果是缓存函数,则更是,因为这个函数无论如何都是会创建的,只不过是让引用一样,避免作为某个依赖而反复执行或者作为子组件依赖而反复渲染
- 如果在工厂函数中使用了状态,则需要依赖状态,如果只是使用setState或者在setState中访问这个状态,则不需要
- 如果使用了ref,则不需要依赖,因为访问的一直是ref的最新值

memo缓存组件

function memo<P extends object>(
    Component: FunctionComponent<P>,
    propsAreEqual?: (prevProps: Readonly<P>, nextProps: Readonly<P>) => boolean,
): NamedExoticComponent<P>;

- https://zh-hans.react.dev/reference/react/useMemo#skipping-re-rendering-of-components
- 当函数组件属性变时,会重新渲染
- 默认是浅值比较,可以传递自定义比较函数
- 和key的效果差不多
- useMemo、useCallback用于把缓存的值传递给这个memo组件,如果不是,则无任何意义,直接使用状态更新或者定义函数(无论是否依赖状态)才是更好的选择

useCallback

function useCallback<T extends Function>(callback: T, deps: DependencyList): T;

- useMemo的丐版,仅用来缓存函数
- 实现目的主要是为了让缓存函数和缓存值区分开来

useContext

function useContext<T>(context: Context<T> /*, (not public API) observedBits?: number|boolean */): T;


//首先肯定得是先创建Context
function createContext<T>(
    defaultValue: T,
): Context<T>;
interface Context<T> {
    Provider: Provider<T>;
    Consumer: Consumer<T>;
    displayName?: string | undefined;
}

- context仅把数据传递给子组件
- useContext仅从祖先组件中取context

例子

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

自定义Hook

  • https://zh-hans.react.dev/learn/reusing-logic-with-custom-hooks
  • 大致来说,就是最新use前缀命名即可,像正常一样在自定义Hook中使用hook。