本次分析的源码采用的是 16.2.0 的版本 目前网上现有的 react 源码分析文章基于的都是版本 16 以前的源码, 入口和核心构造器不一样了, 如下图所示
本想借鉴前人的源码分析成果, 奈何完全对不上号, 只好自己慢慢摸索
水平有限, 如果有错误和疏忽的地方, 还请指正
最快捷开始分析源码的办法
- mkdir analyze-react@16.0.2
- cd analyze-react@16.0.2
- npm init -y
- npm i react --save
然后打开项目, 进入
node_nodules => react
先看入口文件 ndex.js
- 'use strict';
- if (process.env.NODE_ENV === 'production') {
- module.exports = require('./cjs/react.production.min.js');
- } else {
- module.exports = require('./cjs/react.development.js');
- }
看开发环境下的版本即可, 压缩版本是打包到生产环境用的
打开图中文件
核心接口
分析源码先找对外的暴露接口, 当然就是 react 了, 直接拉到最下面
- var React = {
- Children: {
- map: mapChildren,
- forEach: forEachChildren,
- count: countChildren,
- toArray: toArray,
- only: onlyChild
- },
- Component: Component,
- PureComponent: PureComponent,
- unstable_AsyncComponent: AsyncComponent,
- Fragment: REACT_FRAGMENT_TYPE,
- createElement: createElementWithValidation,
- cloneElement: cloneElementWithValidation,
- createFactory: createFactoryWithValidation,
- isValidElement: isValidElement,
- version: ReactVersion,
- };
- ReactChildren
ReactChildren 提供了处理
this.props.children
的工具集, 跟旧版本的一样
- Children: {
- map: mapChildren,
- forEach: forEachChildren,
- count: countChildren,
- toArray: toArray,
- only: onlyChild
- },
组件
旧版本只有 ReactComponent 一种
新版本定义了三种不同类型的组件基类 Component,PureComponent ,
- unstable_AsyncComponent
- Component: Component,
- PureComponent: PureComponent,
- unstable_AsyncComponent: AsyncComponent,
等下再具体看都是什么
生成组件
- createElement: createElementWithValidation,
- cloneElement: cloneElementWithValidation,
- createFactory: createFactoryWithValidation,
判断组件: isValidElement
校验是否是合法元素, 只需要校验类型, 重点是判断.$$typeof 属性
- function isValidElement(object) {
- return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
- }
- _assign
其实是 object-assign, 但文中有关键地方用到它, 下文会讲
var _assign = require('object-assign');
React 组件的本质
组件本质是对象
不急着看代码, 先通过例子看看组件是什么样子的 用 creact-react-app 生成一个最简单的 react 项目 在 App.js 文件加点东西, 然后打印组件 A 看一下是什么
npm start
启动项目看看
其实就是个对象, 有很多属性, 注意到 props 里面, 没有内容
现在给组件 A 里面加一点内容
- componentDidMount() {
- console.log('组件 A',<A><span > 加点内容看看 </span></A>)
- }
可以看到, props.children 里面开始嵌套内容了
以我们聪明的程序员的逻辑思维能力来推理一下, 其实不断的页面嵌套, 就是不断的给这个对象嵌套 props 而已
不信再看一下
- componentDidMount() {
- console.log('组件 A',<A><span > 加点内容看看 < a > 不信再加多一点 </a></span></A>)
- }
虚拟 DOM 概念
所以到目前为止, 我们知道了 react 的组件只是对象, 而我们都知道真正的页面是由一个一个的 DOM 节点组成的, 在比较原生的 jQuery 年代, 通过 JS 来操纵 DOM 元素, 而且都是真实的 DOM 元素, 而且我们都知道复杂或频繁的 DOM 操作通常是性能瓶颈产生的原因, 所以 React 引入了虚拟 DOM(Virtual DOM) 的概念
总的说起来, 无论多复杂的操作, 都只是先进行虚拟 DOM 的 JS 计算, 把这个组件对象计算好了以后, 再一次性的通过 Diff 算法进行渲染或者更新, 而不是每次都要直接操作真实的 DOM
在即时编译的时代, 调用 DOM 的开销是很大的而 Virtual DOM 的执行完全都在 Javascript 引擎中, 完全不会有这个开销
组件的本源
知道了什么是虚拟 DOM 以及组件的本质后, 我们还是来看一下代码吧
先从生成组件开始切入, 因为要生成组件就肯定会去找组件是什么
createElement: createElementWithValidation
摘取一些核心概念出来看就好
- function createElementWithValidation(type, props, children) {
- var element = createElement.apply(this, arguments);
- return element;
- }
可以看到, 返回了一个 element, 这个元素又是由 createElement 方法生成的, 顺着往下找
- function createElement(type, config, children) {
- var props = {};
- var key = null;
- var ref = null;
- var self = null;
- var source = null;
- return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
- }
返回的是 ReactElement 方法, 感觉已经很近了, 马上要触及本源了
- var ReactElement = function (type, key, ref, self, source, owner, props) {
- var element = {
- $$typeof: REACT_ELEMENT_TYPE,
- type: type,
- key: key,
- ref: ref,
- props: props,
- _owner: owner
- };
- return element;
- };
bingo, 返回了一个对象, 再看这个对象, 是不是跟上面打印出来的对象格式很像? 再看一眼
这就是组件的本源
组件三种基类
前面说了, 版本 16 以后, 封装了三种组件基类: 分别是组件纯组件异步组件
- Component: Component,
- PureComponent: PureComponent,
- unstable_AsyncComponent: AsyncComponent,
一个个去看一下区别在哪里, 先看 Component
- function Component(props, context, updater) {
- this.props = props;
- this.context = context;
- this.refs = emptyObject;
- this.updater = updater || ReactNoopUpdateQueue;
- }
很简单, 一个构造函数, 通过它构造的实例对象有四个私有属性, refs 则是个 emptyObject, 看名字就知道是空对象 这个 emptyObject 也是引入的插件
var emptyObject = require('fbjs/lib/emptyObject');
再去看 PureComponent,AsyncComponent, 定义的时候居然跟 Component 是一样的
区别
区别呢?
这里就需要用到原型链方面的知识了
虽然原型和继承在日常项目和工作中用的不多
但, 那是因为我们平时很大部分在面向过程编程, 特别是业务代码, 但想要进阶, 就要去读别人的源码, 去自己封装组件, 这时它们就派上用场了, 这就是为什么它们很重要的原因
核心的方法, 和属性, 以及这三种组件直接的关系都是通过原型的知识联系起来的, 关键代码如下, 我画了个简图, 希望能对看文章的各位有所帮助, 如果有画错的, 希望能指正我
先上核心代码 setState 和 forceUpdate 这两个方法挂载在 Component(组件构造器) 的原型上
- Component.prototype.setState = function (partialState, callback) {
- ...
- };
- Component.prototype.forceUpdate = function (callback) {
- ...
- };
接下来定义一个 ComponentDummy, 其实也是一个构造器, 按照名字来理解就是假组件, 它是当做辅助用的
让 ComponentDummy 的原型指向 Component 的原型, 这样它也能访问原型上面的共有方法和属性了, 比如 setState 和 forceUpdate
- function ComponentDummy() {}
- ComponentDummy.prototype = Component.prototype;
下面这句话, 假组件构造器 ComponentDummy 实例化出来一个对象
pureComponentPrototype
, 然后把这个对象的 constructor 属性又指向了 PureComponent, 因此 PureComponent 也成为了一个构造器, 也就是上面的第二种组件基类
- var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();
- pureComponentPrototype.constructor = PureComponent;
AsyncComponent 基类也是一样
- var asyncComponentPrototype = AsyncComponent.prototype = new ComponentDummy();
- asyncComponentPrototype.constructor = AsyncComponent;
但是 AsyncComponent 的原型多了一个方法 render, 看到了吗, 妈妈呀, 这就是 render 的出处
- asyncComponentPrototype.render = function () {
- return this.props.children;
- };
所以到目前为止, 可以得出一个原型图
但是, 有个问题来了, render 方法挂载在 AsyncComponent 的原型上, 那通过 Component 构造器构造出来的实例岂不是读不到 render 方法, 那为什么日常组件是这样写的?
其实还有两句代码, 上面做了个小剧透的_assign
- _assign(pureComponentPrototype, Component.prototype);
- _assign(asyncComponentPrototype, Component.prototype);
每句话上面还特意有个注释,
Avoid an extra prototype jump for these methods.
, 避免这些方法额外的原型跳转, 先不管它, 先看_assign 做了什么
把 Component 的原型跟 AsyncComponent 的原型合并,
那么到这里, 答案就呼之欲出了, 如此一来, AsyncComponent 上面的 render 方法, 不就相当于挂载到 Component 上面了吗?
以此类推, 三种基类构造器最后都是基于同一个原型, 共享所以方法, 包括 rendersetStateforceUpdate 等等, 最后的原型图应该就变成了这样
到这里, 有个问题要思考的是:
既然最后三个基类共用同一个原型, 那为什么要分开来写? 中间还通过一个假组件构造器 ComponentDummy 来辅助构建两个实例
源码还没读完, 这个地方我目前还没弄明白, 应该是后面三个基类又分别挂载了不一样的方法, 希望有大佬能提前回答一下
这是我的 github 博客, 我在学习过程中喜欢做记录, 分享的是我在前端之路上的一些积累和思考, 也希望能跟大家一起交流与进步, 能给个 star 的话就更加感激不尽了
来源: https://juejin.im/post/5a9b95156fb9a028b86d7c4a