【Vue3+TS+JSX】极简入门

252 阅读3分钟

创建项目

使用vite创建vue3+ts项目

// npm
npm create vite@latest
// yarn
yarn create vite

Vite支持jsx

1、使vite支持jsx需要依赖插件@vitejs/plugin-vue-jsx

yarn add @vitejs/plugin-vue-jsx -D

2、插件配置

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import jsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), jsx()],
})

jsx组件的几种写法

1、箭头函数

1.无法定义响应式,只能做参数接收、事件处理

export default () => {
  return (
      <>
       <div>jsx render</div>
      </>
  )
}

1.1、接收参数

import { Ref, defineComponent, ref } from 'vue'

export const RefExample = defineComponent({
    setup() {
        const count = ref(0)
        return () => {
            return (
              <Counter count={count} />
            )
        }
    },
})

// Counter.tsx
const Counter = ({count}: {count: Ref<number>}) => {
  return (
    <div>
      {count.value}
    </div>
  )
}

1.2、事件响应

import { Ref, defineComponent, ref } from 'vue'

export const RefExample = defineComponent({
    setup() {
        const count = ref(0)
        return () => {
            return (
                <Counter count={count} />
            )
        }
    },
})

const Counter = ({count}: {count: Ref<number>}) => {
  // 不能在此处定义响应式,无效
  const countf = ref(0)
  return (
    <div>
      <button class="px-8 py-2 mr-2 rounded bg-sky-300" onClick={() => count.value++}>+</button>
      {count.value}
      {/* 此处响应事件无效 */}
      <p>
        <button class="px-8 py-2 mr-2 rounded bg-sky-300" onClick={() => countf.value++}>+</button>
        {countf.value}
      </p>
    </div>
  )
}

1.3、事件回调

import { Ref, defineComponent } from 'vue'

export const RefExample03 = defineComponent({
    setup() {
        return () => (
            <Counter count={count} onChange={(e) => console.log(e)} />
        )
    },
})

const Counter = ({count, onChange}: {count: Ref<number>, onChange: (e: number|string) => void}) => {
  const handler = (e: Event) => {
    onChange((e.target as HTMLInputElement).value)
  }
  return (
    <div>
      <input type='text' onInput={handler}>+</input>
    </div>
  )
}

1.4、数据双向绑定

import { Ref, defineComponent, ref } from 'vue'

export const RefExample04 = defineComponent({
    setup() {
        const count = ref(0)
        return () => (
            <Counter count={count} onChange={(e) => count.value = +e} />
        )
    },
})

const Counter = ({count, onChange}: {count: Ref<number>, onChange: (e: number|string) => void}) => {
  const handler = (e: Event) => {
    onChange((e.target as HTMLInputElement).value)
  }
  return (
    <div>
      <button class="px-8 py-2 mr-2 rounded bg-sky-300" onClick={() => count.value++}>+</button>
      <p>{count.value}</p>
      <input type='text' onInput={handler}>+</input>
    </div>
  )
}

1.5、插槽

箭头函数无法使用slots, 使用slots需要使用setup或者render

import { defineComponent } from "vue";

export const SlotsExample = defineComponent({
  setup() {
    return () => (
      <div class="flex space-x-4">      
        <SlotComp 
          header={<h1>header</h1>} 
          footer={<h1>footer</h1>}>
            <h1>content</h1>
        </SlotComp>
        <SlotComp 
          header={<h1>header</h1>}>
            <h1>content</h1>
        </SlotComp>
      </div>
    )
  }
})

const SlotComp = ({ header, footer }: { header: JSX.Element, footer?: JSX.Element }) => {
  return (
    <div class="bg-sky-200 inline-block p-4 rounded-md">
      <div class="header">{header}</div>
      {/* 箭头函数无法使用slots, 使用slots需要使用setup或者render */}
      <div>content</div>
      <div class="footer">{footer && footer}</div>
    </div>
  )
}

效果

image.png

1.6、模版语法

1.6.1、v-html

export const SlotsExample2 = () => {
  return(
    <div v-html="<button style='color: red'>hello h1</button>"></div>
  )
}

2、setup

1.可以使用vue3的组合式api

2.可以使用响应式、生命周期、watch等钩子函数

3.可以使用箭头函数、render函数

import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const count = ref(0)

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

2.1、接收参数

import { Ref, defineComponent, ref } from 'vue'

export const RefExample = defineComponent({
    setup() {
        const count = ref(0)
        return () => {
            return (
              <Counter count={count} />
            )
        }
    },
})

// Counter.tsx
const Count = defineComponent({
  // 进行参数绑定
  props: {
    count: {
      type: Object as PropType<Ref<Number>>,
      required: true
    }
  },
  setup(props) {
    return () => (
      <div>
        {props.count.value}
      </div>
    )
  }
})

2.2、事件响应

import { Ref, defineComponent, ref } from 'vue'

export const RefExample = defineComponent({
    setup() {
        const count = ref(0)
        return () => {
            return (
              <Counter count={count} />
            )
        }
    },
})

// counter.tsx
const Count = defineComponent({
  props: {
    count: {
      type: Object as PropType<Ref<number>>,
      required: true
    }
  },
  setup({ count }) {
    return () => (
      <div>
        <p>{count.value}</p>
        <button class="px-8 py-2 mr-2 rounded bg-sky-300" onClick={() => count.value++}>+</button>
      </div>
    )
  }
})

2.3、事件回调

export const RefExample02 = defineComponent({
    setup() {
      const count = ref(0)
        return () => (
            <Count count={count} onClickAction={(val) => console.log('--', val)} />
        )
    },
})

const Count = defineComponent({
  props: {
    count: {
      type: Object as PropType<Ref<number>>,
      required: true
    }
  },
  // 定义向外暴露的函数
  emits: ['clickAction'],
  setup({ count }, { emit }) {
    const handler = () => {
      // 组件内触发函数
      emit('clickAction', count.value + 1)
    }
    return () => (
      <div>
        <p>{count.value}</p>
        <button class="px-8 py-2 mr-2 rounded bg-sky-300" onClick={handler}>+</button>
      </div>
    )
  }
})

2.4、响应式数据

export const RefExample = defineComponent({
    setup() {
        const count = ref(0)
        const obj = reactive({
          name: 'mz',
          age: 18
        })

        // 监听数据变化
        watch(count, (oldVal, newVal) => {
          console.log('count发生改变', oldVal, newVal);
        })

        const changeInfo = () => {
          count.value++
          obj.name = 'hello mz'
        }

        return () => {
            return (
                <div>
                    <p>{JSON.stringify(obj)}</p>
                    <button onClick={changeInfo}>add</button>
                </div>
            )
        }
    },
})

2.5、数据的双向绑定

export const RefExample05 = defineComponent({
    setup() {
        const count = ref(0)
        return () => (
             <div>
              <Input />
              {/* 自定义数据双向绑定 */}
              <InputField v-model:value={count.value} />
            </div>
        )
    },
})

// 数据双向绑定
const Input = defineComponent({
  setup() {
    const inputRef = ref('')
    return () => <div>
      <p>{inputRef.value}</p>
      <input type="text" v-model={inputRef.value} />
    </div>
  }
})

// 自定义数据双向绑定
const InputField = defineComponent({
  props: ['value'],
  setup(props, { emit }) {
    const handler = () => {
      let newValue = props.value
      console.log(newValue++);
      emit('update:value', ++newValue)
    }
    return () => <div>
      <p>{props.value}</p>
      <button class="px-8 py-2 mr-2 rounded bg-sky-300" onClick={handler}>+</button>
    </div>
  }
})

2.6、向组件外暴露方法

import { defineComponent, onMounted, ref } from "vue";

export const ExposeExample = defineComponent({
  setup() {
    const exposeRef = ref<any>(null)

    onMounted(() => {
      exposeRef.value.testF()
    })

    return () => (
      <Expose ref={exposeRef}>
        <h1>dd</h1>
      </Expose>
    )
  }
})

const Expose = defineComponent({
  setup(_, { expose, slots }) {
    // 向组件外暴漏方法
    expose({
      testF: () => {
        console.log('test');
      }
    })

    const Child = (slots.default) ? (slots.default) as any as () => JSX.Element : () => null
    return () => (
      <div>
        <Child />
      </div>
    )
  }
})

2.7、插槽

2.7.1、默认插槽

export const Slots = defineComponent({
  setup() {
    return () => (
      <SlotDefaultComponent>
        <h1>default slot</h1>
      </SlotDefaultComponent>
    )
  }
})

const SlotDefaultComponent = defineComponent({
  setup(_, { slots }) {
    return () => (
      <>
        {slots.default?.()}
      </>
    )
  }
})

2.7.2、具名插槽

import { defineComponent } from "vue";

export const Slots = defineComponent({
  setup() {
    // 对象形式
    return () => <SlotNameComponent>
      {{
        default: () => <h1>default</h1>,
        header: () => <h1>header</h1>,
        footer: () => <h1>footer</h1>,
      }}
    </SlotNameComponent>

    // slots形式
    // return () => (
    //   <SlotNameComponent v-slots={{
    //     default: () => <h1>default</h1>,
    //     header: () => <h1>header</h1>,
    //     footer: () => <h1>footer</h1>,
    //   }}>
    //   </SlotNameComponent>
    // )
  }
})

const SlotNameComponent = defineComponent({
  setup(_, { slots }) {
    return () => (
      <div>
        <div>
          {slots.header?.()}
        </div>
        <div>
          {slots.footer?.()}
        </div>
        <div>
          {slots.default?.()}
        </div>
      </div>
    )
  }
})

2.7.3、插槽传参

export const Slots = defineComponent({
  setup() {
    return () => <SlotComponent>
      {{
        default: () => <h1>default</h1>,
        header: () => <h1>header</h1>,
        footer: ({ msg }: { msg: string}) => <h1>footer-{msg}</h1>,
      }}
    </SlotComponent>

    // return () => (
    //   <SlotComponent v-slots={{
    //     default: () => <h1>default</h1>,
    //     header: () => <h1>header</h1>,
    //     footer: ({ msg }: { msg: string}) => <h1>footer-{msg}</h1>,
    //   }}>
    //   </SlotComponent>
    // )
  }
})

const SlotComponent = defineComponent({
  setup(_, { slots }) {
    return () => (
      <div>
        <div onClick={withModifiers(() => {
          console.log(this)
        }, ['self'])} />
        <div>
          {slots.header?.()}
        </div>
        <div>
          {slots.footer?.({ msg: 'footer msg' })}
        </div>
        <div>
          {slots.default?.()}
        </div>
      </div>
    )
  }
})

3、render渲染函数

1.可以与vue2、vue3语法配合使用

2.render中访问属性或者事件需通过this

const RenderExample = defineComponent({
  render() {
    return (
      <>
        <p>RenderExample</p>
      </>
    )
  }
})

3.1、与setup配合

export const RenderExample2 = defineComponent({
    setup() {
        const inputVal = ref(12)
        const obj = reactive({
          name: 'mz',
          age: 19
        })
        const say = (msg: string) => {
          console.log('msg: ', msg);
        }
        return {
          inputVal,
          ...toRefs(obj),
          say
        }
    },
    render() {
        return (
            <div>
                <div>{this.inputVal}</div>
                <div>{this.name}</div>
                <div>{this.age}</div>
                <button onClick={() => this.say('hello')}>hello</button>
            </div>
        )
    },
})

3.2、与vue语法配合

export const RenderExample3 = defineComponent({
      data() {
        return {
          inputVal: 12,
          obj: {
            name: 'mz',
            age: 19
          }
        }
      },
      methods: {
        say(msg: string) {
          console.log('msg: ', msg);
        }
      },
      render() {
        const { name, age } = this.obj;
        return (
            <div>
                <div>{this.inputVal}</div>
                <div>name: {name}</div>
                <div>age: {age}</div>
                <button onClick={() => this.say('hello')}>hello</button>
            </div>
        )
    },
})