Bobolo
  • Home
  • Me

Qwik Signal Serialize

Qwik 如何实现更新 - Lazy

12 Min Read

TUE JUN 13 2023

Written By Bobolo

之前的文章已经说明过 Qwik 更新的 SSR 部分,相关的内容就不在这里过多描述,这里主要是探讨 Lazy 部分。依然会采用同样的 Example。

Example

下面我们以 Qwik 的 init project 代码来作为案例,通过 count 的加减来去理解 state 和 ui 的关联

import { component$, useSignal } from '@builder.io/qwik'
import styles from './counter.module.css'

export default component$(() => {
  const count = useSignal(70)

  return (
    <div class={styles['counter-wrapper']}>
      <button
        class="button-dark button-small"
        onClick$={() => (count.value -= 1)}
      >
        -
      </button>
      {count.value + 12}
      {count.value}
      <button
        class="button-dark button-small"
        onClick$={() => (count.value += 1)}
      >
        +
      </button>
    </div>
  )
})

在 Dev 环境下打包出来的界面

Render

当用户点击 + 之后,Qwik 会去加载三个文件:

  • 切割出来的用户交互行为(click)的回调函数的 JS,简称 click.js
  • @builder.io/qwik/core 是 Qwik 的核心代码
  • @builder.io/qwik/build 就是环境变量
// click.js
import { useLexicalScope } from '/node_modules/@builder.io/qwik/core.mjs?v=e747bb0b'
export const counter_component_div_button_onClick_1_LkCVrojX09Y = () => {
  const [count] = useLexicalScope()
  return (count.value += 1)
}

// @builder.io/qwik/build
export const isServer = false
export const isBrowser = true
export const isDev = true

click.js 以及 qwik/build 都很简单,这里重点是要去理解 useLexicalScope 是怎么做到绑定 state 以及 count.value += 1 为什么界面也能跟着 +1。

代码部分会把异常提示的 console 先去掉,如 assertDefinedassertQrl,因为对逻辑影响不大,去掉可以更好的理解代码.

useLexicalScope

const useLexicalScope = () => {
  const context = getInvokeContext()
  let qrl = context.$qrl$
  if (!qrl) {
    const el = context.$element$
    const container = getWrappingContainer(el)
    qrl = parseQRL(decodeURIComponent(String(context.$url$)), container)
    resumeIfNeeded(container)
    const elCtx = getContext(el, _getContainerState(container))
    inflateQrl(qrl, elCtx)
  } else {
    // assertQrl assertDefined
  }
  return qrl.$captureRef$
}

我们按源码去理解它里面的所用到的每个函数,虽然很多函数通过名字都可以知道它的作用,但是也会简单介绍。

getInvokeContex

const seal = obj => {
  if (qDev) {
    Object.seal(obj) // 封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。
  }
}

const newInvokeContext = (locale, hostElement, element, event, url) => {
  const ctx = {
    $seq$: 0,
    $hostElement$: hostElement,
    $element$: element,
    $event$: event,
    $url$: url,
    $locale$: locale,
    $qrl$: undefined,
    // ... code
  }
  seal(ctx)
  return ctx // context 的内容
}

const newInvokeContextFromTuple = context => {
  const element = context[0]
  const container = element.closest(QContainerSelector)
  const locale = container?.getAttribute(QLocaleAttr) || undefined
  locale && setLocale(locale) // 用于设置 lang,和本质上的逻辑关系不大
  return newInvokeContext(locale, undefined, element, context[1], context[2])
}

let _context

const tryGetInvokeContext = () => {
  if (!_context) {
    const context =
      typeof document !== 'undefined' && document && document.__q_context__
    if (!context) {
      return undefined
    }
    if (isArray(context)) {
      return (document.__q_context__ = newInvokeContextFromTuple(context))
    }
    return context
  }
  return _context
}

const getInvokeContext = () => {
  const ctx = tryGetInvokeContext()
  return ctx
}

getInvokeContext 就是只是获取一个 context,可以是缓存中的 _context 或者 document.__q_context__。 element 首次恢复时用的是 document.q_context,它在 qwikload 中处理,在 import 前定义,finally 后进行重置。

Flow

getInvokeContext

getWrappingContainer

// 匹配特定选择器且离当前元素最近的祖先元素(可以是当前元素本身)
const getWrappingContainer = el => el.closest(QContainerSelector)

getWrappingContainer,就只是获取 el container

parseQRL

const createQRL = (
  chunk,
  symbol,
  symbolRef,
  symbolFn,
  capture,
  captureRef,
  refSymbol
) => {
  // ... 函数、变量的定义
  const invokeQRL = async function (...args) {
    const fn = invokeFn.call(this, tryGetInvokeContext())
    const result = await fn(...args)
    return result
  }
  const resolvedSymbol = refSymbol ?? symbol
  const hash = getSymbolHash(resolvedSymbol)
  const QRL = invokeQRL
  const methods = {
    getSymbol: () => resolvedSymbol,
    // ... code
    $capture$: capture,
    $captureRef$: captureRef,
    dev: null,
  }
  const qrl = Object.assign(invokeQRL, methods)
  seal(qrl)
  return qrl
}

const parseQRL = (qrl, containerEl) => {
  const endIdx = qrl.length
  const hashIdx = indexOf(qrl, 0, '#')
  const captureIdx = indexOf(qrl, hashIdx, '[')
  const chunkEndIdx = Math.min(hashIdx, captureIdx)
  const chunk = qrl.substring(0, chunkEndIdx)
  const symbolStartIdx = hashIdx == endIdx ? hashIdx : hashIdx + 1
  const symbolEndIdx = captureIdx
  const symbol =
    symbolStartIdx == symbolEndIdx
      ? 'default'
      : qrl.substring(symbolStartIdx, symbolEndIdx)
  const captureStartIdx = captureIdx
  const captureEndIdx = endIdx
  const capture =
    captureStartIdx === captureEndIdx
      ? EMPTY_ARRAY
      : qrl.substring(captureStartIdx + 1, captureEndIdx - 1).split(' ')
  const iQrl = createQRL(chunk, symbol, null, null, capture, null, null)
  if (containerEl) {
    iQrl.$setContainer$(containerEl)
  }
  return iQrl
}

parseQRL,就是将 qurl,也就是 on:click="./chunk.js#onClick" 中的 ./chunk.js#onClick 进行处理,解析出来一个 object。

resumeIfNeeded

const directGetAttribute = (el, prop) => el.getAttribute(prop)

const resumeIfNeeded = containerEl => {
  const isResumed = directGetAttribute(containerEl, QContainerAttr)
  if (isResumed === 'paused') {
    resumeContainer(containerEl)
  }
}

resumeIfNeeded 就是判断 containerEl 的 q:container="paused",如果是 paused 则进行 resume

resumeContainer

const resumeContainer = containerEl => {
  const pauseState = containerEl['_qwikjson_'] ?? getPauseState(containerEl) // qwik/json
  containerEl['_qwikjson_'] = null

  const doc = getDocument(containerEl)
  const isDocElement = containerEl === doc.documentElement
  const parentJSON = isDocElement ? doc.body : containerEl
  // const getQwikInlinedFuncs 获取 q:func="qwik/json" 的 script
  const inlinedFunctions = getQwikInlinedFuncs(parentJSON)
  const containerState = _getContainerState(containerEl)
  moveStyles(containerEl, containerState)
  // Collect all elements
  const elements = new Map()
  const text = new Map()
  let node = null
  let container = 0
  // Collect all virtual elements
  const elementWalker = doc.createTreeWalker(containerEl, SHOW_COMMENT$1)
  while ((node = elementWalker.nextNode())) {
    // ... 遍历收集 elements 和 text
  }

  const slotPath = containerEl.getElementsByClassName('qc📦').length !== 0
  // Collect all q:id dom
  containerEl.querySelectorAll('[q\\:id]').forEach(el => {
    if (slotPath && el.closest('[q\\:container]') !== containerEl) {
      return
    }
    const id = directGetAttribute(el, ELEMENT_ID)
    const index = strToInt(id)
    elements.set(index, el)
  })

  const parser = createParser(containerState, doc) // 根据不同的 prefix 使用不同的 serializer
  const finalized = new Map() // 缓存 obj
  const revived = new Set()
  const getObject = id => {
    if (finalized.has(id)) {
      return finalized.get(id)
    }
    return computeObject(id)
  }
  const computeObject = id => {
    // Handle elements
    if (id.startsWith('#')) {
      // ... return elements 中 的 el
      return rawElement
    } else if (id.startsWith('@')) {
      // ... return 执行的 function
      return func
    } else if (id.startsWith('*')) {
      // ... return dom 的 text
      return str
    }
    const index = strToInt(id)
    const objs = pauseState.objs // qwik/json 中的 objs
    let value = objs[index]
    if (isString(value)) {
      value = value === UNDEFINED_PREFIX ? undefined : parser.prepare(value)
    }
    let obj = value
    for (let i = id.length - 1; i >= 0; i--) {
      // ... 对 obj 进行处理
    }
    finalized.set(id, obj)
    if (!isPrimitive(value) && !revived.has(index)) {
      revived.add(index)
      reviveSubscriptions(
        value,
        index,
        pauseState.subs,
        getObject,
        containerState,
        parser
      )
      reviveNestedObjects(value, getObject, parser)
    }
    return obj
  }
  containerState.$elementIndex$ = 100000
  containerState.$pauseCtx$ = {
    getObject,
    meta: pauseState.ctx,
    refs: pauseState.refs,
  }
  // 处理把 containerEl q:container 改为 "resumed" 以及执行 qresume 的注册事件
  directSetAttribute(containerEl, QContainerAttr, 'resumed')
  emitEvent$1(containerEl, 'qresume', undefined, true)
}

resumeContainer,它就跟命名一样,用来恢复当前的 Container

  1. 收集 element,包含属性为 q:id 和 Comment(<!--qv q:id=4-->) 节点
  2. 通过 createParser 生成 parser
  3. 生成并处理 containerState,包含 pauseCtxelementIndex 等信息
  4. getObject,先查询 finalized,没有缓存的 obj,再通过 computeObject 生成
  5. computeObject 根据 id 来做处理
    1. startsWith('#'), return elements 中 的 el
    2. startsWith('@'), return 执行的 function
    3. startsWith('*'), return dom 的 text
    4. 上述情况外则 return parser.prepare(value) 生成的 obj,并执行 reviveSubscriptions(将 parseSubscription 添加到 serializers 的订阅) 和 reviveNestedObjects(将 obj 的 $func$、$args$ 进行处理)

      不同的情况会使用不同 serializers,demo 中的会使用 SignalSerializer 和 DerivedSignalSerializer

  6. 处理把 containerEl q:container 改为 "resumed" 以及执行 qresume 的注册事件

reviveSubscriptions

const parseSubscription = (sub, getObject) => {
  const parts = sub.split(' ')
  const type = parseInt(parts[0], 10)
  const host = getObject(parts[1])
  if (!host) {
    return undefined
  }
  if (isSubscriberDescriptor(host) && !host.$el$) {
    return undefined
  }
  const subscription = [type, host]
  if (type === 0) {
    subscription.push(
      parts.length === 3 ? decodeURI(parts[parts.length - 1]) : undefined
    )
  } else if (type <= 2) {
    subscription.push(
      getObject(parts[2]),
      getObject(parts[3]),
      parts[4],
      parts[5]
    )
  } else if (type <= 4) {
    subscription.push(getObject(parts[2]), getObject(parts[3]), parts[4])
  }
  return subscription
}

const reviveSubscriptions = (
  value,
  i,
  objsSubs,
  getObject,
  containerState,
  parser
) => {
  // objsSubs = [['3 #9 2 #9', '3 #a 4 #a']]
  const subs = objsSubs[i]
  if (subs) {
    const converted = []
    let flag = 0
    // 将 parseSubscription push 到 converted,subs= ['3 #9 2 #9', '3 #a 4 #a']
    for (const sub of subs) {
      // ... code
      const parsed = parseSubscription(sub, getObject)
      if (parsed) {
        converted.push(parsed)
      }
    }
    if (flag > 0) {
      setObjectFlags(value, flag)
    }
    // 重点在执行这个 parser.subs,将 converted 添加到订阅中
    if (!parser.subs(value, converted)) {
      // ... code
    }
  }
}

reviveSubscriptions,将 qwik/json 中的 subs 进行处理,通过 parseSubscription 生成一个带有 type 以及 getObject() 的数组(converted),并将 converted 和 value 添加到 parser 的订阅中。

reviveNestedObjects

const reviveNestedObjects = (obj, getObject, parser) => {
  if (parser.fill(obj, getObject)) {
    return
  }
  if (obj && typeof obj == 'object') {
    if (isArray(obj)) {
      for (let i = 0; i < obj.length; i++) {
        obj[i] = getObject(obj[i])
      }
    } else if (isSerializableObject(obj)) {
      for (const key in obj) {
        obj[key] = getObject(obj[key])
      }
    }
  }
}

reviveNestedObjects 用来对 obj 进行处理,

SignalSerializer

const SignalSerializer = {
  $prefix$: '\u0012',
  // ... code
  $prepare$: (data, containerState) => {
    return new SignalImpl(
      data,
      containerState?.$subsManager$?.$createManager$(),
      0
    )
  },
  $subs$: (signal, subs) => {
    signal[QObjectManagerSymbol].$addSubs$(subs)
  },
  $fill$: (signal, getObject) => {
    signal.untrackedValue = getObject(signal.untrackedValue)
  },
}

class SignalBase {}
class SignalImpl extends SignalBase {
  constructor(v, manager, flags) {
    super()
    this[_a$1] = 0
    this.untrackedValue = v
    this[QObjectManagerSymbol] = manager
    this[QObjectSignalFlags] = flags
  }

  // ... valueOf toString toJSON

  get value() {
    if (this[QObjectSignalFlags] & SIGNAL_UNASSIGNED) {
      throw SignalUnassignedException
    }
    const sub = tryGetInvokeContext()?.$subscriber$
    if (sub) {
      this[QObjectManagerSymbol].$addSub$(sub)
    }
    return this.untrackedValue
  }

  set value(v) {
    // ...code
    const manager = this[QObjectManagerSymbol]
    const oldValue = this.untrackedValue
    if (manager && oldValue !== v) {
      this.untrackedValue = v
      manager.$notifySubs$()
    }
  }
}

SignalSerializer 用来生成 SignalImpl,用在 DerivedSignalSerializer 的 this.$func$ 作为 args,并且它具有发布订阅功能,在 set 的时候执行 manager.$notifySubs$(),而 manager 是由 _getContainerState 生成的 LocalSubscriptionManager,它可以执行 notifyChange 来进行 UI 的更新。

DerivedSignalSerializer

class SignalDerived extends SignalBase {
  constructor($func$, $args$, $funcStr$) {
    super()
    this.$func$ = $func$
    this.$args$ = $args$
    this.$funcStr$ = $funcStr$
  }
  get value() {
    return this.$func$.apply(undefined, this.$args$)
  }
}

const DerivedSignalSerializer = {
  $prefix$: '\u0011',
  // ... code
  $prepare$: data => {
    const ids = data.split(' ')
    const args = ids.slice(0, -1)
    const fn = ids[ids.length - 1]
    return new SignalDerived(fn, args, fn)
  },
  $fill$: (fn, getObject) => {
    fn.$func$ = getObject(fn.$func$)
    fn.$args$ = fn.$args$.map(getObject)
  },
}

DerivedSignalSerializer 用来生成 SignalDerived,SignalDerived 和 SignalImpl 有点不同,它主要是用 $func$ 和 $args$ 去 return value,它类似于 Recoil 的 Selector,SignalImpl 类似于 Atom。

Flow

resumeIfNeeded

getContext

const getDomListeners = (elCtx, containerEl) => {
  const attributes = elCtx.$element$.attributes
  const listeners = []
  for (let i = 0; i < attributes.length; i++) {
    const { name, value } = attributes.item(i)
    if (
      name.startsWith('on:') ||
      name.startsWith('on-window:') ||
      name.startsWith('on-document:')
    ) {
      const urls = value.split('\n')
      for (const url of urls) {
        const qrl = parseQRL(url, containerEl)
        if (qrl.$capture$) {
          inflateQrl(qrl, elCtx)
        }
        listeners.push([name, qrl])
      }
    }
  }
  return listeners
}

const createContext = element => {
  const ctx = {
    $flags$: 0,
    $id$: '',
    $element$: element,
    $refMap$: [],
    li: [],
    // ... code
  }
  element[Q_CTX] = ctx
  return ctx
}

const getContext = (el, containerState) => {
  const ctx = tryGetContext(el)
  if (ctx) {
    return ctx
  }
  const elCtx = createContext(el)
  const elementID = directGetAttribute(el, 'q:id')
  if (elementID) {
    const pauseCtx = containerState.$pauseCtx$
    elCtx.$id$ = elementID
    if (pauseCtx) {
      const { getObject, meta, refs } = pauseCtx
      if (isElement(el)) {
        const refMap = refs[elementID]
        if (refMap) {
          elCtx.$refMap$ = refMap.split(' ').map(getObject)
          elCtx.li = getDomListeners(elCtx, containerState.$containerEl$)
        }
      } else {
        // ... code
      }
    }
  }
  return elCtx
}

getContext 主要是获取 element 的 context,如果没有就通过 createContext 生成的 context

  • 设置 $refMap$,map 执行 getObject 得到一个数组,后续 infalteQrl 会用到
  • 通过 getDomListeners 来获取 dom 事件

Flow

getContext

_getContainerState

class LocalSubscriptionManager {
    constructor($groupToManagers$, $containerState$, initialMap) {
        this.$groupToManagers$ = $groupToManagers$;
        this.$containerState$ = $containerState$;
        this.$subs$ = [];
        if (initialMap) {
            this.$addSubs$(initialMap);
        }
    }
    $addSub$(sub, key) {
        // ... code
        this.$addToGroup$(group, this);
    }
    $notifySubs$(key) {
        const subs = this.$subs$;
        for (const sub of subs) {
            // ... code
            notifyChange(sub, this.$containerState$);
        }
    }
    // ... $addSubs$(subs) $addToGroup$(group, manager) $unsubGroup$(group)
}

const createSubscriptionManager = containerState => {
  const groupToManagers = new Map()
  const manager = {
    $groupToManagers$: groupToManagers,
    $createManager$: initialMap => {
      return new LocalSubscriptionManager(groupToManagers, containerState, initialMap)
    },
    $clearSub$: group => {
      const managers = groupToManagers.get(group)
      if (managers) {
        for (const manager of managers) {
          manager.$unsubGroup$(group)
        }
        groupToManagers.delete(group)
        managers.length = 0
      }
    },
  }
  return manager
}

const createContainerState = (containerEl, base) => {
  const containerState = {
    $containerEl$: containerEl,
    $elementIndex$: 0,
    ...
  }
  containerState.$subsManager$ = createSubscriptionManager(containerState)
  return containerState
}

const _getContainerState = containerEl => {
  let set = containerEl[CONTAINER_STATE]
  if (!set) {
    containerEl[CONTAINER_STATE] = set = createContainerState(
      containerEl,
      directGetAttribute(containerEl, 'q:base') ?? '/'
    )
  }
  return set
}

_getContainerState 在没有 state 的时候生成一个带有发布订阅功能的 state,LocalSubscriptionManager 就是发布订阅的 class,主要是它的 $notifySubs$ 会调用 notifyChange。

notifyChange

const scheduleFrame = containerState => {
  if (containerState.$renderPromise$ === undefined) {
    containerState.$renderPromise$ = getPlatform().nextTick(() =>
      renderMarked(containerState)
    )
  }
  return containerState.$renderPromise$
}

const notifySignalOperation = (op, containerState) => {
  const activeRendering = containerState.$hostsRendering$ !== undefined
  containerState.$opsNext$.add(op)
  if (!activeRendering) {
    scheduleFrame(containerState)
  }
}

const notifyChange = (subAction, containerState) => {
  if (subAction[0] === 0) {
    const host = subAction[1]
    if (isSubscriberDescriptor(host)) {
      notifyWatch(host, containerState)
    } else {
      notifyRender(host, containerState)
    }
  } else {
    notifySignalOperation(subAction, containerState)
  }
}

notifyChange 主要用来调用 renderMarked,先收集了 $opsNext$(dom 的处理操作),并且在 !activeRendering 的时候使用 nextTick 来异步执行 renderMarked。

renderMarked

const renderMarked = async containerState => {
  const doc = getDocument(containerState.$containerEl$)
  try {
    const rCtx = createRenderContext(doc, containerState)
    const staticCtx = rCtx.$static$
    const hostsRendering = (containerState.$hostsRendering$ = new Set(
      containerState.$hostsNext$
    ))
    containerState.$hostsNext$.clear()
    await executeWatchesBefore(containerState, rCtx)
    containerState.$hostsStaging$.forEach(host => {
      hostsRendering.add(host)
    })
    containerState.$hostsStaging$.clear()
    const signalOperations = Array.from(containerState.$opsNext$)
    containerState.$opsNext$.clear()
    const renderingQueue = Array.from(hostsRendering)
    sortNodes(renderingQueue)
    for (const elCtx of renderingQueue) {
      const el = elCtx.$element$
      if (!staticCtx.$hostElements$.has(el)) {
        if (elCtx.$componentQrl$) {
          staticCtx.$roots$.push(elCtx)
          await renderComponent(rCtx, elCtx, getFlags(el.parentElement))
        }
      }
    }
    signalOperations.forEach(op => {
      executeSignalOperation(staticCtx, op)
    })
    // Add post operations
    staticCtx.$operations$.push(...staticCtx.$postOperations$)
    // Early exist, no dom operations
    if (staticCtx.$operations$.length === 0) {
      printRenderStats(staticCtx)
      await postRendering(containerState, rCtx)
      return
    }
    await executeContextWithTransition(staticCtx)
    printRenderStats(staticCtx)
    return postRendering(containerState, rCtx)
  } catch (err) {
    logError(err)
  }
}

renderMarked 执行需要 update 的元素,所有的 dom operation 都在这里执行。operations 就是所有 dom 操作,然后遍历 apply 执行。

  1. 处理 $hostsRendering$,用于在下次执行时判断当前是否 activeRendering
  2. 执行 executeWatchesBefore,如果在 notifyWatch 中有添加的 watch task
  3. 对 node 做排序处理
  4. $opsNext$ 循环执行 executeSignalOperation,根据不同的 type 来生成对应的 $operations$
  5. executeContextWithTransition,执行生成的 $operations$
  6. 执行 postRendering,重置 containerState 的各种信息和 executeWatchesAfter,return rendering 的状态

executeSignalOperation

const setProperty = (staticCtx, node, key, value) => {
  staticCtx.$operations$.push({
    $operation$: _setProperty,
    $args$: [node, key, value],
  })
}
const _setProperty = (node, key, value) => {
  try {
    node[key] = value == null ? '' : value // 将 node[key] 设置为 value
    if (value == null && isNode$1(node) && isElement$1(node)) {
      node.removeAttribute(key)
    }
  } catch (err) {
    // logError
  }
}

const executeSignalOperation = (staticCtx, operation) => {
  try {
    const type = operation[0]
    switch (type) {
      case 1:
      case 2: {
        let elm
        let hostElm
        if (type === 1) {
          elm = operation[1]
          hostElm = operation[3]
        } else {
          elm = operation[3]
          hostElm = operation[1]
        }
        const elCtx = tryGetContext(elm)
        if (elCtx == null) {
          return
        }
        const prop = operation[4]
        const isSVG = elm.namespaceURI === SVG_NS
        staticCtx.$containerState$.$subsManager$.$clearSignal$(operation)
        let value = trackSignal(operation[2], operation.slice(0, -1))
        if (prop === 'class') {
          value = serializeClassWithHost(value, tryGetContext(hostElm))
        } else if (prop === 'style') {
          value = stringifyStyle(value)
        }
        const vdom = getVdom(elCtx)
        if (vdom.$props$[prop] === value) {
          return
        }
        vdom.$props$[prop] = value
        return smartSetProperty(staticCtx, elm, prop, value, isSVG)
      }
      case 3:
      case 4: {
        const elm = operation[3]
        if (!staticCtx.$visited$.includes(elm)) {
          staticCtx.$containerState$.$subsManager$.$clearSignal$(operation)
          const value = trackSignal(operation[2], operation.slice(0, -1))
          return setProperty(staticCtx, elm, 'data', jsxToString(value))
        }
      }
    }
  } catch (e) {
    // Ignore
  }
}

executeContextWithTransition

const executeDOMRender = staticCtx => {
  for (const op of staticCtx.$operations$) {
    op.$operation$.apply(undefined, op.$args$) // 这个就是 UI 更新的地方
  }
  resolveSlotProjection(staticCtx)
}

const executeContextWithTransition = async ctx => {
  // try to use `document.startViewTransition`
  if (isBrowser && !qTest) {
    if (document.__q_view_transition__) {
      document.__q_view_transition__ = undefined
      if (document.startViewTransition) {
        await document.startViewTransition(() => executeDOMRender(ctx)).finished
        return
      }
    }
  }
  executeDOMRender(ctx) // fallback
}

executeContextWithTransition 执行之前生成的 $operations$,也就是 UI 的更新

Flow

_getContainerState

infalteQrl

const inflateQrl = (qrl, elCtx) => {
  // `./chunk#symbol[captures]
  return (qrl.$captureRef$ = qrl.$capture$.map(idx => {
    const int = parseInt(idx, 10)
    const obj = elCtx.$refMap$[int]
    return obj
  }))
}

infalteQrl 就是处理 qrl,将 $refMap$ 中对应的 obj(getObject)return,click 中的 callback 就是操作这个 obj。

Lazy Flow

qwik-update-flow

Conclusion

结合前面的 SSR 部分,就是整个 State 的更新机制,Lazy 的部分相对前面的更加复杂,它包含 Core 的部分。也可以通过代码看出,它相对于 React 更新的性能会更好,因为直接操作对应的 Dom。

它目前 version 是 1.1.5 还是个比较新的框架,上面内容也都是它当前 version 的代码逻辑,未来可能会调整也说不定,但是它一些做法和理念还是非常好的

  • 将需要的 data(qwik/json q:func) 提前 render 到 html 中
  • 编译时已经将需要用到的交互、state 等都可以做优化处理
  • 尽可能的 lazy,这会使得更关注代码的分离,也更好的去 split 代码

Reference

Powered by Bobolo

Copyright © Bobolo Blog 2021