[react]24、自定义hooks

2021-12-02  本文已影响0人  史记_d5da

1、自定义Hook的基本使用

自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。

function CustomHookLifeDemo01() {
    useLoggingLifeCycle('CustomHookLifeDemo01')
    return (
        <div>
            <h2>CustomHookLifeDemo01</h2>
            <Home />
            <Profile />
        </div>
    )
}
// 必须以use开头的function才能使用 useEffect函数
function useLoggingLifeCycle(name) {
    useEffect(() => {
        console.log(`${name}组件被创建`);
        return () => {
            console.log(`${name}组件被销毁掉`);
        }
    })
}

2、自定义useContext

App.js

export const UserContext = createContext()
export const ThemContext = createContext()
export const TokenContext = createContext()

user-context.js

import { useContext } from "react";
import { TokenContext, UserContext } from "../App";
export function useUserContext() {
    const user = useContext(UserContext);
    const token = useContext(TokenContext);
    return [user, token];
}

useContext的Demo

import React, { useCallback, useContext } from 'react'
import { TokenContext, UserContext } from '../App'
import { useUserContext } from '../hooks/user-hook';

function CustomContextShare() {
    const [user, token] = useUserContext()
    console.log(user, token);
    return (
        <div>
            <h2>CustomContextShare</h2>
        </div>
    )
}
export default CustomContextShare

3、自定义useScrollPosition

scroll-position-hook.js

import { useEffect, useState } from "react";
export function useScrollPosition() {
    const [scrollPosition, setScrollPosition] = useState(0);
    useEffect(() => {
        const handleScroll = () => {
            setScrollPosition(window.scrollY)
            console.log(window.scrollY);
        }
        document.addEventListener("scroll", handleScroll)
        return (() => {
            document.removeEventListener("scroll", handleScroll)
        })
    })
    return scrollPosition;
}

CustomScrollPositionhook.js

import React, { useEffect, useState } from 'react'
import { useScrollPosition } from '../hooks/scroll-position-hook'
function CustomScrollPositionhook() {
    const scrollPosition = useScrollPosition()

    return (
        <div style={{padding: "1000px 0"}}>
            <h2 style={{position: "fixed", left: 0, right: 0, top: 0}}>CustomScrollPositionhook {scrollPosition}</h2>
        </div>
    )
}
export default CustomScrollPositionhook

4、练习

local-store-hook.js

import { useEffect, useState } from "react";
export function useLocalStorage(key) {
    const [name, setName] = useState(() => {
        const name = JSON.parse(window.localStorage.getItem(key));
        return name;
    })

    useEffect(() => {
        window.localStorage.setItem(key, JSON.stringify(name));
    }, [name])
    return [name, setName];
}

CustomDataStorageHook.js

import React, { useEffect, useState } from 'react'
import { useLocalStorage } from '../hooks/local-store-hook';
function CustomDataStorageHook() {

    const [name, setName] = useLocalStorage("name")
    return (
        <div>
            <h2>CustomDataStorageHook-{name}</h2>
            <button onClick={e => setName("codeWhy")}>设置name</button>
        </div>
    )
}
export default CustomDataStorageHook

useState源码

// ReactFiberHooks.new.js line 346
export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;

  if (__DEV__) {
    hookTypesDev =
      current !== null
        ? ((current._debugHookTypes: any): Array<HookType>)
        : null;
    hookTypesUpdateIndexDev = -1;
    // Used for hot reloading:
    ignorePreviousDependencies =
      current !== null && current.type !== workInProgress.type;
  }

  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  // The following should have already been reset
  // currentHook = null;
  // workInProgressHook = null;

  // didScheduleRenderPhaseUpdate = false;

  // TODO Warn if no hooks are used at all during mount, then some are used during update.
  // Currently we will identify the update render as a mount because memoizedState === null.
  // This is tricky because it's valid for certain types of components (e.g. React.lazy)

  // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
  // Non-stateful hooks (e.g. context) don't get added to memoizedState,
  // so memoizedState would be null during updates and mounts.
  if (__DEV__) {
    if (current !== null && current.memoizedState !== null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
    } else if (hookTypesDev !== null) {
      // This dispatcher handles an edge case where a component is updating,
      // but no stateful hooks have been used.
      // We want to match the production code behavior (which will use HooksDispatcherOnMount),
      // but with the extra DEV validation to ensure hooks ordering hasn't changed.
      // This dispatcher does that.
      ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
    } else {
      ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
    }
  } else {
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null // 保存state状态
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }

  let children = Component(props, secondArg);

  // Check if there was a render phase update
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    // Keep rendering in a loop for as long as render phase updates continue to
    // be scheduled. Use a counter to prevent infinite loops.
    let numberOfReRenders: number = 0;
    do {
      didScheduleRenderPhaseUpdateDuringThisPass = false;
      invariant(
        numberOfReRenders < RE_RENDER_LIMIT,
        'Too many re-renders. React limits the number of renders to prevent ' +
          'an infinite loop.',
      );

      numberOfReRenders += 1;
      if (__DEV__) {
        // Even when hot reloading, allow dependencies to stabilize
        // after first render to prevent infinite render phase updates.
        ignorePreviousDependencies = false;
      }

      // Start over from the beginning of the list
      currentHook = null;
      workInProgressHook = null;

      workInProgress.updateQueue = null;

      if (__DEV__) {
        // Also validate hook order for cascading updates.
        hookTypesUpdateIndexDev = -1;
      }

      ReactCurrentDispatcher.current = __DEV__
        ? HooksDispatcherOnRerenderInDEV
        : HooksDispatcherOnRerender;

      children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);
  }

  // We can assume the previous dispatcher is always this one, since we set it
  // at the beginning of the render phase and there's no re-entrancy.
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

  if (__DEV__) {
    workInProgress._debugHookTypes = hookTypesDev;
  }

  // This check uses currentHook so that it works the same in DEV and prod bundles.
  // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
  const didRenderTooFewHooks =
    currentHook !== null && currentHook.next !== null;

  renderLanes = NoLanes;
  currentlyRenderingFiber = (null: any);

  currentHook = null;
  workInProgressHook = null;

  if (__DEV__) {
    currentHookNameInDev = null;
    hookTypesDev = null;
    hookTypesUpdateIndexDev = -1;
  }

  didScheduleRenderPhaseUpdate = false;

  invariant(
    !didRenderTooFewHooks,
    'Rendered fewer hooks than expected. This may be caused by an accidental ' +
      'early return statement.',
  );

  return children;
}
上一篇下一篇

猜你喜欢

热点阅读