| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- import {
- ComponentInternalInstance,
- FunctionalComponent,
- Data,
- getComponentName
- } from './component'
- import {
- VNode,
- normalizeVNode,
- createVNode,
- Comment,
- cloneVNode,
- VNodeArrayChildren,
- isVNode,
- blockStack
- } from './vnode'
- import { handleError, ErrorCodes } from './errorHandling'
- import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared'
- import { warn } from './warning'
- import { isHmrUpdating } from './hmr'
- import { NormalizedProps } from './componentProps'
- import { isEmitListener } from './componentEmits'
- import { setCurrentRenderingInstance } from './componentRenderContext'
- import {
- DeprecationTypes,
- isCompatEnabled,
- warnDeprecation
- } from './compat/compatConfig'
- /**
- * dev only flag to track whether $attrs was used during render.
- * If $attrs was used during render then the warning for failed attrs
- * fallthrough can be suppressed.
- */
- let accessedAttrs: boolean = false
- export function markAttrsAccessed() {
- accessedAttrs = true
- }
- export function renderComponentRoot(
- instance: ComponentInternalInstance
- ): VNode {
- const {
- type: Component,
- vnode,
- proxy,
- withProxy,
- props,
- propsOptions: [propsOptions],
- slots,
- attrs,
- emit,
- render,
- renderCache,
- data,
- setupState,
- ctx,
- inheritAttrs
- } = instance
- let result
- let fallthroughAttrs
- const prev = setCurrentRenderingInstance(instance)
- if (__DEV__) {
- accessedAttrs = false
- }
- try {
- if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
- // withProxy is a proxy with a different `has` trap only for
- // runtime-compiled render functions using `with` block.
- const proxyToUse = withProxy || proxy
- result = normalizeVNode(
- render!.call(
- proxyToUse,
- proxyToUse!,
- renderCache,
- props,
- setupState,
- data,
- ctx
- )
- )
- fallthroughAttrs = attrs
- } else {
- // functional
- const render = Component as FunctionalComponent
- // in dev, mark attrs accessed if optional props (attrs === props)
- if (__DEV__ && attrs === props) {
- markAttrsAccessed()
- }
- result = normalizeVNode(
- render.length > 1
- ? render(
- props,
- __DEV__
- ? {
- get attrs() {
- markAttrsAccessed()
- return attrs
- },
- slots,
- emit
- }
- : { attrs, slots, emit }
- )
- : render(props, null as any /* we know it doesn't need it */)
- )
- fallthroughAttrs = Component.props
- ? attrs
- : getFunctionalFallthrough(attrs)
- }
- } catch (err) {
- blockStack.length = 0
- handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
- result = createVNode(Comment)
- }
- // attr merging
- // in dev mode, comments are preserved, and it's possible for a template
- // to have comments along side the root element which makes it a fragment
- let root = result
- let setRoot: ((root: VNode) => void) | undefined = undefined
- if (
- __DEV__ &&
- result.patchFlag > 0 &&
- result.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
- ) {
- ;[root, setRoot] = getChildRoot(result)
- }
- if (fallthroughAttrs && inheritAttrs !== false) {
- const keys = Object.keys(fallthroughAttrs)
- const { shapeFlag } = root
- if (keys.length) {
- if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.COMPONENT)) {
- if (propsOptions && keys.some(isModelListener)) {
- // If a v-model listener (onUpdate:xxx) has a corresponding declared
- // prop, it indicates this component expects to handle v-model and
- // it should not fallthrough.
- // related: #1543, #1643, #1989
- fallthroughAttrs = filterModelListeners(
- fallthroughAttrs,
- propsOptions
- )
- }
- root = cloneVNode(root, fallthroughAttrs)
- } else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
- const allAttrs = Object.keys(attrs)
- const eventAttrs: string[] = []
- const extraAttrs: string[] = []
- for (let i = 0, l = allAttrs.length; i < l; i++) {
- const key = allAttrs[i]
- if (isOn(key)) {
- // ignore v-model handlers when they fail to fallthrough
- if (!isModelListener(key)) {
- // remove `on`, lowercase first letter to reflect event casing
- // accurately
- eventAttrs.push(key[2].toLowerCase() + key.slice(3))
- }
- } else {
- extraAttrs.push(key)
- }
- }
- if (extraAttrs.length) {
- warn(
- `Extraneous non-props attributes (` +
- `${extraAttrs.join(', ')}) ` +
- `were passed to component but could not be automatically inherited ` +
- `because component renders fragment or text root nodes.`
- )
- }
- if (eventAttrs.length) {
- warn(
- `Extraneous non-emits event listeners (` +
- `${eventAttrs.join(', ')}) ` +
- `were passed to component but could not be automatically inherited ` +
- `because component renders fragment or text root nodes. ` +
- `If the listener is intended to be a component custom event listener only, ` +
- `declare it using the "emits" option.`
- )
- }
- }
- }
- }
- if (
- __COMPAT__ &&
- isCompatEnabled(DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, instance) &&
- vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT &&
- root.shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.COMPONENT)
- ) {
- const { class: cls, style } = vnode.props || {}
- if (cls || style) {
- if (__DEV__ && inheritAttrs === false) {
- warnDeprecation(
- DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE,
- instance,
- getComponentName(instance.type)
- )
- }
- root = cloneVNode(root, {
- class: cls,
- style: style
- })
- }
- }
- // inherit directives
- if (vnode.dirs) {
- if (__DEV__ && !isElementRoot(root)) {
- warn(
- `Runtime directive used on component with non-element root node. ` +
- `The directives will not function as intended.`
- )
- }
- root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
- }
- // inherit transition data
- if (vnode.transition) {
- if (__DEV__ && !isElementRoot(root)) {
- warn(
- `Component inside <Transition> renders non-element root node ` +
- `that cannot be animated.`
- )
- }
- root.transition = vnode.transition
- }
- if (__DEV__ && setRoot) {
- setRoot(root)
- } else {
- result = root
- }
- setCurrentRenderingInstance(prev)
- return result
- }
- /**
- * dev only
- * In dev mode, template root level comments are rendered, which turns the
- * template into a fragment root, but we need to locate the single element
- * root for attrs and scope id processing.
- */
- const getChildRoot = (
- vnode: VNode
- ): [VNode, ((root: VNode) => void) | undefined] => {
- const rawChildren = vnode.children as VNodeArrayChildren
- const dynamicChildren = vnode.dynamicChildren
- const childRoot = filterSingleRoot(rawChildren)
- if (!childRoot) {
- return [vnode, undefined]
- }
- const index = rawChildren.indexOf(childRoot)
- const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
- const setRoot = (updatedRoot: VNode) => {
- rawChildren[index] = updatedRoot
- if (dynamicChildren) {
- if (dynamicIndex > -1) {
- dynamicChildren[dynamicIndex] = updatedRoot
- } else if (updatedRoot.patchFlag > 0) {
- vnode.dynamicChildren = [...dynamicChildren, updatedRoot]
- }
- }
- }
- return [normalizeVNode(childRoot), setRoot]
- }
- export function filterSingleRoot(
- children: VNodeArrayChildren
- ): VNode | undefined {
- let singleRoot
- for (let i = 0; i < children.length; i++) {
- const child = children[i]
- if (isVNode(child)) {
- // ignore user comment
- if (child.type !== Comment || child.children === 'v-if') {
- if (singleRoot) {
- // has more than 1 non-comment child, return now
- return
- } else {
- singleRoot = child
- }
- }
- } else {
- return
- }
- }
- return singleRoot
- }
- const getFunctionalFallthrough = (attrs: Data): Data | undefined => {
- let res: Data | undefined
- for (const key in attrs) {
- if (key === 'class' || key === 'style' || isOn(key)) {
- ;(res || (res = {}))[key] = attrs[key]
- }
- }
- return res
- }
- const filterModelListeners = (attrs: Data, props: NormalizedProps): Data => {
- const res: Data = {}
- for (const key in attrs) {
- if (!isModelListener(key) || !(key.slice(9) in props)) {
- res[key] = attrs[key]
- }
- }
- return res
- }
- const isElementRoot = (vnode: VNode) => {
- return (
- vnode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.ELEMENT) ||
- vnode.type === Comment // potential v-if branch switch
- )
- }
- export function shouldUpdateComponent(
- prevVNode: VNode,
- nextVNode: VNode,
- optimized?: boolean
- ): boolean {
- const { props: prevProps, children: prevChildren, component } = prevVNode
- const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
- const emits = component!.emitsOptions
- // Parent component's render function was hot-updated. Since this may have
- // caused the child component's slots content to have changed, we need to
- // force the child to update as well.
- if (__DEV__ && (prevChildren || nextChildren) && isHmrUpdating) {
- return true
- }
- // force child update for runtime directive or transition on component vnode.
- if (nextVNode.dirs || nextVNode.transition) {
- return true
- }
- if (optimized && patchFlag >= 0) {
- if (patchFlag & PatchFlags.DYNAMIC_SLOTS) {
- // slot content that references values that might have changed,
- // e.g. in a v-for
- return true
- }
- if (patchFlag & PatchFlags.FULL_PROPS) {
- if (!prevProps) {
- return !!nextProps
- }
- // presence of this flag indicates props are always non-null
- return hasPropsChanged(prevProps, nextProps!, emits)
- } else if (patchFlag & PatchFlags.PROPS) {
- const dynamicProps = nextVNode.dynamicProps!
- for (let i = 0; i < dynamicProps.length; i++) {
- const key = dynamicProps[i]
- if (
- nextProps![key] !== prevProps![key] &&
- !isEmitListener(emits, key)
- ) {
- return true
- }
- }
- }
- } else {
- // this path is only taken by manually written render functions
- // so presence of any children leads to a forced update
- if (prevChildren || nextChildren) {
- if (!nextChildren || !(nextChildren as any).$stable) {
- return true
- }
- }
- if (prevProps === nextProps) {
- return false
- }
- if (!prevProps) {
- return !!nextProps
- }
- if (!nextProps) {
- return true
- }
- return hasPropsChanged(prevProps, nextProps, emits)
- }
- return false
- }
- function hasPropsChanged(
- prevProps: Data,
- nextProps: Data,
- emitsOptions: ComponentInternalInstance['emitsOptions']
- ): boolean {
- const nextKeys = Object.keys(nextProps)
- if (nextKeys.length !== Object.keys(prevProps).length) {
- return true
- }
- for (let i = 0; i < nextKeys.length; i++) {
- const key = nextKeys[i]
- if (
- nextProps[key] !== prevProps[key] &&
- !isEmitListener(emitsOptions, key)
- ) {
- return true
- }
- }
- return false
- }
- export function updateHOCHostEl(
- { vnode, parent }: ComponentInternalInstance,
- el: typeof vnode.el // HostNode
- ) {
- while (parent && parent.subTree === vnode) {
- ;(vnode = parent.vnode).el = el
- parent = parent.parent
- }
- }
|