React | 2020-06-27 11:16:58 734次 2次
在此之前可以先了解 React16 模拟实现系列文章,对 React16 的重构做一个简单梳理,理解其中的调度和 Fiber 架构模式,这样再阅读起来源码就有一个大致的反向,当然源码中要复杂很多。先从 React.js 入手,根据它暴露出的 API 先进行分析。
一、createElement
JSX 代码会被 Babel 编译为 React.createElement,它接收三个参数,分别是 type, config, children,type 指的是 ReactElement 的类型。
1.字符串 div 或 p 等代表原生 DOM,称为 HostComponent;
2.class 类型继承 Component 或 PureComponent 组件的称为 ClassComponent; 方法就是 FunctionalComponent;
3.原生提供的 Fragment、AsyncMode 等是 Symbol,会特殊处理;
4.其他;
ReactElement.js /** * * React.createElement( 'div', { id: '1' }, '1' ) 1.字符串 div 或 p 等代表原生 DOM,称为 HostComponent; 2.class 类型继承 Component 或 PureComponent 组件的称为 ClassComponent; 方法就是 FunctionalComponent; 3.原生提供的 Fragment、AsyncMode 等是 Symbol,会特殊处理; 4.其他; */ export function createElement(type, config, children) { let propName; // Reserved names are extracted const props = {}; let key = null; let ref = null; let self = null; let source = null; if (config != null) { //是否包含 ref 属性 if (hasValidRef(config)) { ref = config.ref; } //是否包含 key 属性 if (hasValidKey(config)) { key = '' + config.key; } /** * @babel/plugin-transform-react-jsx-self @babel/plugin-transform-react-jsx-source 编译后的两个属性 */ self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object for (propName in config) { if ( /** * const hasOwnProperty = Object.prototype.hasOwnProperty; * 特定的自身(非继承)属性 * 并且排除 key ref __self __source属性 */ hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } } //子节点 const childrenLength = arguments.length - 2; //单个则直接挂载 if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { //多个子结点转为数组 const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } ... props.children = childArray; } // 组件上的默认属性设置 XXX.defaultProps = {} if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } ... return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); }
二、ReactElement
createElement 方法中返回一个 ReactElement 方法,这个方法最终创建一个虚拟 dom 对象出来:
ReactElement.js const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { /** * react的数据是一个对象,用户可随意构造 dangerouslySetInnerHTML * let expectedTextButGotJSON = { type: 'div', props: { dangerouslySetInnerHTML: { __html: ' put your exploit here ' } }, }; let message = { text: expectedTextButGotJSON }; <p> {message.text} </p> * 因为如果服务器有一个漏洞,允许用户存储任意JSON对象则可能会有 XSS 攻击, * 而json是无法传递 Symbol 格式数据 * 不支持此属性的则设置为 0xeac7 因为看起来有点像“React”。 */ $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner, }; ... return element; };
三、Component
我们在编写组件时候一般会继承自 Component(函数组件除外),提供给我们 props、state、setState 等属性方法,也可以使用 React 中的生命周期函数,其实它的实现很简单:
ReactBaseClasses.js /** * 基类组件 */ function Component(props, context, updater) { this.props = props; this.context = context; //初始是一个空对象 this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } //判断类组件标识 Component.prototype.isReactComponent = {}; //setState方法 异步更新 Component.prototype.setState = function(partialState, callback) { //格式校验 invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.', ); this.updater.enqueueSetState(this, partialState, callback, 'setState'); }; //强制更新,调用forceUpdate()会导致组件跳过shouldComponentUpdate(),直接调用render()。 Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); }; ... function ComponentDummy() {} ComponentDummy.prototype = Component.prototype; //会自动浅比较当前组件是否需要更新 function PureComponent(props, context, updater) { this.props = props; this.context = context; this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } //PureComponent 继承自 Component const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); //修正PureComponent中的constructor指向 pureComponentPrototype.constructor = PureComponent; /** * pureComponentPrototype此时自身没有,__proto__ 上才有Component.prototype的方法,所以需要拷贝一下 * 补充: Object.assing 是不能拷贝到继承或原型上的方法的 * 实现:var c2 = Object.create( * Object.getPrototypeOf(c), * Object.getOwnPropertyDescriptors(c) * ); */ Object.assign(pureComponentPrototype, Component.prototype); pureComponentPrototype.isPureReactComponent = true; export {Component, PureComponent};
这里面重要的一件事就是创建了 updater 这个对象,后续再分析这个,其余的就是使得 PureComponent 继承自 Component 方法并暴露出去。
四、createRef & forwardRef
//暴露出的就是一个对象而已 export function createRef(): RefObject { const refObject = { current: null, }; ... return refObject; } export default function forwardRef<Props, ElementType: React$ElementType>( render: (props: Props, ref: React$Ref<ElementType>) => React$Node, ) { ... return { $$typeof: REACT_FORWARD_REF_TYPE, render, }; }
这里的 $$typeof 和上面的 ReactElement 中创建的并不是同一个,而是给这个组件挂载上一个此属性,放在了 type 里面去。
五、createContext
这里只是简单看下如何创建的,真正的实现需要等后面更新调度时候才会执行。
/** * calculateChangedBits * 当 changedBits = 0,将不再触发更新。 * 而在 Consumer 中有一个不稳定的 props,unstable_observedBits, * 若 Provider 的changedBits & observedBits = 0,也将不触发更新 */ export function createContext<T>( defaultValue: T, calculateChangedBits: ?(a: T, b: T) => number, ): ReactContext<T> { if (calculateChangedBits === undefined) { calculateChangedBits = null; } else { ... } const context: ReactContext<T> = { $$typeof: REACT_CONTEXT_TYPE, _calculateChangedBits: calculateChangedBits, _currentValue: defaultValue, _currentValue2: defaultValue, Provider: (null: any), Consumer: (null: any), }; context.Provider = { $$typeof: REACT_PROVIDER_TYPE, _context: context, }; ... if (__DEV__) { ... } else { //指向自己,这样渲染时候会取到上面定义的 _currentValue,作为回调参数传给组件使用 context.Consumer = context; } ... return context; }
六、concurrent & flushSync
concurrent 是利用 fiber 的结构,可以进行任务的高低优先级分类执行且可中断可恢复,默认它的所有子节点都是最低优先级的,可以通过 flushSync 来控制为最高优先级被执行。flushSync 来自 react-dom。
legacy 模式(默认)
blocking 模式
concurrent 模式
ReactDOM.createBlockingRoot(rootNode).render(<App />) //blocking ReactDOM.createRoot(document.getElementById('root')).render(<App />); // 调用:控制下 tag 标识就可以了,后面判断区分不同模式 this._internalRoot = createRootImpl(container, tag, options); //render方法和普通的render有区别,具体看第三节的render方法 ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function( children: ReactNodeList, ): void { const root = this._internalRoot; ... //这里直接调用了updateContainer,普通render使用 unbatchedUpdates包裹进行up updateContainer(children, root, null, null); };
七、Suspense & Fragment
Suspence 使用时注意如果结果是 promise 则使用 throw 来抛出,其他的直接 return 即可,这些东西都是一堆 symbol 标识。
const React = { ... REACT_FRAGMENT_TYPE as Fragment, REACT_PROFILER_TYPE as Profiler, REACT_STRICT_MODE_TYPE as StrictMode, REACT_SUSPENSE_TYPE as Suspense, ... };
八、memo & lazy
export default function memo<Props>( type: React$ElementType, compare?: (oldProps: Props, newProps: Props) => boolean, ) { ... return { $$typeof: REACT_MEMO_TYPE, type, compare: compare === undefined ? null : compare, }; }
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> { return { $$typeof: REACT_LAZY_TYPE, _ctor: ctor, // thenable对象返回的状态 | pendding -> -1 _status: -1, //记录thenable对象 resolve之后的结果 _result: null, }; }
可以看到某些方法直接就是一个对象,这证明 react 中只是单纯的定了节点类型,用不同的 typeof 区分,那么如何是作为组件被使用的呢?这个问题需要留到后面调度渲染时候进行再次分析。
本片介绍了一些常用的 api,还有一部分不常用的没有一一列举,其中还有一个不常用但非常有意思的 children 方法,等下一篇再进行分析。
2人赞