React16源码: Hooks源码实现

发布时间:2024年01月02日

Hooks

1 )概述

  • Hooks 在 React16.7版本出现的新功能
  • Hooks 改变了整体应用开发的模式,同时开发体验会和以前会变得不一样
  • Hooks 让函数组件具有类组件的能力
    • 在 function component 里面没有this无法保存 state
    • 通过 Hooks可以让 function component 代替 class component
    • Hooks 让函数组件变得更强大

2 )用例演示

import React, { useState, useEffect } from 'react'

export default () => {
  const [name, setName] = useState('Wang')

  useEffect(() => {
    console.log('component update')

    return () => {
      console.log('unbind')
    }
  }, [])

  return (
    <>
      <p>My Name is: {name}</p>
      <input type="text" value={name} onChange={e => setName(e.target.value)} />
    </>
  )
}
  • 这里声明了一个functional component ,在以前对比class component它缺少的是什么
    • 缺少就是this对象,它没有this对象,那么它就不能有 this.state
    • 它就具有包含自己本身的状态的这么一个功能
    • 它没有生命周期方法
  • 在这里面我们使用了hooks来给我们的组件去存储了 state
    • 使用 useState 传入了一个默认值是 Wang
    • 然后它返回一个数组,这是一个数组的解构
    • 这个数组第一项是state的对应的这个变量
    • 第二项是让我们去改变这个state的方法
    • 这就是我们通过useState返回给我们的唯一的两个东西
  • 然后我们就可以在我们渲染的过程当中去使用这个state了,同样可以去修改这个state
    • 比如说我们绑定了这个input的 onchange 事件
    • 就是去修改这个state,就是我们的name
    • 输入的内容之后它的state就自动更新
    • 在下一次渲染的时候能够拿到 state
  • 这就是hooks,它给 function component 提供了class component 所具有的能力
    • 它的意义不仅仅是为了替代 class compoment
    • 是想要去帮助我们去拆分一些在组件内部的逻辑
    • 把他们提取出来,能够给更多的组件进行一个复用
    • 以前在class compoment 里面是很难去拆分这部分逻辑的
  • 还有一个是跟 class component 的最大区别,就是生命周期方法
    • 在function component 里面,使用hooks可以通过一个叫做useEffect的这个API
    • 这个东西呢我们就可以传入一个方法,这个方法里面
    • 比如说,随便写一句 component updated
    • 在hooks里面,他没有着重的去区分 mounted 和 updated
    • 它的理念是 mounted和updated 都是 updated
    • 每一次有组件内容更新的时候都会去调用我们传入的这个 effect 回调函数
  • 如果需要事件绑定什么的,之前在unmount的时候,去解除事件绑定,那这时怎么办
    • 很简单,看到上面 return一个方法,这个方法就是解除我们的绑定,这边叫做 unbind
    • 在目前这种情况下,每一次有更新的时候都会先执行unbind,然后再重新bind
    • 这是比较符合react更新的一个逻辑的,就是在它有任何更新的时候
    • 都会把之前的状态全部消除,然后返回新的状态
  • 当然这对于一些事件监听的绑定不是特别友好, 解决方案如下
    • 把它改造成行为,类似于 componentDidMount 和 componentWillUnmount
    • 直接在这个 useEffect 传入第二个参数,然后传一个空数组
    • 比如说传入一个props来进行一个它是否有变化的一个区分
    • 如果传一个空数组,它就没有东西区分,就代表着只要执行一次
    • 这样,在输入改变state时,就没有了,只再刷新后,才会执行一次
  • 同样,在跳出的时候打印输出了 unbind
  • 这就是我们使用 hooks 模拟生命周期方法的一个用法

3 ) 源码分析

这个要在 React 16.7 版本上来找,在 React.js 中可看到

import {
  useCallback,
  // ... 很多 khoos
} from './ReactHooks';

现在定位到 ReactHooks.js

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type {
  MutableSource,
  MutableSourceGetSnapshotFn,
  MutableSourceSubscribeFn,
  ReactContext,
} from 'shared/ReactTypes';
import type {OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig';

import invariant from 'shared/invariant';

import ReactCurrentDispatcher from './ReactCurrentDispatcher';

type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  invariant(
    dispatcher !== null,
    '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.',
  );
  return dispatcher;
}

export function useContext<T>(
  Context: ReactContext<T>,
  unstable_observedBits: number | boolean | void,
): T {
  const dispatcher = resolveDispatcher();
  if (__DEV__) {
    if (unstable_observedBits !== undefined) {
      console.error(
        'useContext() second argument is reserved for future ' +
          'use in React. Passing it is not supported. ' +
          'You passed: %s.%s',
        unstable_observedBits,
        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])
          ? '\n\nDid you call array.map(useContext)? ' +
              'Calling Hooks inside a loop is not supported. ' +
              'Learn more at https://reactjs.org/link/rules-of-hooks'
          : '',
      );
    }

    // TODO: add a more generic warning for invalid values.
    if ((Context: any)._context !== undefined) {
      const realContext = (Context: any)._context;
      // Don't deduplicate because this legitimately causes bugs
      // and nobody should be using this in existing code.
      if (realContext.Consumer === Context) {
        console.error(
          'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' +
            'removed in a future major release. Did you mean to call useContext(Context) instead?',
        );
      } else if (realContext.Provider === Context) {
        console.error(
          'Calling useContext(Context.Provider) is not supported. ' +
            'Did you mean to call useContext(Context) instead?',
        );
      }
    }
  }
  return dispatcher.useContext(Context, unstable_observedBits);
}

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

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

export function useRef<T>(initialValue: T): {|current: T|} {
  const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useLayoutEffect(create, deps);
}

export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}

export function useImperativeHandle<T>(
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useImperativeHandle(ref, create, deps);
}

export function useDebugValue<T>(
  value: T,
  formatterFn: ?(value: T) => mixed,
): void {
  if (__DEV__) {
    const dispatcher = resolveDispatcher();
    return dispatcher.useDebugValue(value, formatterFn);
  }
}

export const emptyObject = {};

export function useTransition(): [(() => void) => void, boolean] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useTransition();
}

export function useDeferredValue<T>(value: T): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useDeferredValue(value);
}

export function useOpaqueIdentifier(): OpaqueIDType | void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useOpaqueIdentifier();
}

export function useMutableSource<Source, Snapshot>(
  source: MutableSource<Source>,
  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
): Snapshot {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMutableSource(source, getSnapshot, subscribe);
}
  • 定位到 useState

    export function useState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      const dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }
    
    • 这边调用了一个 dispatcher.useState(initialState);
    • 这个dispatch等于 const dispatcher = resolveDispatcher();
  • 对应的再去找到这个 resolveDispatcher ,它是个function

    function resolveDispatcher() {
      const dispatcher = ReactCurrentOwner.currentDispatcher;
      invariant(
        dispatcher !== null,
        'Hooks can only be called inside the body of a function component.',
      );
      return dispatcher;
    }
    
    • 可看到是通过 ReactCurrentOwner.currentDispatcher 去获取的
    • 这个就要涉及到后续react-dom渲染的过程
    • 在我们使用阶段,是没有拿到任何真正节点的实例的。
    • 比如在我们创建了react element,我们传进去的是这个class component的类
    • 而不是它的一个new的一个实例,拿不到对应的东西
    • 只有在 react-dom 进行真正的渲染的过程当中,才会去为我们创建这个实例
    • 它这边提供了这个方法,在实际调用是要在我们的 react-dom进行渲染的时候
    • 它才会为我们这个 ReactCurrentOwner 去设置属性
  • ReactCurrentOwner 它是一个全局的类,定位一下

    /**
     * Copyright (c) Facebook, Inc. and its affiliates.
     *
     * This source code is licensed under the MIT license found in the
     * LICENSE file in the root directory of this source tree.
     *
     * @flow
     */
    
    import type {Fiber} from 'react-reconciler/src/ReactFiber';
    import typeof {Dispatcher} from 'react-reconciler/src/ReactFiberDispatcher';
    
    /**
     * Keeps track of the current owner.
     *
     * The current owner is the component who should own any components that are
     * currently being constructed.
     */
    const ReactCurrentOwner = {
      /**
       * @internal
       * @type {ReactComponent}
       */
      current: (null: null | Fiber),
      currentDispatcher: (null: null | Dispatcher),
    };
    
    export default ReactCurrentOwner;
    
  • 我们可以看到 ReactCurrentOwner ,它就是一个对象,里面有两个属性

  • 一个是 current , 就是这个current就对应正在目前正在渲染的哪一个节点的一个实例

  • currentDispatcher 是实例对应的 dispatcher

  • 它一开始初始化的时候,是两个 null

  • 然后到后期每一个组件进行渲染的时候,它才会进行一个渲染

  • 就跟我们在class component里面看到的,它的 setState 调用的是 this.updateSetState

  • 在这里的 dispatch 也是从不同平台上面,它传入进来的一个东西,不是我们在react中定义的

  • 所以在react中我们可以看到的源码就非常的简单,就是这么一些相关的东西

  • 同理,useEffect和其他内容也基本是类似的,只不过它最终调用的方法会不一样

  • 比如说useReducer,那么它调用的是 dispatcher.useReducer

  • 它们最终都是调用 dispatcher 上面的方法

文章来源:https://blog.csdn.net/Tyro_java/article/details/135333231
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。