React源码解析————ReactHooks(一)

React源码解析————ReactHooks(一)

  • 2021SC@SDUSC
  • Hooks使用
    • userState():状态钩子
    • useContext():共享状态钩子
    • useReducer():Action钩子
    • useEffect():副作用钩子
  • ReactHooks.js

2021SC@SDUSC

2021SC@SDUSC

Hooks使用

众所周知,React的组件创建方式,一种是类组件,一种是函数组件,一般来说,开发人员都喜欢使用类组件,因为功能全啊,但是React团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可,也就是说组件的最佳写法应该是函数,而不是类。但是,我们知道函数组件缺少很多类组件所拥有的,ex:state,为了鼓励大家尽量使用函数组件,Hooks应运而生:组件尽量写成纯函数,如果需要外部功能和副作用,就用Hooks把外部代码"钩"进来
这里只展示四种最常用的:

userState():状态钩子

import React, {useState} from 'react'
const AddNumber = () => {
  const [ num, setNum ] = useState(0)
  const addNum = () => {
    let unum = num
    setNum(unum+=1)
  } 
  return (
    <>
      

{num}

) }

在useState()中,它接受状态的初始值作为参数,它返回一个数组,其中数组第一项为一个变量,指向状态的当前值。类似this.state,第二项是一个函数,用来更新状态,类似setState。
注意:class this.setState更新是state是合并, useState中setState是替换。

useContext():共享状态钩子

组件之间共享状态

import React,{ useContext } from 'react'
const Test = () => {
  const TestContext = React.createContext({})
  const TestA =() => {
    const { name } = useContext(TestContext)
    return (
        

名字:{name}

) } const TestB =() => { const { name } = useContext(TestContext) return (

名字:{name}

) } return ( ) }

useReducer():Action钩子

在使用React的过程中,如遇到状态管理,我们一般会用到Redux,而React本身是不提供状态管理的。而useReducer()为我们提供了状态管理。

const [state, dispatch] = useReducer(reducer, initialState);

reducer就是一个只能通过action将state从一个过程转换成另一个过程的纯函数;useReducer就是一种通过(state,action) => newState的过程,和redux工作方式一样。数据流: dispatch(action) ——> reducer更新state ——> 返回更新后的state

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case ‘decrement’:
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      
      
    
  );
}

useEffect():副作用钩子

useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。

useEffect(() => {},[array])

只要这个数组发生变化,useEffect()就会执行。当第二项省略时,useEffect()会在每次组件渲染时执行。这一点类似于类组件的componentDidMount。下面我们通过代码模拟一个异步加载数据。

function Demo3() {
  const [data, setData] = useState();
  useEffect(() => {
    console.log("useEffect—[]”);
    fetch(“xxx网站”)
      .then(res => res.json())
      .then(res => {
        setData(res);
      });
  }, []);
  useEffect(() => {
    console.log("useEffect ---> 无依赖");
  });
  useEffect(() => {
    console.log(“useEffect 依赖data: data发生了变化”);
  }, [data]);

  return (
    

data: {JSON.stringify(data)}

); } export default Demo3;

React源码解析————ReactHooks(一)_第1张图片

effect在render后按照前后顺序执行。
effect在没有任何依赖的情况下,render后每次都按照顺序执行。
effect内部执行是异步的。
依赖[]可以实现类似componentDidMount的作用,但最好忘记生命周期, 只记副作用。

ReactHooks.js

当我们使用hooks时是从react.js中引入那些方法
以useState为例:

import { useState } from 'react'

而react.js又是从ReactHooks.js中引入,代码如下:

export function useState(
  initialState: (() => S) | S,
): [S, Dispatch>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

这个resolveDispatcher()又是:

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;

但是当我们去找到ReactCurrentDispatcher,却发现它的current此时为null

const ReactCurrentDispatcher = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Dispatcher),
};

而对于Dispatcher,其实只是定义了一些派发者方法类型,实际项目开发过程中,可以稍微参照一下这边的泛型定义:

export type Dispatcher = {|
  getCacheForType?: (resourceType: () => T) => T,
  readContext(context: ReactContext): T,
  useState(initialState: (() => S) | S): [S, Dispatch>],
  useReducer(
    reducer: (S, A) => S,
    initialArg: I,
    init?: (I) => S,
  ): [S, Dispatch],
  useContext(context: ReactContext): T,
  useRef(initialValue: T): {|current: T|},
  useEffect(
    create: () => (() => void) | void,
    deps: Array | void | null,
  ): void,
  useInsertionEffect(
    create: () => (() => void) | void,
    deps: Array | void | null,
  ): void,
  useLayoutEffect(
    create: () => (() => void) | void,
    deps: Array | void | null,
  ): void,
  useCallback(callback: T, deps: Array | void | null): T,
  useMemo(nextCreate: () => T, deps: Array | void | null): T,
  useImperativeHandle(
    ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
    create: () => T,
    deps: Array | void | null,
  ): void,
  useDebugValue(value: T, formatterFn: ?(value: T) => mixed): void,
  useDeferredValue(value: T): T,
  useTransition(): [boolean, (() => void) => void],
  useMutableSource(
    source: MutableSource,
    getSnapshot: MutableSourceGetSnapshotFn,
    subscribe: MutableSourceSubscribeFn,
  ): Snapshot,
  useSyncExternalStore(
    subscribe: (() => void) => () => void,
    getSnapshot: () => T,
    getServerSnapshot?: () => T,
  ): T,
  useOpaqueIdentifier(): any,
  useCacheRefresh?: () => (?() => T, ?T) => void,

  unstable_isNewReconciler?: boolean,
|};

但是到此为止,我们无法从current里面找到任何东西,所以我们需要将目光转向ReactFiberBeginWork.js,从函数组件执行开始找,首先我们看到两种情况,分别是初始化和更新:

value = renderWithHooks(
      null,
      workInProgress,
      Component,
      props,
      context,
      renderLanes,
    );
nextChildren = renderWithHooks(
      current,
      workInProgress,
      render,
      nextProps,
      ref,
      renderLanes,
    );

其中就有我们希望的current,其他的参数:workInProgress是 workInProgress Fiber,Component是组件本身,props就是我们用来传值的this.props,context是上下文,其余不在赘述,接下来我们看renderWithHooks(这里为了提高观感,我删去了部分开发情况的条件处理):

export function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;
  
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;

  let children = Component(props, secondArg);

  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    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;
      currentHook = null;
      workInProgressHook = null;

      workInProgress.updateQueue = null;

    
      ReactCurrentDispatcher.current = __DEV__
        ? HooksDispatcherOnRerenderInDEV
        : HooksDispatcherOnRerender;

      children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);
  } 
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
  
  const didRenderTooFewHooks =
    currentHook !== null && currentHook.next !== null;

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

  currentHook = null;
  workInProgressHook = null;

  didScheduleRenderPhaseUpdate = false;

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

该函数:首先先置空即将调和渲染的workInProgress树的memoizedState和updateQueue,为什么这么做,因为在接下来的函数组件执行过程中,要把新的hooks信息挂载到这两个属性上,然后在组件commit阶段,将workInProgress树替换成current树,替换真实的DOM元素节点。并在current树保存hooks信息。

然后根据当前函数组件是否是第一次渲染,赋予ReactCurrentDispatcher.current不同的hooks(HooksDispatcherOnMount or HooksDispatcherOnUpdate)。对于第一次渲染组件,那么用的是HooksDispatcherOnMount hooks对象。 对于渲染后,需要更新的函数组件,则是HooksDispatcherOnUpdate对象,那么两个不同就是通过current树上是否memoizedState(hook信息)来判断的,当然如果current不存在(null),证明是第一次渲染函数组件。

接下来let children = Component(props, secondArg);开始真正的执行函数组件,当然这里也有一个很妙的地方:ReactCurrentDispatcher.current = ContextOnlyDispatcher;就是说我们没有在函数组件内部调用的hooks,都是ContextOnlyDispatcher对象上hooks,并且里面的大部分都会调用throwInvalidHookError()来发出警告:

export const ContextOnlyDispatcher: Dispatcher = {
  readContext,
  useCallback: throwInvalidHookError,
  useContext: throwInvalidHookError,
  useEffect: throwInvalidHookError,
  useImperativeHandle: throwInvalidHookError,
  useInsertionEffect: throwInvalidHookError,
  useLayoutEffect: throwInvalidHookError,
  useMemo: throwInvalidHookError,
  useReducer: throwInvalidHookError,
  useRef: throwInvalidHookError,
  useState: throwInvalidHookError,
  useDebugValue: throwInvalidHookError,
  useDeferredValue: throwInvalidHookError,
  useTransition: throwInvalidHookError,
  useMutableSource: throwInvalidHookError,
  useSyncExternalStore: throwInvalidHookError,
  useOpaqueIdentifier: throwInvalidHookError,
  unstable_isNewReconciler: enableNewReconciler,
};
function throwInvalidHookError() {
  invariant(
    false,
    'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
      ' one of the following reasons:\n' +
      '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
      '2. You might be breaking the Rules of Hooks\n' +
      '3. You might have more than one copy of React in the same app\n' +
      'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
  );
}

react-hooks就是通过这种函数组件执行赋值不同的hooks对象方式,判断在hooks执行是否在函数组件内部,捕获并抛出异常的。

这里借用掘金社区的一个图来直观的了解该过程,但是来源我也不太清楚,网上找的,如果作者看见,请联系我。

React源码解析————ReactHooks(一)_第2张图片

接下来让我们看看HooksDispatcherOnMount 和HooksDispatcherOnUpdate:

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useInsertionEffect: mountInsertionEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useSyncExternalStore: mountSyncExternalStore,
  useOpaqueIdentifier: mountOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};
const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useInsertionEffect: updateInsertionEffect,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useSyncExternalStore: updateSyncExternalStore,
  useOpaqueIdentifier: updateOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};

很明显可以看出来,这里使用了两套不同的方法,这两套方法的联系以及源码的分析,我会放在下一篇文章去讲。

你可能感兴趣的:(react源码解析,react.js,javascript,前端)