Previous articles:
- Vue3-vuex-typescript trampling tour
preface
Vue3 is finally here. It’s a matter of opinion whether it works or not, but for now, I prefer react
Reasons for favoring React (personal opinion only)
- 1. React has perfected hooks; Vue3 borrows from the React hooks, but not completely
- 2.Fiber scheduling mechanism, excellent one
- 3. Vue2 GC is not very good because template compilation using with(CTX), vue3 has been fixed and is worth 👍
- 4. Vue data stream management does not like the react is rigorous, some would say, vue is very strict, I can do well, but in the actual project development, in order to achieve certain requirements (especially take over other people’s code), you have to write some syntactic sugar type code, serious damage of top-down data flow, increase the risk of a certain project quality, I’ve been cheated a few times, and I can’t make fun of it
- Vue doesn’t support typescript as well as React. See the vue3-vuex-typescript pit tour
- React abstract common components. High-order components are more flexible and convenient than VUE. JSX is more flexible and easy to control than Template
- 7. Vue ecosystem is inferior to React ~
-
- Antd Vue is maintained by individuals, while ANTD React is maintained by a large team of technical communities, especially corporate projects. Sustainability is the foundation
-
- In practice, both Recoil and Mobx are better than Vuex
Vuex can only be used on VUE, because VUex is mounted on VUE and listening data is based on new VUE
Recoil can only be used with React, because recoil has four apis based on Provide and useEffect in React, which are very simple and effective.
Mobx can be used by anyone, because it is based on Observable and mobx has its own data flow management functions, such as a store, which can manage its own flow. For example, the constructor can perform some basic operations on data, such as when, autorun hook to listen to data, in the actual application, the data flow can be completely managed in mobx, outside only reference and call store method, data flow management, clear, see the effect.
Anyway, we still have to learn, so let’s get started…
vue3 VS vue2
1. The source code structure has changed
-
All vue2 methods are mounted on vue instances, and are packaged into artifacts whether you use them or not. The listening data is based on the browser Object. Observer capabilities, and the Observer API is not exposed
-
Vue3 is a change from code structure, written in typescript. Each module can be used externally as a separate function, which is tree-shaking, and Reactive etc can be exposed for external use
Packages directory structure
- Reactivity Reactive data processing
-
- Reactive Ref Effect can be referenced separately without reference to vUE
- Runtime-core Platform-independent Runtime (VNode, renderer vue component instance…)
- Runtime-dom Specifies the runtime of the browser
- Complier-core platform-independent compiler (render)
- Complier-dom is compiled for the browser
- Complier-sfc compilation handles single files
-
- A vue file contains three parts: SFC breaks them up for processing separately, template => vue, script => js, and style => CSS
- The complier-SRr server-side rendering compiler
- Server-renderer Server rendering
- Shared Internal utility functions and constants are shared
- Size-check private package, used to check the runtime size after tree-shaking
- Template-explorer online template compiler
- Vue main entrance
2. Template compilation
AST analysis tool
vue2
- Template => AST => traverse the AST (look for static nodes and tag them) => render => VNode => diff
-
- Diff from top to bottom, without distinguishing between dynamic and static templates, is expensive
-
- Vue2 uses with because the browser does not recognize the dynamic string {{greeting}}.
vue2 template-explorer
-
- Vue2 is optimized at compile time, react is optimized at runtime with JSX => react. CreateElment
-
- Template compilation takes up a lot of performance. In the process of compiling template into AST, the template is implemented through regular matching, which degrades performance due to the backwardness of regular matching
For example: match /xf{1,3}fz/ XFFZ XFFFZ XFFFFZ keeps backtracking until the match, namely the regular greed featureCopy the code
- OptionsAPI Configuration API
new Vue({
data: {},
mounted: {},
methods: {}
})
Copy the code
vue3
- Template => AST=> iterate over AST=> generate new AST=> render
-
- Vue3 does not use with because vue3 is compiled using @babel/ Parser
Vue 3 Template Explorer
-
- Vue3 is optimized for static compilation: static analysis is done at compile time, and static nodes that can be promoted are pushed out of the rendering function so that nodes do not need to be created again
Such as:
<div>
<section>
<span> {{ name }}</span>
</section>
</div>-- Dynamic node<span>{{ name }}</span> ---
Copy the code
When the name changes, instead of diff recursively from top to bottom, just direct the update through dynamicChildren, which greatly improves compilation performance
1.block tree
const vnode = {
tag: 'div'.children: [{tag: 'section'.children: [{tag: 'span'.children: ctx.name,
patchFlag: 1 // Dynamic textContent},],},],// This array collects all dynamic child nodes under the current VNode
dynamicChildren: [{tag: 'span'.children: ctx.name, patchFlag: 1},],}Copy the code
However, there are cases where directional update mode cannot be used, such as V-if, v-for, which recreates a layer of blocks
2. Static nodes are promoted
3. Static attribute enhancement
4. Event caching
5. If the number of static nodes exceeds 6, static nodes will be stringed.
-
- When the template is compiled, it iterates over the string based on the state machine
- ComppositionAPI: Hooks API
createApp({
setup() {}
}).mount('#app')
Copy the code
3. Responsive systems
vue2
- Object. DefinePrototype, which can’t directly listen for new keys, deep nesting (recursive), arrays (resulting in multiple get/set triggers)
vue3
-
It is expensive to create an instance. Every time you create an instance, you need to expose a lot of things on this. Every exposed attribute needs to be defined in Object.defineProperty
-
- Proxy can be understood as a layer of “interception” in front of the target object. All external access to the object must pass this layer of interception. Therefore, it provides a mechanism for filtering and rewriting external access, which can be translated as “Proxy”.
-
- The Proxy can monitor the changes of the object itself and perform corresponding operations after the changes. You can track objects, and it’s also useful for data binding
var proxy = new Proxy(target, handler);
The new Proxy() parameter generates a Proxy instance, the target parameter represents the target object to intercept, and the handler parameter is also an object that is used to customize the interception behavior. Note that for the Proxy to work, you must operate on the Proxy instance (in this example, the Proxy object). Instead of operating on the target object
-
Based on proxy, you can listen for new keys, but can not directly listen for deep nesting and array through GET, which requires additional processing
-
- Array, by overriding array methods, intercepting these methods to gain the ability to listen
-
- Object deep nesting uses reactive only when a key is obtained to solve the performance problem of deep nesting
new Proxy(target, baseHandler);
const baseHandler = {
get(target, key) {
// Collect dependencies
track(target, key);
const res = Reflect.get(target, key);
return typeof res === 'object' ? reactive(res) : res;
},
set(target, key, val) {
const res = Reflect.set(target, key, val);
// Trigger the update
return res;
},
apply (target, ctx, args) {
return Reflect.apply(...args);
}
};
Copy the code
Vue3 overall process
import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')
Copy the code
App mounting process
1. Call createApp(App) to generate an application instance, and then call mount
2. In the mount stage, first create vnodes according to the entry component App, and then trigger the render(Reactive +effect) function. The whole render stage is the Patch VNode stage. It is divided into initial mount and update component mount
3. For the stage of internal patch, different node types will be treated differently, mainly components and elements. Components will continue to be created and mounted
Data /props hangs on CTX
CreateApp => App
-
- Run-time/dom/ SRC /index: createApp -> ensureRenderer -> createRenderer
export const createApp = ((. args) = > {
// Create an app instance object
constapp = ensureRenderer().createApp(... args)// Override the mount method (which can be implemented independently of other platforms)
const { mount } = app
app.mount = (containerOrSelector) = > {
// ...
// Container processing
const proxy = mount(container)
ontainer.removeAttribute('v-cloak')
container.setAttribute('data-v-app'.' ')
return proxy
}
return app
})
// Dynamically load render, so that import can guarantee the tree shake
function ensureRenderer() {
return renderer || (renderer = createRendere(rendererOptions))
}
Copy the code
-
- runtime-core/src/renderer: createRenderer -> baseCreateRenderer => {
render, hydrate, createApp: createAppAPI(render, hydrate)
}
Node differs from Element: All Element elements are node elements. Node is not necessarily an Element, because a text node is not an element. An element is a node
// Create logic
export function createRenderer(options) {
return baseCreateRenderer(options)
}
// Return render and createApp methods
function baseCreateRenderer(options,createHydrationFns) {
const{dom manipulation methods... } = options/** Patch method, accept the new and old VNode nodes and corresponding containers, sibling nodes of the node, parent components, compression optimization, etc., the whole patch process is actually the corresponding VNode depth-first traversal process; The result is a complete tree */
const patch: PatchFn = (n1,n2,container,anchor = null,parentComponent = null,
parentSuspense = null,isSVG = false,optimized = false) = > {
// Old vnodes exist, and the new VNode type is different, destroy the corresponding old node
if(n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense,true)
// Set n1 to null to ensure that the entire node mount logic is followed
n1 = null
}
// The node is of an unoptimized type
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
/** Patch has two internal cases: 1. Mount N1 is null 2. Update * /
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:// Process text
case Comment:// Handle comments
case Static:// Handle static nodes
case Fragment:
default: //elemment handles DOM elements
if (shapeFlag & ShapeFlags.ELEMENT) {
// Element processing logic
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// Component processing logic
/** Determine whether to mount or update the component, mount the mountComponent and update updateComponent STEP: 2. For first mounted mountedComponent inside, 1. Create the component instance according to the component VNode. 2. Set the component instance, call the setup method, and handle options, etc. 3. Trigger and run a render function with side effects, internally using Effect and render calls STEP: 3. Render real logic 1. Generate the corresponding subTree, since the component will be the case of other sub-components, this process is the process of calling render, and then generate the corresponding subTree 2. Render the corresponding subTree into the container by performing the corresponding patch and then entering the function again. If the patch is element, the render task is triggered */
} else if (shapeFlag & ShapeFlags.TELEPORT) {
/ / processing TELEPORT
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
/ / deal with SUSPENSE}}// set ref
if(ref ! =null && parentComponent) {
setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
}
}
// Process text
const processText = (n1, n2, container, anchor) = > {}
// Handle comments
const processCommentNode: ProcessTextOrCommentFn (n1,n2,container,anchor)
// Handle static
const mountStaticNode = (n2,container,anchor,isSVG)
const processFragment = (...) {}
// Handle element: 1. Initialize the mount for the first time. 2
const processElement = (n1, n2) = > {
if (n1 == null) {
// Initialize the mount node element
mountElement(n2,container,...)
} else {
// The process of updating a nodepatchElement(n1, n2, ...) }}// Mount the element node
const mountElement = (vnode,container,...) = > {
// Create a DOM element node and call the platform method of options directly
el = vnode.el = hostCreateElement(vnode.type,isSVG,props && props.is)
// Process text nodes
hostSetElementText(el, vnode.children as string);
// The child nodes are arrays
mountChildren(vnode.children,el,null,parentComponent,...)
// Handle props, class style event, etc
hostPatchProp(...)
// Insert the created element into the node
hostInsert(el, container, anchor)
//
queuePostRenderEffect(() = >{ vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode) needCallTransitionHooks && transition! .enter(el) dirs && invokeDirectiveHook(vnode,null, parentComponent, 'mounted')
}, parentSuspense)
}
// When the child node is an array, the patch needs to be traversed
const mountChildren = (children,container,anchor,...) = > {
patch(....)
}
// Update the element node, mainly to update props and child nodes, and internally to update the dynamic patch
const patchElement()
// Changes triggered by dynamic Black Tree
const patchBlockChildren = (.) = > {}
// Update props of a node, such as class style, event, etc
const patchProps = (.) = > {}
// The component processing logic determines whether the sub-component needs to be updated. If so, the sub-component's side effect rendering function is recursively executed, otherwise the VNode is directly updated
const processComponent() {
// Initialize the mount of the component
if{mountComponent(...) }else {
/ / updateupdateComponent(...) }}// Mount the component
const mountComponent = (...) {
// 1. Create a component instance
const instance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// 2. Set the component instance
setupComponent(instance)
// 3. Set up and run the render function with side effectsSetupRenderEffect (instance,...). }// Update component: DOM diff updates the DOM to trigger
const updateComponent = (n1: VNode, n2: VNode,...) = > {
const instance = (n2.component = n1.component)!
// Determine whether a new child component is needed according to the old and new child component VNode
if (shouldUpdateComponent(n1, n2, optimized)) {
// Update next of the corresponding component instance to the new VNode
// Call the render function of the child component
instance.update()
} else {
// There is no need to update, just copy the properties of the old and new components}}// Run the render function with side effects
const setupRenderEffect = (instance,...) = > {
// Create a responsive side effect rendering function that re-executes the internal logic of componentEffect when the data inside the component changes, and mounts it to the update method of the component instance for subsequent updates
instance.update = effect(function componentEffect() {
if(! instance.isMounted) {// Initialize render the first time the component is mounted
// 1. Generate the corresponding subTree
// 2. Render the corresponding subTree into the container
// Render component generates subtree VNode,
const subTree = (instance.subTree = renderComponentRoot(instance))
} else {
// Render the corresponding subTree into the container. The subTree may be Element, text, Component, etc
path(null,subTree,container,...) }})}// Update the component instance information, such as the component props, the Vnode reference, and the component instance that the Vnode points to
const updateComponentPreRender = (instance,nextVNode,...) = > {}
If the old node is text 1. If the new node is text, replace 2 directly. New node is array, empty text, add array 2. If the old node is empty 1. If the new node is text, add text 2. If the new node is empty, do not handle 3. If the old node is an array 1. If the new node is text, delete the node and add text 2. New node is empty, delete old node 3. New node is array, need full diff child node */
const patchChildren: PatchChildrenFn = () = > {}
// diff, the child node of the array is changed, which is handled by updating, deleting, adding, or moving
const patchKeyedChildren = () = > {}
// Render and mount processes
const render = (vnode, container) = > {
/ / unloading
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null.null.true)}}else {
// Create or update the component
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs();
// Cache the VNode node (already rendered)
container._vnode = vnode
}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
Copy the code
— runtime-core/src/apiCreateApp: createAppAPI
export function createAppAPI(render,hydrate) {
// Accepts two arguments, the object and props of the root component, which are null by default
return function createApp(rootComponent, rootProps = null) {
// Create the application context
const context = createAppContext();
/** ctx = { app, config: { isNativeTag: NO, performance: false, globalProperties: {}, optionMergeStrategies: {}, isCustomElement: NO, errorHandler: undefined, warnHandler: undefined }, mixins: [], components: {}, directives: {}, provides: Object.create(null) } */
// All plug-ins
const installedPlugins = new Set(a);let isMounted = false
/ / app instance
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent,
_props: rootProps,//props
_container: null.// Mount the container
_context: context,
version,
get config() {
return context.config
},
// Mount the middleware through use,
use(plugin, ... options){ installedPlugins.add(plugin) plugin.install(app, ... options)return app;
},
/ / use mixins
mixin(mixin) {
context.mixins.push(mixin)
return app
},
// Define the component on the app instance
component(name, component) {
if(! component) {return context.components[name]
}
context.components[name] = component
return app
},
// Define directives
directive(name, directive) {
if(! directive) {return context.directives[name]
}
context.directives[name] = directive
return app
},
// Mount the component core rendering logic
mount(rootContainer, isHydrate) {
if(! isMounted) {// No mount, create VNode of root node
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
);
// Bind context
vnode.appContext = context
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// Instantiate trigger render, use the renderer to render VNode, pass in VNode and container
render(vnode, rootContainer)
}
isMounted = true
app._container = rootContainer
returnvnode.component! .proxy } },/ / unloading
unmount() {
if (isMounted) {
render(null, app._container)
}
}
provide(key, value) {
context.provides[key as string] = value
return app
}
}
}
Copy the code
Data response analysis
- Reactive: Generates complex (Object, array, etc.) data and returns it as a reactive data proxy
- Ref: Returns the basic (number, string, etc.) data proxy
- Effect (FN) : listens for data change handlers
When initialized, fn is executed, collecting dependencies => render the page
Render => VNode => patch => mount
When reactive data in FN changes, Effect re-performs FN => updates => renders the page
Render => VNode => patch => diff => update, e.g
const state = reactive({ count: 1 })
effect(() = > {
const count = state.count;
console.log(count);
})
Copy the code
reactive
- Get: Collect depends on track
- Set: triggers the update trigger
// Convert raw data to response data
export const proxyMap = new WeakMap<Target, any> () /export function reactive(target: object) {
// Read only data, return directly
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// Create reactive data
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
function createReactiveObject(target, isReadonly, mutableHandlers, mutableCollectionHandlers) {
// If it is not Object, return target, which means that the data you are listening for must be {}.
// ...
// If the data is already responsive and not read-only, return it directly
// ...
// If the original data already has proxy data, find the corresponding proxy data from proxyMap
const existingProxy = proxyMap.get(target)
if (existingProxy) { return existingProxy }
// If not found, process proxy data
const proxy = new Proxy(target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers)
// Insert proxy into proxyMap for retention
proxyMap.set(target, proxy);
return proxy
}
// ------- handler --------
// mutableHandlers
export const base: ProxyHandler<object> = {
get:createGetter,// Collect dependencies
set, // Publish a subscription to trigger an update
deleteProperty,
has,
ownKeys
}
function createGetter() {
return function get(target: Target, key: string | symbol, receiver: object) {
// If it is an array, special handling is required, because array changes can cause multiple sets and get fires
const targetIsArray = isArray(target);
// Array calls built-in methods instead of methods on the prototype chain
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// Other types
const res = Reflect.get(target, key, receiver)
// If it is an object
/** data: { bar: { foo: 1 }, car: { price: 100 } } 1. When data.bar is executed, only bar is converted to Reactive 2. Foo is converted to reactive */ when data.bar.foo is executed
if (isObject(res)) {
// If the object is reactive, it is recursive, but it does not recurse all the keys in the first place
return isReadonly ? readonly(res) : reactive(res)
}
// Start collecting dependencies instead of read-only data
if(! isReadonly) { track(target, TrackOpTypes.GET, key) }returnres; }}// Override array, override method directly, avoid multiple trigger get/set problem
const arrayInstrumentations: Record<string.Function> = {}
// Override the method of getting data
(['includes'.'indexOf'.'lastIndexOf'] as const).forEach((key) = > {
// Get the original method of the array
const method = Array.prototype[key] as any
arrayInstrumentations[key] = function (this: unknown[], ... args: unknown[]) {
// Get the raw data of the proxy data
const arr = toRaw(this)
// Collect dependencies
for (let i = 0, l = this.length; i < l; i++) {
track(arr, ‘get’, i + ' ')}// Execute for the first time, possibly with reactive data
const res = method.apply(arr, args)
if (res === -1 || res === false) {
// If the data is proxied, find the original data and reprocess it
return method.apply(arr, args.map(toRaw))
} else {
return res
}
})
(['push'.'pop'.'shift'.'unshift'.'splice'] as const).forEach((key) = > {
/ /...
}
function createSetter(shallow = false) {
return function set(target: object,
key: string | symbol,
value: unknown,
receiver: object
) :boolean {
// Get the original value
const oldValue = (target as any)[key]
if(! shallow) {// handle the case of the corresponding ref data
value = toRaw(value)
if(! isArray(target) && isRef(oldValue) && ! isRef(value)) {// when ref, value needs to be modified to trigger internal methods for the corresponding data
oldValue.value = value
return true}}else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
// Determine whether the key is newly added or modified
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// Trigger the change logic
const result = Reflect.set(target, key, value, receiver)
// receiver is a Proxy or an object that inherits from Proxy,
// We need to deal with the case of the prototype chain, because if the prototype chain also inherits a proxy, modifying the properties on the prototype chain via reflect.set fires two setters
if (target === toRaw(receiver)) {
if(! hadKey) {//trigger Sends a notification
trigger(target, 'add', key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, 'set', key, value, oldValue)
}
}
return result
}
}
Copy the code
effect
- Effect (() => state.name) procedure
-
- Initialization execution, fn => wrapper createReactiveEffect(FN) => activeEffect
-
- Fn () => Triggers get => track to collect dependencies
/** * effect wraps the function-dependent logic and returns activeEffect ** fn which function to execute * options configuration item */
export function effect<T = any> (fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ) :ReactiveEffect<T> {
// If fn is already an effect wrapped function, point directly to the original function
if (isEffect(fn)) {
fn = fn.raw
}
/** * Create a wrapper logic * because dependencies need to be collected at the time the data is fetched, the processing logic should be assigned to reactiveEffect * before execution
const effect = createReactiveEffect(fn, options)
// Determine if lazy is required. If not, execute it once
if(! options.lazy) { effect() }// Return the wrapped function
return effect
}
/ / stack effect
const effectStack: ReactiveEffect[] = [
// Wrap the function
function createReactiveEffect<T = any> (fn: () => T, options: ReactiveEffectOptions) :ReactiveEffect<T> {
const effect = function reactiveEffect() :unknown {
// If effect is not activated and there is no scheduling option, then fn is executed directly
if(! effect.active) {return options.scheduler ? undefined : fn()
}
// During initialization, effectStack collects effects before fn
EffectStack can be used to avoid this problem, since effect is triggered by modifying the corresponding data during fn execution
if(! effectStack.includes(effect)) {// console.log(effect.deps, 'dependencies during current data execution ')
// Clean up the dependent effects in effect.dep to avoid multiple rendering,
Const state = reactive({show:true, name:' XFZ ', default: 'girl'}); // Start the listener, rely on the collection, collect the show and name fields, map to effect, Effect (function(){if(state.show){console.log(state.name)} else {console.log(state.default)}}) // Modify show Name state. Name = 'zc' // will print girl again if you do not clear all dependencies in the dep
cleanup(effect)
try {
// Enable dependency collection
enableTracking()
/ / effect into the stack
effectStack.push(effect)
// Set the active effect
activeEffect = effect
// Execute FN logic to trigger the collection of internal data dependencies and start collecting ActiveEffects
return fn(); // fn() => Trigger GET => track to collect dependencies
} finally {
/ / out of the stack
effectStack.pop()
// Restore the previous state
resetTracking()
// point to the last effect on the stack
activeEffect = effectStack[effectStack.length - 1]}}}asReactiveEffect effect.id = uid++ effect.allowRecurse = !! options.allowRecurse effect._isEffect =true // indicates an effect function
effect.active = true // Active state
effect.raw = fn // The original function
effect.deps = [] // effect dependency. An effect may have multiple listeners
effect.options = options
return effect
}
// Clear the corresponding dependency execution
function cleanup(effect: ReactiveEffect) {
const { deps } = effect; // An effect may have multiple data dependencies
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0}}let shouldTrack = true // Whether dependencies should be collected
const trackStack: boolean[] = [] // Control the state of multiple nesting of collection dependencies
// Suspend dependent collection
export function pauseTracking() {
trackStack.push(shouldTrack)
shouldTrack = false
}
// Start dependency collection
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
// Fall back to the previous dependent collection
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
Copy the code
Track collection dependency
{[' target1 ']: depsMap}
const targetMap = new WeakMap<any, KeyToDepMap>();
/** * Collection depends on * target raw data * type Trigger type, such as get, add * key to obtain a specific key value */
export function track(target: object.type: TrackOpTypes, key: unknown) {
ActiveEffect What needs to be done after data changes
// The dependency should not be collected, or there is no effect to activate
if(! shouldTrack || activeEffect ===undefined) {
return
}
/* {target< original data >:{key< corresponding key>:[effect]< current key data, all related monitoring places, a key, may be used in multiple places, there is a key pair for each effect>}} */
let depsMap = targetMap.get(target)
if(! depsMap) {// Each target corresponds to a depsMap
targetMap.set(target, (depsMap = new Map()))}let dep = depsMap.get(key)
if(! dep) {// depsMap maintains a set of corresponding keys to the DEP
depsMap.set(key, (dep = new Set()))}if(! dep.has(activeEffect)) {// Collect the currently active effects as dependencies
dep.add(activeEffect)
// Collect the DEP as a dependency for the currently active effect. Other effects may be triggered within the current effect
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
Copy the code
Trigger Triggers updates and sends notifications
export function trigger(target, type, key, newValue, oldValue, oldTarget) {
// Get the dependency set of the corresponding original data
const depsMap = targetMap.get(target)
if(! depsMap) {// Not collected, return directly
return
}
// Create an effect collection to run
const effects = new Set<ReactiveEffect>()
// Define the function to iterate over to add effect
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) = > {
if (effectsToAdd) {
effectsToAdd.forEach(effect= > {
if(effect ! == activeEffect || effect.allowRecurse) { effects.add(effect) } }) } }// The corresponding action is triggered to modify, delete, add, and add all dependencies to effects
if (type === TriggerOpTypes.CLEAR) {
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) = > {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if(key ! = =void 0) {
add(depsMap.get(key))
}
switch (type) {
case TriggerOpTypes.ADD:
if(! isArray(target)) { add(depsMap.get(ITERATE_KEY))if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))}break
case TriggerOpTypes.DELETE:
if(! isArray(target)) { add(depsMap.get(ITERATE_KEY))if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break}}// Define the execution function
const run = (effect: ReactiveEffect) = > {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
// If there is a scheduling function, execute the scheduling function first
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
// If not, execute directly
effect()
}
}
effects.forEach(run);// Execute effect
}
Copy the code
ref
// Create ref data, handle basic type data listening
export function ref(value? : unknown) {
return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
// If it is ref, return it directly
if (isRef(rawValue)) {
return rawValue
}
/ / create the ref
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue) // Handle data transformations, and if complex data, modify it to Reactive
}
// Trigger the entire track process by setting.value
get value() {
// Collect dependencies
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
// send notifications
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
Copy the code
Template compilation
compile -> parse -> AST -> render
export function compile(template, options {
return baseCompile(template, extend({}, parserOptions, options, {...})
}
export function baseCompile(template,options) {
//The AST is generated through baseParse compilationconst ast = isString(template) ? baseParse(template, options) : template
//Transform (AST,extend({}, options, {... }));return ast;
}
Copy the code
scheduling
- Maintain a promise queue, join the task by push, and flush performs the task
const queue: SchedulerJob[] = []
export function nextTick(
this: ComponentPublicInstance | void, fn? : () = >void
) :Promise<void> {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p
}
---------------------------------------------
export function queueJob(job: SchedulerJob) {
if((! queue.length || ! queue.includes( job, isFlushing && job.allowRecurse ? flushIndex +1: flushIndex )) && job ! == currentPreFlushParentJob ) { queue.push(job) queueFlush() } }function queueFlush() {
if(! isFlushing && ! isFlushPending) { isFlushPending =true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
Copy the code
The end of the
I don’t want to read it anymore. I hope I don’t use it in the future. I’ll study React instead
Subsequent articles
- React source code
Join us at ❤️
Bytedance Xinfoli team
Nice Leader: Senior technical expert, well-known gold digger columnist, founder of Flutter Chinese community, founder of Flutter Chinese community open source project, well-known developer of Github community, author of dio, FLY, dsBridge and other well-known open source projects
We look forward to your joining us to change our lives with technology!!
Recruitment link: job.toutiao.com/s/JHjRX8B