types: massive refactor
This commit is contained in:
@@ -77,6 +77,6 @@ export function createComponent<
|
||||
}
|
||||
|
||||
// implementation, close to no-op
|
||||
export function createComponent(options: any) {
|
||||
export function createComponent(options: unknown) {
|
||||
return isFunction(options) ? { setup: options } : options
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@ export function provide<T>(key: InjectionKey<T> | string, value: T) {
|
||||
|
||||
export function inject<T>(key: InjectionKey<T> | string): T | undefined
|
||||
export function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
|
||||
export function inject(key: InjectionKey<any> | string, defaultValue?: any) {
|
||||
export function inject(
|
||||
key: InjectionKey<any> | string,
|
||||
defaultValue?: unknown
|
||||
) {
|
||||
if (currentInstance) {
|
||||
const provides = currentInstance.provides
|
||||
if (key in provides) {
|
||||
|
||||
@@ -16,7 +16,7 @@ function injectHook(
|
||||
target: ComponentInternalInstance | null
|
||||
) {
|
||||
if (target) {
|
||||
;(target[type] || (target[type] = [])).push((...args: any[]) => {
|
||||
;(target[type] || (target[type] = [])).push((...args: unknown[]) => {
|
||||
if (target.isUnmounted) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
NOOP
|
||||
} from '@vue/shared'
|
||||
import { computed } from './apiReactivity'
|
||||
import { watch, WatchOptions, CleanupRegistrator } from './apiWatch'
|
||||
import { watch, WatchOptions, WatchHandler } from './apiWatch'
|
||||
import { provide, inject } from './apiInject'
|
||||
import {
|
||||
onBeforeMount,
|
||||
@@ -40,7 +40,7 @@ import { Directive } from './directives'
|
||||
import { ComponentPublicInstance } from './componentProxy'
|
||||
import { warn } from './warning'
|
||||
|
||||
interface ComponentOptionsBase<
|
||||
export interface ComponentOptionsBase<
|
||||
Props,
|
||||
RawBindings,
|
||||
D,
|
||||
@@ -119,12 +119,6 @@ export type ExtractComputedReturns<T extends any> = {
|
||||
: ReturnType<T[key]>
|
||||
}
|
||||
|
||||
export type WatchHandler<T = any> = (
|
||||
val: T,
|
||||
oldVal: T,
|
||||
onCleanup: CleanupRegistrator
|
||||
) => any
|
||||
|
||||
type ComponentWatchOptions = Record<
|
||||
string,
|
||||
string | WatchHandler | { handler: WatchHandler } & WatchOptions
|
||||
@@ -134,7 +128,7 @@ type ComponentInjectOptions =
|
||||
| string[]
|
||||
| Record<
|
||||
string | symbol,
|
||||
string | symbol | { from: string | symbol; default?: any }
|
||||
string | symbol | { from: string | symbol; default?: unknown }
|
||||
>
|
||||
|
||||
// TODO type inference for these options
|
||||
@@ -294,12 +288,12 @@ export function applyOptions(
|
||||
set: isFunction(set)
|
||||
? set.bind(ctx)
|
||||
: __DEV__
|
||||
? () => {
|
||||
warn(
|
||||
`Computed property "${key}" was assigned to but it has no setter.`
|
||||
)
|
||||
}
|
||||
: NOOP
|
||||
? () => {
|
||||
warn(
|
||||
`Computed property "${key}" was assigned to but it has no setter.`
|
||||
)
|
||||
}
|
||||
: NOOP
|
||||
})
|
||||
} else if (__DEV__) {
|
||||
warn(`Computed property "${key}" has no getter.`)
|
||||
|
||||
@@ -21,7 +21,12 @@ import {
|
||||
} from './errorHandling'
|
||||
import { onBeforeUnmount } from './apiLifecycle'
|
||||
import { queuePostRenderEffect } from './createRenderer'
|
||||
import { WatchHandler } from './apiOptions'
|
||||
|
||||
export type WatchHandler<T = any> = (
|
||||
value: T,
|
||||
oldValue: T,
|
||||
onCleanup: CleanupRegistrator
|
||||
) => any
|
||||
|
||||
export interface WatchOptions {
|
||||
lazy?: boolean
|
||||
@@ -58,11 +63,7 @@ export function watch<T>(
|
||||
// overload #3: array of multiple sources + cb
|
||||
export function watch<T extends readonly WatcherSource<unknown>[]>(
|
||||
sources: T,
|
||||
cb: (
|
||||
newValues: MapSources<T>,
|
||||
oldValues: MapSources<T>,
|
||||
onCleanup: CleanupRegistrator
|
||||
) => any,
|
||||
cb: WatchHandler<MapSources<T>>,
|
||||
options?: WatchOptions
|
||||
): StopHandle
|
||||
|
||||
@@ -84,9 +85,7 @@ export function watch<T = any>(
|
||||
|
||||
function doWatch(
|
||||
source: WatcherSource | WatcherSource[] | SimpleEffect,
|
||||
cb:
|
||||
| ((newValue: any, oldValue: any, onCleanup: CleanupRegistrator) => any)
|
||||
| null,
|
||||
cb: WatchHandler | null,
|
||||
{ lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
|
||||
): StopHandle {
|
||||
const instance = currentInstance
|
||||
@@ -95,11 +94,10 @@ function doWatch(
|
||||
let getter: () => any
|
||||
if (isArray(source)) {
|
||||
getter = () =>
|
||||
source.map(
|
||||
s =>
|
||||
isRef(s)
|
||||
? s.value
|
||||
: callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
|
||||
source.map(s =>
|
||||
isRef(s)
|
||||
? s.value
|
||||
: callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
|
||||
)
|
||||
} else if (isRef(source)) {
|
||||
getter = () => source.value
|
||||
@@ -218,7 +216,7 @@ export function instanceWatch(
|
||||
return stop
|
||||
}
|
||||
|
||||
function traverse(value: any, seen: Set<any> = new Set()) {
|
||||
function traverse(value: unknown, seen: Set<unknown> = new Set()) {
|
||||
if (!isObject(value) || seen.has(value)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export const enum LifecycleHooks {
|
||||
ERROR_CAPTURED = 'ec'
|
||||
}
|
||||
|
||||
export type Emit = ((event: string, ...args: unknown[]) => void)
|
||||
export type Emit = (event: string, ...args: unknown[]) => void
|
||||
|
||||
export interface SetupContext {
|
||||
attrs: Data
|
||||
@@ -86,13 +86,13 @@ export interface ComponentInternalInstance {
|
||||
accessCache: Data | null
|
||||
// cache for render function values that rely on _ctx but won't need updates
|
||||
// after initialized (e.g. inline handlers)
|
||||
renderCache: any[] | null
|
||||
renderCache: Function[] | null
|
||||
|
||||
components: Record<string, Component>
|
||||
directives: Record<string, Directive>
|
||||
|
||||
asyncDep: Promise<any> | null
|
||||
asyncResult: any
|
||||
asyncResult: unknown
|
||||
asyncResolved: boolean
|
||||
|
||||
// the rest are only for stateful components
|
||||
|
||||
@@ -30,12 +30,12 @@ interface PropOptions<T = any> {
|
||||
type?: PropType<T> | true | null
|
||||
required?: boolean
|
||||
default?: T | null | undefined | (() => T | null | undefined)
|
||||
validator?(value: any): boolean
|
||||
validator?(value: unknown): boolean
|
||||
}
|
||||
|
||||
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
|
||||
|
||||
type PropConstructor<T> = { new (...args: any[]): T & object } | { (): T }
|
||||
type PropConstructor<T = any> = { new (...args: any[]): T & object } | { (): T }
|
||||
|
||||
type RequiredKeys<T, MakeDefaultRequired> = {
|
||||
[K in keyof T]: T[K] extends
|
||||
@@ -53,10 +53,12 @@ type OptionalKeys<T, MakeDefaultRequired> = Exclude<
|
||||
type InferPropType<T> = T extends null
|
||||
? any // null & true would fail to infer
|
||||
: T extends { type: null | true }
|
||||
? any // somehow `ObjectConstructor` when inferred from { (): T } becomes `any`
|
||||
: T extends ObjectConstructor | { type: ObjectConstructor }
|
||||
? { [key: string]: any }
|
||||
: T extends Prop<infer V> ? V : T
|
||||
? any // somehow `ObjectConstructor` when inferred from { (): T } becomes `any`
|
||||
: T extends ObjectConstructor | { type: ObjectConstructor }
|
||||
? { [key: string]: any }
|
||||
: T extends Prop<infer V>
|
||||
? V
|
||||
: T
|
||||
|
||||
export type ExtractPropTypes<
|
||||
O,
|
||||
@@ -96,7 +98,7 @@ type NormalizedPropsOptions = Record<string, NormalizedProp>
|
||||
|
||||
export function resolveProps(
|
||||
instance: ComponentInternalInstance,
|
||||
rawProps: any,
|
||||
rawProps: Data | null,
|
||||
_options: ComponentPropsOptions | void
|
||||
) {
|
||||
const hasDeclaredProps = _options != null
|
||||
@@ -105,18 +107,18 @@ export function resolveProps(
|
||||
return
|
||||
}
|
||||
|
||||
const props: any = {}
|
||||
let attrs: any = void 0
|
||||
const props: Data = {}
|
||||
let attrs: Data | undefined = void 0
|
||||
|
||||
// update the instance propsProxy (passed to setup()) to trigger potential
|
||||
// changes
|
||||
const propsProxy = instance.propsProxy
|
||||
const setProp = propsProxy
|
||||
? (key: string, val: any) => {
|
||||
? (key: string, val: unknown) => {
|
||||
props[key] = val
|
||||
propsProxy[key] = val
|
||||
}
|
||||
: (key: string, val: any) => {
|
||||
: (key: string, val: unknown) => {
|
||||
props[key] = val
|
||||
}
|
||||
|
||||
@@ -192,7 +194,7 @@ export function resolveProps(
|
||||
instance.attrs = options
|
||||
? __DEV__ && attrs != null
|
||||
? readonly(attrs)
|
||||
: attrs
|
||||
: attrs!
|
||||
: instance.props
|
||||
}
|
||||
|
||||
@@ -279,8 +281,8 @@ type AssertionResult = {
|
||||
|
||||
function validateProp(
|
||||
name: string,
|
||||
value: any,
|
||||
prop: PropOptions<any>,
|
||||
value: unknown,
|
||||
prop: PropOptions,
|
||||
isAbsent: boolean
|
||||
) {
|
||||
const { type, required, validator } = prop
|
||||
@@ -317,7 +319,7 @@ function validateProp(
|
||||
|
||||
const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
|
||||
|
||||
function assertType(value: any, type: PropConstructor<any>): AssertionResult {
|
||||
function assertType(value: unknown, type: PropConstructor): AssertionResult {
|
||||
let valid
|
||||
const expectedType = getType(type)
|
||||
if (simpleCheckRE.test(expectedType)) {
|
||||
@@ -342,7 +344,7 @@ function assertType(value: any, type: PropConstructor<any>): AssertionResult {
|
||||
|
||||
function getInvalidTypeMessage(
|
||||
name: string,
|
||||
value: any,
|
||||
value: unknown,
|
||||
expectedTypes: string[]
|
||||
): string {
|
||||
let message =
|
||||
@@ -368,7 +370,7 @@ function getInvalidTypeMessage(
|
||||
return message
|
||||
}
|
||||
|
||||
function styleValue(value: any, type: string): string {
|
||||
function styleValue(value: unknown, type: string): string {
|
||||
if (type === 'String') {
|
||||
return `"${value}"`
|
||||
} else if (type === 'Number') {
|
||||
@@ -378,7 +380,7 @@ function styleValue(value: any, type: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function toRawType(value: any): string {
|
||||
function toRawType(value: unknown): string {
|
||||
return toTypeString(value).slice(8, -1)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@ import { ComponentInternalInstance, Data, Emit } from './component'
|
||||
import { nextTick } from './scheduler'
|
||||
import { instanceWatch } from './apiWatch'
|
||||
import { EMPTY_OBJ, hasOwn, isGloballyWhitelisted } from '@vue/shared'
|
||||
import { ExtractComputedReturns } from './apiOptions'
|
||||
import {
|
||||
ExtractComputedReturns,
|
||||
ComponentOptionsBase,
|
||||
ComputedOptions,
|
||||
MethodOptions
|
||||
} from './apiOptions'
|
||||
import { UnwrapRef, ReactiveEffect } from '@vue/reactivity'
|
||||
import { warn } from './warning'
|
||||
|
||||
@@ -12,8 +17,8 @@ export type ComponentPublicInstance<
|
||||
P = {},
|
||||
B = {},
|
||||
D = {},
|
||||
C = {},
|
||||
M = {},
|
||||
C extends ComputedOptions = {},
|
||||
M extends MethodOptions = {},
|
||||
PublicProps = P
|
||||
> = {
|
||||
[key: string]: any
|
||||
@@ -26,7 +31,7 @@ export type ComponentPublicInstance<
|
||||
$parent: ComponentInternalInstance | null
|
||||
$emit: Emit
|
||||
$el: any
|
||||
$options: any
|
||||
$options: ComponentOptionsBase<P, B, D, C, M>
|
||||
$forceUpdate: ReactiveEffect
|
||||
$nextTick: typeof nextTick
|
||||
$watch: typeof instanceWatch
|
||||
@@ -134,7 +139,10 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||
if (__RUNTIME_COMPILE__) {
|
||||
// this trap is only called in browser-compiled render functions that use
|
||||
// `with (this) {}`
|
||||
PublicInstanceProxyHandlers.has = (_: any, key: string): boolean => {
|
||||
PublicInstanceProxyHandlers.has = (
|
||||
_: ComponentInternalInstance,
|
||||
key: string
|
||||
): boolean => {
|
||||
return key[0] !== '_' && !isGloballyWhitelisted(key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
createComponentInstance,
|
||||
setupStatefulComponent,
|
||||
handleSetupResult,
|
||||
Component
|
||||
Component,
|
||||
Data
|
||||
} from './component'
|
||||
import {
|
||||
renderComponentRoot,
|
||||
@@ -36,7 +37,8 @@ import {
|
||||
ReactiveEffectOptions,
|
||||
isRef,
|
||||
Ref,
|
||||
toRaw
|
||||
toRaw,
|
||||
DebuggerEvent
|
||||
} from '@vue/reactivity'
|
||||
import { resolveProps } from './componentProps'
|
||||
import { resolveSlots } from './componentSlots'
|
||||
@@ -70,7 +72,7 @@ function isSameType(n1: VNode, n2: VNode): boolean {
|
||||
return n1.type === n2.type && n1.key === n2.key
|
||||
}
|
||||
|
||||
function invokeHooks(hooks: Function[], arg?: any) {
|
||||
function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
hooks[i](arg)
|
||||
}
|
||||
@@ -555,8 +557,8 @@ export function createRenderer<
|
||||
function patchProps(
|
||||
el: HostElement,
|
||||
vnode: HostVNode,
|
||||
oldProps: any,
|
||||
newProps: any,
|
||||
oldProps: Data,
|
||||
newProps: Data,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: HostSuspenseBoundary | null,
|
||||
isSVG: boolean
|
||||
@@ -1160,72 +1162,83 @@ export function createRenderer<
|
||||
) {
|
||||
// create reactive effect for rendering
|
||||
let mounted = false
|
||||
instance.update = effect(function componentEffect() {
|
||||
if (!mounted) {
|
||||
const subTree = (instance.subTree = renderComponentRoot(instance))
|
||||
// beforeMount hook
|
||||
if (instance.bm !== null) {
|
||||
invokeHooks(instance.bm)
|
||||
}
|
||||
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
|
||||
initialVNode.el = subTree.el
|
||||
// mounted hook
|
||||
if (instance.m !== null) {
|
||||
queuePostRenderEffect(instance.m, parentSuspense)
|
||||
}
|
||||
mounted = true
|
||||
} else {
|
||||
// updateComponent
|
||||
// This is triggered by mutation of component's own state (next: null)
|
||||
// OR parent calling processComponent (next: HostVNode)
|
||||
const { next } = instance
|
||||
instance.update = effect(
|
||||
function componentEffect() {
|
||||
if (!mounted) {
|
||||
const subTree = (instance.subTree = renderComponentRoot(instance))
|
||||
// beforeMount hook
|
||||
if (instance.bm !== null) {
|
||||
invokeHooks(instance.bm)
|
||||
}
|
||||
patch(
|
||||
null,
|
||||
subTree,
|
||||
container,
|
||||
anchor,
|
||||
instance,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
)
|
||||
initialVNode.el = subTree.el
|
||||
// mounted hook
|
||||
if (instance.m !== null) {
|
||||
queuePostRenderEffect(instance.m, parentSuspense)
|
||||
}
|
||||
mounted = true
|
||||
} else {
|
||||
// updateComponent
|
||||
// This is triggered by mutation of component's own state (next: null)
|
||||
// OR parent calling processComponent (next: HostVNode)
|
||||
const { next } = instance
|
||||
|
||||
if (__DEV__) {
|
||||
pushWarningContext(next || instance.vnode)
|
||||
}
|
||||
if (__DEV__) {
|
||||
pushWarningContext(next || instance.vnode)
|
||||
}
|
||||
|
||||
if (next !== null) {
|
||||
updateComponentPreRender(instance, next)
|
||||
}
|
||||
const prevTree = instance.subTree
|
||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||
// beforeUpdate hook
|
||||
if (instance.bu !== null) {
|
||||
invokeHooks(instance.bu)
|
||||
}
|
||||
// reset refs
|
||||
// only needed if previous patch had refs
|
||||
if (instance.refs !== EMPTY_OBJ) {
|
||||
instance.refs = {}
|
||||
}
|
||||
patch(
|
||||
prevTree,
|
||||
nextTree,
|
||||
// parent may have changed if it's in a portal
|
||||
hostParentNode(prevTree.el as HostNode) as HostElement,
|
||||
// anchor may have changed if it's in a fragment
|
||||
getNextHostNode(prevTree),
|
||||
instance,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
)
|
||||
instance.vnode.el = nextTree.el
|
||||
if (next === null) {
|
||||
// self-triggered update. In case of HOC, update parent component
|
||||
// vnode el. HOC is indicated by parent instance's subTree pointing
|
||||
// to child component's vnode
|
||||
updateHOCHostEl(instance, nextTree.el)
|
||||
}
|
||||
// updated hook
|
||||
if (instance.u !== null) {
|
||||
queuePostRenderEffect(instance.u, parentSuspense)
|
||||
}
|
||||
if (next !== null) {
|
||||
updateComponentPreRender(instance, next)
|
||||
}
|
||||
const prevTree = instance.subTree
|
||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||
// beforeUpdate hook
|
||||
if (instance.bu !== null) {
|
||||
invokeHooks(instance.bu)
|
||||
}
|
||||
// reset refs
|
||||
// only needed if previous patch had refs
|
||||
if (instance.refs !== EMPTY_OBJ) {
|
||||
instance.refs = {}
|
||||
}
|
||||
patch(
|
||||
prevTree,
|
||||
nextTree,
|
||||
// parent may have changed if it's in a portal
|
||||
hostParentNode(prevTree.el as HostNode) as HostElement,
|
||||
// anchor may have changed if it's in a fragment
|
||||
getNextHostNode(prevTree),
|
||||
instance,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
)
|
||||
instance.vnode.el = nextTree.el
|
||||
if (next === null) {
|
||||
// self-triggered update. In case of HOC, update parent component
|
||||
// vnode el. HOC is indicated by parent instance's subTree pointing
|
||||
// to child component's vnode
|
||||
updateHOCHostEl(instance, nextTree.el)
|
||||
}
|
||||
// updated hook
|
||||
if (instance.u !== null) {
|
||||
queuePostRenderEffect(instance.u, parentSuspense)
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
||||
},
|
||||
__DEV__ ? createDevEffectOptions(instance) : prodEffectOptions
|
||||
)
|
||||
}
|
||||
|
||||
function updateComponentPreRender(
|
||||
|
||||
@@ -65,7 +65,7 @@ function applyDirective(
|
||||
props: Record<any, any>,
|
||||
instance: ComponentInternalInstance,
|
||||
directive: Directive,
|
||||
value?: any,
|
||||
value?: unknown,
|
||||
arg?: string,
|
||||
modifiers: DirectiveModifiers = EMPTY_OBJ
|
||||
) {
|
||||
|
||||
@@ -54,9 +54,9 @@ export function callWithErrorHandling(
|
||||
fn: Function,
|
||||
instance: ComponentInternalInstance | null,
|
||||
type: ErrorTypes,
|
||||
args?: any[]
|
||||
args?: unknown[]
|
||||
) {
|
||||
let res: any
|
||||
let res
|
||||
try {
|
||||
res = args ? fn(...args) : fn()
|
||||
} catch (err) {
|
||||
@@ -69,7 +69,7 @@ export function callWithAsyncErrorHandling(
|
||||
fn: Function | Function[],
|
||||
instance: ComponentInternalInstance | null,
|
||||
type: ErrorTypes,
|
||||
args?: any[]
|
||||
args?: unknown[]
|
||||
) {
|
||||
if (isFunction(fn)) {
|
||||
const res = callWithErrorHandling(fn, instance, type, args)
|
||||
|
||||
@@ -2,8 +2,12 @@ import { VNodeChild } from '../vnode'
|
||||
import { isArray, isString, isObject } from '@vue/shared'
|
||||
|
||||
export function renderList(
|
||||
source: any,
|
||||
renderItem: (value: any, key: string | number, index?: number) => VNodeChild
|
||||
source: unknown,
|
||||
renderItem: (
|
||||
value: unknown,
|
||||
key: string | number,
|
||||
index?: number
|
||||
) => VNodeChild
|
||||
): VNodeChild[] {
|
||||
let ret: VNodeChild[]
|
||||
if (isArray(source) || isString(source)) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Data } from '../component'
|
||||
import { Slot } from '../componentSlots'
|
||||
import {
|
||||
VNodeChildren,
|
||||
@@ -11,7 +12,7 @@ import { PatchFlags } from '@vue/shared'
|
||||
export function renderSlot(
|
||||
slots: Record<string, Slot>,
|
||||
name: string,
|
||||
props: any = {},
|
||||
props: Data = {},
|
||||
// this is not a user-facing function, so the fallback is always generated by
|
||||
// the compiler and guaranteed to be an array
|
||||
fallback?: VNodeChildren
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { isArray, isPlainObject, objectToString } from '@vue/shared'
|
||||
|
||||
// for converting {{ interpolation }} values to displayed strings.
|
||||
export function toString(val: any): string {
|
||||
export function toString(val: unknown): string {
|
||||
return val == null
|
||||
? ''
|
||||
: isArray(val) || (isPlainObject(val) && val.toString === objectToString)
|
||||
? JSON.stringify(val, null, 2)
|
||||
: String(val)
|
||||
? JSON.stringify(val, null, 2)
|
||||
: String(val)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user