apiSetup.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { Component } from 'types/component'
  2. import { PropOptions } from 'types/options'
  3. import { popTarget, pushTarget } from '../core/observer/dep'
  4. import { def, invokeWithErrorHandling, isReserved, warn } from '../core/util'
  5. import VNode from '../core/vdom/vnode'
  6. import {
  7. bind,
  8. emptyObject,
  9. isArray,
  10. isFunction,
  11. isObject
  12. } from '../shared/util'
  13. import { currentInstance, setCurrentInstance } from './currentInstance'
  14. import { shallowReactive } from './reactivity/reactive'
  15. import { isRef } from './reactivity/ref'
  16. /**
  17. * @internal
  18. */
  19. export interface SetupContext {
  20. attrs: Record<string, any>
  21. slots: Record<string, () => VNode[]>
  22. emit: (event: string, ...args: any[]) => any
  23. expose: (exposed: Record<string, any>) => void
  24. }
  25. export function initSetup(vm: Component) {
  26. const options = vm.$options
  27. const setup = options.setup
  28. if (setup) {
  29. const ctx = (vm._setupContext = createSetupContext(vm))
  30. setCurrentInstance(vm)
  31. pushTarget()
  32. const setupResult = invokeWithErrorHandling(
  33. setup,
  34. null,
  35. [vm._props || shallowReactive({}), ctx],
  36. vm,
  37. `setup`
  38. )
  39. popTarget()
  40. setCurrentInstance()
  41. if (isFunction(setupResult)) {
  42. // render function
  43. // @ts-ignore
  44. options.render = setupResult
  45. } else if (isObject(setupResult)) {
  46. // bindings
  47. if (__DEV__ && setupResult instanceof VNode) {
  48. warn(
  49. `setup() should not return VNodes directly - ` +
  50. `return a render function instead.`
  51. )
  52. }
  53. vm._setupState = setupResult
  54. // __sfc indicates compiled bindings from <script setup>
  55. if (!setupResult.__sfc) {
  56. for (const key in setupResult) {
  57. if (!isReserved(key)) {
  58. proxyWithRefUnwrap(vm, setupResult, key)
  59. } else if (__DEV__) {
  60. warn(`Avoid using variables that start with _ or $ in setup().`)
  61. }
  62. }
  63. } else {
  64. // exposed for compiled render fn
  65. const proxy = (vm._setupProxy = {})
  66. for (const key in setupResult) {
  67. proxyWithRefUnwrap(proxy, setupResult, key)
  68. }
  69. }
  70. } else if (__DEV__ && setupResult !== undefined) {
  71. warn(
  72. `setup() should return an object. Received: ${
  73. setupResult === null ? 'null' : typeof setupResult
  74. }`
  75. )
  76. }
  77. }
  78. }
  79. export function proxyWithRefUnwrap(
  80. target: any,
  81. source: Record<string, any>,
  82. key: string
  83. ) {
  84. Object.defineProperty(target, key, {
  85. enumerable: true,
  86. configurable: true,
  87. get: () => {
  88. const raw = source[key]
  89. return isRef(raw) ? raw.value : raw
  90. },
  91. set: newVal => {
  92. const raw = source[key]
  93. isRef(raw) ? (raw.value = newVal) : (source[key] = newVal)
  94. }
  95. })
  96. }
  97. function createSetupContext(vm: Component): SetupContext {
  98. let exposeCalled = false
  99. return {
  100. get attrs() {
  101. return initAttrsProxy(vm)
  102. },
  103. get slots() {
  104. return initSlotsProxy(vm)
  105. },
  106. emit: bind(vm.$emit, vm) as any,
  107. expose(exposed?: Record<string, any>) {
  108. if (__DEV__) {
  109. if (exposeCalled) {
  110. warn(`expose() should be called only once per setup().`, vm)
  111. }
  112. exposeCalled = true
  113. }
  114. if (exposed) {
  115. Object.keys(exposed).forEach(key =>
  116. proxyWithRefUnwrap(vm, exposed, key)
  117. )
  118. }
  119. }
  120. }
  121. }
  122. function initAttrsProxy(vm: Component) {
  123. if (!vm._attrsProxy) {
  124. const proxy = (vm._attrsProxy = {})
  125. def(proxy, '_v_attr_proxy', true)
  126. syncSetupAttrs(proxy, vm.$attrs, emptyObject, vm)
  127. }
  128. return vm._attrsProxy
  129. }
  130. export function syncSetupAttrs(
  131. to: any,
  132. from: any,
  133. prev: any,
  134. instance: Component
  135. ) {
  136. let changed = false
  137. for (const key in from) {
  138. if (!(key in to)) {
  139. changed = true
  140. defineProxyAttr(to, key, instance)
  141. } else if (from[key] !== prev[key]) {
  142. changed = true
  143. }
  144. }
  145. for (const key in to) {
  146. if (!(key in from)) {
  147. changed = true
  148. delete to[key]
  149. }
  150. }
  151. return changed
  152. }
  153. function defineProxyAttr(proxy: any, key: string, instance: Component) {
  154. Object.defineProperty(proxy, key, {
  155. enumerable: true,
  156. configurable: true,
  157. get() {
  158. return instance.$attrs[key]
  159. }
  160. })
  161. }
  162. function initSlotsProxy(vm: Component) {
  163. if (!vm._slotsProxy) {
  164. syncSetupSlots((vm._slotsProxy = {}), vm.$scopedSlots)
  165. }
  166. return vm._slotsProxy
  167. }
  168. export function syncSetupSlots(to: any, from: any) {
  169. for (const key in from) {
  170. to[key] = from[key]
  171. }
  172. for (const key in to) {
  173. if (!(key in from)) {
  174. delete to[key]
  175. }
  176. }
  177. }
  178. /**
  179. * @internal use manual type def
  180. */
  181. export function useSlots(): SetupContext['slots'] {
  182. return getContext().slots
  183. }
  184. /**
  185. * @internal use manual type def
  186. */
  187. export function useAttrs(): SetupContext['attrs'] {
  188. return getContext().attrs
  189. }
  190. function getContext(): SetupContext {
  191. if (__DEV__ && !currentInstance) {
  192. warn(`useContext() called without active instance.`)
  193. }
  194. const vm = currentInstance!
  195. return vm._setupContext || (vm._setupContext = createSetupContext(vm))
  196. }
  197. /**
  198. * Runtime helper for merging default declarations. Imported by compiled code
  199. * only.
  200. * @internal
  201. */
  202. export function mergeDefaults(
  203. raw: string[] | Record<string, PropOptions>,
  204. defaults: Record<string, any>
  205. ): Record<string, PropOptions> {
  206. const props = isArray(raw)
  207. ? raw.reduce(
  208. (normalized, p) => ((normalized[p] = {}), normalized),
  209. {} as Record<string, PropOptions>
  210. )
  211. : raw
  212. for (const key in defaults) {
  213. const opt = props[key]
  214. if (opt) {
  215. if (isArray(opt) || isFunction(opt)) {
  216. props[key] = { type: opt, default: defaults[key] }
  217. } else {
  218. opt.default = defaults[key]
  219. }
  220. } else if (opt === null) {
  221. props[key] = { default: defaults[key] }
  222. } else if (__DEV__) {
  223. warn(`props default key "${key}" has no corresponding declaration.`)
  224. }
  225. }
  226. return props
  227. }