jarweb / thinking-in-deep Goto Github PK
View Code? Open in Web Editor NEW一些思考
一些思考
代码版本
应用入口
function MyApp (props) {
return (
<div className="wrap">
<div className="inner">hi</div>
</div>
)
}
render(<MyApp />, root)
经过 babel-transform-jsx 编译后
// 将 jsx 的 dsl 代码转成 createElement 函数调用
// createElement(type, props, children, ...)
function MyApp (props) {
return createElement(
'div', // type
{className: 'wrap'}, // props
createElement( // children
'div',
{className: 'inner'},
'hi'
)
)
}
render(createElement(MyApp, null, undefined), root)
type 的类型
props 上有什么
children
整体流程
// vnode
constructor: undefined
key: null
props: {}
ref: null
type: ƒ App() // 组件/dom tag
_children: null
_component: null
_depth: 0
_dom: null
_nextDom: undefined
_original: {props: {…}, key: null, ref: null, _children: null, type: ƒ, …}
_parent: null
// parentDom => dom root
// 入口 render
function render(vnode, parentDom, replaceNode) {
...
// 包裹一层 fragment
vnode = createElement(Fragment, null, [vnode])
...
let commitQueue = []
diff(....)
commitRoot(commitQueue, vnode)
}
function diff(
parentDom, // dom root
newVNode, // fragment
oldVNode, // {}
globalContext, // {}
isSvg, // false
excessDomChildren, // null
commitQueue, // []
oldDom, // {}
isHydrating // false
) {
...
// class 组件
if ('prototype' in newType && newType.prototype.render) {
// 实例化
newVNode._component = c = new newType(newProps, componentContext);
}
// 函数组件
else {
// 通过 Component 进行实例化,所以类组件和函数组件是没啥区别的,函数组件的没有定义生命函数
// fragment 也是函数组件
newVNode._component = c = new Component(newProps, componentContext);
c.constructor = newType;
c.render = (props, state, context) {
return this.constructor(props, context);
};
}
// 往 c 上挂着 props/state/context/_dirty/_rendeCallback 等
// 类组件,执行生命函数 getDerivedStateFromProps(newprops,newstate),得到的返回值合并到 state 上
// 首次渲染阶段
// 类组件,执行生命函数 componentWillMount,把 componentDidMount 存到 _rendeCallback 数组里
// 执行组件的 render 函数
c.render(c.props, c.state, c.context)
// 当 vnode 是组件/内置组件,执行 diffChildren(...)
// 当 vnode 是 dom tag,执行 diffElementNodes(...)
// 更新阶段
// 类组件,执行 componentWillReceiveProps
// 执行 shouldComponentUpdate
// 将当前组件存到 commitQueue 里。等待递归子组件后,再拿出来执行其他生命函数
// 执行 c.componentWillUpdate
// 执行组件的 render 函数
// 执行 c.getSnapshotBeforeUpdate(oldProps, oldState)
// diffChildren(...)
}
function diffChildren(
parentDom, // dom root
renderResult, // 父组件 render 函数的返回值,是一个数组,元素是 vnode
newParentVNode, // 父组件的 vnode
oldParentVNode, // {}
globalContext, // {}
isSvg, // false
excessDomChildren, // null
commitQueue, // []
oldDom, // {}
isHydrating // false
) {
// 遍历 renderResult 数组,根据元素的 vnode 类型进行不同的处理
childVNode = renderResult[i]
// 如果是 null/boolean,统一转成 null
// 如果是 string/number,创建新的 vnode,createVNode
// 如果是 数组,包一层 fragment
// 如果 vnode._dom / vnode._component 存在,创建新的 vnode
// 绑定 vnode 的 _parent 和 _depth。父 vnode 和 当前 vnode 的层级
// 从 oldParentVNode 里得到 oldChildren ,进行新旧自组件 vnode 的调和。更新阶段
...
newDom = diff(...)
...
// 当前 vnode 的父 parentDom 插入当前 vnode 的 dom
}
function diffElementNodes(
dom, // undefined
newVNode, // vnode dom tag 类型
oldVNode, // {}
globalContext, // {}
isSvg, // false
excessDomChildren, // null
commitQueue, // []
isHydrating // false
) {
// 首次渲染
// 创建 dom/text
// diffProps(...), 绑定 class ,style,dom 属性,事件
// diffChildren(...)
}
// 首次渲染
App 组件
<Main></Main>
Main 组件
<div>hi</div>
fragment vnode
App vnode
Main vnode
div vnode
hi vnode
render
diff fragment vnode
diffChildren App vnode
diff App vnode, App 组件实例化,执行生命函数
diffChildren Main vnode
diff Main vnode,Main 组件实例化,执行生命函数
diffChildren div vnode
diff div vnode
diffElement div vnode, div dom 创建,属性/事件挂载
diffChildren div vnode
diff text vnode
diffElement text vnode,text dom 创建
diffChildren text vnode, div append text。root append div。一层层往根 append
递归结束,一层层返回...
...
commitRoot,执行剩余的生命函数和 callback
// 类组件生命函数执行
实例化
父 getDerivedStateFromProps
父 componentWillMount,旧的,后面会移除
父 render
子 getDerivedStateFromProps
子 componentWillMount
子 render
子 componentDidMount
父 componentDidMount
// 更新阶段生命函数
父 getDerivedStateFromProps
父 componentWillReceiveProps,旧的,会被移除
父 shouldComponentUpdate
父 componentWillUpdate
父 render
子 getSnapshotBeforeUpdate,在子组件 diff 之前执行
子 getDerivedStateFromProps
子 componentWillReceiveProps,旧的,会被移除
子 shouldComponentUpdate
子 componentWillUpdate
子 render
子 getSnapshotBeforeUpdate,在子组件 diff 之前执行。dom 还没更新。返回值将作为第三参数传给 componentDidUpdate
子 componentDidUpdate
父 componentDidUpdate
// 卸载阶段
unapply ref
父 componentWillUnmout
子 componentWillUnmout
// setState
// 合并更新 state 为 this._nextstate
// 执行 enqueueRender(this) // this 就是当前 setstate 的组件实例
// 组件实例
context: {}
handle: ƒ () // 事件
props: {path: "/", url: "/", matches: {…}} // props
state: {count: 1} // 旧的 state
_dirty: false
_force: false
_globalContext: {}
_nextState: {count: 2} // 新的 state
_parentDom: div#app
_renderCallbacks: []
_vnode: {props: {…}, key: undefined, ref: undefined, __k: null, type: ƒ, …} // 组件对应的 vnode
function enqueueRender(c) {
// 标记当前组件实例为 _dirty: true
// 将需要更新的组件存到 rerenderQueue 数组中
// 执行 defer(process)
// 该组件第一次触发更新时,就会调用 defer(process)
// 后续的重复 setState 都不会再进入下面逻辑。后续重复 setState 只会直接更新 state,而渲染要等到当前宏任务执行完
if (
(!c._dirty &&
(c._dirty = true) &&
rerenderQueue.push(c) &&
!rerenderCount++) ||
prevDebounce !== options.debounceRendering
) {
prevDebounce = options.debounceRendering;
(prevDebounce || defer)(process);
}
}
// 在当前宏任务后的微任务中执行更新 process,从而达到批量更新的效果
const defer =
typeof Promise == 'function'
? Promise.prototype.then.bind(Promise.resolve())
: setTimeout;
function process() {
let queue;
while ((rerenderCount = rerenderQueue.length)) {
// 将所有需要更新的组件实例排序,从层级小到大,即父到子
queue = rerenderQueue.sort((a, b) => a._vnode._depth - b._vnode._depth);
rerenderQueue = [];
queue.some(c => {
if (c._dirty) renderComponent(c);
});
}
}
// 最外一个脏组件开始更新
function renderComponent(component) {
diff(...)
commitRoot(...)
updateParentDomPointers(...)
}
// hook
function getHookState(index, type) {
if (options._hook) {
options._hook(currentComponent, index, currentHook || type);
}
currentHook = 0;
// 初始化组件实例上的 __hooks 属性
const hooks =
currentComponent.__hooks ||
(currentComponent.__hooks = {
_list: [],
_pendingEffects: []
});
// 新的 hook
if (index >= hooks._list.length) {
hooks._list.push({});
}
// 已存在的 hook,直接返回。当组件多次渲染时,hook 不会重复创建
return hooks._list[index]; // 返回当前的 hook
}
// useState
function useState(initialState) {
currentHook = 1; // 全局属性
return useReducer(invokeOrReturn, initialState);
}
// useReducer
function useReducer(reducer, initialState, init) {
const hookState = getHookState(currentIndex++, 2);
hookState._reducer = reducer;
// 首次添加,每个 hook 里维护一个组件实例,来判断这个 hook 是否已经维护
if (!hookState._component) {
hookState._component = currentComponent;
// [count, set]
hookState._value = [
!init ? invokeOrReturn(undefined, initialState) : init(initialState),
action => {
// 计算新的 state
const nextValue = hookState._reducer(hookState._value[0], action);
if (hookState._value[0] !== nextValue) {
// 更新 state
hookState._value[0] = nextValue;
// 通过 setState 来触发渲染
hookState._component.setState({});
}
}
];
}
return hookState._value;
}
// useEffect
function useEffect(callback, args) {
const state = getHookState(currentIndex++, 3);
// 当依赖变化,会重新存储 useEffect callback
if (!options._skipEffects && argsChanged(state._args, args)) {
state._value = callback;
state._args = args;
// 当前 hook 所属的组件实例
currentComponent.__hooks._pendingEffects.push(state);
}
}
function useLayoutEffect(callback, args) {
const state = getHookState(currentIndex++, 4);
if (!options._skipEffects && argsChanged(state._args, args)) {
state._value = callback;
state._args = args;
// 与 useEffect 的区别,执行的时机不一样。在 didmount 时执行
currentComponent._renderCallbacks.push(state);
}
}
// options,通过 options 上的钩子,在组件的不同生命函数中执行,来达到函数组件也有生命周期
diffed: vnode => {…} // 所有组件 diff 完成后执行,在 commitRoot 前
unmount: vnode => {…} // 组件卸载时执行
_catchError: ƒ _catchError(error, vnode) // 组件出错时执行
_commit: (vnode, commitQueue) => {…} // 组件 didMount 时执行,即 commitRoot
_render: vnode => {…} // 组件 render 前执行
// render,在组件 render 前执行
options._render = vnode => {
// 执行上一次 render
if (oldBeforeRender) oldBeforeRender(vnode);
currentComponent = vnode._component;
currentIndex = 0;
const hooks = currentComponent.__hooks;
if (hooks) {
// 每次执行时,先清除 useeffect 请求副作用
hooks._pendingEffects.forEach(invokeCleanup);
// 执行 useeffect
hooks._pendingEffects.forEach(invokeEffect);
hooks._pendingEffects = [];
}
}
function invokeEffect(hook) {
hook._cleanup = hook._value(); // 副作用清除存到 _cleanup 上
}
function invokeCleanup(hook) {
if (typeof hook._cleanup == 'function') hook._cleanup();
}
// 在 diff 递归结束后执行,所有组件都 diff 完成。但 commitRoot 之前
options.diffed = vnode => {
if (oldAfterDiff) oldAfterDiff(vnode);
const c = vnode._component;
if (c && c.__hooks && c.__hooks._pendingEffects.length) {
// 下一帧的 raf 中执行
// 也是执行 c.__hooks._pendingEffects 里的 effect
afterPaint(afterPaintEffects.push(c));
}
};
// 在 commitRoot 时执行,即 dom 插入后。didmount 执行前
// 可认为是函数组件的 didmount 钩子
options._commit = (vnode, commitQueue) => {
commitQueue.some(component => {
try {
// useLayoutEffect 的执行时机
component._renderCallbacks.forEach(invokeCleanup);
component._renderCallbacks = component._renderCallbacks.filter(cb =>
cb._value ? invokeEffect(cb) : true
);
} catch (e) {
commitQueue.some(c => {
if (c._renderCallbacks) c._renderCallbacks = [];
});
commitQueue = [];
options._catchError(e, component._vnode);
}
});
if (oldCommit) oldCommit(vnode, commitQueue);
};
// 清除副作用
options.unmount = vnode => {
if (oldBeforeUnmount) oldBeforeUnmount(vnode);
const c = vnode._component;
if (c && c.__hooks) {
try {
c.__hooks._list.forEach(invokeCleanup);
} catch (e) {
options._catchError(e, c._vnode);
}
}
}
// hook 对应的组件实例
constructor: ƒ HookDemo()
context: {}
props: {}
render: ƒ doRender(props, state, context)
state: {}
__hooks: {
_list: [
{
_component: Component {…} // 组件实例
_reducer: ƒ invokeOrReturn(arg, f) //
_value: (2) [0, ƒ] // [count, set] // 每次 set 后就执行当前组件的 setState 来触发渲染。所以多个更新会合并渲染
},
{
_args: []
_value: ƒ () // useEffect 的回调函数
}
]
_pendingEffects: [
{
_args: []
_value: ƒ ()
}
]
__proto__: Object
}
_dirty: false
_globalContext: {}
_nextState: {}
_parentDom: div.page
_renderCallbacks: []
_vnode: {props: {…}, key: null, ref: null, _children: null, type: ƒ, …}
// 分组
useState, useReduce
useEffect
useLayoutEffect, useImperativeHandle
useRef, useMemo, useCallback
useContext
useErrorBoundary
// hook 的生命周期顺序
函数组件实例化
// getDerivedStateFromProps
// componentWillMount
options._render // hook useEffect
render
// 子 diff
options.diffed // hook useEffect,下一帧 raf 中执行
options._commit // hook useLayoutEffect
// componentDidMount
// ref
当该组件内的所有子组件都递归完成后,把子组件的dom插入到父的dom内后
进行 ref 和 dom 实例的绑定
ref 绑定在 compnentDidMount 之前全部完成
更新阶段,在 ref 绑定之前会进行组件的移除(如果存在)
// context
const MyContext = createContext('a')
<MyContext.Provider value="b">
<Demo />
</MyContext.Provider>
<MyContext.Consumer>
{value => (
<div>{value}</div>
)}
</MyContext.Consumer>
// 当 diff 到 Provider 组件时,Provider 的 vnode 结构:
constructor: undefined
key: undefined
props: {value: "b", children: {…}} // value 是 Provider 接收的数据
ref: undefined
type: Provider(props) {...}
_contextRef: {_id: "__cC0", _defaultValue: "a", Consumer: ƒ, Provider: ƒ} // Provider 函数的属性
_children: null
// Provider 组件也是函数组件,也是通过 Component 来实例化
// 得到的实例是:
constructor: Provider(props) {...}
context: {}
props: {value: "b", children: {…}} // props.value 是 Consumer 组件要拿到的值
render: ƒ doRender(props, state, context) // render 返回子组件 Demo
globalContext = assign(assign({}, globalContext), c.getChildContext()) // 返回当前的组件实例和 contextid 的映射对象
// globalContext
__cC0: Component {...} // 通过 id 拿到对应的组件实例
this.getChildContext = () => {
ctx[context._id] = this;
return ctx;
}
// globalContext 会通过 diff 传给子组件,然后挂到子组件的实例上
c.context = componentContext;
c._globalContext = globalContext;
// 当 diff 到 Consumer 组件时,Consumer 组件的 vnode:
constructor: undefined
key: null
props: {children: ƒ}
ref: null
type: ƒ Consumer(props, context)
contextType: {_id: "__cC0", _defaultValue: "a", Consumer: ƒ, Provider: ƒ} // Consumer 函数上的属性
// 通过 diff 传入的 globalContext 和 contextType 上的 _id 可以拿到 Provider 组件实例的 props。
let componentContext = tmp // componentContext 就是 props.value
? provider
? provider.props.value
: tmp._defaultValue
: globalContext;
// 当 consumner 组件实例化时,c = new Component(newProps, componentContext) // value 作为 context 参数传入给 Component 进行实例化
context: "b"
props: {children: ƒ}
// Provider 组件实例收集了 Consumer 组件的实例。当 provider 上的 context value 变化时,会触发 consumer 组件实例更新
if (provider) provider.sub(c)
c.context = componentContext; // Consumer 实例的 context
c._globalContext = globalContext;
// render,即 Consumer 函数执行。就是一个 render props
c.render(c.props, c.state, c.context)
// Consumer 组件定义
Consumer(props, context) {
return props.children(context);
}
// context 是如何更新的?
// 当 Provider 组件的 props 更新时,会触发 Provider 组件的更新,会执行 shouldComponentUpdate 生命函数
this.shouldComponentUpdate = _props => {
if (this.props.value !== _props.value) { // 浅比较,有性能优化
// 之前 Provider 组件实例收集的 Consumer 组件实例
subs.some(c => {
c.context = _props.value; // 更新 context 的值
enqueueRender(c); // 触发渲染。当 context 频繁更新时,也会合并渲染
});
}
}
// vnode
constructor: undefined
key: null
props: {}
ref: null
type: ƒ App() // 组件函数/类
_children: null // 子组件 vnode,组件实例化后 render 的返回值
_component: null // 组件实例
_depth: 0 // 层级
_dom: null // dom 实例
_nextDom: undefined // 新的 dom 实例
_original: {props: {…}, key: null, ref: null, _children: null, type: ƒ, …} // vnode 备份
_parent: null // 父组件 vnode,子组件 vnode 通过 _parent 一层层往上连接父 vnode
// 组件实例
base: div.page // dom 实例
constructor: ƒ Fragment(props)
context: {} // context value
props: {children: Array(1)}
render: ƒ doRender(props, state, context) // render 函数
state: {}
_dirty: false // 是否需要更新
_force: false // 是否强制更新
_globalContext: {} // 组件实例和 context id 的映射
_nextState: {}
_parentDom: div#app // 父的 dom 实例,父创建好了才创建子的 dom 实例
_renderCallbacks: [] // render 后的生命函数/callback
_vnode: {props: {…}, key: null, ref: null, _children: Array(1), type: ƒ, …} // 对应的 vnode
// setState 总是批量的异步的?
setState 会直接更新 state,当渲染总是在当前宏任务后的微任务中更新
当两个 setTimeout 进行 setstate, 这些渲染更新会分成两次
当在 一个 setTimeout 里多次 setstate,那么渲染更新是批量的
与 react 有很大的不同
*/\*#__PURE__\*/*
,当代码在压缩过程中,压缩工具会找到这些标记的代码,然后移除掉相关。但效果没有 rollup 好,会有一些 webpack 的运行时代码打包进去。presets:[["env",{"loose": false}]]
来启用宽松的编译模式,这样不会用 iife 的模式来编译类import clone from 'lodash/clone'
来避免整个包引入import {Button} from 'antd'
在编译时被改成import Button from 'antd/lib/button'
版本
大概原理
// Router 组件
<Router history={...} defaultUrl={...} onChange={...}>
<A path="/a" />
<B path="/b" />
<C path="/c" />
</Router>
class Router extends Component {
contructor(props) {
super(props)
this.state = {
url: getCurrentUrlFromLocation(props.history) // 维护当前 url,通过 url 匹配相应的视图组件
}
}
shouldComponentUpdate() {...} // 优化性能,避免重复渲染
componentDidMount() {
// history 监听变化,触发路由跳转
// 这里是关键,相关逻辑在 history 库
// 通过 history 库,很容易实现一个简单前端路由
// browser router:监听 popstate 事件
// hash router:监听 hashchange 事件
this.unlisten = this.props.history.listen((location) => {
const url = getUrlFromLocation(location)
this.routeTo(url)
})
}
componentWillUnmount() {
this.unlisten()
}
routeTo(url) {
this.setState({url}) // 通过 setstate 来触发视图渲染
}
getMatchingChildren(children, url) {
// 通过 url 找到 children 数组里匹配的组件
const matches = exec(...) // 匹配函数
const newProps = {url, matches}
cloneElement(vnode, newProps)
return vnode
}
render() {
let active = this.getMatchingChildren(this.props.children, this.state.url)
if (url !=== this.prevUrl) {
this.preUrl = url
this.props.onChange(...)
}
return active
}
}
function getCurrentUrlFromLocation(history) {
// 从 history.location / history.getCurrentLocation() / window.location 上批接出简单的 url
url = pathname + search
}
// 触发 url 改变的方法
// 都是封装过的方法,与原生 history 的方法不一样,不会触发浏览器刷新
// 组件型
Link 组件
// 方法调用型
history.push(...)
history.replace(...)
history.go(n)
history.goBack()
history.goForward
// Link 组件
a 标签,监听 click 事件,阻止默认事件,通过 history.replace/push 来改变 url
当 a 标签带有 target 为非 _self 值,Link 组件会失效
// Redirect 组件
<Redirect from="*" to="/bar" />
其实就是在 willmount 的时候切换到指定的路由
自身 render 返回值是 null
popstate、hashchange 事件
browser router 和 hash router 的区别
// 简单版的 webpack runtime,有很多 runtime 函数没有加入的
// modules 是所有的模块集合,通过 url 为 key,模块函数为 value
(function (modules) {
// The module cache
var installedModules = {};
// 根据 moduleId 返回对应的模块对象
function require(moduleId) {
// 缓存中是否存在
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 构建初始对象
var module = installedModules[moduleId] = {
i: moduleId,
l: false, // 是否已缓存
exports: {}
};
// 在所有的模块里找到相应模块,传入一些参数执行
modules[moduleId].call(module.exports, module, module.exports, require);
// 执行完后,标记该模块已缓存
module.l = true;
// 返回该模块出去
return module.exports;
}
// 将所有模块挂到 require 函数上
require.m = modules;
// 将缓存的模块挂到 require 函数上
require.c = installedModules;
// 定义一个设置 getter 的函数,并挂到 require 函数上
require.d = function (exports, name, getter) {
if (!require.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
require.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
require.t = function (value, mode) {
if (mode & 1) value = require(value);
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
require.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if (mode & 2 && typeof value != 'string') for (var key in value) require.d(ns, key, function (key) { return value[key]; }.bind(null, key));
return ns;
};
// getDefaultExport function for compatibility with non-harmony modules
require.n = function (module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
require.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
require.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
require.p = "";
// Load entry module and return exports
return require(require.s = "./index.js"); // 执行入口文件
})({
"./a.js": (function (module, export, require) {
eval("
require.r(export);
require.d(export, \"add\", function() { return add; });
var bjs = require(\"./b.js\");
function add(a, b) {return Object(bjs[\"sum\"])() + a + b};
");
}),
"./b.js": (function (module, export, require) {
eval("
require.r(export);
require.d(export, \"sum\", function() { return sum; });
function sum() {return 100}
");
}),
"./index.js": (function (module, export, require) {
eval("
require.r(export);
var ajs = require(\"./a.js\");
Object(ajs[\"add\"])(1, 2)
");
})
});
// 构建初始对象
// {
// i: "./index.js", // url 就是 moduleId
// l: false,
// exports: { }
// }
//
// modules[moduleId].call(module.exports, module, module.exports, require);
// modules[moduleId] 是一个包装过的函数
//
// function (module, export , require) {
// eval("
// require.r(export);
// var ajs = require(\"./a.js\");
// Object(ajs[\"add\"])(1, 2)
// ");
// }
// 该函数绑定 this 为初始的模块对象,这里是空对象 {}
// 传入参数为:module, module.exports, require
// eval("
// require.r(export); // 为模块对象定义一个 esModule 的 key
// var ajs = require(\"./a.js\"); // 加载当前模块的依赖 ajs,会一直加载依赖的依赖
// Object(ajs[\"add\"])(1, 2) // 执行模块
// ");
//
// var ajs = require(\"./a.js\");
// eval("
// require.r(export ); // 为模块对象定义一个 esModule 的 key
// require.d(export, \"add\", function() { return add; }); // 为模块的 add 属性定义一个 getter 属性,当模块被调用时,读取模块上的 add
// var bjs = require(\"./b.js\"); // 加载当前的模块依赖 bjs
// function add(a, b) {return Object(bjs[\"sum\"])() + a + b};
// ");
//
// var bjs = require(\"./b.js\");
// eval("
// require.r(export ); // // 为模块对象定义一个 esModule 的 key
// require.d(export, \"sum\", function() { return sum; }); // 为模块的 add 属性定义一个 getter 属性
// 这是最后的依赖模块,没有依赖了,然后回去执行入口模块的逻辑
// function sum() { return 100 }
// ");
// 复杂版的 runtime
(function (modules) {
// 这个函数很重要,用来加载初始化模块的
// 所有后续加载的模块都是通过 window["webpackJsonp"].push(...) 来实现的
// 这个 push 函数就是 webpackJsonpCallback
// 当 push 一个 data 时,其实就是执行,维护一个当前 chunk 的数组,数组的每个元素都是模块函数,通过索引来查找,索引是模块的 id,所以该数组不是连续的
// data:是什么?push([['chunkid'],{该 chunk 包含的所有模块}, ['入口模块id',‘依赖’]])
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
var executeModules = data[2];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if (parentJsonpFunction) parentJsonpFunction(data);
while (resolves.length) {
resolves.shift()();
}
// add entry modules from loaded chunk to deferred list
deferredModules.push.apply(deferredModules, executeModules || []);
// run deferred modules when all chunks ready
return checkDeferredModules();
};
function checkDeferredModules() {
var result;
for (var i = 0; i < deferredModules.length; i++) {
var deferredModule = deferredModules[i];
var fulfilled = true;
for (var j = 1; j < deferredModule.length; j++) {
var depId = deferredModule[j];
if (installedChunks[depId] !== 0) fulfilled = false;
}
if (fulfilled) {
deferredModules.splice(i--, 1);
result = webpackRequire(webpackRequire.s = deferredModule[0]);
}
}
return result;
}
// The module cache
var installedModules = {};
// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
var installedChunks = {
"runtime": 0
};
var deferredModules = [];
// script path function
function jsonpScriptSrc(chunkId) {
return webpackRequire.p + "static/js/" + ({}[chunkId] || chunkId) + "." + { "1": "bbc47b9f", "2": "9b6415cc", "3": "a9a988f4" }[chunkId] + ".chunk.js"
}
// The require function
function webpackRequire(moduleId) {
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, webpackRequire);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
webpackRequire.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if (installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function (resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (webpackRequire.nc) {
script.setAttribute("nonce", webpackRequire.nc);
}
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if (chunk !== 0) {
if (chunk) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
installedChunks[chunkId] = undefined;
}
};
var timeout = setTimeout(function () {
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
// expose the modules object (__webpack_modules__)
webpackRequire.m = modules;
// expose the module cache
webpackRequire.c = installedModules;
// define getter function for harmony exports
webpackRequire.d = function (exports, name, getter) {
if (!webpackRequire.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
webpackRequire.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
webpackRequire.t = function (value, mode) {
if (mode & 1) value = webpackRequire(value);
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
webpackRequire.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if (mode & 2 && typeof value != 'string') for (var key in value) webpackRequire.d(ns, key, function (key) { return value[key]; }.bind(null, key));
return ns;
};
// getDefaultExport function for compatibility with non-harmony modules
webpackRequire.n = function (module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
webpackRequire.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
webpackRequire.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
webpackRequire.p = "/static/app/";
// on error function for async loading
webpackRequire.oe = function (err) { console.error(err); throw err; };
// push 其实就是 runtime 里的 webpackJsonpCallback
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
// run deferred modules from other chunks
checkDeferredModules();
})([]);
// 一般打包出来的模块形式:
// polyfill.js // dll
// vender.js // dll
// inline runtime
// jsonp chunk // 通过 jsonp 形式加载的
// jsonp chunk
// ...
模块数组
一个异步加载示例
// jsonp 加载的模块
window["webpackJsonp"].push([
["offLine"], // 当前的模块名称
{
// 依赖
"./src/assets/onLineTest.txt": (function (module, exports, __webpack_require__) {
module.exports = __webpack_require__.p + "static/media/onLineTest.d41d8cd9.txt";
}),
// 模块源码
"./src/utils/offLine.ts": (function (module, exports, __webpack_require__) {
function testOnLine() {}
if (window.navigator) {
setTimeout(function () {
testOnLine()
}, 2000);
}
})
},
[["./src/utils/offLine.ts", "runtime"]]
]);
复杂的异步加载示例
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
["guard_pay"], // 当前模块名称
{
// react-dom 依赖
"./node_modules/react-dom/index.js": (function (module, exports, __webpack_require__) {
module.exports = (__webpack_require__("dll-reference vendor_6f16b710b6ec15e318df"))(495);
}),
// react 依赖
"./node_modules/react/index.js": (function (module, exports, __webpack_require__) {
module.exports = (__webpack_require__("dll-reference vendor_6f16b710b6ec15e318df"))(163);
}),
// .... 忽略很多依赖
"./src/utils/throttle.ts": (function (module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "default", function () { return throttle; });
function throttle(fn, wait) { var timer; return function () { if (timer) return; fn.apply(void 0, arguments); timer = setTimeout(function () { clearTimeout(timer); timer = null; }, wait || 500); }; }
}),
// .... 忽略很多依赖
"./src/apps/GuardPay.tsx": function (module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
// 入口文件的依赖
var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./node_modules/react/index.js");
var react__WEBPACK_IMPORTED_MODULE_1___default = __webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
var react_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./node_modules/react-dom/index.js");
var react_dom__WEBPACK_IMPORTED_MODULE_2___default = __webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_2__);
var _ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__("./src/ErrorBoundary.tsx");
var _pages_VipCenter_GuardPay__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__("./src/pages/VipCenter/GuardPay/index.tsx");
var _hooks_useDebug__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__("./src/hooks/useDebug/index.ts");
var _hooks_useDocumentTitle__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__("./src/hooks/useDocumentTitle/index.ts");
// app 组件
var App = Object(_utils_checkNetWork__WEBPACK_IMPORTED_MODULE_3__["default"])(
function () {
Object(_hooks_useDocumentTitle__WEBPACK_IMPORTED_MODULE_9__["default"])('title');
Object(_hooks_useDebug__WEBPACK_IMPORTED_MODULE_8__["default"])();
return react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div",
{ className: _pages_VipCenter_GuardPay_style_module_scss__WEBPACK_IMPORTED_MODULE_5___default.a.page },
react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(_pages_VipCenter_GuardPay__WEBPACK_IMPORTED_MODULE_7__["default"], null));
}
);
// render dom
react_dom__WEBPACK_IMPORTED_MODULE_2___default.a.render(react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(_ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__["default"], null, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(App, null)), document.getElementById('root'));
}),
// 入口文件,一般标记为 0
0: (function (module, exports, __webpack_require__) {
module.exports = __webpack_require__("./src/apps/GuardPay.tsx");
}),
"dll-reference lib_e4f1cebcccf7c26ba350": (function (module, exports) {
module.exports = lib_e4f1cebcccf7c26ba350;
}),
"dll-reference polyfill_73fb46439efc9a918d08": (function (module, exports) {
module.exports = polyfill_73fb46439efc9a918d08;
}),
"dll-reference vendor_6f16b710b6ec15e318df": (function (module, exports) {
module.exports = vendor_6f16b710b6ec15e318df;
})
},
[[0, "runtime", "babelHelper", 0]]
]);
不能在业务中一直做重复的事情,需要站在业务对开发者的要求的角度,看自己能在业务开发的同时能做哪些方面的深入沉淀
webpack-dev-server hot + hotModuleReplacePlugin + module.hot.accept
热更新过程?
socket 通信过程?
拿到 hot-update.js 后是怎样更新的?
webpackHotUpdate("main", {
"./node_modules/css-loader/dist/cjs.js?!./node_modules/postcss-loader/src/index.js?!./src/index.css": (function (module, exports, __webpack_require__) {
var ___CSS_LOADER_API_IMPORT___ = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js");
exports = ___CSS_LOADER_API_IMPORT___(false);
// 这是一整个 module,不是某个代码片段,看上去是按一个个文件来的
exports.push([module.i, "body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n color: yellow;\n}\n", ""]);
module.exports = exports;
})
})
function webpackHotUpdateCallback(chunkId, moreModules) {
hotAddUpdateChunk(chunkId, moreModules);
if (parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules);
}
function hotAddUpdateChunk(chunkId, moreModules) {
if (!hotAvailableFilesMap[chunkId] || !hotRequestedFilesMap[chunkId])
return;
hotRequestedFilesMap[chunkId] = false;
for (var moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
hotUpdate[moduleId] = moreModules[moduleId];
}
}
if (--hotWaitingFiles === 0 && hotChunksLoading === 0) {
hotUpdateDownloaded();
}
}
function hotUpdateDownloaded() {
hotSetStatus("ready");
var deferred = hotDeferred;
hotDeferred = null;
if (!deferred) return;
if (hotApplyOnUpdate) {
// Wrap deferred object in Promise to mark it as a well-handled Promise to
// avoid triggering uncaught exception warning in Chrome.
// See https://bugs.chromium.org/p/chromium/issues/detail?id=465666
Promise.resolve()
.then(function() {
// 热更逻辑
return hotApply(hotApplyOnUpdate);
})
.then(
function(result) {
deferred.resolve(result);
},
function(err) {
deferred.reject(err);
}
);
} else {
var outdatedModules = [];
for (var id in hotUpdate) {
if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
outdatedModules.push(toModuleId(id));
}
}
deferred.resolve(outdatedModules);
}
}
// 更新的逻辑在这里,在 hotModuleReplacementRuntime.js 里
function hotApply(options) {...}
// 简单来说:
// 将 hotUpdate 上对应的模块挂到 modules 对象上
appliedUpdate[moduleId] = hotUpdate[moduleId];
modules[moduleId] = appliedUpdate[moduleId];
// 删除旧的模块
// 执行 module.hot.accept 回调函数队列,因为可以注册多个 accept 函数
cb = module.hot._acceptedDependencies[dependency]
cb(moduleOutdatedDependencies)
一些相关的库?
代码压缩、混淆、拆分、合并、tree shaking
预加载,preload、prefetch、dns-preconnect
非阻塞加载 css js
懒加载,首屏资源最小化
缓存持久化、稳定化:通用框架、polyfill、common bundle,利用 v8 优化:热运行、script streaming
图片压缩、字体压缩、字体图标、css sprite、base 64
骨架屏注入
pwa service worker 缓存处理
prepack 代码预处理
分析流程
// 项目代码
// app.svelte
<script>
import { onMount } from 'svelte'
import Demo from './Demo.svelte'
export let name;
let count = 0
const handle = () => {
count += 1
}
// 可以写多个
onMount(() => console.log('mount'))
onMount(() => console.log('mount'))
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Helloza {name}! {count}</h1>
<button on:click={handle}>click</button>
<Demo msg={count} />
// demo.svelte
<script>
// export 表示,接收外表的 props
export let msg;
</script>
<h1>Hello {msg}!</h1>
// 编译阶段
// code is input,svelt-loader 进行对 svelte 文件编译
// 每个 svelte 文件,执行一次这个方法
// source 是文件的源码字符串
preprocess(source, ...).then(processed => {
// ...
compile(processed.toString(), compileoptions)
})
// 主要作用是根据 option 对 source 做预处理
async function preprocess(source, preprocessor, options) {
// 如果有 preprocessor, 根据 preprocessor 对 source 进行处理
// 返回一个对象
return {
code: source,
dependencies: ...,
toString() {
return source
}
}
}
function compile(source, options = {}) {
// 构建一个 stat 实例,主要是保存编译速度之类
// 对 source 进行 parse 为 ast
const ast = parse$2(source, options)
// 进行组件实例化,组件类里面处理了大量逻辑
component = new Component(ast, source, ...)
// 对组件进行浏览器端代码生成,区分 ssr/dom
// 通过 dom 函数处理,得到的是一个 function create_fragment(ctx){...} 函数的声明字符串
// 也就是说 js 是一段字符串,最后会将这段字符串与项目源码一起输出到 thunk 里
js = ssr ? ssr(component, options) : dom(component, options)
// 返回一个对象
res = component.generate(js)
return res
}
function parse$2(template, options = {}) {
// parser 实例化
const parser = new Parser$2(template, options)
// parser 对象结构为:
{
stack:[
{start, end, type, children}
],
css: [
{start,end,attributes,children, content}
],
js: [
{start, end, context, content}
],
html: {start, end, type, children}
meta_tags,
template,
filename,
customeElement,
}
// 返回一个对象
return {
html: parser.html,
css: parser.css[0],
instance: js context is default [0],
module: js context is module [0]
}
}
class Parser$2 {
// 对模版进行 ast 编译,分离 css/js/html 等。
}
// 很复杂
class Component {}
// 生成运行时代码片段
function dom(component, options) {
// 构造一个 renderer 实例
const renderer = new Renderer(component, options)
// 构造一个 builder 实例,之后完这个 builder 里添加各种代码片段
const builder = new CodeBuilder()
// 各种处理,往 builder 里插入代码字符串片段
...
// 返回这个 builder
return builder.toString()
}
//////////// dom 生成的函数片段 start /////////
// 根据实际情况,生产的片段可能不一样
function create_fragment(ctx) {
var h1, t0, t1, t2, t3, current;
var demo = new Demo({ props: { msg: "aa" } });
return {
c() {
h1 = element("h1");
t0 = text("Helloza ");
t1 = text([✂129 - 133✂]);
t2 = text("!");
t3 = space();
demo.$$.fragment.c();
attr(h1, "class", "svelte-i7qo5m");
dispose = listen(button, "click", [✂348-354✂])
},
m(target, anchor) {
insert(target, h1, anchor);
append(h1, t0);
append(h1, t1);
append(h1, t2);
insert(target, t3, anchor);
mount_component(demo, target, anchor);
current = true;
},
p(changed, ctx) {
if (!current || changed.name) {
set_data(t1, [✂129 - 133✂]);
}
},
i(local) {
if (current) return;
transition_in(demo.$$.fragment, local);
current = true;
},
o(local) {
transition_out(demo.$$.fragment, local);
current = false;
},
d(detaching) {
if (detaching) {
detach(h1);
detach(t3);
}
destroy_component(demo, detaching);
}
};
}
function instance($$self, $$props, $$invalidate) {
[✂10 - 60✂] // 是一个占位符???
$$self.$set = $$props => {
if ('name' in $$props) $$invalidate('name', name = $$props.name);
};
return { name };
}
class App extends SvelteComponent {
constructor(options) {
super();
// 执行 init 方法
init(this, options, instance, create_fragment, safe_not_equal, ["name"]);
}
}
//////////// dom 生成的函数片段 end /////////
res = component.generate(js)
// 的到的对象结构是:
{
js: {
code // 这里包含着我们自己也的代码,以及 dom 函数生成的运行时代码,就是上面的代码片段。最后这些代码经过 babal 编译后,输出到文件 zhunk 中
map // soucemap 实例
},
css:{
code // css 代码
map // soucemap 实例
},
ast, // parse 生成的 ast 对象
warnings: [],
// 代码里所有的声明
// eg. in demo.svelte file
// export let name
// <div>{name}</div>
vars: [
{
name: 'name',
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
writable: false
}
],
stat // 编译的统计
}
// 运行时流程
// 入口是
const app = new App({
target: document.body,
props: {name: 'hhha'}
})
// 编译后的 App 组件
new App extends SvelteComponent {
constructor(options) {
super();
// 执行 init 方法
init(this, options, instance, create_fragment, safe_not_equal, ["name"]);
}
}
// svelte 没有虚拟 dom,首次渲染时,从父一直往子插入到 html 文档中。更新阶段,直接更新 dom
function init(component, options, instance, create_fragment, not_equal$$1, prop_names) {
// 保存当前组件的实例
// 初始化 component.$$ 属性,往这个属性上挂载一些属性:
const $$ = component.$$ = {
fragment,
ctx,
props,
update,
bound,
on_mount: [],
on_destroy: [],
before_render: [],
after_render: [],
context,
callbacks,
dirty
}
// 执行 instance 函数,得到当前组件需要的 props, 挂到 ctx 上。这里需要注意 cb 参数,这个函数会将所有的反应数据进行包装。更新数据操作会执行这个 cb 函数。
$$.ctx = instance(component,prosp,cb) || props
// 执行 所有的 $$.before_render 数组里存的函数,首次 $$.before_render 数组 为空
// 执行 create_fragment 函数,拿到一个对象,可以看上面的例子
$$.fragment = create_fragment($$.ctx)
// 执行 $$.fragment.c(),该函数会创建当前组件的所有 dom 元素,挂载 html 属性到 dom 上,绑定组件的事件。同时执行子组件的 $$.fragment.c() 函数。子组件的实例有了吗?子组件的实例化在父组件执行 create_fragment 函数的时候,就进行实例创建了。这里其实是从父到子,一直执行各自的 create_fragment 函数。生成各自的 dom 和属性挂载。但还没有插入到 html 文档上。
// 执行顺序是:父创建dom -》子创建dom ... -〉挂载子属性 -》子事件绑定到 dom -》挂载父属性 -〉 父事件绑定到 dom
// 执行 mount_component 函数, 该函数会从父一直递归到子组件,把前面生成好的 dom + 属性 插入到父中。
// 流程是:父插入到 root -》子插入到父 ... 一直到完成为止。这时候所有的 dom 都插入到html 文档了。首次渲染相当于结束
mount_component(component, options.target, options.anchor)
// 更新操作,首次渲染基本可以忽略这个函数
flush()
}
// 看一下 init 函数的 instance 参数
// 编译后,有这个东西 [✂10-249✂],是原来代码的展位符??
function instance($$self, $$props, $$invalidate) {
let {name} = $$props
let count = 0
// 可以看出,事件触发 state 更新时,会先执行 $$invalidate 函数, 该函数会执行 make_dirty 方法,从而触发更新
const handle = () => {
$$invalidate('count', count += 1)
}
setTimeout(() => {
$$invalidate('count', count += 1)
}, 3000)
// 组件实例的 $set 方法, 某些方式的更新数据会用到这个方法。例如:调用子的 $set 方法,来更新子的 props
$$self.$set = $$props => {
if ('name' in $$props) $$invalidate('name', name = $$props.name)
}
return {name, count, handle}
}
// 首次渲染阶段,不断递归这个函数
function mount_component(component, target, anchor) {
// 执行 $$.fragment.m 函数。该函数会将之前创建好的 dom 插入到 html 文档上,然后递归执行 mount_component 函数。从父到子一直插入到 html 文档上
// 递归完后,执行 after_render 函数,该函数执行时,会执行该组件的 onMount 生命函数,svelte 的生命函数一个组件可以写多个。同时收集该组件的 onMount 函数返回值,等组件卸载时执行。可以用来卸载时清除副作用。
// onMount 函数执行顺序: 子组件 -》 父组件
}
// 首次渲染生命函数:
父 beforeUpdate -》子 beforeUpdate -》父 afterUpdate -》 子 afterUpdate -》 父 onMount -》子 onMount
// 更新阶段生命函数
父 beforeUpdate -》子 beforeUpdate -》子 afterUpdate -》子 onDestroy -》 父 afterUpdate -》父 onDestroy
// 更新阶段
// 每个反应式的属性都会被 $$invalidate 函数包裹:事件、ajax、异步 等
// $$invalidate 执行时,触发 make_dirty
function make_dirty(component, key) {
// 缓存需要更新的组件实例 push in dirty_components
// 执行 schedule_update
}
function schedule_update() {
// 异步执行 flush,在本次事件循环后紧跟的微任务队列中执行
}
function flush() {
// 如果有 dirty_components.length,循环拿到当前组件,循环执行 update 函数
// 如果有 binding_callbacks ,执行 binding_callbacks
// 如果有 render_callbacks,执行 render_callbacks
// 如果有 flush_callbacks,执行 flush_callbacks
}
function update($$) {
// 执行生命函数 before_render
// 执行 $$.fragment.p 函数,更新所有的的 state
$$.fragment.p($$.dirty, $$.ctx)
// 收集 after_render
}
// 每次更新所有的反应式数据
// 同时更新子组件的 props
p(changed, ctx) {
if (!current || changed.name) {
// 更新数据
set_data(t1, [✂280-284✂]);
}
if (!current || changed.count) {
set_data(t3, [✂288-293✂]);
}
if (!current || changed.hi) {
set_data(t5, [✂296-298✂]);
}
var demo_changes = {};
// 更新子的 props
if (changed.count) demo_changes.msg = [✂357-362✂];
demo.$set(demo_changes);
// 当有显示隐藏某些片段时,会执行该片段的 p/c/m 函数
if ([✂706-719✂]) {
if (if_block) {
// 执行片段的 p,进行数据更新或片段更新,这是一个递归操作。不断对片段里的片段或数据进行更新
if_block.p(changed, ctx);
transition_in(if_block, 1);
} else {
// create_if_block 这个函数很重要。当有 if/else 的模版逻辑时。都会编译生成这个函数代码。所以编译后的代码量会很大。这个函数的逻辑与 create_fragment 函数的逻辑相似,该很熟生成子组件的实例。然后返回一个包含操作函数的对象。{c,m,o,i,o,d}
if_block = create_if_block(ctx);
// 执行片段的 c,进行 dom 元素生成和属性挂载
if_block.c();
transition_in(if_block, 1);
// 执行片段的 m,进行 dom 插入
if_block.m(if_block_anchor.parentNode, if_block_anchor);
}
} else if (if_block) {
group_outros();
transition_out(if_block, 1, () => {
if_block = null;
});
check_outros();
}
}
// $set 其实也是调用 $$invalidate
$$self.$set = $$props => {
if ('name' in $$props) $$invalidate('name', name = $$props.name);
};
svelte 总结
如何提高工作效率?
// optimization
// 抽出 runtime,提升文件稳定性
runtimeChunk: {
name: 'runtime'
},
// webpack 会自动处理分割代码,包括初始化 initial 或异步 async 的
// splitchunks... chunks:“all”,
cacheGroups: {
// 从 main 里抽 lodash,优先级比 vender 高
lodash: {
test: (module) => {
return /lodash/.test(module.resource)
},
chunks: 'all', // 从全部代码里抽
name: 'lodash',
priority: 30,
},
// 从 main 里抽 moment,优先级比 vender 高
moment: {
test: (module) => {
return /moment/.test(module.resource)
},
chunks: 'all',
name: 'moment',
priority: 40,
},
// 剩余的全部 node_module 的代码都抽到一个 vender 里
vender: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
name: 'vender',
priority: 20,
enforce: true
},
// 精确抽取 entry 定义的 sdk chunk,这个代码如果不依赖其他库的话,可以直接执行
sdk: {
name: 'sdk',
priority: 10,
chunks:(chunk) {
return chunk.name === 'sdk';
}
},
// 抽去整个项目的所有css打到一起,通过非阻塞加载 css 文件
styles: {
name: 'styles',
test: /\.less|css$/,
chunks: 'all',
enforce: true
}
}
inline 注入 config 数据
构建完成后
携带应用名(唯一)参数,请求配置后台,配置后台返回计算好的版本号,配置后台也会更新应用版本号
根据版本号决定是否通知增量更新服务
把资源发到 cdn
// example
|--app
|--|--projectA
|-----|--components // 业务组件
|-----|--assets
|-----|--index.jsx
|-----|--style.module.scss
|-----|--routes.ts
|--|--projectB
|--|--projectC
|--components // 通用组件、通用业务组件
|--utils // 通用工具函数
|--assets // 通用图片、css、字体等
// dev
npm run dev
// build
npm run build --project projectA
架构设计
模式介绍
请求流程
接口设计
getPageConfig
组件设计
管理后台
分析流程
// 项目代码
// app.svelte
<script>
import { onMount } from 'svelte'
import Demo from './Demo.svelte'
export let name;
let count = 0
const handle = () => {
count += 1
}
// 可以写多个
onMount(() => console.log('mount'))
onMount(() => console.log('mount'))
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Helloza {name}! {count}</h1>
<button on:click={handle}>click</button>
<Demo msg={count} />
// demo.svelte
<script>
// export 表示,接收外表的 props
export let msg;
</script>
<h1>Hello {msg}!</h1>
// 编译阶段
// code is input,svelt-loader 进行对 svelte 文件编译
// 每个 svelte 文件,执行一次这个方法
// source 是文件的源码字符串
preprocess(source, ...).then(processed => {
// ...
compile(processed.toString(), compileoptions)
})
// 主要作用是根据 option 对 source 做预处理
async function preprocess(source, preprocessor, options) {
// 如果有 preprocessor, 根据 preprocessor 对 source 进行处理
// 返回一个对象
return {
code: source,
dependencies: ...,
toString() {
return source
}
}
}
function compile(source, options = {}) {
// 构建一个 stat 实例,主要是保存编译速度之类
// 对 source 进行 parse 为 ast
const ast = parse$2(source, options)
// 进行组件实例化,组件类里面处理了大量逻辑
component = new Component(ast, source, ...)
// 对组件进行浏览器端代码生成,区分 ssr/dom
// 通过 dom 函数处理,得到的是一个 function create_fragment(ctx){...} 函数的声明字符串
// 也就是说 js 是一段字符串,最后会将这段字符串与项目源码一起输出到 thunk 里
js = ssr ? ssr(component, options) : dom(component, options)
// 返回一个对象
res = component.generate(js)
return res
}
function parse$2(template, options = {}) {
// parser 实例化
const parser = new Parser$2(template, options)
// parser 对象结构为:
{
stack:[
{start, end, type, children}
],
css: [
{start,end,attributes,children, content}
],
js: [
{start, end, context, content}
],
html: {start, end, type, children}
meta_tags,
template,
filename,
customeElement,
}
// 返回一个对象
return {
html: parser.html,
css: parser.css[0],
instance: js context is default [0],
module: js context is module [0]
}
}
class Parser$2 {
// 对模版进行 ast 编译,分离 css/js/html 等。
}
// 很复杂
class Component {}
// 生成运行时代码片段
function dom(component, options) {
// 构造一个 renderer 实例
const renderer = new Renderer(component, options)
// 构造一个 builder 实例,之后完这个 builder 里添加各种代码片段
const builder = new CodeBuilder()
// 各种处理,往 builder 里插入代码字符串片段
...
// 返回这个 builder
return builder.toString()
}
//////////// dom 生成的函数片段 start /////////
// 根据实际情况,生产的片段可能不一样
function create_fragment(ctx) {
var h1, t0, t1, t2, t3, current;
var demo = new Demo({ props: { msg: "aa" } });
return {
c() {
h1 = element("h1");
t0 = text("Helloza ");
t1 = text([✂129 - 133✂]);
t2 = text("!");
t3 = space();
demo.$$.fragment.c();
attr(h1, "class", "svelte-i7qo5m");
dispose = listen(button, "click", [✂348-354✂])
},
m(target, anchor) {
insert(target, h1, anchor);
append(h1, t0);
append(h1, t1);
append(h1, t2);
insert(target, t3, anchor);
mount_component(demo, target, anchor);
current = true;
},
p(changed, ctx) {
if (!current || changed.name) {
set_data(t1, [✂129 - 133✂]);
}
},
i(local) {
if (current) return;
transition_in(demo.$$.fragment, local);
current = true;
},
o(local) {
transition_out(demo.$$.fragment, local);
current = false;
},
d(detaching) {
if (detaching) {
detach(h1);
detach(t3);
}
destroy_component(demo, detaching);
}
};
}
function instance($$self, $$props, $$invalidate) {
[✂10 - 60✂] // 是一个占位符???
$$self.$set = $$props => {
if ('name' in $$props) $$invalidate('name', name = $$props.name);
};
return { name };
}
class App extends SvelteComponent {
constructor(options) {
super();
// 执行 init 方法
init(this, options, instance, create_fragment, safe_not_equal, ["name"]);
}
}
//////////// dom 生成的函数片段 end /////////
res = component.generate(js)
// 的到的对象结构是:
{
js: {
code // 这里包含着我们自己也的代码,以及 dom 函数生成的运行时代码,就是上面的代码片段。最后这些代码经过 babal 编译后,输出到文件 zhunk 中
map // soucemap 实例
},
css:{
code // css 代码
map // soucemap 实例
},
ast, // parse 生成的 ast 对象
warnings: [],
// 代码里所有的声明
// eg. in demo.svelte file
// export let name
// <div>{name}</div>
vars: [
{
name: 'name',
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
writable: false
}
],
stat // 编译的统计
}
// 运行时流程
// 入口是
const app = new App({
target: document.body,
props: {name: 'hhha'}
})
// 编译后的 App 组件
new App extends SvelteComponent {
constructor(options) {
super();
// 执行 init 方法
init(this, options, instance, create_fragment, safe_not_equal, ["name"]);
}
}
// svelte 没有虚拟 dom,首次渲染时,从父一直往子插入到 html 文档中。更新阶段,直接更新 dom
function init(component, options, instance, create_fragment, not_equal$$1, prop_names) {
// 保存当前组件的实例
// 初始化 component.$$ 属性,往这个属性上挂载一些属性:
const $$ = component.$$ = {
fragment,
ctx,
props,
update,
bound,
on_mount: [],
on_destroy: [],
before_render: [],
after_render: [],
context,
callbacks,
dirty
}
// 执行 instance 函数,得到当前组件需要的 props, 挂到 ctx 上。这里需要注意 cb 参数,这个函数会将所有的反应数据进行包装。更新数据操作会执行这个 cb 函数。
$$.ctx = instance(component,prosp,cb) || props
// 执行 所有的 $$.before_render 数组里存的函数,首次 $$.before_render 数组 为空
// 执行 create_fragment 函数,拿到一个对象,可以看上面的例子
$$.fragment = create_fragment($$.ctx)
// 执行 $$.fragment.c(),该函数会创建当前组件的所有 dom 元素,挂载 html 属性到 dom 上,绑定组件的事件。同时执行子组件的 $$.fragment.c() 函数。子组件的实例有了吗?子组件的实例化在父组件执行 create_fragment 函数的时候,就进行实例创建了。这里其实是从父到子,一直执行各自的 create_fragment 函数。生成各自的 dom 和属性挂载。但还没有插入到 html 文档上。
// 执行顺序是:父创建dom -》子创建dom ... -〉挂载子属性 -》子事件绑定到 dom -》挂载父属性 -〉 父事件绑定到 dom
// 执行 mount_component 函数, 该函数会从父一直递归到子组件,把前面生成好的 dom + 属性 插入到父中。
// 流程是:父插入到 root -》子插入到父 ... 一直到完成为止。这时候所有的 dom 都插入到html 文档了。首次渲染相当于结束
mount_component(component, options.target, options.anchor)
// 更新操作,首次渲染基本可以忽略这个函数
flush()
}
// 看一下 init 函数的 instance 参数
// 编译后,有这个东西 [✂10-249✂],是原来代码的展位符??
function instance($$self, $$props, $$invalidate) {
let {name} = $$props
let count = 0
// 可以看出,事件触发 state 更新时,会先执行 $$invalidate 函数, 该函数会执行 make_dirty 方法,从而触发更新
const handle = () => {
$$invalidate('count', count += 1)
}
setTimeout(() => {
$$invalidate('count', count += 1)
}, 3000)
// 组件实例的 $set 方法, 某些方式的更新数据会用到这个方法。例如:调用子的 $set 方法,来更新子的 props
$$self.$set = $$props => {
if ('name' in $$props) $$invalidate('name', name = $$props.name)
}
return {name, count, handle}
}
// 首次渲染阶段,不断递归这个函数
function mount_component(component, target, anchor) {
// 执行 $$.fragment.m 函数。该函数会将之前创建好的 dom 插入到 html 文档上,然后递归执行 mount_component 函数。从父到子一直插入到 html 文档上
// 递归完后,执行 after_render 函数,该函数执行时,会执行该组件的 onMount 生命函数,svelte 的生命函数一个组件可以写多个。同时收集该组件的 onMount 函数返回值,等组件卸载时执行。可以用来卸载时清除副作用。
// onMount 函数执行顺序: 子组件 -》 父组件
}
// 首次渲染生命函数:
父 beforeUpdate -》子 beforeUpdate -》父 afterUpdate -》 子 afterUpdate -》 父 onMount -》子 onMount
// 更新阶段生命函数
父 beforeUpdate -》子 beforeUpdate -》子 afterUpdate -》子 onDestroy -》 父 afterUpdate -》父 onDestroy
// 更新阶段
// 每个反应式的属性都会被 $$invalidate 函数包裹:事件、ajax、异步 等
// $$invalidate 执行时,触发 make_dirty
function make_dirty(component, key) {
// 缓存需要更新的组件实例 push in dirty_components
// 执行 schedule_update
}
function schedule_update() {
// 异步执行 flush,在本次事件循环后紧跟的微任务队列中执行
}
function flush() {
// 如果有 dirty_components.length,循环拿到当前组件,循环执行 update 函数
// 如果有 binding_callbacks ,执行 binding_callbacks
// 如果有 render_callbacks,执行 render_callbacks
// 如果有 flush_callbacks,执行 flush_callbacks
}
function update($$) {
// 执行生命函数 before_render
// 执行 $$.fragment.p 函数,更新所有的的 state
$$.fragment.p($$.dirty, $$.ctx)
// 收集 after_render
}
// 每次更新所有的反应式数据
// 同时更新子组件的 props
p(changed, ctx) {
if (!current || changed.name) {
// 更新数据
set_data(t1, [✂280-284✂]);
}
if (!current || changed.count) {
set_data(t3, [✂288-293✂]);
}
if (!current || changed.hi) {
set_data(t5, [✂296-298✂]);
}
var demo_changes = {};
// 更新子的 props
if (changed.count) demo_changes.msg = [✂357-362✂];
demo.$set(demo_changes);
// 当有显示隐藏某些片段时,会执行该片段的 p/c/m 函数
if ([✂706-719✂]) {
if (if_block) {
// 执行片段的 p,进行数据更新或片段更新,这是一个递归操作。不断对片段里的片段或数据进行更新
if_block.p(changed, ctx);
transition_in(if_block, 1);
} else {
// create_if_block 这个函数很重要。当有 if/else 的模版逻辑时。都会编译生成这个函数代码。所以编译后的代码量会很大。这个函数的逻辑与 create_fragment 函数的逻辑相似,该很熟生成子组件的实例。然后返回一个包含操作函数的对象。{c,m,o,i,o,d}
if_block = create_if_block(ctx);
// 执行片段的 c,进行 dom 元素生成和属性挂载
if_block.c();
transition_in(if_block, 1);
// 执行片段的 m,进行 dom 插入
if_block.m(if_block_anchor.parentNode, if_block_anchor);
}
} else if (if_block) {
group_outros();
transition_out(if_block, 1, () => {
if_block = null;
});
check_outros();
}
}
// $set 其实也是调用 $$invalidate
$$self.$set = $$props => {
if ('name' in $$props) $$invalidate('name', name = $$props.name);
};
总结
@babel/parser
parser -> AST
-> transformer[s] -> AST
-> @babel/generator
-> output string@babel/parser
@babel/core
修改 ast,增删改
const babel = require('babel-core')
babel.transformFile('./demo.js', {
presets: [],
plugins: []
}, function(err, result) {
console.log(result)
})
@babel/traverse
@babel/generator
@babel/cli
@babel/types
@babel/polyfill
core-js 和 regenerator-runtime 的封装
用于模拟完整的 es2015+ 的环境(不包括第四阶段的提议)
在入口顶部引入
建议在应用中使用,而不是在库开发中使用
@babel/polyfill 是一个大而全的方案,除非是不考虑体积性能的项目,一般不建议使用。
可以使用 @babel/preset-env useBuildIn 来代替,或者从 core-js 中手动引入需要的 polyfill
babel 7.4 不建议使用了,直接用 core-js 和 regenerator-runtime
import "core-js/stable";
import "regenerator-runtime/runtime";
@babel/runtime
包含 Babel modular runtime helpers 和 regenerator-runtime
作为 @babel/plugin-transform-runtime 的 runtime 依赖。
简单来说:
babel 会在源码中插入一些 helper 来解决兼容性问题
@babel/plugin-transform-runtime 能识别这些 helper,然后改成从 @babel/runtime 里引入的方式,来达到减少代码体积
// code
class Circle {}
// turn into
function _classCallCheck(instance, Constructor) {
// this is a helper func
}
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
// use @babel/plugin-transform-runtime
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck"); // helper from babel-runtime
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
将 babel 插入到源码中的一些 helper 提取到一个独立的模块中引入,以减少代码重复来减少 polyfill 的体积
同时还可以避免某些全局污染,例如:promise、set、map 等,@babel/runtime 会将他们重新命名,避免全局污染
具体做了什么?
@babel/runtime/regenerator
当您使用生成器/异步功能(可通过该regenerator
选项切换)时自动需要。corejs
选项切换)@babel/runtime/helpers
中引入对应模块(可通过该helpers
选项切换)。 {
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false, // 推荐 corejs 3
"helpers": true, // 通过独立模块来替换 inline 的 helper
"regenerator": true, // 修改名称,避免全局命名污染
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
}
// 代码调用
require("@babel/core").transform("code", {
plugins: ["@babel/plugin-transform-runtime"],
})
// corejs option
false npm install --save @babel/runtime
2 npm install --save @babel/runtime-corejs2
3 npm install --save @babel/runtime-corejs
@babel/runtime-corejs-2 和 @babel/runtime-corejs-3 有什么不同?
@babel/register
实时编译,适用于开发阶段,多用在 node 应用,做实时编译
如何使用?
// register.js
require('babel-register)
require('./my-demo.js')
// run:
node register.js
// 或者 npm script
'build': 'babel-node register.js'
babel-node vs babel-cli
@babel/template
@babel/helpers
@babel/core-frame
preset 是 plugin 的集合
@babel/preset-env
@babel/preset-react
@babel/preset-typescript
@babel/preset-flow
如何开发自己的 preset?
module.exports = () => ({
presets: [
require("@babel/preset-env"),
],
plugins: [
[require("@babel/plugin-proposal-class-properties"), { loose: true }],
require("@babel/plugin-proposal-object-rest-spread"),
],
});
preset 的执行顺序?
{
"presets": [
"env"
],
"plugins": [
["transform-runtime", {
"helpers": true,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
// input
class A {
constructor(name) {
this.name = name
console.log(new.target)
}
get() {
return 'a'
}
static say() {
return 'hi'
}
}
Promise.resolve().then(() => {
console.log(1)
}).finally(() => {
console.log(2)
});
[1,2].map(item => item + 1);
'x'.padStart(2, 'a');
let m = new Map();
let s = new Set();
[1,2].includes(item => item === 1);
// demo 1:usage + runtime-corejs3
module.exports = {
presets: [
['@babel/preset-env', {
'debug': false,
'useBuiltIns': 'usage',
'modules': false,
targets: {
"node": "current",
browsers: ['last 2 versions']
}
}],
],
plugins: [
["@babel/plugin-transform-runtime", {
corejs: 3 ,
helpers: true
}],
]
}
// output
import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
import _Set from "@babel/runtime-corejs3/core-js-stable/set";
import _Map from "@babel/runtime-corejs3/core-js-stable/map";
import _padStartInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/pad-start";
import _mapInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/map";
import _Promise from "@babel/runtime-corejs3/core-js-stable/promise"; // 有重复引入
import "core-js/modules/es6.promise";
import "core-js/modules/es6.object.to-string";
import "core-js/modules/es7.promise.finally";
import "core-js/modules/es6.function.name";
import _classCallCheck from "@babel/runtime-corejs3/helpers/classCallCheck";
import _createClass from "@babel/runtime-corejs3/helpers/createClass";
var _context, _context2, _context3;
var A =
/*#__PURE__*/
function () {
function A(name) {
_classCallCheck(this, A);
this.name = name;
console.log(this instanceof A ? this.constructor : void 0);
}
_createClass(A, [{
key: "get",
value: function get() {
return 'a';
}
}], [{
key: "say",
value: function say() {
return 'hi';
}
}]);
return A;
}();
_Promise.resolve().then(function () {
console.log(1);
}).finally(function () {
console.log(2);
});
_mapInstanceProperty(_context = [1, 2]).call(_context, function (item) {
return item + 1;
});
_padStartInstanceProperty(_context2 = 'x').call(_context2, 2, 'a');
var m = new _Map();
var s = new _Set();
_includesInstanceProperty(_context3 = [1, 2]).call(_context3, function (item) { // 实例方法兼容
return item === 1;
});
// 总结
1、存在重复引入
2、从 core-js 和 @babel/runtime-corejs3 中引入
3、可以对实例方法进行 helper
// demo2: usage + runtime-corejs2
module.exports = {
presets: [
['@babel/preset-env', {
'debug': false,
'useBuiltIns': 'usage',
'modules': false,
targets: {
"node": "current",
browsers: ['last 2 versions']
}
}],
],
plugins: [
["@babel/plugin-transform-runtime", {
corejs: 2 ,
helpers: true
}],
]
}
// output
import "core-js/modules/es7.array.includes";
import "core-js/modules/es6.string.includes";
import _Set from "@babel/runtime-corejs2/core-js/set";
import _Map from "@babel/runtime-corejs2/core-js/map";
import "core-js/modules/es7.string.pad-start";
import _Promise from "@babel/runtime-corejs2/core-js/promise";
import "core-js/modules/es6.promise";
import "core-js/modules/es6.object.to-string";
import "core-js/modules/es7.promise.finally";
import "core-js/modules/es6.function.name";
import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck";
import _createClass from "@babel/runtime-corejs2/helpers/createClass";
var A =
/*#__PURE__*/
function () {
function A(name) {
_classCallCheck(this, A);
this.name = name;
console.log(this instanceof A ? this.constructor : void 0);
}
_createClass(A, [{
key: "get",
value: function get() {
return 'a';
}
}], [{
key: "say",
value: function say() {
return 'hi';
}
}]);
return A;
}();
_Promise.resolve().then(function () {
console.log(1);
}).finally(function () {
console.log(2);
});
[1, 2].map(function (item) {
return item + 1;
});
'x'.padStart(2, 'a');
var m = new _Map();
var s = new _Set();
[1, 2].includes(function (item) {
return item === 1;
});
// 总结
1、重复引入
2、从 core-js 和 @babel/runtime-corejs2 中引入
3、@babel/runtime-corejs2 无法实现实例方法兼容
4、core-js 可以实例方法兼容
// demo 3: usage + @babel/runtime
module.exports = {
presets: [
['@babel/preset-env', {
'debug': false,
'useBuiltIns': 'usage',
'modules': false,
targets: {
"node": "current",
browsers: ['last 2 versions']
}
}],
],
plugins: [
["@babel/plugin-transform-runtime", { // 默认从 @babel/runtime 里导入
helpers: true
}],
]
}
// output
import "core-js/modules/es7.array.includes";
import "core-js/modules/es6.string.includes";
import "core-js/modules/es6.set";
import "core-js/modules/web.dom.iterable";
import "core-js/modules/es6.array.iterator";
import "core-js/modules/es6.string.iterator";
import "core-js/modules/es6.map"; // 直接覆盖内置类型
import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es6.promise";
import "core-js/modules/es6.object.to-string";
import "core-js/modules/es7.promise.finally";
import "core-js/modules/es6.function.name";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; // 从 @babel/runtime 里导入
import _createClass from "@babel/runtime/helpers/createClass";
var A =
/*#__PURE__*/
function () {
function A(name) {
_classCallCheck(this, A);
this.name = name;
console.log(this instanceof A ? this.constructor : void 0);
}
_createClass(A, [{
key: "get",
value: function get() {
return 'a';
}
}], [{
key: "say",
value: function say() {
return 'hi';
}
}]);
return A;
}();
Promise.resolve().then(function () {
console.log(1);
}).finally(function () {
console.log(2);
});
[1, 2].map(function (item) {
return item + 1;
});
'x'.padStart(2, 'a');
var m = new Map();
var s = new Set();
[1, 2].includes(function (item) {
return item === 1;
});
// 总结
1、覆盖内置类型
2、从 core-js 中引入
// demo 4: 'useBuiltIns': false + babel/runtime-corejs3
module.exports = {
presets: [
['@babel/preset-env', {
'debug': false,
'useBuiltIns': false,
'modules': false,
targets: {
"node": "current",
browsers: ['last 2 versions']
}
}],
],
plugins: [
["@babel/plugin-transform-runtime", {
corejs: 3 ,
helpers: true
}],
]
}
// output
import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
import _Set from "@babel/runtime-corejs3/core-js-stable/set";
import _Map from "@babel/runtime-corejs3/core-js-stable/map";
import _padStartInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/pad-start";
import _mapInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/map";
import _Promise from "@babel/runtime-corejs3/core-js-stable/promise";
import _classCallCheck from "@babel/runtime-corejs3/helpers/classCallCheck";
import _createClass from "@babel/runtime-corejs3/helpers/createClass";
var _context, _context2, _context3;
var A =
/*#__PURE__*/
function () {
function A(name) {
_classCallCheck(this, A);
this.name = name;
console.log(this instanceof A ? this.constructor : void 0);
}
_createClass(A, [{
key: "get",
value: function get() {
return 'a';
}
}], [{
key: "say",
value: function say() {
return 'hi';
}
}]);
return A;
}();
_Promise.resolve().then(function () {
console.log(1);
}).finally(function () {
console.log(2);
});
_mapInstanceProperty(_context = [1, 2]).call(_context, function (item) {
return item + 1;
});
_padStartInstanceProperty(_context2 = 'x').call(_context2, 2, 'a');
var m = new _Map();
var s = new _Set();
_includesInstanceProperty(_context3 = [1, 2]).call(_context3, function (item) {
return item === 1;
});
// 总结
1、全部从 runtime-corejs3 中导出
2、无覆盖,无命名冲突
3、没有 useBuiltIns 也能插入 helper???
版本:5.2.0
使用/接口设计
<Router>
<Switch>
<Route exact path="/"> <Home /> </Route>
<Route path="/about"> <About /> </Route>
<Route path="/list"> <List /> </Route>
<Route path="*">
<Redirect
to={{
pathname: "/404",
state: { msg: 'notfount' }
}}
/>
</Route>
</Switch>
</Router>
<Switch>
<Route path="/" exact children={<Home />} />
<Route path="/about" children={<About/ >} />
<Route path="/list" children={<List />} />
<Route
path="/about"
render={props => (
<About {...props} routes={about.routes} /> // routes 子路由嵌套
)}
/>
</Switch>
// router
BrowserRouter
HashRouter
// 或者使用 Router + custom history
// hook
let history = useHistory()
let location = useLocation()
let match = useRouteMatch({
path: to,
exact: activeOnlyWhenExact
})
let {id} = useParams()
// hoc
withRouter
// 切换
<Link to="/about">about</Link>
history.replace()
history.push()
// Switch 组件,只显示一个路由组件
源码阅读
// Router 组件
props {
history
children
}
class Router extends Component {
contructor(props) {
super(props)
this.state = {
location: props.hisory.location // 通过 location 来区分视图
}
this.unlisten = props.history.listen(location => {
this.setState({location})
})
}
render() {
// 将一些值通过 context 传给 Route 组件
return (
<RouterContext.Provider value={{history, location, match, state, staticContext}}>
<HistoryContext.Provider value={{history}}>
{this.props.children}
</HistoryContext.Provider>
</RouterContext.Provider>
)
}
}
// Switch 组件
props {
location
children
}
class Switch extends Component {
render () {
return (
<RouterContext.Consumer>
{context => {
// 返回匹配到的组件,通过 matchPath 函数匹配 props.children 里 path 一致的组件
}}
</RouterContext.Consumer>
)
}
}
// Route 组件
props {
path
exact
location
strict
sensitive
render // render props
children // 子组件
component
}
class Route extends Component {
render() {
return (
<RouterContext.Consumer>
{context => {
// ...
return (
<RouterContext.Provider value={props}> // props 是 context,location,match 的合并
// 子组件是:
component/children/children(props)/render/render(props)
</RouterContext.Provider>
)
}}
</RouterContext.Consumer>
)
}
}
// Rdirect 组件
在组件 didmount/didUpdate 的时候进行路由切换
通过 push/replace 来进行
render null
// hook
// 都是通过 historyContext, RouterContext 上获取 context 的值来实现的
function useHistory() {
return useContext(HistoryContext);
}
了解一下 useContext
const MyContext = createContext(null)
// provider, parent
<MyContext.Provider value={data}>
<Demo />
</MyContext.Provider>
// 1、consumer, children, Demo
const data = useContext(MyContext)
// 2、consumber
<MyContext.Consumer>
{data => ...}
</MyContext.Consumer>
// 简单例子:通过 context 来实现一个状态管理
const StoreContext = createContext(null)
const [count, set] = useState(0)
const state = initState
const updateState = (nextState) => {
Object.assign(state, nextState)
setState(count+1)
}
}
<StoreContext.Provider value={{state, updateState}}>
<Demo />
</StoreContext.Provider>
const {state, updateState} = useContext(StoreContext)
$$typeof: Symbol(react.element)
key: null
props:
children:
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
_store: {validated: true}
_self: null
_source: {fileName: "/Users/renjiazheng/Jar/jar-github/read-source/read-source/src/index.js", lineNumber: 9, columnNumber: 5}
__proto__: Object
__proto__: Object
ref: null
type: Symbol(react.strict_mode)
_owner: null
_store: {validated: false}
const Demo = (props) => {
return (<div className="demo">demo, {props.data}</div>)
}
const App = () => {
return (
<div className="app">
<Demo data={'hi'} />
</div>
)
}
// ==> jsx to react element
var Demo = function Demo(props) {
// type: html tag/comp/内建组件
// props: props/config/ref/key 等
// children: 后面的参数都是 children
return React.createElement("div", {className: 'demo'}, "demo, ", props.data);
};
var App = function App() {
return React.createElement("div", {className: 'app'}, React.createElement(Demo, {
data: 'hi'
}));
// 所有的 jsx 都会被转成 createElement,最终导出的就只有一个 App 函数,进入 render
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
this.current = null; // fiber tree 的根节点
this.containerInfo = containerInfo; // root dom
this.hydrate = hydrate; // 是否要与以后的 dom 融合
this.pendingChildren = null;
this.pingCache = null;
this.finishedExpirationTime = NoWork;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.callbackNode = null;
this.callbackPriority = NoPriority;
this.firstPendingTime = NoWork;
this.firstSuspendedTime = NoWork;
this.lastSuspendedTime = NoWork;
this.nextKnownPendingLevel = NoWork;
this.lastPingedTime = NoWork;
this.lastExpiredTime = NoWork;
this.interactionThreadID = tracing.unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
function FiberNode(tag, pendingProps, key, mode) {
this.tag = tag; // 不同的组件类型
this.key = key; // react element 的 key
this.elementType = null; // react element 的 type
this.type = null; // 异步组件resolved之后返回的内容,一般是`function`或者`class`
this.stateNode = null; // 跟当前Fiber相关本地状态(比如浏览器环境就是DOM节点)
this.return = null; // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
this.child = null; // 指向自己的第一个子节点
this.sibling = null; // 兄弟节点的return指向同一个父节点
this.index = 0;
this.ref = null; // ref
this.pendingProps = pendingProps; // 新的 props
this.memoizedProps = null; // 旧的 props
this.memoizedState = null; // 新旧 state
this.updateQueue = null; // 该Fiber对应的组件产生的Update会存放在这个队列里面
this.dependencies = null;
this.mode = mode; // Effects 表示这个子树是否默认是异步渲染的
this.effectTag = NoEffect; // 用来记录Side Effect
this.nextEffect = null; // 用来快速查找下一个side effect
this.firstEffect = null; // 第一个side effect
this.lastEffect = null; // 最后一个side effect
this.expirationTime = NoWork; // 过期时间
this.childExpirationTime = NoWork; // 快速确定子树中是否有不在等待的变化
this.alternate = null; // `current <==> workInProgress` 在渲染完成之后他们会交换位置,类似于备份,方便重用
}
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class,未知类型
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5; // html tag
export const HostText = 6; // text
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const NoEffect = /* */ 0b00000000000;
export const PerformedWork = /* */ 0b00000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b00000000010; // 插入/移到
export const Update = /* */ 0b00000000100; // props 更新
export const PlacementAndUpdate = /* */ 0b00000000110;
export const Deletion = /* */ 0b00000001000;
export const ContentReset = /* */ 0b00000010000;
export const Callback = /* */ 0b00000100000;
export const DidCapture = /* */ 0b00001000000;
export const Ref = /* */ 0b00010000000;
export const Snapshot = /* */ 0b00100000000;
// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b00110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b00111111111;
export const Incomplete = /* */ 0b01000000000;
export const ShouldCapture = /* */ 0b10000000000;
// 通过链表来存储所有的 update
export type Update<State> = {
// 更新的过期时间
expirationTime: ExpirationTime,
// export const UpdateState = 0;
// export const ReplaceState = 1;
// export const ForceUpdate = 2;
// export const CaptureUpdate = 3;
// 指定更新的类型,值为以上几种
tag: 0 | 1 | 2 | 3,
// 更新内容,比如`setState`接收的第一个参数,函数或者一个 state 对象
payload: any,
// 对应的回调,`setState`,`render`都有
callback: (() => mixed) | null,
// 指向下一个更新
next: Update<State> | null,
// 指向下一个`side effect`
nextEffect: Update<State> | null,
};
// 一个普通的对象
export type UpdateQueue<State> = {
// 每次操作完更新之后的`state`,当更新阶段,basestate 存的是 state
baseState: State,
// 队列中的第一个`Update`
firstUpdate: Update<State> | null,
// 队列中的最后一个`Update`
lastUpdate: Update<State> | null,
// 第一个捕获类型的`Update`
firstCapturedUpdate: Update<State> | null,
// 最后一个捕获类型的`Update`
lastCapturedUpdate: Update<State> | null,
// 第一个`side effect`
firstEffect: Update<State> | null,
// 最后一个`side effect`
lastEffect: Update<State> | null,
// 第一个和最后一个捕获产生的`side effect`
firstCapturedEffect: Update<State> | null,
lastCapturedEffect: Update<State> | null,
};
render(element, container, callback)
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback)
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate)
封装 render 传入的 callback
updateContainer(children, fiberRoot, parentComponent, callback)
scheduleWork(current$1, expirationTime) ,即执行 scheduleUpdateOnFiber 函数
performSyncWorkOnRoot(root),同步的任务处理,还有 performConcurrentWorkOnRoot 模式的
workLoopSync(),执行该函数直到完成,执行不同组件的初始化,生成 fiber node
函数组件执行
,这里可能多次执行组件实例化
getDerivedStateFromProps
componentWillMount
事件绑定
,ensureListeningTo(rootContainerElement, propKey)
finishSyncRender,对 fiber node 生成 dom 片段,插入 dom
commitRoot,commitRootImpl,
commitBeforeMutationEffects
commitMutationEffects
commitLayoutEffects,执行生命周期函数
commitLifeCycles
flushSyncCallbackQueue,执行 render 传入的 callback
总结
setstate
var fiber = get(inst),根据 instance 找到对应的 fiber node
enqueueSetState,创建一个 update,将 update 存到链表中的 sharedQueue.pending
scheduleWork,即 scheduleUpdateOnFiber,进入循环更新
// 为更新创建对应的 update 对象
callback: null
expirationTime: 1073741823 // 过期时间
next: // 通过链表链接其他的 update
callback: null
expirationTime: 1073741823
next: {expirationTime: 1073741823, suspenseConfig: null, tag: 0, payload: {…}, callback: null, …}
payload: {count: 3} // setstate({count: 3})
priority: 98
suspenseConfig: null
tag: 0
__proto__: Object
payload: {count: 3} // setstate 的 state
priority: 98 // 优先级
suspenseConfig: null
tag: 0
// fiber 上的 updatequeue 对象
updateQueue:
baseState: {count: 1} // 最初的 state,更新后的 state 会更新这里
effects: null
baseQueue: { // 已处理的 update 链
callback: null
expirationTime: 1073741823
next: {…}
payload: {count: 2}
priority: 97
suspenseConfig: null
tag: 0
}
shared: {
pending: { // shared.pending,等待处理的 update
callback: null
expirationTime: 1073741823
next: {
callback: null
expirationTime: 1073741823
next: {expirationTime: 1073741823, suspenseConfig: null, tag: 0, payload: {…}, callback: null, …}
payload: {count: 3} // 第一个 setstate
priority: 98
suspenseConfig: null
tag: 0
}
payload: {count: 4} // 并列的第二个 setstate,会批量更新
priority: 98
suspenseConfig: null
tag: 0
}
}
只有一个节点时,update 对象的 next 执行自身。循环链表
payload,setstate 传入的对象
当连续 setstate 时
正常情况:
事件循环情况:
当 setState 被推到事件循环中时,由于事件循环的机制,所有的任务都是分开执行的,所以每个 setstate 会执行一次调度更新
const handle = () => {
// 两个 setTimeout 的任务是在事件循坏中分开执行的,当事件 handle 执行完后,不会有调度更新,当一个 settimeout task 执行时,会进行一次更新
setTimeout(() => {
// 里面的 setstate 也是逐个更新的,不是批量的,为什么?因为 excutionContext 是 nocontext。默认情况下就是每个更新动作都会触发一次调度更新。react 针对合成事件/生命函数进行了优化,阻止了调度更新。把调度更新移到了合成事件/生命函数执行完之后。使得一次合成事件执行,里面即使有多个更新动作,也只在最后才触发一次调度更新。
this.setState({
count: 0
})
this.setState({
count: 1
})
}, 0)
setTimeout(() => {
this.setState({
count: 0
})
}, 0)
}
forceupdate
replaceState
useState
总结
触发更新动作,创建 update 存到对应的 fiber.updateQueue.shared.pending.payload 上,当多个更新触发时,会通过 next 指针形成链表。
每个 update 都是由更新动作触发而创建的。forceupdate/replacestate/setstate/usestate 的调用都会转化为一个个 update 对象,最后形成 update 链表
多个 setState 触发会如何?
事件是如何绑定的?
p 标签上有 onclick 事件,p 标签的父是 div
在 div 对应的 fiber 调和子节点的时候生成 p 的 fiber,p 的事件还存在 p fiber.pendingProps 上。beginwork 完成后会将 fiber.pendingProps 上的属性存到 fiber.memoizedProps 上。即事件回调存到了 fiber.memoizedProps 上
在 completeWork 时,创建 fiber.stateNode。然后在 updateFiberProps 函数中 fiber.memoizedProps 赋给 fiber.stateNode
// 事件回调存在 fiber.stateNode 上,不管是函数组件还是类组件
fiber.stateNode: {
handleClick: f,
handleChange: f,
}
渲染阶段,在 finalizeInitialChildren 函数里进行
setInitialProperties,对某些标签的事件进行绑定,如:load,error,reset,submit,toggle,invalid 等,对表单元素会做很多额外的封装
setInitialDOMProperties,设置 dom style,innerhtml,innertext,通过 ensureListeningTo 进行事件绑定等。这里的事件绑定是代理到 dom root/document 的
ensureListeningTo,除了绑定对应的事件,还有其他相同组的事件,
如:绑定的是 onchange,但 react 同时还会绑定 ["blur", "change", "click", "focus", "input", "keydown", "keyup", "selectionchange"]
react 会将事件回调进行封装。比如,onchange = () => {console.log('hi')}。这个事件回调是存在 fiber.stateNode 上的
在事件绑定的时候,通过 dispatchDiscreteEvent 返回事件回调。即事件回调是被 dispatchDiscreteEvent 封装了
// null,click,1,body
listener = dispatchDiscreteEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM, container)
discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, container, nativeEvent)
dispatchEvent
var blockedOn = attemptToDispatchEvent(topLevelType, eventSystemFlags, container, nativeEvent); // 回去对应的 dom 实例,即 fiber.stateNode
queueDiscreteEvent(blockedOn, topLevelType, eventSystemFlags, container, nativeEvent)
discreteUpdates
事件是如何执行的?
const App = () => {
const [a, seta] = useState(11) // hook 1
const ref = useRef('ref') // hook 2
const [b, setb] = useState(22) // hook 3
useEffect(() => { // hook 4
console.log('effect')
return () => {
console.log('clean')
}
}, [])
return (<div>app</div>)
}
// 当前 fiber node
...
elementType: ƒ App()
memoizedState: {
baseQueue: null
baseState: 11
memoizedState: 11 // hook 1
next: {
baseQueue: null
baseState: null
memoizedState: {current: div.App} // hook 2
next: {
baseQueue: null
baseState: 22
memoizedState: 22 // hook 3
next: { // hook 4, effect hook 没有 memoizedState
create: () => {…} // effect
deps: null
destroy: () => { console.log('clean'); } // effect cleanup
next: {tag: 5, deps: null, next: {…}, create: ƒ, destroy: ƒ}
tag: 5
}
queue: {
dispatch: ƒ () // setb
lastRenderedReducer: ƒ basicStateReducer(state, action)
lastRenderedState: 22
pending: null
}
}
queue: null
}
}
queue: {
dispatch: ƒ () // seta
lastRenderedReducer: ƒ basicStateReducer(state, action)
lastRenderedState: 11
pending:
}
}
hook 是如何执行的?
useState
mountState(initialState)
创建一个空的 hook 对象,计算 initialstate,因为有可能是函数,将 state 存到 hook.baseState 上,同时初始化 hook.queue, 绑定 hook.dispatcher
hook: {
baseQueue: null
baseState: null
memoizedState: null
next: null
queue: null
}
hook.memoizedState = hook.baseState = initialState
hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
}
dispatch = hook.queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue)
// [a, seta] = useState(1)
return [hook.memoizedState, dispatch]
dispatchAction
seta(33)
dispatchAction(fiber, queue, action)
// 参数:
action: 33
queue: {
dispatch: ƒ ()
lastRenderedReducer: ƒ basicStateReducer(state, action)
lastRenderedState: 11
pending: {
action: 33
eagerReducer: ƒ basicStateReducer(state, action)
eagerState: 33
expirationTime: 1073741823
next: {expirationTime: 1073741823, suspenseConfig: null, action: 33, eagerState: 33, eagerReducer: ƒ, …}
priority: 98
}
}
然后创建一个 update
接着开始调度更新:scheduleWork(fiber, expirationTime) // 所有更新最终都是结果 scheduleWork 来调度
useRef
mountRef(initialValue)
创建一个空的 hook 对象,创建 ref 对象,将 ref 对象存到 hook 上
hook:{
baseQueue: null
baseState: null
memoizedState: null
next: null
queue: null
}
ref: {
current: initialvalue
}
hook.memoizedState = ref
useEffect
mountEffect(create, deps)
mountEffectImpl(Update | Passive, Passive$1, create, deps)
创建一个 hook,标记 effectTag = fiberEffectTag,将 effect 挂到 hook.memoizedState 上
hook: {
baseQueue: null
baseState: null
memoizedState: {tag: 5, destroy: undefined, deps: null, next: {…}, create: ƒ}
next: null
queue: null
}
useCallback
useContext
readContext(callback, deps)
// 首个 context
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = { // 在 fiber dependencies 上存储 context 信息
expirationTime: NoWork,
firstContext: {
context: context,
observedBits: deps,
next: null
},
responders: null
};
// 新增的 context
lastContextDependency = lastContextDependency.next = {
context: context,
observedBits: deps,
next: null
}
useLayoutEffect
mountLayoutEffect(create, deps)
mountEffectImpl
mountEffectImpl(Update | Passive, Passive$1, create, deps) // useEffect
mountEffectImpl(Update, Layout, create, deps) // useLayoutEffect
useReducer
当组件多长执行时,只是重复创建 hook ,相当于 hook 的初始化,不会进行 hook 的执行。hook 还没有和 fiber 相关联
但 seta 的时候,会进行 dispatch 当前的 fiber 和当前的 hook.queue
react element
$$typeof: Symbol(react.element)
key: null
props: {className: "App", children: Array(3)}
ref: {current: "hiref"}
type: "div"
_owner: FiberNode {tag: 2, key: null, stateNode: null, elementType: ƒ, type: ƒ, …}
_store: {validated: false}
class DemoCls extends React.Component {
state = {
count: 1
}
handle = () => {
this.setState({
count: 3
})
}
render() {
return (
<div onClick={this.handle}>class, {this.state.count}</div>
)
}
}
function App() {
const [a, seta] = useState(11)
const ref = useRef('hiref')
const [b, setb] = useState(22)
useEffect(() => {
console.log('effect')
return () => {
console.log('clean')
}
})
const handle = () => {
console.log(ref)
}
const handleChange = () => {
console.log('input')
}
const handleFocus = () => {
console.log('focus')
}
return (
<div>
<p
onClick={handle}
style={{color: '#fff'}}
className="test"
id="test"
key="test"
ref={ref}
>
say <span>hi</span> haaa!
</p>
<input
id="inpuid"
onFocus={handleFocus}
onChange={handleChange}
/>
<DemoCls />
</div>
);
}
render(<App />, root)
fiberRoot {
containerInfo: 指向 dom root
current: 指向 root fiber,fiber tree 的根节点
}
hostFiber { // fiber tree 的根节点
alternate: 自身
stateNode: 指向 fiberRoot
type: null
child: 指向 App 的 fiber
}
App fiber {
alternate: 指向自身
stateNode: null
type: f App() // 组件
elemenType: f App() // 组件
return: 指向 host fiber
sibling:null
child:指向 div fiber
firstEffect: 需要更新的 fiber
lastEffect: 需要更新的 fiber
nextEffect: null
memoizedProps: {}
memoizedState: {
baseQueue: null
baseState: 11
memoizedState: 11
queue: {
dispatch: f // seta
lastRenderedReducer: f
lastRenderedState: 11
pending: null
}
next: {
baseQueue: null
baseState: null
memoizedState: {
current: f
}
queue: null
next: {
baseQueue: null
baseState: 22
memoizedState: 22
queue: {
dispatch: f // setb
lastRenderedReducer: f
lastRenderedState: 22
pending: null
}
next: {
baseQueue: null
baseState: null
memoizedState: {
create: f
deps: null
destroy: f
next: {}
}
queue: null
}
}
}
}
pendingProps: {}
ref: null
updateQueue: {
lastEffect: { // useEffect
create: () => {...}
deps: null
destroy: () => {...} // clean up
next: {...} // next useEffect
}
}
}
div fiber {
alternate: 自身
stateNode: dom div
type: "div"
elementType: "div"
return: 指向 app fiber
sibling:null
child:指向 p fiber,第一个 child
ref: null
key: null
firstEffect: 需要更新的 fiber
lastEffect: 需要更新的 fiber
nextEffect: null
memoizedProps: 指向 children 组件,react element,不是 fiber,3个 children。p、input、democl
memoizedState: null
prndingProps: 指向 children 组件,react element,不是 fiber,3个 children
}
p fiber {
alternate: 自身
stateNode:dom p
type: "p"
elementType: "p"
return: 指向 div fiber
sibling:指向 input fiber
child:指向 say fiber
ref:{current: f}
key: "test"
fistEffect: null
lastEffect: null
nextEffect: null
memoizedProps: {
children: ["say", {...}, "haaa!"] // react element
className: "test"
id: "test"
onClick: f
style: {color: "#fff"}
},
memoizedState: null
pendingProps: {
children: ["say", {...}, "haaa!"] // react element
className: "test"
id: "test"
onClick: f
style: {color: "#fff"}
}
}
say fiber {
alternate: null
stateNode:text say
type: null
elementType: null
return: 指向 p fiber
sibling:指向 span fiber
child:null
ref:null
key: null
fistEffect: null
lastEffect: null
nextEffect: null
}
...
Democls fiber {
alternate: 自身
stateNode: Democls fiber
type: class Democls
elementType: class Democls
return: 指向 div fiber
sibling: null
child: 指向 div fiber
memoizedState: {count: 1}
memoizedProps: {}
...
updateQueue: {
baseQueue: {
callback: null
next: {...}
pauload: {count: 2} // 还没处理的 update
}
baseState: {count: 1} // 已完成的 update
effects: null
shared: {pending: null}
}
}
递归流程
渲染流程
流程回顾
// 渲染阶段
scheduleUpdateOnFiber
performSyncWorkOnRoot
workLoopSync
performUnitOfWork
beginWork // 通过父 fiber 生成 child element 相应的 fiber,循环完,最终会生成整颗 fiber tree
/updateFunctionComponent // 函数组件执行,hook dispatcher 绑定
/updateClassComponent // 类组件执行,相关生命函数执行
/updateHostRoot
/updateHostComponent
/updateHostText
reconcileChildren // child element to fiber
completeUnitOfWork
completeWork // 给每个 fiber 创建 dom 实例,同时存到对应 fiber.stateNode。循环将子的 dom 实例插入到父的 dom 实例中。然后初始化每个 fiber.statenode 的 dom 属性、事件等
finishSyncRender // 如果是组件,执行相关的生命函数、hook。将 hostfiber.stateNode 插入到 dom root。最后执行 callback
hastFiber,对 updateQueue.shared.pending.payload 上的 react element(即 app 组件) 生成对应的 fiber
然后通过 app fiber 去 reconcileChildren 调合 children element 的到 children fiber。最终完成整颗 fiber tree
在 completeWork 的时候将 instance 存到 fiber.stateNode
fiber tree 创建后,会进行 finishSyncRender 生成每个 fiber 对应的 stateNode
整体流程分为:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.