Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react 源码阅读1-总览 #15

Open
shaozj opened this issue Sep 19, 2017 · 3 comments
Open

react 源码阅读1-总览 #15

shaozj opened this issue Sep 19, 2017 · 3 comments

Comments

@shaozj
Copy link
Owner

shaozj commented Sep 19, 2017

react 源码阅读1-总览

我们阅读的 react 源码版本为 16.0.0-rc.3

facebook 自定义的模块系统

facebook 内部自建了一个模块系统叫做“Haste”。它和 CommonJS 类似,也使用 require(),但是有些不同点容易引起大家的困惑。在 Haste 里,所有文件名是全局唯一的。在 react 源码中,只要根据模块的名字来引入模块即可:

var setInnerHTML = require('setInnerHTML');

Haste 最早是为 Facebook 这种大型 app 设计的。他使得在不同的文件夹间移动文件很容易,无需担心相对路径的变化。在编辑器中搜索文件时也变得更方便准确。

React 在编译时,首先会有个脚本将所有文件拷贝到一个目录下 lib,然后在 require() 路径中加入 ./

外部依赖

react 源码中的外部依赖很少,一般如果在 src 目录下找不到一个文件,可以去 fbjs 的 npm 包中查找。
然而,在 react 的入口文件 ReactEntry.js 中就看到了一个外部依赖包 object-assign。而且,react 自己写了个 babel-plugin , 在 scripts/babel/transform-object-assign-require 目录,将 Object.assign 转换为 require('object-assign')

目录结构

顶层目录(只保留部分阅读源码相关目录)

.
├── build       // 编译打包后的代码
├── docs        // 文档和官网
├── fixtures    // 包括一些小的 react 测试 app
├── packages    // react 的 npm 包的模板
├── scripts     // 开发构建相关的脚本
└── src         // 源码目录

src 目录结构

src
├── ReactVersion.js  // 一句代码,react 版本
├── __mocks__        // 
├── fb               // 暂时用不到,不管
├── isomorphic       // React 核心 apis (如 React.Component React.Children)
├── node_modules     // 
├── package.json     // 
├── renderers        // 将 react 树转换为不同平台的底层调用
├── shared           // isomorphic 和 renderers 共享的代码
└── test             // 一些测试用到的代码

单元测试

react 源码没有一个顶层目录专门用于测试代码,而是将单元测试的代码放在每个源码的附近。例如setInnerHTML.js 的测试代码在附近的 __tests__/setInnerHTML-test.js

shared 目录下的代码

react 源码里有很多 shared 目录。按照 react 源码的惯例(这个惯例不是强制的),一个文件一般只会 import 同级或下级目录的其他模块。但是有些不同目录下的模块需要共享,于是 react 源码将这些不同目录下的文件需要共享的代码放在 shared 目录。其规则是寻找最近的共同父目录,将共享代码存在 shared 文件夹。例如,src/renderers/dom/stack/clientsrc/renderers/dom/stack/server 共享的代码在 src/renderers/dom/shared

isomorphic 目录

这块的代码被称为 react 核心。它包含了所有的 React 顶级 API。例如:

React.createElement()
React.Component
React.Children

react 核心只包含了定义组件(components)所必需的 APIs。 它不包含协调器相关的代码,以及任何平台相关的代码。它同时被 React DOM 和 React Native 的组件所使用。这里面的代码编译后即为 react 的 npm 包。在编译后的浏览器 react 版本中,它暴露出一个全局变量 React。

renderers 目录

src/renderers
├── __tests__
├── art
├── testing
├── native
├── noop
├── shared
|   ├── stack
|   |   └── reconciler // 协调器
|   └── shared
|       └── event // 事件处理
└── dom     // WEB 平台,暂时先关注这个目录即可
    ├── ReactDOMNodeStreamRenderer.js
    ├── ReactDOMServerBrowserEntry.js
    ├── ReactDOMServerNodeEntry.js
    ├── ReactDOMServerStackEntry.js
    ├── ReactDOMStackEntry.js
    ├── ReactDOMStringRenderer.js
    ├── __tests__
    ├── fiber  // 核心算法重构,先不管
    ├── shared
    ├── stack
    |   ├── client
    |   └── server
    └── test

react 需要支持不同的平台,如 React DOM 和 React Native。这些支持不同平台的代码在 renderers 中。Renderers 将 React 树转换为不同平台的底层调用。

我们主要阅读 web 平台相关的代码,关注 dom 目录。dom 目录下是 React DOM Renderer。它将 react 组件渲染为 DOM。这里实现了 ReactDOM 的顶层 APIs

render()
unmountComponentAtNode()
findDOMNode()

React DOM 的代码编译为 ReactDOM npm 包。在浏览器编译版本中,暴露了全局变量 ReactDOM

  • dom/stack/client
    • 各种ReactComponent
  • dom/stack/server
    • 服务端渲染方法
  • dom/shared
    • CSSProperty, DOMProperty, 合成事件处理,DOM操作方法,如findDOMNode, setInnerHTML等
  • dom/fiber
    • 核心算法的重构
  • shared/stack/reconciler
    • 协调器,包含自定义组件实现ReactCompositeComponent, setState机制,生命周期方法流程,DOM diff等
  • shared/event
    • 事件处理

协调器 (Reconcilers)

不同的渲染平台如 React DOM 和 React Native,它们之间的很多逻辑是可以共用的。像声明式渲染,自定义组件,state,生命周期方法这些都应该是共用的。在不同平台上,它们的行为应该是一致的。

为了解决这个问题,不同平台间这些部分的代码是共用的。这部分代码被称为“协调器”(reconciler)。当 一次更新例如 setState() 开始执行,协调器调用组件树中组件的 render() 方法,然后执行 mounts、 updates 或 unmounts 组件。

协调器并没有被独立打包因为目前它没有公共的 API 。相反,它们仅仅被渲染器如 React DOM 和 React Native 调用。

栈协调器 (Stack Reconciler)

目前生产用的 React 版本,都是用的 Stack Reconciler。它位于 src/renderers/shared/stack/reconciler,并且同时被 React DOM 和 React Native 使用。

栈协调器用面向对象的方式编码,并且为每个 react 组件维护了一份“内部实例”的独立树。用户定义的("composite") 和平台自有的("host") 组件都存在内部实例。用户不能直接访问内部实例,并且他们的树也不会被暴露。

宿主组件(Host Components)

平台自带的组件(Host Components),例如 <div><View>,运行平台相关的代码。例如: React DOM 委托 stack reconciler 用 ReactDOMComponent 来处理 DOM 组件的 mounting, updates, 和 unmounting

无论在哪个平台, <div><View> 以类似的方式处理多个字节点。 为了方便,stack reconciler 提供了一个 helper 叫做 ReactMultiChild。DOM 和 Native renderers 都到这个 helper.

合成组件(Composite Components)

用户自定义的组件(Composite Components)在不同平台渲染器下的行为应该是一致的。这也是 Stack Reconciler 在 ReactCompositeComponent 中提供了一个共享的实现的原因。无论是什么渲染器,它应该是保持一致的。

未完待续。。。

@nelling
Copy link

nelling commented Sep 19, 2017

收藏

@shaozj shaozj changed the title react 源码阅读-总览 react 源码阅读1-总览 Sep 20, 2017
@marsk6
Copy link

marsk6 commented May 30, 2018

m

@dingtianxiu
Copy link

收藏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants