入口文件 packages/react/src/React.js
自用笔记,如需浏览请自行思考。内容未补完。
shared/ReactVersion.js
输出react当前版本
shared/ReactSymbols.js
- react常量命名:
REACT_ELEMENT_TYPE
大写字母命名。这种方式也可以作为自己项目的规范。 - 素数,只能被1和他自身整除的数叫做素数。用js代码实现一个素数生成器
- Symbol
- 虽然symbol为es6引入的新数据类型,但值通过Symbol函数生成。因此
typeof Symbol === 'function'
。 Symbol.for()
重用symbol值Symbol.iterable
对象的Symbol.iterator属性,指向该对象的默认遍历器方法@@iterator
属性的初始值是和Array.prototype.values
属性的初始值相同的对象。Array.prototype.values
是Array.prototype[Symbol.iterator]
的默认实现。
- 虽然symbol为es6引入的新数据类型,但值通过Symbol函数生成。因此
ReactBaseClasses.js
class 基础
class Point{}
typeof Point === 'object'
类中声明所有的方法都定义在prototype
上
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。es6 class内部申明的属性是不可枚举的
实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。在react中,以下写法有何区别:
class Test extends Component {
state = { a: '1'}
}
class Test1 extends Component {
constructor(props){
super(props)
this.state = {a: '1'}
}
}
阮一峰给出的解释是:这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。在实际开发过程中,我们可以自行斟酌使用。
在开发过程中,我们通过__proto__
获取实例的原型,但该属性是非标准的,实际开发中,可以使用Object.getPrototypeOf
来获取对象的原型
对象内部的get和set是设置在属性的 Descriptor 对象上的Object.getOwnPropertyDescriptor(o, key)
new.target
返回new命令作用于的那个构造函数,该属性一般用在构造函数之中,用于判断构造函数是否通过new方法调用的,如果不是,new.target
将会返回undefined
super
关键字,调用父类的constructor
方法。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
super
作为super()
函数调用时,返回子对象的实例,等同于Parent.prototype.constructor.call(this)
,其中的this为子对象的实例。作为super
对象调用时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
回到源码
console可以使用%s
作为占位符,输出后续传入的内容。在日常项目的底层代码中,也可以通过此功能提示开发者对应的信息。
...
console.warn(
'%s(...) is deprecated in plain JavaScript React classes. %s',
info[0],
info[1],
);
...
以下代码和react update有关,放到后续分析,可以先看看
function Component(props, context, updater) {
...
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.setState = function(partialState, callback) {
...
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
PureComponent 的实现
es5经典的继承实现方式,先声明一个ComponentDummy,并将Component的原型赋给ComponentDummy
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
然后声明PureComponent函数,和Component的实现方式一致
/**
* Convenience component with default shallow equality check for sCU.
*/
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
将PureComponent的prototype指向一个ComponentDummy的实例
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
注意这里有一个优化点:
Object.assign(pureComponentPrototype, Component.prototype);
这里直接通过Object.assign
方法,把Component.prototype
上的属性的引用赋值给了PureComponent.prototype
。这样在PureComponent
内部获取setState
这样的方法时,不需要通过原型链(__proto__.__proto__
)进行查找
ReactCreateRef.js
这里可以看到createRef的实现:
// RefObject 为对应的type类型
export function createRef(): RefObject {
const refObject = {
current: null,
};
if (__DEV__) {
Object.seal(refObject);
}
return refObject;
}
Object.seal()方法封闭一个对象,即refObject,经过 Object.seal
的对象,引用不变,但是不能够添加或删除属性,但是可以给current赋值
ReactChildren.js
isArray
封装了Array.isArray
方法
hasOwnProperty
封装了Object.prototype.hasOwnProperty
方法
可以看到react把一些方法与原生js做了解耦处理,方便后续的维护
ReactElement.js
预设config
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
react 特殊属性ref和key
Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。 返回如下对象:
{
value, // any
writable, // true/false
configurable, // true/false
enumerable, // true/false
get, // getter function 访问器函数/ undefined
set, // setter function 设置器函数/ undefined
}
通过defineKeyPropWarningGetter
和defineRefPropWarningGetter
我们可以看到,key和ref属性的getter访问器函数被重新设置了,因此我们访问这两个属性会得到undefined
。
安全的转字符串的方式:
const toBeString = '' + o
function jsx(type, config, maybeKey) {
....
return ReactElement(
type,
key,
ref,
undefined,
undefined,
ReactCurrentOwner.current,
props,
)
}
// self and source are DEV only properties.
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
// 用于校验是否是一个reactElement
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
...
return element;
}
/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
*/
function createElement(type, config, children){
...
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
ReactCurrentOwner.js
当前组建的父组件
const ReactCurrentOwner = {
/**
* @internal
* @type {ReactComponent}
*/
current: (null: null | Fiber),
};
ReactContext.js
reactContext的工作原理?
function createContext<T>(defaultValue: T): ReactContext<T> {
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
...
context.Consumer = context;
return context
}
ReactLazy.js
// 自定义promise类型常量
...
function lazyInitializer<T>(payload: Payload<T>): T {
if (payload._status === Uninitialized) {
const ctor = payload._result;
// 执行import()
const thenable = ctor();
// 变更状态为pending
// Transition to the next state.
const pending: PendingPayload = (payload: any);
pending._status = Pending;
pending._result = thenable;
// 执行then(onFullfilled, onRejected)方法
thenable.then(
moduleObject => {
if (payload._status === Pending) {
const defaultExport = moduleObject.default;
if (__DEV__) {
if (defaultExport === undefined) {
console.error(
'lazy: Expected the result of a dynamic import() call. ' +
'Instead received: %s\n\nYour code should look like: \n ' +
// Break up imports to avoid accidentally parsing them as dependencies.
'const MyComponent = lazy(() => imp' +
"ort('./MyComponent'))",
moduleObject,
);
}
}
// Transition to the next state.
const resolved: ResolvedPayload<T> = (payload: any);
resolved._status = Resolved;
resolved._result = defaultExport;
}
},
error => {
if (payload._status === Pending) {
// Transition to the next state.
const rejected: RejectedPayload = (payload: any);
rejected._status = Rejected;
rejected._result = error;
}
},
);
}
if (payload._status === Resolved) {
return payload._result;
} else {
throw payload._result;
}
}
function lazyfunction lazy<T>(
ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
const payload: Payload<T> = {
// We use these fields to store the result.
_status: -1,
_result: ctor,
};
const lazyType: LazyComponent<T, Payload<T>> = {
$$typeof: REACT_LAZY_TYPE,
_payload: payload,
// init函数,接受payload作为参数
_init: lazyInitializer,
};
return lazyType
}
ReactForwardRef.js
React.forwardRef 接受渲染函数作为参数。React 将使用 props 和 ref 作为参数来调用此函数。此函数应返回 React 节点。
export function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
const elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render,
};
return elementType;
}
获取函数的参数个数:function.length
- 使用memo的方式
memo(forwardRef(...))
- forwardRef render functions接受两个参数:
props and ref
- render 必须是一个函数,返回一个React 节点
- 不支持
propTypes or defaultProps
ReactMemo.js
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
export function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
const elementType = {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare,
};
return elementType;
}
ReactHooks.js
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
if (__DEV__) {
if (dispatcher === null) {
console.error(
'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.',
);
}
}
// Will result in a null access error if accessed outside render phase. We
// intentionally don't throw our own error because this is in a hot path.
// Also helps ensure this is inlined.
return ((dispatcher: any): Dispatcher);
useContext
export function useContext<T>(Context: ReactContext<T>): T {
const dispatcher = resolveDispatcher();
...
return dispatcher.useContext(Context);
}
useState
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
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);
}
React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 dispatch。
useRef
export function useRef<T>(initialValue: T): {|current: T|} {
const dispatcher = resolveDispatcher();
return dispatcher.useRef(initialValue);
}
useEffect
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
尽可能使用标准的 useEffect 以避免阻塞视觉更新。
- 同步执行,阻塞渲染
- 在render前执行
export function useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useLayoutEffect(create, deps);
}
useCallback
export function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, deps);
}
useMemo
export function useMemo<T>(
create: () => T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, deps);
}
useImperativeHandle
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
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);
}
useDebugValue
useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。
export function useDebugValue<T>(
value: T,
formatterFn: ?(value: T) => mixed,
): void {
if (__DEV__) {
const dispatcher = resolveDispatcher();
return dispatcher.useDebugValue(value, formatterFn);
}
}
useTransition
export function useTransition(): [boolean, (() => void) => void] {
const dispatcher = resolveDispatcher();
return dispatcher.useTransition();
}
useDeferredValue
export function useDeferredValue<T>(value: T): T {
const dispatcher = resolveDispatcher();
return dispatcher.useDeferredValue(value);
}
ReactCurrentDispatcher.js
const ReactCurrentDispatcher = {
/**
* @internal
* @type {ReactComponent}
*/
current: (null: null | Dispatcher),
};
ReactElementValidator.js
开发环境的__DEV__
一些校验方法
其他知识点
在 CSS 中,如果你不希望节点成为布局的一部分,则可以使用 display: contents 属性。