vuejs / jsx-vue2

monorepo for Babel / Vue JSX related packages
https://jsx-vue2-playground.netlify.app/
1.47k stars 96 forks source link

Vue 3 JSX Design #141

Closed Amour1688 closed 3 years ago

Amour1688 commented 4 years ago

Edit by @yyx990803 : there are currently two JSX transform implementations for Vue 3 with slightly differing syntax (for Vue specific features). We are using this thread to unify the design and land on an official specification of how Vue features should be handled in JSX.

Babel JSX Transform [Github] alpha

Syntax

Content

functional component

const App = () => <div></div>

with render

const App = {
  render() {
    return <div>Vue 3.0</div>
  }
}
const App = defineComponent(() => {
  const count = ref(0);

  const inc = () => {
    count.value++;
  };

  return () => (
    <div onClick={inc}>
      {count.value}
    </div>
  )
})

Fragment

const App = () => (
  <>
    <span>I'm</span>
    <span>Fragment</span>
  </>
)

Attributes/Props

const App = () => <input type="email" />

with a dynamic binding:

const placeholderText = 'email'
const App = () => (
  <input
    type="email"
    placeholder={placeholderText}
  />
)

Directives

It is recommended to use camelCase version of it (vModel) in JSX, but you can use kebab-case too (v-model).

v-show

const App = {
  data() {
    return { visible: true };
  },
  render() {
    return <input vShow={this.visible} />;
  },
};

v-model

export default {
  data: () => ({
    test: 'Hello World',
  }),
  render() {
    return (
      <>
        <input type="text" vModel_trim={this.test} />
        {this.test}
      </>
    )
  },
}

custom directive

const App = {
  directives: { custom: customDirective },
  setup() {
    return () => (
      <a
        vCustom={{
          value: 123,
          arg: 'arg',
        }}
      />
    );
  },
}
tangjinzhou commented 4 years ago

Compatible with Vue2 should give a deprecated warning. I prefer not to support in future, if compatible, we need to add runtime, performance will be lost.

tangjinzhou commented 4 years ago

About slots,proposed API look like:

<com vSlots={{xxx: ({val})=>[<div>{val}</div>]}}>
    <span></span>
</com>

children has higher priority than vSlots.default or does not support vSlots.default.

HcySunYang commented 4 years ago

@Amour1688 Great job, in addition to the above, I have some same or different proposals:

Experimental project: vue-next-jsx

Unified syntax

Since tsx does not support JSXNamespacedName and the . cannot appear in the attribute name, it is necessary to unify how to write directives in jsx.

Proposed syntax:

Examples

pros

cons

Restricted slot

Since 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 KeepAlive and Teleport components will not be built as slots, so we need to handle it.

Fragment

Although 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 mode

Vue3 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 source

Some people install vue, but some install @vue/runtime-dom, so this should be configurable:

{
  "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-text

In Typescript, we must use domPropsInnerHTML, if we support v-html it will be more friendly.

tangjinzhou commented 4 years ago

@HcySunYang

Don't support directives shorthand in jsx Use - instead of : and use _ instead of .

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. I prefer the camel case.

tangjinzhou commented 4 years ago

@HcySunYang

Don't support directives shorthand in jsx Use - instead of : and use _ instead of .

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. I prefer the camel case.

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. I am not familiar with ts, is there any other better way.

HcySunYang commented 4 years ago

@tangjinzhou

In Typescript, if you do this:

<p onClick_stop={ handler }>text</p>

will get an error:

image

This is because onClick is treated as a standard html attribute.

In fact, users can still use onClick, but it is not allowed to add modifiers on it, if you want to add modifiers, please use:

<p v-on-click_stop={ handler }></p>
Amour1688 commented 4 years ago

In Typescript, if you do this:

<p onClick_stop={ handler }>text</p>

will get an error:

image

This is because onClick is treated as a standard html attribute.

In fact, users can still use onClick, but it is not allowed to add modifiers on it, if you want to add modifiers, please use:

<p v-on-click_stop={ handler }></p>

If it's possible to extend types.

v-on-click It doesn't look good.

I think use props.onXX may be a better method

tangjinzhou commented 4 years ago

@tangjinzhou

In Typescript, if you do this:

<p onClick_stop={ handler }>text</p>

will get an error:

image

This is because onClick is treated as a standard html attribute.

In fact, users can still use onClick, but it is not allowed to add modifiers on it, if you want to add modifiers, please use:

<p v-on-click_stop={ handler }></p>

@HcySunYang

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;
  }
}
fgr-araujo commented 4 years ago

Dots to separate is more readable. We know that dot is a layer inside something Unserscore means compound names.

fgr-araujo commented 4 years ago

I love to use dash in HTML but in JSX can be confused with a minus sign.

tangjinzhou commented 4 years ago

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.

sonicoder86 commented 4 years ago

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.

HcySunYang commented 4 years ago

@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 onClick.

lloydjatkinson commented 4 years ago
    return (
      <>
        <input type="text" vModel_trim={this.test} />
        {this.test}
      </>
    )

I think v-model-trim or vModelTrim would be infinitely better than vModel_trim which is both camel case and snake case at the same time. 😬

Amour1688 commented 4 years ago
    return (
      <>
        <input type="text" vModel_trim={this.test} />
        {this.test}
      </>
    )

I think v-model-trim or vModelTrim would be infinitely better than vModel_trim which is both camel case and snake case at the same time.

vModelTrim is better than vModel_trim. It's an excellent idea. We need consistent design to reduce the cognitive cost of users.

Ericnr commented 4 years ago

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
  } />
)
HcySunYang commented 4 years ago

@Amour1688 The vModelTrim is confusing, what is the dir name(Model or ModelTrim?) and what is the modifier? 🤔

Amour1688 commented 4 years ago

@Amour1688 The vModelTrim is confusing, what is the dir name(Model or ModelTrim?) and what is the modifier? 🤔

Em... So vModelTrim maybe not a good choice. 🤔

edimitchel commented 4 years ago

What about <input type="text" vModel={ trim(this.test) } /> where trim is to be imported from 'vue'. The trim function wouldn't transform the content of this.test, but doing something with the get/set for getting a model trimmed. It's more closer to the JSX philosophy but looks like a filter (feature removed in Vue 3).

Another examples :

<button onClick={middle(clickHandler)} />
<input onChange={ctrl(e(textHandler))} />
edimitchel commented 4 years ago

Or better one using pipes :

<button onClick={clickHandler |> middle} />
<input onKeyUp={textChanged |> ctrl |> e} />
<textarea vModel={clickHandler |> trim}></textarea>
LexSwed commented 4 years ago

Or better one using pipes

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 jsx is for using "just javascript" (despite jsx being not js).

What about <input type="text" vModel={ trim(this.test) } /> where trim is to be imported from 'vue'. The trim function wouldn't transform the content of this.test, but doing something with the get/set for getting a model trimmed.

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.

HcySunYang commented 4 years ago

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.

JasKang commented 4 years ago

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 JSX parse modifiers , when factoryXXX from vue

tangjinzhou commented 4 years ago

Directives and Modifiers are optional features. Maybe @JasKang plan is a good choice.

yyyanghj commented 4 years ago

I like vModel-foo_trim for v-model:foo.trim="bar"

antfu commented 4 years ago

Adding my cent here, thought the mix of dashes and underscores is not visually good to me. Overall, the 1:1 mapping of @HcySunYang 's syntax I think is more straightforward for people to read without reading extract docs.

Vote for v-on:myevent.a.b -> v-on-myevent_a_b

edimitchel commented 4 years ago

Overall, the 1:1 mapping of @HcySunYang 's syntax I think is more straightforward for people to read without reading extract docs. @antfu

Yes, but with some function utilities could be more explicit and less wierd to use. Not to mention wierd props we could have.. These functions could be added outside of the Vue core and directly added to the JSX project with a tree shaking behaviour.

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 !

wonderful-panda commented 4 years ago

I like @edimitchel 's plan(function style) or @JasKang 's plan, because other plans breaks type-safety.

magicdawn commented 4 years ago

explicit is better than implicit, I think just use plain Objects

<Comp v-model:foo.trim="bar" />

// to

<Comp 
  vModel={[
    { prop: 'foo', value: 'foo1', options: { trim: true } },
    { prop: 'bar', value: 'bar1', options: { trim: false, ...extraOptions } }
  ]} 
/>
fxxjdedd commented 4 years ago

Just a little personal opinion about function style:

lloydjatkinson commented 4 years ago

As a Vue user, I prefer to use syntactic sugar because vue's syntactic sugar is already familiar to me, and it does help a lot.

But that syntax is from the template syntax, they are two totally different use cases.

bjarkihall commented 4 years ago

Just make typings/inference easy in .tsx files (it also gives IDE's like vscode info for .jsx files) and reduce the need for maintenance.

Whether you do v-on-click_stop_prevent or onClick_stop_prevent - doesn't every combination of event handlers + modifier have to be typed in this plugin via a declare module shim (even the order of modifiers like onClick_prevent_stop etc.)? If they're not provided I'd guess we won't get any auto-completion or even warnings for spelling mistakes or wrong types when passing down props.

I'd think it's best to keep standard TSX functionality like in React (since it has a big ecosystem and IDE support) like has been discussed. Here's React's definitions but I've seen meta-frameworks like next-js augment these types and add jsx elements like , which is typed with a shim and then handled when bundling.

React's typings are unnecessarily long since their codebase is written in flow (instead of typescript) but this is where Vue 3 could have an advantage, but according to Typescript's documentation on JSX - for better integration with a library one should use that library's typings. I've only seen wonderful-panda's vue-tsx-support and while I like the effort, I think a lot of the syntax can be provided from vue itself now or its default jsx library - at least we should be able to type our jsx elements and components properly in Vue 3 without any massive boilerplate (we already have defineComponent).

Regarding the "syntactic sugar" we can limit these to simple props like vShow/v-show is easy to support, but directives/models/events with modifiers etc. could also be simple tags, like vOn and vModel, which accept something like an object or generate via factories, like mentioned already in this thread.

Evan You describes how a simpler signature for custom render functions might be made available via wrapping it:

withModel(h(SomeComp), {
  prop: 'foo', // optional
  value: foo,
  update: value => (foo = value),
  modifiers: { trim: true }
})

But I think JSX can make the step from template syntax to render function smoother... The template syntax:

<Comp
  v-model:foo.trim="text"
  v-model:bar.number="num"
/>

becomes something like @magicdawn mentioned:

<Comp vModel={[
  { prop: 'foo', value: text, modifiers: { trim: true } },
  { prop: 'bar', value: num, modifiers: { number: true } }
]}/>

which can be simplified with utilities (vModel prop accepts an array of VModels or a single VModel object):

import {createVModel as m} from "vue/jsx";
<Comp vModel={[
  m('foo', text, {trim: true}),
  m('bar', num, {number: true})
]}/>

which is finally compiled to h (or even straight to createVNode depending on how the plugin is written):

h(Comp, {
  foo: text,
  'onUpdate:foo': value => (text = value),
  fooModifiers: { trim: true },
  bar: num,
  'onUpdate:bar': value => (bar = value),
  barModifiers: { number: true }
})

I'm not sure what the JSX plugin is capable of - would the helper function create runtime overhead or can the expression be transpiled into an optimized render function straight away - or is that the reason you're striving for long string-based props? If that's the case I guess the plugin could "generate" all possible combinations of typescript JSX properties shim when it's packaged.

One final thing I'd like to mention is that there's discussions going on in Typescript regarding React's new jsx factory rfc for React 17 and autoimports, which gained experimental support in Babel 7.9.0. It would be great if Vue team had something to say in these matters since Vue components could be simplified to a functional component in a .tsx file without the import of the h function.

Vue3 is usually advertised with "better typescript support" since the team spent a lot of effort rewriting the whole library in it - so I'd think developing in .tsx files could be less awkward if the new jsx implementation would keep typescript support in mind. Providing a playground (like codesandbox) where it's possible to try out the tsx support would be great, both for experimentation and giving feedback - I had trouble with both vue-cli and vite when it came to TSX since the support isn't ready in these.

sqal commented 4 years ago

@bjarkihall I like the simplified syntax very much. I wonder if createVModel can be skipped (not sure what it does ;p). Also, what you think about changing the order of the arguments so that they would be aligned with arguments order of the withDirectives helper

<Comp vModel={[text, 'foo', { trim: true }]} /> // v-model:foo.trim="text"

<Comp 
  vModel={[
    [text,, 'foo', { trim: true }], // v-model:foo.trim="text"
    [num, dynamicProp] // v-model:[dynamicProp]="num"
  ]} 
/>

<Comp 
  vPin={[200, undefined, { top: true }]} // v-pin.top="200"
/>
bjarkihall commented 4 years ago

@sqal good catch with the withDirectives syntax, the order was just based on the comments above but should ofc align with other structures in vue, like directives.

// Directive, value, argument, modifiers so vModel is: // ("model"), value, argument, modifiers

This example is from vue docs:

<div v-demo:foo.a.b="message" />
// name: "demo"
// value: "hello!"
// expression: "message"
// argument : "foo"
// modifiers: {"a": true, "b": true}

So that would be:

withDirectives(
  h('div'), [
    [ resolveDirective("demo"), message, "foo", {"a": true, "b": true} ]
  ]
);

But JSX could be:

<div vDirectives={[
  [ resolveDirective("demo"), message, "foo", {"a": true, "b": true} ]
]} />

Where vDirectives/vDirs prop accepts the type DirectiveArguments (2-dimensional array) but vDirective/vDir a single array. If the first element is a string, it would be automatically resolved, so "demo" -> resolveDirective("demo").

So this is the end result:

<div vDir={[ "demo", message, "foo", {"a": true, "b": true} ]} />

or if the modifiers are always :true, it could be even more compact:

<div vDir={[ "demo", message, "foo", ["a", "b"] ]} />

The goal here is to start with a generic enough prop which the others can derive from and be typed without having to do anything too manual. I've not seen JSX tags being added by most users, so your example of v-pin:

<Comp 
  vPin={[200, undefined, { top: true }]} // v-pin.top="200"
/>

would preferably be converted to:

<Comp 
  vDir={["pin", 200, undefined, { top: true }]} // v-pin.top="200"
/>

except if vue or a 3rd party library would like to include a special tag (the obvious case is vShow but could also be vPin), they could just do that and add the types.

Regarding utility/factory function vs. array/tuples - the core could be array based but functions just optionally offer better aid when typing these out, since functions have named arguments and logic while tuples just depend on their indices while being faster:

// vue/jsx:
const createDirectiveArg(directive: Directive, value?: any, argument?: string, modifiers?: DirectiveModifiers) => ([directive, value, argument, modifiers]);
// and this would even be possible:
const createDirectiveArg(directive: string, value?: any, argumentOrModifiers?: string | string[]) => (typeof argumentOrModifiers === "string" ? [directive, value, argumentOrModifiers] : [directive, value, undefined, argumentOrModifiers]);

// Comp.tsx:
import {createDirectiveArg as d} from "vue/jsx";
<Comp vDir={d("pin", 200, ["top"])} /> // v-pin.top="200"

It's just a matter of how much info/flexibility you want to get from the typing system. There's nothing stopping you from writing a resolver for the string in general:

<Comp vDir={'v-pin.top="200"'} />

where the transformation step just magically finds out what the string means like in templates, but you lose the type safety.

HcySunYang commented 4 years ago

I did some research and I started to like @bjarkihall 's proposal, vModel can be used on intrinsic elements and value-based elements. For intrinsic elements, vModel can not take args, so we can simplify it(only value and modifiers):

In the jsx library, we can extend the IntrinsicElements interface:

// type of modifiers
type IntrinaicVModelModifiers = {
  trim?: boolean
  lazy?: boolean
  number?: boolean
}
// type of value
type ModelValue = string | number | boolean

type IntrinsicVModelArg =
  | ModelValue
  | [ModelValue]
  | [ModelValue, IntrinaicVModelModifiers]

declare module 'vue' {
  interface InputHTMLAttributes {
    vModel?: IntrinsicVModelArg
  }
  interface SelectHTMLAttributes {
    vModel?: IntrinsicVModelArg
  }
  interface TextareaHTMLAttributes {
    vModel?: IntrinsicVModelArg
  }
}

With these, we can get good type support:

const App = {
  setup() {
    const refText = ref('')

    return () => (
      <>
        {/* Only value */}
        <input vModel={refText.value} />
        {/* with modifier */}
        <input vModel={[refText.value, { trim: true }]} />
      </>
    )
  }
}

For further, we can encapsulate the function, which is optional for users:

function m(v: ModelValue, m?: IntrinaicVModelModifiers):  IntrinsicVModelArg{
  return !m ? v : [v, m]
}

Type hint just like this: image

For value-based elements(Components):

// value, arg, modifiers
type ComponentModelArg = 
  | [any]
  | [any, string]
  | [any, string, Record<string, boolean>]

// Just for demonstration, maybe we can do better type inference(the `defineComponent` in vue core).
const Child = ({
  setup() {
    return <p>child</p>
  }
} as any) as { new (): { $props: { vModel?: ComponentModelArg[] } } }

With these, we can get some basic types of support:

const App = {
  setup() {
    const refText = ref('')

    return () => (
      <>
        <Child vModel={[
          [refText.value],
          [refText.value, 'foo', { a: true }]
        ]} />
      </>
    )
  }
}

In addition, users can use utility functions for type hinting (optionally):

function cm(value: any, props: string, modifiers?: Record<string, boolean>): ComponentModelArg {
  return modifiers ? [value, props, modifiers] : [value, props]
}

Type hint: image

For vOn, we can do something similar.

bjarkihall commented 4 years ago

@HcySunYang that's exactly what I was trying to hint at, the suggestions we'd get from the IDE etc - it's nice to see the visuals from your experimentation.

vShow/vHtml/vSlots, vOn/vOns/vEvent/vEvents, vModel/vModels, vDir/vDirs/vDirective/vDirectives is a pretty compact list and easy to type and they kind of rely on the same principles, order of arguments and logic - they are also opt-in for vue usage so onClick would work normally and even the utility functions are just optionally there to guide users and make the code more readable, while giving type safety. If users or library authors would like more built-in props, they can be optionally added too, as long as the jsx transformation can detect it, but I'd call that a more advanced use case.

It's the flexibility vs simplicity tradeoff: e.g. v-pin would not be part of the official directives but could be declared in many ways:

<Comp v-pin.top={200} /> // ❌ jsx could support this - but tsx would break
<Comp vPin_top={200} /> // ❌ would need to be added specially with each modifier
<Comp vPin={[200, undefined, { top: true }]} /> // ⚠ custom tag which would either need to provide a function too or even be handled by the library providing it
<Comp vDir={'v-pin.top="200"'} /> // ⚠ this could be officially supported - not type checked though
<Comp vDir={["pin", 200, undefined, { top: true }]} /> // ✔ no dependencies needed, fully type safe
<Comp vDir={d("pin", 200, ["top"])} /> // ✔ vue/jsx could provide some default utility functions, still type safe
<Comp vDir={vPin(200, "top")} /> // ✔ a mix - library author/user only needs to supply the vPin function, still type safe
<Comp vDirs={[...]}/> // etc.

There are many ways to do things because people tend to disagree on syntax but I'd think having the options limited, offering the low-level array syntax but recommending utility functions first in the docs would be solid, both for usage and maintenance.

bjarkihall commented 4 years ago

So here's some kind of a summary, if I understand the TSX usage correctly:

const el = <div/>; // el would become VueElement/VNode?
let fc: FunctionalComponent = (p, ctx) => <div/>; // I'd love it if we could just import FC, but {FunctionalComponent as FC} should work...
let fc2: FunctionalComponent<{foo: string}> = (p, ctx) => <div data-foo={p.foo}>{ctx.slots}</div>; // these are mostly typed!
Amour1688 commented 4 years ago

@bjarkihall Excellent Job.

For events, I prefer @JasKang or @edimitchel 's design.

<button onClick={clickHandler} />
import { factoryClick } from 'vue'
<button onClick={factoryClick(clickHandler,['middle'])} />

In template, Vue provides a method named withModifiers, so we got this.

// Before

<div @click.self="xx">click me</div>

// after

{
  onClick: withModifiers(xx, ["self"])
}

Then in JSX, I think when you want to use modifiers with your event, withModifiers will be a better choice which can keep type-safety well, without any syntactic sugar.

import { withModifiers } from 'vue'

<div onClick={withModifiers(xx, ['self'])}>click me</div>

As for vOn

<a vOn={[doThat, 'click', {stop: true, prevent: true}]} />

This proposal may lose type safe. Since emit is not type safe, we recommend using events declared in props by props.onEvent.

ant-design-vue and vant are now using vueComponent/jsx to refactor.

bjarkihall commented 4 years ago

@Amour1688 ah, I was confused with the events in Vue 2 but can see the change now in rfc/0008, so events are syntactically like directives in templates but are really just props that start with onXXX in render functions.

I was just describing how any v- tag could be written and parsed with the same low-level array syntax - that wouldn't need any imported functions and were using the same type structure as Vue itself underneath for directives/models/events/etc. The singular/plural naming was to tell the single/many tags of the same directive-type apart (if the array is 1 vs 2 dimensional), and the imported helpers were just to show how to make them more descriptive.

Both methods (tuple or helperFunction->tuple) can be typed properly and are therefor type safe and I think the only syntactic sugar would be the vOn tag itself - which I think would be good to reserve and handle either way, just for completion.

A short typing example: image These kind of types can be created/imported straight from the core vue library and be hidden away, like @HcySunYang demonstrated, the only way we'd not be able to type things is if the jsx syntax was based on parsing a string pattern like "v-pin:foo.bar" - but keys (string unions), tuples, objects and functions can all provide inference/typing, it just depends on how standalone the plugin is supposed to be, not having to import any functions but still have a feature-complete and type-checked vue-jsx is a nice possibility while others prefer helper functions - I personally really like the withModifiers method - it closely resembles what I generally had in mind, thanks for pointing that out. :)

JasKang commented 4 years ago

@Amour1688 agree with you , I think JSX should remain unified and avoid more mental burdens

bjarkihall commented 4 years ago

Hopefully this explains the hierarchy better:

4 parameters (directive, value, argument, modifiers)

<div v-demo:foo.a.b="message" />
<div vDir={...} /> // the only syntactic sugar (you have to have something) is the vDir tag, which can be typed

use either:

  1. ['demo', message, 'foo', {a: true, b: true}]
  2. ['demo', message, 'foo', ['a', 'b']] or import a utility function that returns this array:
  3. d('demo', message, 'foo', ['a', 'b'])

3 parameters (value, argument, modifiers)

<div v-model:foo.a.b="message" />
<div vModel={...} />

use either:

  1. [message, 'foo', {a: true, b: true}]
  2. [message, 'foo', ['a', 'b']] or import a utility function that returns this array:
  3. m(message, 'foo', ['a', 'b'])

What I described above resembled this but maybe it doesn't make sense after all:

<div v-on:click.a.b="message" />
<div vOn={...} />

use either:

  1. [message, 'click', {a: true, b: true}]
  2. [message, 'click', ['a', 'b']] or import a utility function that returns this array:
  3. ev(message, 'click', ['a', 'b'])

2 parameters (value, modifiers)

<div v-on-click.a.b="message" />
<div onClick={...} />

use either:

  1. [message, {a: true, b: true}]
  2. [message, ['a', 'b']] or import a utility function that returns this array:
  3. ev(message, ['a', 'b']) // ev could be called "withModifiers", like you were using above
  4. ev2(message, 'a', 'b') // would also be possible - ev2: (handler: EventHandler, ...(modifiers: EventModifiers[])) => EventArg

And for (value, argument):

<div v-model:foo="message" />
<div vModel={...} />

use either:

  1. [message, 'foo'] or import a utility function that returns this array:
  2. m(message, 'foo') // m could be called "withParameter" like withModifiers, or it can just be the same function with different signatures

1 parameter (value)

<div v-on-click="message" />
<div onClick={message} /> // just use the value, this also applies to simple vModel, vShow, etc.

Note that the functions d, m, ev, ev2 are just placeholders for demonstration and by no means the finalized API - these could all be the same function, like h is for render functions, but instead of returning a VNode it's returning a DirectiveArgs tuple. They could be "withDirective", "withModel", "withModifiers" functions already found in vue, as long as they help people with the types and return the correct data structure for the transformation step.

The Vue JSX tags (vDir, vModel, vOn, etc) would have the same base type - a tuple of (directive, value, argument, modifiers), which has subsets depending on how much of the tuple you're going to use - the imported utility function can help you type and construct the tuple (depending on if it's a directive, event or a model for example). If the tag has a plural name (vDirs), it's just an array of these tuples.

dsonet commented 4 years ago

I do like this short style, very clean and can be well self explained with hint. <a vOn={[doThat,'click', ['stop','prevent'icsoftfont>} /> It's really unnecessary to introduce extra import just for this syntax hint.

HcySunYang commented 4 years ago

For the purpose of experimenting and collecting feedback, I implemented the syntax discussed above:

<>
    <h1>The 1:1 mapping syntax, it reduces the mental burden, recommended for users who use js:</h1>

    <input v-model_number={ refVal.value } />
    <input v-on-click_stop={ handler } />

    <p>You can still use the original jsx syntax:</p>
    <div onClick={ hander }></div>

    <h1>Better type hinting and type safety, recommended to typescript users</h1>

    <input vModel={ [refVal.value, { number: true }] } />
    <input vModel={ [refVal.value, ['number']] } />
    <Comp vModel={ [refVal.value, 'foo', { a: true }] } />
    <Comp vModel={ [refVal.value, 'foo', ['a']] } />
    <Comp vModel={ [refVal.value, dynamic, ['a']] } />

    <p>withModifiers:</p>
    <div onClick={ withModifiers(handler, ['self']) }></div>
</>

See this Readme for details: vue-next-jsx#for-typescript-users.

For type hints, you can check our dts test: intrinaicElements.test-d.tsx.

Note: Type support for components needs to wait until this issue to be fixed

And the Playground: https://vue-next-jsx.netlify.app/.

marsprince commented 4 years ago

Personly, I think the main principle of vue3 in JSX should be :

typescript first

more syntactic, less function

It's confused for newer about using vue's internal function like withModifiers or other utility functions provided by this repo in JSX.

I like <input vModel={ [refVal.value, { number: true }] } />

provided by @HcySunYang above

wonderful-panda commented 4 years ago

About vOns.

I think we can make event handlers type-safe by this feature.

And in terms of typing, I think A is more preferable than B.

// A
<a vOns={{
  mousemove: ev(doThis, ...),
  click: ev(doThat, ...)
}} />

// B
<a vOns={[
  ev(dThis, 'mousemove', ...),
  ev(doThat, 'click', ...)
]} />
yyx990803 commented 4 years ago

I've seen some great discussions here, while I don't have definitive answers to all the questions yet, I'll just write down some of my thoughts here:

Amour1688 commented 4 years ago

For Directive, My implementation in v1.0.0-beta.3.

Users who don't want to use templates also need an easier way to write their code. It's unnecessary to write vModel={[val]} with single param. So I also support it in this way.

<input vModel={val} />
<A vModel={[val, 'arg']} />
<A vModel={[val, ['a', 'b']]}  />
<A vModel={[val, 'arg', ['a', 'b']]}  />

As for arg and modifiers:

const x = 'xx';
<A vModel={[val, x]} />
HcySunYang commented 4 years ago

Users who don't want to use templates also need an easier way to write their code. It's unnecessary to write vModel={[val]} with single param

This is for consideration of type hints, since users can bind any value, in other words, the type of the value is any, we can define the type like this:

export type IntrinsicVModelArg = [any] | [any, IntrinaicVModelModifiers]

But we can't do this(can we?):

export type IntrinsicVModelArg = any | [any] | [any, IntrinaicVModelModifiers]
thisisandy commented 4 years ago
export type IntrinsicVModelArg = any | [any] | [any, IntrinaicVModelModifiers]

Is it possible that we can use generic types like following

export type IntrinsicVModelArg<ModelType> = ModelType | [ModelType] | [ModelType, IntrinaicVModelModifiers]

And user can nominate the type while using the directives.

edimitchel commented 4 years ago

s>@Amour1688</s @HcySunYang I see the JSX explorer, thanks you ! That is very great ! https://vue-next-jsx.netlify.app/