componentProps.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. import {
  2. EMPTY_ARR,
  3. NO,
  4. YES,
  5. camelize,
  6. hasOwn,
  7. isFunction,
  8. isString,
  9. } from '@vue/shared'
  10. import type { VaporComponent, VaporComponentInstance } from './component'
  11. import {
  12. type NormalizedPropsOptions,
  13. baseNormalizePropsOptions,
  14. currentInstance,
  15. isEmitListener,
  16. popWarningContext,
  17. pushWarningContext,
  18. resolvePropValue,
  19. simpleSetCurrentInstance,
  20. validateProps,
  21. warn,
  22. } from '@vue/runtime-dom'
  23. import { normalizeEmitsOptions } from './componentEmits'
  24. import { renderEffect } from './renderEffect'
  25. export type RawProps = Record<string, () => unknown> & {
  26. // generated by compiler for :[key]="x" or v-bind="x"
  27. $?: DynamicPropsSource[]
  28. }
  29. type DynamicPropsSource =
  30. | (() => Record<string, unknown>)
  31. | Record<string, () => unknown>
  32. // TODO optimization: maybe convert functions into computeds
  33. export function resolveSource(
  34. source: Record<string, any> | (() => Record<string, any>),
  35. ): Record<string, any> {
  36. return isFunction(source) ? source() : source
  37. }
  38. export function getPropsProxyHandlers(
  39. comp: VaporComponent,
  40. ): [
  41. ProxyHandler<VaporComponentInstance> | null,
  42. ProxyHandler<VaporComponentInstance>,
  43. ] {
  44. if (comp.__propsHandlers) {
  45. return comp.__propsHandlers
  46. }
  47. const propsOptions = normalizePropsOptions(comp)[0]
  48. const emitsOptions = normalizeEmitsOptions(comp)
  49. const isProp = (
  50. propsOptions
  51. ? (key: string | symbol) =>
  52. isString(key) && hasOwn(propsOptions, camelize(key))
  53. : NO
  54. ) as (key: string | symbol) => key is string
  55. const isAttr = propsOptions
  56. ? (key: string) =>
  57. key !== '$' && !isProp(key) && !isEmitListener(emitsOptions, key)
  58. : YES
  59. const getProp = (instance: VaporComponentInstance, key: string | symbol) => {
  60. if (!isProp(key)) return
  61. const rawProps = instance.rawProps
  62. const dynamicSources = rawProps.$
  63. if (dynamicSources) {
  64. let i = dynamicSources.length
  65. let source, isDynamic, rawKey
  66. while (i--) {
  67. source = dynamicSources[i]
  68. isDynamic = isFunction(source)
  69. source = isDynamic ? (source as Function)() : source
  70. for (rawKey in source) {
  71. if (camelize(rawKey) === key) {
  72. return resolvePropValue(
  73. propsOptions!,
  74. key,
  75. isDynamic ? source[rawKey] : source[rawKey](),
  76. instance,
  77. resolveDefault,
  78. )
  79. }
  80. }
  81. }
  82. }
  83. for (const rawKey in rawProps) {
  84. if (camelize(rawKey) === key) {
  85. return resolvePropValue(
  86. propsOptions!,
  87. key,
  88. rawProps[rawKey](),
  89. instance,
  90. resolveDefault,
  91. )
  92. }
  93. }
  94. return resolvePropValue(
  95. propsOptions!,
  96. key,
  97. undefined,
  98. instance,
  99. resolveDefault,
  100. true,
  101. )
  102. }
  103. const propsHandlers = propsOptions
  104. ? ({
  105. get: (target, key) => getProp(target, key),
  106. has: (_, key) => isProp(key),
  107. getOwnPropertyDescriptor(target, key) {
  108. if (isProp(key)) {
  109. return {
  110. configurable: true,
  111. enumerable: true,
  112. get: () => getProp(target, key),
  113. }
  114. }
  115. },
  116. ownKeys: () => Object.keys(propsOptions),
  117. } satisfies ProxyHandler<VaporComponentInstance>)
  118. : null
  119. if (__DEV__ && propsOptions) {
  120. Object.assign(propsHandlers!, {
  121. set: propsSetDevTrap,
  122. deleteProperty: propsDeleteDevTrap,
  123. })
  124. }
  125. const getAttr = (target: RawProps, key: string) => {
  126. if (!isProp(key) && !isEmitListener(emitsOptions, key)) {
  127. return getAttrFromRawProps(target, key)
  128. }
  129. }
  130. const hasAttr = (target: RawProps, key: string) => {
  131. if (isAttr(key)) {
  132. return hasAttrFromRawProps(target, key)
  133. } else {
  134. return false
  135. }
  136. }
  137. const attrsHandlers = {
  138. get: (target, key: string) => getAttr(target.rawProps, key),
  139. has: (target, key: string) => hasAttr(target.rawProps, key),
  140. getOwnPropertyDescriptor(target, key: string) {
  141. if (hasAttr(target.rawProps, key)) {
  142. return {
  143. configurable: true,
  144. enumerable: true,
  145. get: () => getAttr(target.rawProps, key),
  146. }
  147. }
  148. },
  149. ownKeys(target) {
  150. const rawProps = target.rawProps
  151. const keys: string[] = []
  152. for (const key in rawProps) {
  153. if (isAttr(key)) keys.push(key)
  154. }
  155. const dynamicSources = rawProps.$
  156. if (dynamicSources) {
  157. let i = dynamicSources.length
  158. let source
  159. while (i--) {
  160. source = resolveSource(dynamicSources[i])
  161. for (const key in source) {
  162. if (isAttr(key)) keys.push(key)
  163. }
  164. }
  165. }
  166. return Array.from(new Set(keys))
  167. },
  168. } satisfies ProxyHandler<VaporComponentInstance>
  169. if (__DEV__) {
  170. Object.assign(attrsHandlers, {
  171. set: propsSetDevTrap,
  172. deleteProperty: propsDeleteDevTrap,
  173. })
  174. }
  175. return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
  176. }
  177. export function getAttrFromRawProps(rawProps: RawProps, key: string): unknown {
  178. if (key === '$') return
  179. const dynamicSources = rawProps.$
  180. if (dynamicSources) {
  181. let i = dynamicSources.length
  182. let source, isDynamic
  183. while (i--) {
  184. source = dynamicSources[i]
  185. isDynamic = isFunction(source)
  186. source = isDynamic ? (source as Function)() : source
  187. if (hasOwn(source, key)) {
  188. return isDynamic ? source[key] : source[key]()
  189. }
  190. }
  191. }
  192. if (hasOwn(rawProps, key)) {
  193. return rawProps[key]()
  194. }
  195. }
  196. export function hasAttrFromRawProps(rawProps: RawProps, key: string): boolean {
  197. if (key === '$') return false
  198. const dynamicSources = rawProps.$
  199. if (dynamicSources) {
  200. let i = dynamicSources.length
  201. while (i--) {
  202. if (hasOwn(resolveSource(dynamicSources[i]), key)) {
  203. return true
  204. }
  205. }
  206. }
  207. return hasOwn(rawProps, key)
  208. }
  209. export function normalizePropsOptions(
  210. comp: VaporComponent,
  211. ): NormalizedPropsOptions {
  212. const cached = comp.__propsOptions
  213. if (cached) return cached
  214. const raw = comp.props
  215. if (!raw) return EMPTY_ARR as []
  216. const normalized: NormalizedPropsOptions[0] = {}
  217. const needCastKeys: NormalizedPropsOptions[1] = []
  218. baseNormalizePropsOptions(raw, normalized, needCastKeys)
  219. return (comp.__propsOptions = [normalized, needCastKeys])
  220. }
  221. function resolveDefault(
  222. factory: (props: Record<string, any>) => unknown,
  223. instance: VaporComponentInstance,
  224. ) {
  225. const prev = currentInstance
  226. simpleSetCurrentInstance(instance)
  227. const res = factory.call(null, instance.props)
  228. simpleSetCurrentInstance(prev, instance)
  229. return res
  230. }
  231. export function hasFallthroughAttrs(
  232. comp: VaporComponent,
  233. rawProps: RawProps | null | undefined,
  234. ): boolean {
  235. if (rawProps) {
  236. // determine fallthrough
  237. if (rawProps.$ || !comp.props) {
  238. return true
  239. } else {
  240. // check if rawProps contains any keys not declared
  241. const propsOptions = normalizePropsOptions(comp)[0]!
  242. for (const key in rawProps) {
  243. if (!hasOwn(propsOptions, camelize(key))) {
  244. return true
  245. }
  246. }
  247. }
  248. }
  249. return false
  250. }
  251. /**
  252. * dev only
  253. */
  254. export function setupPropsValidation(instance: VaporComponentInstance): void {
  255. const rawProps = instance.rawProps
  256. if (!rawProps) return
  257. renderEffect(() => {
  258. pushWarningContext(instance)
  259. validateProps(
  260. resolveDynamicProps(rawProps),
  261. instance.props,
  262. normalizePropsOptions(instance.type)[0]!,
  263. )
  264. popWarningContext()
  265. }, true /* noLifecycle */)
  266. }
  267. export function resolveDynamicProps(props: RawProps): Record<string, unknown> {
  268. const mergedRawProps: Record<string, any> = {}
  269. for (const key in props) {
  270. if (key !== '$') {
  271. mergedRawProps[key] = props[key]()
  272. }
  273. }
  274. if (props.$) {
  275. for (const source of props.$) {
  276. const isDynamic = isFunction(source)
  277. const resolved = isDynamic ? source() : source
  278. for (const key in resolved) {
  279. mergedRawProps[key] = isDynamic
  280. ? resolved[key]
  281. : (resolved[key] as Function)()
  282. }
  283. }
  284. }
  285. return mergedRawProps
  286. }
  287. function propsSetDevTrap(_: any, key: string | symbol) {
  288. warn(
  289. `Attempt to mutate prop ${JSON.stringify(key)} failed. Props are readonly.`,
  290. )
  291. return true
  292. }
  293. function propsDeleteDevTrap(_: any, key: string | symbol) {
  294. warn(
  295. `Attempt to delete prop ${JSON.stringify(key)} failed. Props are readonly.`,
  296. )
  297. return true
  298. }