React16源码: Hooks源码实现
2024-01-02 14:12:32
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 回调函数
- 在function component 里面,使用hooks可以通过一个叫做
- 如果需要事件绑定什么的,之前在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
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!