关于前端:自定义React-Hooks

7次阅读

共计 5686 个字符,预计需要花费 15 分钟才能阅读完成。

1. 为什么应用 Hooks

副作用问题:诸如数据获取、订阅、定时执行工作、手动批改 ReactDOM 这些行为都能够称为副作用;而 Hooks 的呈现能够应用 useEffect 来解决这些副作用

简单的状态治理:之前通常应用 redux、mobx 这些第三方状态管理器来治理简单的状态,而 Hooks 能够应用 useReducer、useContext 配合实现简单的状态治理;

开发效率和品质问题:函数式组件比类组件简洁,效率高,性能也好。

2.useState

const [state, setState] = useState(initState)

2.1 每次渲染都是独立的闭包

  • 每次渲染都有本人的 props 和 state
  • 每次渲染都有本人的事件处理函数
  • 组件函数每次渲染都会被调用,每一次调用中 number 的值都是常量, 并且并赋予了以后渲染中的状态值
  • 在单次渲染中,props 和 state 始终不变
function Counter1() {let [info, setInfo] = useState({number: 0});
    const alertNumber = () => {setTimeout(() => {alert(info.number);
        }, 3000);
    }
    return (
        <div>
            <p>{info.number}</p>
            <button onClick={() => setInfo({ number: info.number + 1})}>+</button>
            <button onClick={alertNumber}>alertNumber</button>
        </div>
    )
}

2.2 函数式更新

如果新的 state 须要应用先前的 state 计算得出,能够将函数传递给 setState, 该函数将接管先前的 state 值,并返回一个更新后的值

function Counter2() {let [info, setInfo] = useState({number: 0});
    function lazy() {setTimeout(() => {setInfo({ number: info.number + 1})
        }, 2000);
    }
    function lazyFunction() {setTimeout(() => {setInfo(info => ({ number: info.number + 1}));
            setInfo(info => ({ number: info.number + 1}));
        }, 3000);
    }
    return (
        <div>
            <p>{info.number}</p>
            <button onClick={() => setInfo({ number: info.number + 1})}>+</button>
            <button onClick={lazy}>lazy</button>
            <button onClick={lazyFunction}>lazyFunction</button>
        </div>
    )
}

2.3 惰性初始化

  • initState 参数只会在初始渲染中起作用,后续渲染会被疏忽
  • 如果初始 state 须要通过简单计算取得,则能够传入一个函数,在函数中计算返回初始 state, 此函数只在初始渲染时被调用
  • 与 class 的 setState 办法不同,useState 不会主动合并更新对象,能够用函数式的 setState 联合开展运算符达到合并对象的成果
function Counter3() {let [info, setInfo] = useState(function () {console.log('初始状态 -----')
      return {number: 0, name: "计数器"};
    });
    console.log("Counter5 render");
    return (
      <div>
        <p>
          {info.name}:{info.number}
        </p>
        <button onClick={() => setInfo({ ...info, number: info.number + 1})}>
          +
        </button>
        <button onClick={() => setInfo(info)}>+</button>
      </div>
    );
}

3.useEffect

useEffect(callback, array):副作用解决的钩子;它也是 componentDidMount()、componentDidUpdate()、componentWillUnmount()、 这几个生命周期办法的对立,一个顶三个!

  • 第二个参数如果不写,只有状态扭转都会执行。
  • 第二个参数是个空数组时,不论哪个状态扭转都不执行,只在组件初始时执行一次。
  • 当第一个回调函数中有 return 返回值时,示意 componentWIllUnmount 时执行

4. 性能优化

  • 把回调函数和依赖项数组作为参数传入 useCallback, 将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项扭转时才会更新。(管制组件什么时候更新)
  • 把创立函数和依赖项数组作为参数传入 useMemo 仅会在某个依赖项扭转时才从新计算 memoized 值 这种优化有助于防止在每次渲染时都进行高开销的计算。(管制组件是否更新)
let lastAddClick;
let lastChangeName;
function Counter4() {let [number, setNumber] = useState(0);
    let [name, setName] = useState('zhufeng');
    // 会在每次渲染的时候都 会生成一个新的函数
    // 只有在依赖的变量发生变化的时候才会从新生成
    const addClick = useCallback(() => setNumber(number + 1), [number]);
    console.log(lastAddClick === addClick);
    lastAddClick = addClick;
    const changeName = useCallback(() => setName(Date.now()), [name]);
    console.log(lastChangeName === changeName);
    lastChangeName = changeName;
    return (
        <div>
            <p>{name}:{number}</p>
            <button onClick={addClick}>addClick</button>
            <button onClick={changeName}>changeName</button>
        </div>
    )
}

5.Hooks 规定

自定义 Hook 必须以“**use**”结尾吗?必须如此。这个约定十分重要。不遵循的话,因为无奈判断某个函数是否蕴含对其外部 Hook 的调用,React 将无奈主动查看你的 Hook 是否违反了 Hook 的规定。

  • 只能在 react 的函数组件应用调用 hook
  • 须要在组件顶层调用,不能在判断啊,循环里调用,因为 hook 是按程序执行的,退出放在判断里,第一个调用了,第二次没调用,前面的 hook 调用提前执行,会导致 bug。

每一个 Hook 都有的两个相干函数:mountXxx()updateXxx(),它们别离是 Hook 在 Mount 阶段(即组件的挂载、或者说初始化阶段、又或者说是第一次执行 useXxx()的时候)和 Update 阶段(即组件的更新、或者说组件从新渲染阶段)的逻辑。为了方便管理和调用,react 的工程师把 Hook 在 Mount 阶段的逻辑存到(HooksDispatcherOnMount)对象中,把 Update 阶段的逻辑存到(HooksDispatcherOnUpdate)对象中

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useResponder: createDeprecatedResponderListener,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
};

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useResponder: createDeprecatedResponderListener,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
};

Hook 在 Mount 阶段干了啥?

用 useState 为例。useState 在 Mount 阶段的逻辑写在 mountState() 办法中:

  1. 获取以后 Hook 节点,同时将以后 Hook 增加到 Hook 链表中
  2. 初始化 Hook 的状态,即读取初始 state 值
  3. 创立一个新的链表作为更新队列,用来寄存更新操作(setXxx())
  4. 创立一个 dispatch 办法(即 useState 返回的数组的第二个参数:setXxx()),该办法的用处是用来批改 state,并将此更新操作增加到更新队列中,另外还会将该更新和以后正在渲染的 fiber 绑定起来
  5. 返回以后 state 和 批改 state 的办法(dispatch)
function mountState<S>(initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {

  // 获取以后 Hook 节点,同时将以后 Hook 增加到 Hook 链表中
  const hook = mountWorkInProgressHook();
  
  // 初始化 Hook 的状态,即读取初始 state 值
  if (typeof initialState === 'function') {initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  
  // 创立一个新的链表作为更新队列,用来寄存更新(setXxx())const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
  
  // 创立一个 dispatch 办法(即 useState 返回的数组的第二个参数:setXxx()),// 该办法的作用是用来批改 state,并将此更新增加到更新队列中,另外还会将改更新和以后正在渲染的 fiber 绑定起来
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  
  // 返回以后 state 和 批改 state 的办法
  return [hook.memoizedState, dispatch];
}

存储 Hook 的数据结构——链表

一个函数组件中的所有 Hook 是以 链表 的模式存储的。链表中的每个节点就是一个 Hook

export type Hook = {
    memoizedState: any, // Hook 本身保护的状态
    ...
    queue: UpdateQueue<any, any> | null, // Hook 本身保护的更新队列
    next: Hook | null, // next 指向下一个 Hook
};
const [firstName, setFirstName] = useState('尼古拉斯');
const [lastName, setLastName] = useState('赵四');
useEffect(() => {})

useState 如何解决 state 更新

更新队列链表 queue,用来寄存更新操作,链表中的每一个节点就是一次更新 state 的操作(就是调用了一次 setXxx()),以便前面 Update 阶段能够拿到最新的 state。

const queue = (hook.queue = {
      pending: null,
      dispatch: null,
      lastRenderedReducer: basicStateReducer,
      lastRenderedState: (initialState: any),
});

pending:最近一个期待执行的更新

dispatch:更新 state 的办法(setXxx)

lastRenderedReducer:组件最近一次渲染时用的 reducer(useState 实际上是一个简化版的 useReducer,之所以用户在应用 useState 时不须要传入 reducer,是因为 useState 默认应用 react 官网写好的 reducer:basicStateReducer

lastRenderedState:组件最近一次渲染的 state

正文完
 0