-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: Create app with same appContext? createChildApp #2097
Comments
Maybe you should use resolveComponent import { resolveComponent } from 'vue'
this.$Message.info(() => h(resolveComponent('component-registered-on-app'), ...)) // Error ❌
// Edited
const messageContent = resolveComponent('component-registered-on-app')
this.$Message.info(() => h(messageContent, ...)) |
Teleport won't work. |
Let me explain more percisely. pseudo code in vue 2/3 index
MessagePlugin
use it
However in vue-next. |
This is one way to implement the same features in vue3. No need to use a new app. // Message HOC
import { Teleport, defineComponent, provide, ref } from 'vue'
const Message = ({ open, text }) => (
<Teleport to="body">{open && <div className="modal">{text}</div>}</Teleport>
)
// HOC
const withMessage = (Comp) =>
defineComponent({
setup() {
const show = ref(false)
const text = ref('Message')
provide('message', (t) => {
show.value = true
text.value = t
})
return () => (
<>
<Comp></Comp>
<Message open={show.value} text={text.value}></Message>
</>
)
},
}) use it import { createApp, defineComponent, inject } from 'vue'
const App = defineComponent({
setup() {
// Use
const useMessage = inject('message')
const onClick = () => useMessage(<div>new message</div>)
return () => <button onClick={onClick}>show message</button>
},
})
createApp(withMessage(App)).mount('#app') |
It's reasonable. However it seems the API
|
What's more, when using sfc the usage is too complicate for a component library user.
original
|
If you want your library to render elements, give the user a component to put in their app where you pass the messages. The solution with a hoc by @lawvs also works Remember to use the forum or the Discord chat to ask questions! |
I think this deserves further consideration. The suggestion in the documentation to 'create a factory function' to share application configuration is much less straightforward than it sounds. I've seen people ask about this several times on the forum and Stack Overflow, so it seems to be a common problem, and so far I haven't seen a really compelling answer. Perhaps it's just a documentation problem but either way it is a problem that needs addressing properly. |
I've meet same issue here when I try to upgrade my vue plugin libriary to vue3. It seems like there is no better way except create a factory function or hoc, but the usage is too complicate for a component library user. Hope the vue team can porvide some other ways to slove this problem. |
Indeed @lawvs's solution is a good solution, what about writing 3rd party library? When 3rd party library exports |
It's not documented but yes it's possible to render using existing app context: import { h, render } from 'vue'
const childTree = h('div', 'some message')
treeToRender.appContext = app._context
render(childTree, document.getElementById('target')) We can make this a method on the app instance: app.render(h('div', 'some message'), document.getElementById('target')) |
I did some experimenting using the idea @yyx990803 suggested. Here is a crude demo: https://jsfiddle.net/skirtle/94sfdLvm/ I changed the API slightly: // To add
const vm = app.render(Component, props, el)
// To remove
app.unrender(vm) The reasoning behind my changes was:
I ran into a problem trying to render multiple things to the same parent, which I think is important for the use cases here. In my demo I bodged around it by adding in an extra |
|
I developed a small tool that allows me to use functions to mount VueComponent. |
Why childTree.component cannot get the methods at component, it is undefind. import ShowErrorDialog from './src/errorDialog.ts';
import type { Plugin, App } from 'vue';
export type SFCWithInstall<T> = T & Plugin;
const _showErrorDialog = ShowErrorDialog as SFCWithInstall<
typeof ShowErrorDialog
>;
_showErrorDialog.install = (app: App) => {
_showErrorDialog._context = app._context;
app.config.globalProperties.$systemError = _showErrorDialog;
};
export default _showErrorDialog;
// errorDialog.ts
import ErrorDialogConstruct from './index.vue';
import { isEmpty, isFunction } from 'lodash-es';
import { h, render } from 'vue';
interface Option {
title: string;
}
let instance;
const stack: Option[] = [];
const genContainer = () => {
return document.createElement('div');
};
const showErrorDialog = (opts, appContext) => {
const { title, description, errorCode, traceCode } = opts;
if (!instance) {
const props = {};
const vnode = h(ErrorDialogConstruct, props);
const container = genContainer();
vnode.appContext = appContext ?? showErrorDialog._context;
render(vnode, container);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
document.body.appendChild(container!);
instance = vnode.component;
}
const options = {
};
stack.push(options);
// $open is undefined, how to get the methods?
instance.$open(stack);
// this can work
// instance.ctx.$open(stack);
instance.onOk('ok', () => {
if (isFunction(opts.onOk)) {
opts.onOk(instance);
}
instance = null;
});
};
showErrorDialog._context = null;
export default showErrorDialog;
// index.vue
<template>
<a-modal v-model:visible="visible">demo</a-modal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { message } from 'ant-design-vue';
export default defineComponent({
name: 'ErrorDialog',
emits: ['open', 'close', 'closed', 'opened'],
data() {
return {
visible: false
};
},
methods: {
$open(stack) {
// TODO
}
}
});
</script> |
I tested this approach but using the |
Yes i tested this approach too and props are not reactive. The only way i found to make them reactive was const treeToRender = createApp(() => h('div', { reactiveProp: true }); but i cannot bind the context that way, whereas with the first approach the context is bound successfully. This would be very useful if can happen. |
Exactly my discovery at the moment, second way you showed cannot bind context, but it has reactivity, I really need context :( |
Guys! I figured it out! the full solution to maintain full props reactivity and get the function createComponent ({ app, component, props, el }) {
let expose = null
const childApp = createApp({ render: () => expose = h(component, props) })
Object.assign(childApp._context, app._context)
childApp.mount(el)
return expose.component.exposed
} By supplying :-) Now you can use |
Yes, it works. Thanks! Why does it work tho with object.assign this way and with simple assignment the other way is close to paranormal activity hah |
@nikolas223 |
@yyx990803 are there any considerations around this topic? You mentioned this could be a documented feature and not just a hack. |
the providers and the plugins seems lost, i must reinstall and reprovides them |
the providers and the plugins seems lost, i must reinstall and reprovides them |
What problem does this feature solve?
Somethings I need to created detached components.
For example I may call
this.$Message.info(content)
in which content may be a render function and the component created will be mounted ondocument.body
.For example:
Before calling
$Message.info
After calling
$Message.info
Calling
createApp(MessageComponent).mount(document.body)
inside$Message.info
may render the component in body. However the render function will use the new appContext rather than the original appContext which has already been registered with custom components. For example:What does the proposed API look like?
The text was updated successfully, but these errors were encountered: