-
Notifications
You must be signed in to change notification settings - Fork 96
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
Vue 3 JSX Design #141
Comments
Compatible with Vue2 should give a deprecated warning. |
About slots,proposed API look like: <com vSlots={{xxx: ({val})=>[<div>{val}</div>]}}>
<span></span>
</com> children has higher priority than |
@Amour1688 Great job, in addition to the above, I have some same or different proposals: Experimental project: vue-next-jsx Unified syntaxSince Proposed syntax:
Examples
<p v-on-click_stop={ handler }></p>
<Comp v-on-myevent_a_b={ handler } />
<input v-model={ refVal.value }></p>
<Comp v-model-foo_a_b={ refVal.value } /> pros
cons
Restricted slotSince the scoped slot is still manually built in the Vue2' jsx, so I propose the only way to provide slots for components is: <Comp>{ mySlots }</Comp> const mySlots = {
default: () => [ <p>default</p> ],
foo: (obj) => [ <p>{ obj.somePorp }</p> ]
} pros
cons
KeepAlive And Teleport In Vue3, the children of FragmentAlthough we don't must to support fragment in jsx plugin, because Vue3 will handle it automatically, but it does bring development convenience: render() {
return (
<>
<p>Foo</p>
<div>Bar</div>
</>
)
} Optimization modeVue3 makes full use of compile-time information to generate PatchFlags for runtime update performance improvement, Maybe the jsx plugin can also do some similar work Specify sourceSome people install {
"presets": [
"@babel/env"
],
"plugins": [
["@hcysunyang/vue-next-jsx", {
// Specify source
"source": "@vue/runtime-dom"
}]
]
} It affects the import statement: import { .. } from 'vue'
import { .. } from '@vue/runtime-dom' v-html / v-textIn Typescript, we must use |
We should declare events instead of skipping the check. In fact,when the event is declared in the props, we can still trigger by emit. |
Directives <input vModel={this.newTodoText} /> with a modifier: <input vModel_trim={this.newTodoText} /> with an argument: <input onClick={this.newTodoText} /> with an argument and modifiers: <input onClick_stop_prevent={this.newTodoText} /> v-html: <p domPropsInnerHTML={html} /> If support modifiers, we can also declare it. |
In Typescript, if you do this: <p onClick_stop={ handler }>text</p> will get an error: This is because In fact, users can still use <p v-on-click_stop={ handler }></p> |
If it's possible to extend types.
I think use |
maybe we should try to resolve it. If skip the check, the effect of using ts will no longer exist. We can declare it like react : declare module 'react' {
interface Attributes {
vModel?: any;
// onClick
onClick_stop?: any;
}
} |
Dots to separate is more readable. We know that dot is a layer inside something |
I love to use dash in HTML but in JSX can be confused with a minus sign. |
We are now using the vueComponent/jsx to refactor ant-design-vue. I expect a smaller change cost, so I hope to be compatible with the syntax in vue2. Even if it may cause a performance loss, we should give the old project enough time to migrate. |
Why not use JSX as is and use event handlers like this? <button onClick={activateLasers}>
Activate Lasers
</button> I feel it more intuitive for devs coming with React background. |
@BlackSonic, Of course, you can. One of the purposes of the jsx plugin is to bring the convenience of the template, such as event modifiers, but this does not prevent you from directly using |
return (
<>
<input type="text" vModel_trim={this.test} />
{this.test}
</>
) I think |
|
Imo there is a mismatch with JSX props syntax and JS. JS object syntax would be much easier to use: const placeholderText = 'email'
const name = 'emailInput'
const App = () => (
<input {
type: "email",
placeholder: placeholderText,
name
} />
) |
@Amour1688 The |
Em... So |
What about Another examples : <button onClick={middle(clickHandler)} />
<input onChange={ctrl(e(textHandler))} /> |
Or better one using pipes : <button onClick={clickHandler |> middle} />
<input onKeyUp={textChanged |> ctrl |> e} />
<textarea vModel={clickHandler |> trim}></textarea> |
Unfortunately there is no way Vue can rely on Stage 1 features. But I love the proposal of using just function composition because I think the main goal of using
Maybe easier solution would be to not provide modifiers in JSX as it's not a standard JS feature. In the codebases I worked on there weren't many usages of modifiers, but even if there are many, how difficult it is to use object spread? This can even be transpiled: export default {
data: () => ({
test: 'Hello World',
}),
methods: {
onSubmit: () => {},
},
render() {
return (
<form ...({ 'onSubmit.prevent': onSubmit }) > ...
<input type="text" ...({ 'v-model.trim': this.test }) />
{this.test}
</form>
)
},
} But in the end I don't even care whether it was transpiled. Yes we create new objects on every render, but that's basically it. |
Recognizing that many people have some misunderstandings, in fact, in Vue3 you can write jsx just like in React. We are discussing how to support directives args and modifiers, which are optional additional capabilities. |
how about this : base <button onClick={clickHandler} /> factory function import {factoryClick, factoryDirective } from 'vue'
<button onClick={factoryClick(clickHandler,['middle'])} />
<input vModel={factoryDirective(a, ['trim'])} /> factoryfunction(value, modifiers: string[]) just return value |
Directives and Modifiers are optional features. |
I like |
Adding my cent here, thought the mix of dashes and underscores is not visually good to me. Overall, the Vote for |
Yes, but with some function utilities could be more explicit and less wierd to use. Not to mention wierd props we could have.. import { trim } from 'vue/jsx'; // or 'vue-jsx'
<input vModel={trim(localModel)} /> {/* default model */}
import Comp, { foo } from './comp'
<Comp vModel={trim(foo(localModel))} /> {/* foo model of Comp */} foo could be defined in the component like this : import { toModel } from vue-jsx'';
const localRef = ref('value');
export const foo = toModel(localRef); This improves the link between components and the typing too ! |
I like @edimitchel 's plan(function style) or @jaskang 's plan, because other plans breaks type-safety. |
@HcySunYang yes I took a look at your repo and @Amour1688 's too but I'm a bit confused since there are two repos and I rather want the design to be fully agreed upon - but since a great idea of a syntax can also be impractical to implement/type, we definitely need to do some experimentation, I've seen some neat approaches and I really like the playground you provided. It's also a bit complicated to contribute in general since the vue jsx typings reside in the vue-next repo, and it also has some tests for jsx while @wonderful-panda 's "vue-tsx-support" repo has some great typings too, which could likely benefit the ones in the official vue-next repo - can't there just be a single source where we can branch from and experiment and make PRs? :) Where would the official typings, tests and plugin end up, once most of the features have been decided upon and before Vue 3 is released? |
@bjarkihall We can extend the type without intruding into vue-next, I have made this PR, the type of jsx can be completed on this basis |
In this thread, I learned a lot of knowledge that I didn't realize before, especially the points of @bjarkihall and @wonderful-panda and their past practices. Now I think we have reached a preliminary consensus and we need to work together. @yyx990803 Maybe we need to be in the same repo and contribute together instead of implementing these functionalities separately., this will only bring more confusion to those who want to contribute and users, if that is possible, then, I am willing to archive the vue-next-jsx project and issue a statement to indicate the correct way to the user. |
It would be nice to compile all of the above into a single spec and then we can get to work if we have a proper spec done. P.S. Looks like we can only achieve consensus if we make a spec & same RFC process as Vue 3 went through. |
Sorry to barge in I literally just discovered yesterday I could use jsx and even the jsx on vue2 is a bit confusingspecially the slots part. I didn't see any mentions of slots here were they deprecated in vue3? Additionally are there any plans to just have a single jx source of truth? I have seen at least some 3 jsx repos and then you have the jsx doc page on Vue which doesn't have the same examples as this page. I tried yesterday understanding how to use slots and I saw so many different syntaxes and the whole scoped slots thing which was also deprecated. This issue seems to be very hot right now so I just wanted to leave my feedback/questions for all you guys. Also regarding the syntax for this issue the best one so far is the latest one which has vModel , vDirective and then it receives the same thing (what is hard is understanding what each argument is and it ends up being a bit weird to have an array of strings at the end why not flat that out as the last event and just gather it as an array in the inside. ) |
@dagadbm if you're talking about spreading (not flattening) the mods array I suggested it in "ev2" in a comment above - you'd always have to know if you're either giving arg + mods (string, string[]) og just mods (string[]) - so if you want to spread the mods array, you'd need to know there's no arg first by either always expect it: <Comp vDir={['demo', value, undefined, 'a', 'b']} /> // v-demo.a.b="value" - you can also write ['demo', value, , 'a', 'b'] or ['demo', value, '', 'a', 'b'] which is harder to spot or defining either new attribute (I leaning towards limiting the number of possible attributes since they just confuse things) or a helper function (ev2) which doesn't expect the arg: <Comp vDir={dirWithMods('demo', value, 'a', 'b')} /> // v-demo.a.b - dirWithMods(directive: string | Directive, value: any, ...mods as string[]) I guess the previous one can be typed (and inferred) properly and it's the least of things to learn and doesn't need any array, so maybe it's clearest: Regarding slots, they've been discussed here - pass the object syntax as children, it's even possible in react and is explained here, it's a similar pattern to render props (slots is just an object of named render functions). JSX should work straight away as an h/createVNode-wrapper/factory and TSX could even work without babel. The typings need to be worked on since right now JSX.Element expects any to be a valid element, slots are not inferred like emits, FunctionalComponents return any, declareComponent does not validate the return value either, etc. I think we should start by defining VueElement interface which JSX.Element can extend (no special v- attributes to start with) and clean up the types a bit. If we have a solid JSX core with typescript support we can carefully add attributes like vDirective, vOn, vModel, etc. const el: VueElement = 0 // TS-error
const el2: VueElement = <>{0}</> // valid so: import {FunctionalComponent, VueElement} from "vue-next/jsx" // FunctionalComponent: (props?, ctx?) => VueElement
const FC: FunctionalComponent = () => 0 // should be invalid since JSX.Element can only extend an interface
const FC: FunctionalComponent = () => (0 as unknown as VueElement) // should work
const FC2: FunctionalComponent = () => <>{0}</> // valid I'm still just reading through the v3 docs and just experimenting with vue-next repo locally, but it's promising. |
Guys, as an active user of jsx in Vue, I would like to tell my opinion:
As @yyx990803 mentioned early – people who decided to use jsx just hate templates. Thanks. |
@isnifer plain JSX should already work in Vue, just like React: const el = <div onClick={e=>console.log(e)}></div> // const el = h('div', {onClick: e=>console.log(e)})
const StatelessComp = (props, {attrs, slots}) => <div {...props} {...attrs}>{slots}</div> // const StatelessComp = (props, {attrs, slots}) => h('div', {...props, ...attrs}, slots) The problem is TSX and the amount of
The discussion has mostly been about being able to use TSX with Vue as a replacement for createVNode/h, not template syntax itself - just like it abstracts createElement in React, and I was suggesting we skipped all "magic" as a starter and actually made things just work like they do in React TSX. The additional attributes (vModel, vOn, vShow, etc) are reserved/custom directives, which are a Vue render function feature (not just in templates). Also, "hating templates" isn't the only factor of using JSX in Vue. It creates a nice transition to the library for React users, allows you to get a lower-level access to component creation, allows you to gain greater DX since it has support from TS itself and a huge ecosystem with solutions like CSS-in-JS, which some prefer, it also allows you to create multiple small components per file, etc. |
I'd like to not support directives to push the community going in the right way(ok ok, directive is not the wrong way) and to simplify the vnode object structure. As to better DX, we can just: const count = ref(0);
const {value, onChange} = v_model(count);
// v-model: <input v-model="count" /> to
<input {...v_model(count)} />
// but it seems things like <input {...v_model(reactive_obj.count)}/> is impossible
// v-custom: <div v-loading="is_loading"></div>
<Loading is_loading={is_loading}><div></div></Loading> |
Greetings, I've done some testing with Vue 3 and JSX today and found a difference between the behavior of Since the Vue 3 JSX Discussion is going on here I thought I would add a comment here rather then open a new formal issue on the main https://github.com/vuejs/vue-next repository. Below are example code and demo links. Online Babel Repl This valid JSX: // @jsx Vue.h
function App() {
return (
<Vue.Fragment>
<h1>Hello World</h1>
<div>Test with Vue 3</div>
</Vue.Fragment>
)
}
Vue.render(
<App />,
document.getElementById('root')
); Is compiled to this by Babel // @jsx Vue.h
function App() {
return Vue.h(Vue.Fragment, null, Vue.h("h1", null, "Hello World"), Vue.h("div", null, "Test with Vue 3"));
}
Vue.render(Vue.h(App, null), document.getElementById('root')); The issue is that an array is currently required for the 3rd parameter in Vue while Babel generates each additional child element as an additional function argument. // Will not work with Vue 3 (RC 5)
// Vue.h(type, props, ...children)
Vue.h(Vue.Fragment, null, Vue.h("h1", null, "Hello World"), Vue.h("div", null, "Test with Vue 3"));
// Will work with Vue 3
// Vue.h(type, props, [children])
Vue.h(Vue.Fragment, null, [Vue.h("h1", null, "Hello World"), Vue.h("div", null, "Test with Vue 3")]); Working Demo - Vue 3 online with JSX Original React and Preact Versions of the above page
Changes needed for Vue 3 export function h(type: any, propsOrChildren?: any, children?: any): VNode {
if (arguments.length > 3) {
return createVNode(type, propsOrChildren, Array.from(arguments).slice(2));
} else ... @yyx990803 Can this be a feature |
For example function s(el: RendererElement) {
if (el.props && Reflect.has(el.props,'v-show')) {
let val = el.props['v-show']
Reflect.deleteProperty(el.props, 'v-show')
return withDirectives(h(el), [[vShow, val]])
}
return el
} {s(<span v-show={false} >test</span>)} Syntax sugar is not necessary |
@cereschen, the syntax sugar is being discussed so you don't need to wrap your components with a function or even implement it yourself - of course it's not "necessary", it's just to remove the need of the extra syntax for the user. In your case you're telling vue that // vShow.ts:
import {withDirective, vShow} from 'vue'
export function vShow(el: RendererElement) {
if (el.props && Reflect.has(el.props,'vShow')) {
const val = el.props['vShow']
Reflect.deleteProperty(el.props, 'vShow')
return withDirectives(h(el), [[vShow, val]])
}
return el
}
// Comp.tsx:
import {vShow as s} from 'vShow'
const Comp = () => s(<span vShow={false}>test</span>) But you could just implement s like this: import {vShowSimple as s} from 'vShow'
const Comp = () => s(<span>test</span>, false) Or have the syntax sugar just take care of vShow without any import: const Comp = () => <span vShow={false}>test</span>
I don't think JSX transformation should do too much magic, it's just a higher level syntax for h/createVNode - a Babel plugin could just add performance optimizations but nothing more, starting using vue with JSX/TSX shouldn't require too much setup or tools to get it working. |
@bjarkihall |
@isnifer But JSX reinvented the wheel. Template brings the original wheel back. |
Hi @ConradSollitt, your problem has been solved, will see it in the next release rc8. I understand perfectly that it is not pleasant to use vue 3 like react using jsx / tsx, I love using templates but I consider that we have to give all possible options to the user. But if we don't have real alternatives ... I prefer to use 'h' like react /preact until we have our own 'h' (vh, vueh, createElement...). Here I put some examples where the vue 3 syntax can be applied or not (v-if, v-for, v-show...): vue3 ( webpack + babel): it's possible. Using vue-next-jsx. vue3 (webpack + babel): it's not possible. Without vue-next-jsx. vue3 (without build / transform): it's not possible ( vite (esbuild / rollup): it's not possible now ( Possible changes in esbuildService
In summary, people will do what they want. Have a nice day 😊 |
<Button onClick={handleClick} modifier={['passive', 'capture', 'once']}>btn</Button>
|
Just wanted to point out, TS 4 is released so we can have named tuples now.
|
Thanks @ivanheral and @yyx990803 for making this update! I’ve reviewed the code change and verified that the demo I created works in several browsers directly from a CDN using Babel Standalone or the JSX Complier I wrote. I don’t expect to develop large Vue 3 apps this way myself (or many people to do so) but it’s nice to know the option exists. Personally Vue Templates are my favorite and I found at work that non- developers (for example Data Scientist with some JS knowledge) can develop dynamic apps with Vue as they could understand the templates much quicker than they wanted to spend learning detailed DOM API’s or JSX. In addition to the recent JSX testing I keep verifying new updates of Vue 3 with a variety of Vue 2 code I wrote and so far everything is working nicely! |
wrapper it with 'h', could be a good option. Example updated with v-show: vue3+v-show
|
With the upcoming template string type, we can have fully type checked JSX experience in Vue. |
I hope we can bring issue "HMR" in top priority. If Vue team can find quick solution (or plugin webpack, maybe) support HMR feature it will be best! Other issue like directive - slots etc we had workaround solution really, so we can wait a little longer. |
any news about HMR support for jsx ? |
There is some discussion around slots here, but it's a bit hard to follow. If you're using JSX, I would argue there is no reason to have slots vs props / renderProps (fn=scoped slot). However, I am mostly curious if we will gain the ability to pass the default slot as a prop rather than as the child of an element. This becomes very useful in React, the ability to use the Thanks |
Hi, I'm new to Vue and trying to figure out best practices. Coming from React, I'm familiar with JSX and kind of liked it. I've tried reading this github issue but am a bit lost. |
hey @lhk . what do you expect from using tsx with vue ? As for vue 3 simply setup a new project with vue cli . select vue 3 and typescript when generating the application. and then install this plugin. then setup the plugin in babel config. after the setup . you just make new tsx file and add some thing like import { defineComponent } from 'vue'
const Component = defineComponent({
setup() {
return () => (<div>hi</div>)
}
}) or import { defineComponent } from 'vue'
const Component = defineComponent({
render() {
return (<div>hi</div>)
}
}) jsx for vue 3 |
Hi everybody, what is the difference in vue 3 to use or not use the defineComponent method? export const ComponentOne =defineComponent({
setup(){
return () => <div>Hi</div>
}
});
//and
export const ComponentTwo ={
setup(){
return () => <div>Hi</div>
}
} Both cases work just fine |
Babel JSX Transform [Github]
Syntax
Content
functional component
with render
Fragment
Attributes/Props
with a dynamic binding:
Directives
v-show
v-model
_
) instead of dot (.
) for modifiers (vModel_trim={this.test}
)custom directive
The text was updated successfully, but these errors were encountered: