Skip to content
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

Default slot returns single static node #1281

Closed
sneppy opened this issue Jun 1, 2020 · 6 comments
Closed

Default slot returns single static node #1281

sneppy opened this issue Jun 1, 2020 · 6 comments

Comments

@sneppy
Copy link

sneppy commented Jun 1, 2020

Version

3.0.0-beta.14

Reproduction link

https://github.com/sneppy/vue-next-slot-issue.git

Steps to reproduce

npm i
npm run serve

Navigate to http://localhost:8080/ and open developer console. The console should display two lines: the first one is correct while the second one is not.

What is expected?

slots.default() returns a slot for each div. The console output is (3) [{…}, {…}, {…}] in both cases.

What is actually happening?

slots.default() returns a single slot with type: Symbol(Static) and a string with the three divs in the children field.

@sneppy sneppy changed the title Default slots return single static node Default slot returns single static node Jun 1, 2020
@underfin
Copy link
Member

underfin commented Jun 2, 2020

This is an optimize for many hosited nodes.You can use static vnode as same as normal vnode.

@sneppy
Copy link
Author

sneppy commented Jun 2, 2020

How? I need to access the top elements of the vnodes to register some DOM events; I also need to wrap each node in a custom component. In Vue 2.x I would do it like this:

let elements = this.$slots.default.map((slot) => slot.elm)

and:

let cards = this.$slots.default.map((slot) => h(Card, [slot]))

How am I suppose to do that? Do I have to parse the children string?

I also don't understand why in the first case, no optimization occurs, given that the only differences are three class="title" attributes. There's no optimization either if I use the runtime compiler:

const {createApp, version, defineComponent, onMounted} = require('vue/dist/vue.esm-bundler')

const View = defineComponent({
	setup(_, {slots}) {

		console.log(version) // 3.0.0-beta.14
		onMounted(() => console.log(slots.default()))
	},

	template: `<div><slot/></div>`
})

const App = defineComponent({
	components: {View},

	template: `<div>
		<View>
			<div class="section 1">
				<h1 class="title">Installation</h1>
			</div>

			<div class="section 2">
				<h1 class="title">Usage</h1>
			</div>

			<div class="section 3">
				<h1 class="title">Contributions</h1>
			</div>
		</View>
	</div>`
})

createApp(App).mount('#app')

@underfin
Copy link
Member

underfin commented Jun 2, 2020

The optimization happend when node count and props count reach the dafalut threshold value.Thi is why first case is diffrent of the second. And The optimization is not support for runtime compiler on now.
For the case let cards = this.$slots.default.map((slot) => h(Card, [slot])), you still can do it with v2.
For the case let elements = this.$slots.default.map((slot) => slot.elm), maybe you can do it with use other method attach event,eg. ref, directive, binding.This is unreliable if you do it, even though it can work with v2 on this case, but sometimes some vnode.elm is null, it is also to v3.

@sneppy
Copy link
Author

sneppy commented Jun 2, 2020

you still can do it with v2

You mean, I can do it with Vue 2.x? Because, of course If i simply do this in Vue 3:

let cards = slots.default().map((slot) => h(Card, [slot]))

I end up with a single Card component which contains the three divs, which is not what I want.

maybe you can do it with use other method attach event,eg. ref, directive, binding

I don't think so. What I'm trying to do is upgrading a plugin I'm using in Vue 2.x so that I can use it in my Vue 3 project. The plugin simply registers a global component that can be easily wrapped around a bunch of elements to create slides. This is what I was doing in the render function:

render(h) {
	
	// Get children
	let cards = this.$slots.default.map((child) => h(Card, {}, [child]))

	// Create slider
	let style = `transform: translate(${this.transform.x}px, ${this.transform.y}px);`
	let slider = h('div', {class: 'swoosh-slider', style}, cards)

	let on = {
		'touchstart': this.onTouchStart,
		'touchmove': this.onTouchMove,
		'touchend': this.onTouchEnd
	}
	return h('div', {class: 'swoosh-view', on}, [slider])
}

At this point I'm not sure what to do, I probably have to think of some other way of doing this.

@pikax
Copy link
Member

pikax commented Jun 2, 2020

It looks like an unsuspected behaviour because this do hoist per div:

<div class="section">
	<div class="3">Title 1</div>
</div>
<div class="section">
	<div>Title 2</div>
</div>
<div class="section">
	<div>Title 3</div>
</div>

but this, do hoist to the whole template

<div class="section">
	<div class="3">Title 1</div>
</div>
<div class="section">
	<div class="1">Title 2</div>
</div>
<div class="section">
	<div>Title 3</div>
</div>

@yyx990803
Copy link
Member

There are some internal heuristics based on amount of nodes with bindings to determine whether a piece of content is static hoisted. However I think it's reasonable to bail on multi-element stringification directly inside a slot. i.e. root level nodes inside a slot should be preserved, but they themselves can stringify their inner content.

underfin added a commit to underfin/vue-next that referenced this issue Jun 3, 2020
@github-actions github-actions bot locked and limited conversation to collaborators Nov 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants