@ vue / cli 4.5.10
Custom: default+ts+unit test+no class-style+eslint+prettier+jest
Install Prettier-Code formatter vscode plugin
Create .prettierrc
{
"semi": false,
"singleQuote": true,
"arrowParens": "always",
"trailingComma": "all"
}
Configuration setting: Format On Save, it is recommended to save as workspace configuration
Please check the latest https://github.com/vuejs/rfcs/pull/227
<script setup>
// imported components are also directly usable in template
import Foo from './Foo.vue'
import { ref } from 'vue'
// write Composition API code just like in a normal setup()
// but no need to manually return everything
const count = ref(0)
const inc = () => {
count.value++
}
</script>
<template>
<Foo :count="count" @click="inc" />
</template>
Compiled Output
<script setup>
import Foo from './Foo.vue'
import { ref } from 'vue'
export default {
setup() {
const count = ref(1)
const inc = () => {
count.value++
}
return {
Foo , // see note below
count ,
inc ,
}
} ,
}
</script>
<template>
<Foo :count="count" @click="inc" />
</template>
Note: the SFC compiler also extracts binding metadata from <script setup>
and use it during template compilation. This is why the template can use Foo
as a component here even though it’s returned from setup()
instead of registered via components
option.
Declaring Props and Emits
<script setup>
import { defineProps, defineEmit } from 'vue'
// expects props options
const props = defineProps({
foo: String,
})
// expects emits options
const emit = defineEmits(['update', 'delete'])
</script>
####### defineConponent function
Mainly returns the type definition of the component, the specific implementation is very simple
// vue-next/package/runtime-core/src/apiDefineComponent.ts
// various definitions
...
// implementation, close to no-op
export function defineComponent(options: unknown) {
return isFunction(options) ? { setup: options, name: options.name } : options
}
Define props
...
<script lang="ts">
import { defineComponent, PropType } from "vue";
export default defineComponent ( {
name : "HelloWorld" ,
props : {
// shorthand
msg : String ,
// complete
name : {
type : String as PropType < string > ,
required : true ,
} ,
} ,
} ) ;
</ script >
The pit that require does not work when extracting common props definitions
...
<script lang="ts">
import { defineComponent, PropType } from "vue";
const PropsType = {
msg : String ,
name : {
type : String as PropType < string > ,
// required
required : true ,
} ,
// solution: add as const, manually tell ts that this object is read-only
} as const
export default defineComponent ( {
name : "HelloWorld" ,
props : PropsType ,
mounted ( ) {
// The name prompt here may also be undefined
// Reason: {required: true} here is not tried correctly because ts does not know This object is read-only, and only read-only can allow {required: true} to be tested correctly by ts
// View the comment mentioned in the source defineComponent definition:
// the Readonly constraint allows TS to treat the type of { required: true} as constant instead of boolean.
// PropsOptions extends Readonly<ComponentPropsOptions>,
this . name
}
} ) ;
</ script >
Equivalent to createElement in React
// main.ts
import { createApp, defineComponent, h } from "vue";
// import App from "./App.vue";
import HelloWorld from './components
/
HelloWorld.vue ' // ts verification does not pass the transposition of require's writing // import img from'./assets
/logo.png
' // eslint error: require is not allowed, cancel the current line Eslint check const img = require ( './assets/logo.png' ) // eslint-disable-line
// Use the h function to simulate app files (sfc: single file component) to generate vue components. App files are almost compiled like this
const App = defineComponent ( {
render ( ) {
// h is equivalent to createElement in React for creating nodes
/ / Native nodes use strings
// Parameters: node type, attributes, child nodes
return h ( 'div' , { id : 'app' } , [
h ( 'img' , {
alt : "Vue logo" ,
// image It can’t be displayed, because the image address vue-loader in the template will be addressed. Here you need to import the image yourself
// src: "./assets/logo.png",
src : img ,
}),
h(HelloWorld, {
msg: "Welcome to Your Vue.js + TypeScript App",
name: "naxies",
})
])
}
})
createApp(App).mount("#app");
In fact, it is the package of createVNode. It is also possible to replace the following code h with createVNode
// vue-next/package/runtime-core/src/apiDefineComponent.ts
// various definitions
...
// Actual implementation
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
const l = arguments.length
if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
// single vnode without props
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren])
}
// props without children
return createVNode(type, propsOrChildren)
} else {
// omit props
return createVNode(type, null, propsOrChildren)
}
} else {
if (l > 3) {
children = Array.prototype.slice.call(arguments, 2)
} else if (l === 3 && isVNode(children)) {
children = [children]
}
return createVNode(type, propsOrChildren, children)
}
}
In addition to the three parameters like h, the createVNode parameters also have some optimized parameters, and vue-loader will make some optimizations through these
// App.vue
< template >
< img alt =" Vue logo " src =" ./assets/logo.png " />
< HelloWorld msg =" Welcome to Your Vue.js + TypeScript App " name =" naxies " / >
< h2 > {{state.age}}~ </ h2 >
<!-- It is not necessary. The value is because sfc will judge whether it is a ref currently -->
< h2 > ref {{ageRef}}~ </ h2 >
< h2 >computedAgeRef {{computedAgeRef}} ~ </ h2>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, computed, watchEffect } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent ( {
name : 'App' ,
components : {
HelloWorld ,
} ,
mounted ( ) {
// The ref in this is a proxy or you don't need to write .value, it will automatically determine the
console . log ( this . ageRef )
} ,
// and the same data only once at initialization
Setup ( the props , { slots , attrs , EMIT } ) {
// returns: sfc returns the object
const State = reactive({
age: 18
})
setInterval(() => {
state.age += 1
}, 1000)
// Return {value: xxx} structure object, value is
const ageRef = ref ( 18 )
setInterval ( ( ) => {
ageRef . Value += 1
} , 1000 )
// computed
const computedAgeRef = computed(() => {
return ageRef.value + 2
})
// watchEffect: will be executed when all reactive function references and changes to ref
watchEffect ( ( ) => {
// assignment will be executed each time ageRef
Console . Log ( ageRef . Value ) ;
} )
// Can't use {...state} to return, what is returned is not a responsive
return {
state ,
ageRef ,
computedAgeRef
}
}
})
</script>
Setup returns the usage of render function
// main.ts
import { createApp, defineComponent, h, reactive, ref } from "vue";
// import App from "./App.vue";
import HelloWorld from './components/HelloWorld.vue'
const img = require('./assets/logo.png') // eslint-disable-line
// setup returns the writing of the render function =======================
const App = defineComponent ( {
setup ( ) {
// You can also write other codes here
// Both reactive and ref are used to define reactive data. It is more recommended to define complex data types. ref is more recommended to define basic types. It can be simply understood that ref is a secondary packaging of reactive and access to data defined by ref One more time. value
const state = reactive ( {
age : 18
} )
const ageRef = ref(18)
setInterval(() => {
ageRef.value += 1
}, 1000)
// Return to the render function
return ( ) => {
// Put this sentence outside the render function and the page display will not dynamically change according to the timer
// Because setup is only executed once, ageRefNum is always the initialized value
// reactive or The change of ref will re-execute this render function to form a dom tree, so the final value reading needs to be executed in render
const ageRefNum = ageRef . value
return h ( 'div' , { id : 'app' } , [
h ( 'img' , {
alt : "Vue logo" ,
src : img ,
} ) ,
h ( HelloWorld, {
msg : "Welcome to Your Vue.js + TypeScript App" ,
name : "naxies" ,
} ) ,
// You can get the value of the closure
h ( 'h2' , state . age ) ,
h ( 'h2' , 'ageRef ' + ageRefNum )
] )
}
}
} )
createApp(App).mount("#app");
// watching a getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
// A watcher can also watch multiple sources at the same time using an array
const firstName = ref('');
const lastName = ref('');
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues);
})
firstName.value = "John"; // logs: ["John",""] ["", ""]
lastName.value = "Smith"; // logs: ["John", "Smith"] ["John", ""]
https://github.com/vuejs/jsx-next
Install the plugin with:
npm install @vue/babel-plugin-jsx -D
Then add the plugin to .babelrc:
{
"plugins": ["@vue/babel-plugin-jsx"]
}
Advantages: ts can be verified at compile time (ts cannot recognize the type exported in the vue file, it is a unified type), flexible to use (return and reuse html fragments through functions, etc.), and can use instructions
Used to define json data, verify data, multi-terminal universal
Official draft: json-schema.org
js library of json-schema, https://ajv.js.org/
// or ESM/TypeScript import
// import Ajv from "ajv"
// Node.js require:
const Ajv = require("ajv").default
const addFormats = require("ajv-formats")
const localize = require('ajv-i18n');
// Simple
let schema = {
type : 'string' ,
minLength : 10 ,
}
// More complicated
schema = {
type : 'object' ,
properties : {
name : {
type : 'string' ,
maxLength : 10 ,
} ,
age : {
type : 'number' ,
} ,
pets : {
type :'array' ,
// The first way of defining
// items: {
// type:'string',
// },
// The second kind of
items : [
{ type : 'string' , } ,
{ type : 'number ' , }
]
} ,
email : {
type : 'string' ,
format : 'email' ,
} ,
testFormatProperty : {
type : 'string' ,
format : 'testFormat' ,
} ,
testKeywordProperty : {
type : 'string' ,
testKeyword : 'testKeyword' ,
// provided by ajv-errors
// Any rule error will only display this error message
// errorMessage:'You are wrong again! ',
// Distinguish keyword setting error message
errorMessage : {
type : 'What you passed is not a string, don't you even recognize a string! ' ,
testKeyword : 'Guess where you are wrong again! '
} ,
}
} ,
required : [ 'name' , 'age' ] ,
}
let data = 'naixes'
data = {
name: 'naixes',
age: 18,
pets: ['egg core', 2],
testFormatProperty: 'testFormat',
testKeywordProperty: 'hello!',
}
// To use ajv-errors, you need to pass in {allErrors: true}
const ajv = new Ajv ( { allErrors : true } ) // options can be passed, eg {allErrors: true}
// version 7 and above formats need to install a plug-in separately
addFormats ( ajv )
// 引入 ajv-errors 库
require ( 'ajv-errors' ) ( ajv )
// 定义 定义 format
ajv . addFormat ( 'testFormat' , ( data ) => {
return data === 'testFormat'
} )
// Custom keyword
ajv . addKeyword ( {
keyword : 'testKeyword' ,
// Method 1: Call when validate
// validate: xx = (schema, data) => {
// console.log('schema', schema,'data', data);
// // Custom error message, You can also use the library to customize error information
// xx.errors = [
// {
// keyword:'testKeyword',
// dataPath:'/testKeywordProperty',
// schemaPath:'#/properties/testKeywordProperty/testKeyword',
/ / params: {},
// message:'You are wrong! '
//}
//]
// return data.length === 3
// },
// Method 2: Call when compile
// compile: (sch, parentSchema) => {
// console.log('sch', sch,'parentSchema', parentSchema);
// // To return a function
// return () => true
// },
// // The schema of the value received by this keyword
// metaSchema: (),
// Method 3: Equivalent to combining multiple schema
macros : ( sch , parentSchema ) => {
// console.log('sch', sch,'parentSchema', parentSchema);
// To return a schema
return {
minLength : 10 ,
}
} ,
metaSchema : { } ,
// Will overwrite the custom error message in Method 1
errors : false ,
} )
const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) {
localize.zh(validate.errors)
console.log(validate.errors)
}
installation:npm i ajv
####### Formats
format: some commonly used validation rules, only for string and number types
installation:npm i ajv-formats
From version 7 Ajv does not include formats defined by JSON Schema specification - these and several other formats are provided by ajv-formats plugin.
To add all formats from this plugin:
import Ajv from "ajv"
import addFormats from "ajv-formats"
const ajv = new Ajv ( )
addFormats ( ajv )
Custom format
ajv.addFormat (name: string, format: Format): Ajv
type Format =
| true // to ignore this format (and pass validation)
| string // will be converted to RegExp
| RegExp
| (data: string) => boolean
| Object // format definition (see below and in types)
Add format to validate strings or numbers.
If object is passed it should have properties validate
, compare
and async
:
interface FormatDefinition { // actual type definition is more precise - see types.ts
validate: string | RegExp | (data: number | string) => boolean | Promise<boolean>
compare: (data1: string, data2: string): number // an optional function that accepts two strings
// and compares them according to the format meaning.
// This function is used with keywords `formatMaximum`/`formatMinimum`
// (defined in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package).
// It should return `1` if the first value is bigger than the second value,
// `-1` if it is smaller and `0` if it is equal.
async?: true // if `validate` is an asynchronous function
type?: "string" | "number" // "string" is default. If data type is different, the validation will pass.
}
Formats can be also added via formats
option.
####### keywords
Four ways to customize keywords
ajv.addKeyword({
keyword: "constant",
validate: (schema, data) =>
typeof schema == "object" && schema !== null ? deepEqual(schema, data) : schema === data,
errors: false,
})
const schema = {
constant: 2,
}
const validate = ajv.compile(schema)
console.log(validate(2)) // true
console.log(validate(3)) // false
const schema = {
constant: {foo: "bar"},
}
const validate = ajv.compile(schema)
console.log(validate({foo: "bar"})) // true
console.log(validate({foo: "baz"})) // false
ajv.addKeyword({
keyword: "range",
type: "number",
compile([min, max], parentSchema) {
return parentSchema.exclusiveRange === true
? (data) => data > min && data < max
: (data) => data >= min && data <= max
},
errors: false,
metaSchema: {
// schema to validate keyword value
type: "array",
items: [{type: "number"}, {type: "number"}],
minItems: 2,
additionalItems: false,
},
})
const schema = {
range: [2, 4],
exclusiveRange: true,
}
const validate = ajv.compile(schema)
console.log(validate(2.01)) // true
console.log(validate(3.99)) // true
console.log(validate(2) ) // false
console . log ( validate ( 4 ) ) // false
ajv.addKeyword({
keyword: "range",
type: "number",
macro: ([minimum, maximum]) => ({minimum, maximum}), // schema with keywords minimum and maximum
// metaSchema: the same as in the example above
})
####### Error message
Display Chinese error message:
Install ajv-i18n
var Ajv = require('ajv'); // version >= 2.0.0
var localize = require('ajv-i18n');
// option `i18n` is required for this package to work
var ajv = Ajv({ allErrors: true });
var validate = ajv.compile(schema);
var valid = validate(data);
if (!valid) {
// ru for Russian
localize.ru(validate.errors);
// string with all errors and data paths
console.log(ajv.errorsText(validate.errors, { separator: '\n' }));
}
Custom error message
...
// Custom keyword
ajv . AddKeyword ( {
keyword : 'testKeyword' ,
// Method 1: When calls validate
validate : XX = ( Schema , Data ) => {
Console . Log ( 'Schema' , Schema , 'Data' , Data ) ;
// custom error message
XX . Errors = [
{
keyword : 'testKeyword' ,
dataPath : '/testKeywordProperty' ,
schemaPath : '#/properties/testKeywordProperty/testKeyword' ,
params : { },
message : 'You are wrong! '
}
]
return data . length === 3
} ,
// overrides custom error messages
// errors: false,
} )
. . .
: : Ajv-errors
const Ajv = require("ajv").default
const ajv = new Ajv({allErrors: true})
// Ajv option allErrors is required
require("ajv-errors")(ajv /*, {singleError: true} */)
const schema = {
type: "object",
required: ["foo"],
properties: {
foo: {type: "integer"},
},
additionalProperties: false,
errorMessage: "should be an object with an integer property foo only",
}
const validate = ajv.compile(schema)
console.log(validate({foo: "a", bar: 2})) // false
console.log(validate.errors) // processed errors
<JsonSchemaForm
schema={schema}
value={value}
locale={locale}
onChange={handleChange}
contextRef={someRef}
uiSchema={uiSchema}
></JsonSchemaForm>
json schema object, used to define the data, but also the basis for us to define the form
The data result of the form, you can change the value from the outside. When the form is edited, the onChange
value will be revealed through
It should be noted that because Vue uses variable data, if we change value
the object address every time the data changes , the entire form will need to be re-rendered, which will cause performance degradation. From a practical point of view, the internal modification of the value of the field of the object we passed in will basically not have any side effects, so we will use this method to implement it. In other words, if it value
is an object, then JsonSchemaForm
the value modified from the inside will not change the value
object itself. We will still trigger onChange
, because the user may need to perform some actions after the form changes.
The callback method will be triggered when there is any change in the form value, and the new value will be returned
Language, use the language of the ajv-i18n
specified error message
You need to pass in a vue3 Ref
object, we will mount the doValidate
method on this object , you can pass
const yourRef = ref({})
onMounted(() => {
yourRef.value.doValidate()
})
<JsonSchemaForm contextRef={yourRef} />
In this way, the form is actively verified.
Make some customizations to the display of the form, the types are as follows:
export interface VueJsonSchemaConfig {
title?: string
descrription?: string
component?: string
additionProps?: {
[key: string]: any
}
withFormItem?: boolean
widget?: 'checkbox' | 'textarea' | 'select' | 'radio' | 'range' | string
items?: UISchema | UISchema[]
}
export interface UISchema extends VueJsonSchemaConfig {
properties?: {
[property: string]: UISchema
}
}
The css in js library is based on the secondary development of jss
npm i vue-jss jss jss-preset-default
Code editor
/* eslint no-use-before-define: 0 */
import { defineComponent, ref, onMounted, watch, onBeforeUnmount, shallowReadonly, shallowRef } from 'vue'
import * as Monaco from 'monaco-editor'
import type { PropType, Ref } from 'vue'
import { createUseStyles } from 'vue-jss'
// 返回一个方法
const useStyles = createUseStyles({
container: {
border: '1px solid #eee',
display: 'flex',
flexDirection: 'column',
borderRadius: 5
},
title: {
backgroundColor: '#eee',
padding: '10px 0',
paddingLeft: 20,
},
code: {
flexGrow: 1
}
})
export default defineComponent({
props: {
code: {
type: String as PropType<string>,
required: true
},
onChange: {
type: Function as PropType<(value: string, event: Monaco.editor.IModelContentChangedEvent) => void>,
required: true
},
title: {
type: String as PropType<string>,
required: true
}
},
setup(props) {
// must be shallowRef, if not, editor.getValue() won't work
const editorRef = shallowRef()
const containerRef = ref()
let _subscription: Monaco.IDisposable | undefined
let __prevent_trigger_change_event = false // eslint-disable-line
onMounted(() => {
const editor = editorRef.value = Monaco.editor.create(containerRef.value, {
value: props.code,
language: 'json',
formatOnPaste: true,
tabSize: 2,
minimap: {
enabled: false,
},
})
_subscription = editor.onDidChangeModelContent((event) => {
console.log('--------->', __prevent_trigger_change_event) // eslint-disable-line
if (!__prevent_trigger_change_event) { // eslint-disable-line
props.onChange(editor.getValue(), event);
}
});
})
onBeforeUnmount(() => {
if (_subscription)
_subscription.dispose()
})
watch(() => props.code, (v) => {
const editor = editorRef.value
const model = editor.getModel()
if (v !== model.getValue()) {
editor.pushUndoStop();
__prevent_trigger_change_event = true // eslint-disable-line
// pushEditOperations says it expects a cursorComputer, but doesn't seem to need one.
model.pushEditOperations(
[],
[
{
range: model.getFullModelRange(),
text: v,
},
]
);
editor.pushUndoStop();
__prevent_trigger_change_event = false // eslint-disable-line
}
// if (v !== editorRef.value.getValue()) {
// editorRef.value.setValue(v)
// }
})
const classesRef = useStyles()
return () => {
const classes = classesRef.value
return (
<div class={classes.container}>
<div class={classes.title}><span>{props.title}</span></div>
<div class={classes.code} ref={containerRef}></div>
</div>
)
}
}
})
Plug-in: monaco-editor-webpack-plugin
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
module.exports = {
chainWebpack(config) {
config.plugin('monaco').use(new MonacoWebpackPlugin())
},
}
app.tsx
import { defineComponent, reactive, ref, Ref, watchEffect } from "vue";
import {createUseStyles} from 'vue-jss'
import MonacoEditor from './components/MonacoEditor'
import demos from './demos'
import SchemaForm from '../lib'
// TODO:在lib中export
type Schema = any
type UISchema = any
function jsonToString(data: any) {
return JSON.stringify(data, null, 2)
}
// 样式
const useStyles = createUseStyles({
...
})
export default defineComponent ( {
setup ( ) {
// Record the currently selected demo
const selectedRef : Ref < number > = ref ( 0 )
const demo : {
schema : Schema | null ,
data : any ,
uiSchema : UISchema | null ,
schemaCode : string ,
dataCode : string ,
uiSchemaCode : string ,
} = reactive ( {
schema : null ,
data : { } ,
uiSchema : { } ,
schemaCode: '' ,
dataCode : '' ,
uiSchemaCode : '' ,
} )
watchEffect(() => {
const index = selectedRef.value
const d = demos[index]
demo.schema = d.schema
demo.data = d.default
demo.uiSchema = d.uiSchema
demo.schemaCode = jsonToString(d.schema)
demo.dataCode = jsonToString(d.default)
demo.uiSchemaCode = jsonToString(d.uiSchema)
})
const handleChange = (v: any) => {
demo.data = v
demo.dataCode = jsonToString(v)
}
// 工厂函数
const handleCodeChange = (
field: 'schema' | 'data' | 'uiSchema',
value: string
) => {
let json: any
try {
json = JSON.parse(value)
demo[field] = json
;(demo as any)[`${field}Code`] = value
} catch (err){
console.log(err);
}
}
const handleSchemaChange = (v: string) => handleCodeChange('schema', v)
const handleDataChange = (v: string) => handleCodeChange('data', v)
const handleUISchemaChange = (v: string) => handleCodeChange('uiSchema', v)
const classesRef = useStyles()
return () => {
const classes = classesRef.value
const selected = selectedRef.value
return ( // <StyleThemeProvider>
// <VJSFThemeProvider theme={theme as any}>
<div class={classes.container}>
<div class={classes.menu}>
<h1>Vue3 JsonSchema Form</h1>
<div>
{demos.map((demo, index) => (
<button
class={{
[classes.menuButton]: true,
[classes.menuSelected]: index === selected,
}}
onClick={() => (selectedRef.value = index)}
>
{demo.name}
</button>
))}
</div>
</div>
<div class={classes.content}>
<div class={classes.code}>
<MonacoEditor
code={demo.schemaCode}
class={classes.codePanel}
onChange={handleSchemaChange}
title="Schema"
/>
<div class={classes.uiAndValue}>
<MonacoEditor
code={demo.uiSchemaCode}
class={classes.codePanel}
onChange={handleUISchemaChange}
title="UISchema"
/>
<MonacoEditor
code={demo.dataCode}
class={classes.codePanel}
onChange={handleDataChange}
title="Value"
/>
</div>
</div>
<div class={classes.form}>
<SchemaForm
schema={demo.schema}
onChange={handleChange}
value={demo.data}
/>
{/* <SchemaForm
schema={demo.schema!}
uiSchema={demo.uiSchema!}
onChange={handleChange}
contextRef={methodRef}
value={demo.data}
/> */}
</div>
</div>
</div>
// </VJSFThemeProvider>
// </StyleThemeProvider>
)
}
}
})
SchemaItem: Distribute different types of schemas
fields/xxxField: specific implementation
<template>
<input type="text" :value="value" @input="handleChange" />
</template>
<script lang='ts' setup>
import {defineProps} from 'vue'
import {FieldPropsDefine, Schema} from '../types'
// Using FieldPropsDefine directly will report an error
const props = defineProps ({ ... FieldPropsDefine })
const handleChange = (e: any) => {
props.onChange(e.target.value)
}
</script>
Do not refer to components circularly, it is difficult to find errors, use plug-ins to remindnpm i -D circular-dependency-plugin
Use provide to solve the problem of circular references
Source code: apiInject.ts
// /packages/runtime-core/src/apiInject.ts
import { isFunction } from '@vue/shared'
import { currentInstance } from './component'
import { currentRenderingInstance } from './componentRenderUtils'
import { warn } from './warning'
export interface InjectionKey<T> extends Symbol {}
export function provide < T > ( key : InjectionKey < T > | string | number , value : T ) {
// Determine whether it is in the component rendering process, that is, you can only use
if ( ! currentInstance ) {
if ( __DEV__ ) {
warn ( `provide() can only be used inside setup().` )
}
} else {
let provides = currentInstance .provides
// by default an instance inherits its parent's provides object
// but when it needs to provide values of its own, it creates its
// own provides object using parent provides object as prototype.
// this way in `inject` we can simply look up injections from direct
// parent and let the prototype chain do the work.
// By default, the instance inherits the Provides object of its parent object
// But when it needs to provide its own value, it will use the parent provided object as prototype to create objects
// in this way, the `inject`, we can look directly from the parent object directly injected into the query so that the prototype chain works
const parentProvides =
the currentInstance of . parent && the currentInstance of . parent . the Provides
IF ( parentProvides == = provides) {
provides = currentInstance.provides = Object.create(parentProvides)
}
// TS doesn't allow symbol as index type
provides[key as string] = value
}
}
...
export function inject(
key: InjectionKey<any> | string,
defaultValue?: unknown,
treatDefaultAsFactory = false
) {
// fallback to `currentRenderingInstance` so that this can be called in
// a functional component
const instance = currentInstance || currentRenderingInstance
if (instance) {
// #2400
// to support `app.use` plugins,
// fallback to appContext's `provides` if the intance is at root
const provides =
instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
if (provides && (key as string | symbol) in provides) {
// TS doesn't allow symbol as index type
return provides[key as string]
} else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue()
: defaultValue
} else if (__DEV__) {
warn(`injection "${String(key)}" not found.`)
}
} else if (__DEV__) {
warn(`inject() can only be used inside setup() or functional components.`)
}
}
Optimization: Encapsulate common logic
/**
* Three cases
*
* Single-type arrays, with no length limitation by default, are all the same type
* {
* items: {type:'string'}
*}
* Fixed length, which types are respectively
* {
* items: [
* {type:'string'},
* {type:'numer'}
*]
*}
* enum means optional
items * {
* items: {type:'string', enum: ['1', '2']}
*}
*/
Fixed-length array rendering
Single type array rendering
Contains optional array rendering
Author: Naixes
Source Code: https://github.com/Naixes/vue3-json-schema-form
#vue #vuejs #javascript