乐趣区

关于vue3:在Vue-3中使用typescript和jsx

本文将介绍如何在 Vue 3 中应用 tsx

首先答复两个问题:

Why Vue 3


在 Vue 2 中,组件相干的逻辑都被写在一个文件中,常常会呈现一个组件几千行代码,十分难以保护;而且一个性能的相干代码通常会扩散写在 data methods created 等各个选项中,想要复用一个性能也是比拟艰难的。而 Vue 3 的组合式 API 正是为了解决这个问题。

Why tsx


Vue 中的组件是一个黑盒,你不晓得他的内部结构(属性和事件),只能通过文档甚至浏览源码来理解,这样是很消耗工夫的。而在 tsx 中,在编写代码时就能够取得代码提醒,组件的内部结构能够高深莫测,且具备代码约束力(当你编写了谬误的代码时会取得一个报错),这样是能够极大的进步代码编写效率,缩小低级谬误呈现。

组件的类型申明


Props

ts 会从 vue 组件的 props 申明中推断属性的类型,以下是一些根本的类型推断:

  • String → string
  • Number → number
  • Boolean → boolean
  • Array → unknown[]
  • Object → Record<string, any>
  • Date → string
  • Function → Function
  • Symbol → symbol

你能够通过 PropType 来进行更加准确的类型申明,倡议对所有的 Array Object Function 都进行申明

import {defineComponent, PropType} from 'vue'
export default defineComponent({
  props: {
    title: String,
    values: {type: Array as PropType<number[]>,
      required: true,
    },
    data: Object as PropType<{id: number, name: string}>,
    onClick: Function as PropType<(id: number) => void>
  }
})

属性默认是可选的,在下面例子中的 title 默认会失去一个 string | undefined 的类型。如果你申明了required: true,他的类型就会变成string

有一些第三方组件可能会短少类型申明,这会导致 typescript 类型查看谬误*** 属性并不存在,这种状况倡议应用属性解构,避开 typescript 类型查看

// Property 'onClick' does not exist on type ...
<ElButton onClick={onClickBtn}></Elbutton>

// 应用属性解构
<ElButton {...{onClick: onClickBtn}}></Elbutton>

Events

Events 实际上是 function props 的语法糖,@click="myCallback"等价于 onClick={myCallback} 强烈建议防止应用Event,而应用与之等效的办法属性,这样你会取得更好的类型提醒。

defineComponent({
  emits: {
    // event runtime validation
    'play': (value: string) => {return true},
    'rest-time': () => true},
  setup(props, {emit}) {onMounted(() => {emit('play', 'game')
      emit('rest-time')
    })
  }
})
<MyComponent onPlay={...} onRest-time={...} />

等价于

defineComponent({
  props: {onPlay: Function as PropType<(value: string) => void>,
    'onRest-time': Function as PropType<() => void>,},
  setup(props) {onMounted(() => {props.onPlay('game')
      props['onRest-time']()})
  }
})
<MyComponent onPlay={...} onRest-time={...} />

TSX 语法


你能够持续应用 template 模板,不过咱们更倡议你应用 tsx 模板。

<template>
  <div>contents</div>
</template>
<script lang="tsx">
defineComponent({setup(props) {
    return {// ...}
  }
})
</script>

或者

<script lang="tsx">
defineComponent({setup(props) {return () => (<div>contents</div>)
  }
})
</script>

Attributes / Props

// template
<input type="email" :placeholder="placeholderText" />

// tsx
<input type="email" placeholder={placeholderText} />

Directives

v-model

// template
<input v-model="val" />
<input v-model:name="val">
<input v-model.trim="val">
<input v-model:name.trim="val">

// tsx
<input v-model={val} />
<input v-model={[val, 'name']} />
<input v-model={[val, ['trim']]} />
<input v-model={[val, 'name', ['trim']]} />

// bind ref value
<input v-model={valueRef.value} />

v-models

// template
<A v-model="foo" v-model:bar="bar" />

// tsx
<A v-models={[[foo], [bar, "bar"]]} />

slot

const A = (props, { slots}) => (
  <>
    <h1>{slots.default ? slots.default() : 'foo' }</h1>
    <h2>{slots.bar && slots.bar() }</h2>
  </>
);

const App = {setup() {
    const slots = {default: () => <div>A</div>,
      bar: () => <span>B</span>,};
    return () => <A v-slots={slots} />;
  },
};

Hooks


Hooks 是从 React 借鉴过去的概念,Vue 3 中的 Hooks 叫做组合式 API,它包含 Vue 3 提供的响应性 api(ref, reactive, computed, onMounted…),以及本人编写的 hook 函数。

Hooks 能够对凌乱的代码做逻辑拆分,类似的性能能够不便的进行复用,通过 Hooks 的灵便组合,能够轻松解决大型简单组件。

以下是 Vue 官网的例子,组件的性能被拆分到不同的 Hooks 中。

setup(props) {const { user} = toRefs(props)

  const {repositories, getUserRepositories} = useUserRepositories(user)

  const {searchQuery, repositoriesMatchingSearchQuery} = useRepositoryNameSearch(repositories)

  const {filters, updateFilters, filteredRepositories} = useRepositoryFilters(repositoriesMatchingSearchQuery)

  return {
    repositories: filteredRepositories,
    getUserRepositories,
    searchQuery,
    filters,
    updateFilters
  }
}

在 React 中,应用 Hooks 时须要恪守一些规定。不过 Vue 3 的渲染机制不同于 React,在 Vue 3 中应用 Hooks 规定稍有不同:

1. 只在最顶层应用 Hook, 不要在循环,条件或嵌套函数中调用 Hook

因为 Vue 3 应用 Proxy 来追踪数据扭转,Hooks 调用的程序不会影响数据的依赖关系,所以这条规定不用恪守。

2. 只在 React(Setup 和 Hook) 函数中调用 Hook,不要在一般的 JavaScript 函数中调用 Hook

这条规定依然实用于 Vue 3。Hooks 能够用来保留状态,而一般函数不能够。当数据依赖扭转时,函数会被从新执行,如果刚好函数中申明了一些状态,例如 ref 对象,那么这个状态就会被重置,从而导致意外的状况产生。所以 Hook 只能在 Setup 函数以及其余 Hook 中调用。

// ❎ 不要在一般函数中调用 hook(ref)
function renderInput() {
  // 当 input 从新渲染时,inputValue 会被重置
  const inputValue = ref('')
  return <input v-model={inputValue.value} />
}
// ✅ 在 setup 中调用 hook(ref)
setup() {const inputValue = ref('')
  return ({renderInput(inputValue)}
  )
}

// 通过参数传递 inputValue
function renderInput(inputValue) {return <input v-model={inputValue.value} />
}

Problems


目前,Vue 3 对 ts 的反对度还没有达到 100%,所以仍存在一些问题,例如:

  1. tsx 代码无奈热更新,需手动刷新(截至 2021/4/30)

    <script lang="tsx">
    defineComponent({setup(props) {return () => (<div>the text here won't change with HMR</div>)
      }
    })
    </script>
  2. 函数模式的属性默认值会烦扰 typescript 类型推断,需改为箭头函数(截至 2021/4/30)

    // ❎ 会烦扰 typescript 类型推断
    export default defineComponent({
      props: {
     objectProp: {
       type: Object,
       default() {return {}
       }
     },
     arrayProp: {
       type: Array,
       default() {return []
       }
     }
      }
    })
    
    // ✅ 应用箭头函数
    export default defineComponent({
      props: {
     objectProp: {
       type: Object,
       default: () => ({})
     },
     arrayProp: {
       type: Array,
       default: () => []
     }
      }
    })

References


  1. Vue 3 typescript 反对 (https://v3.cn.vuejs.org/guide/typescript-support.html)
  2. jsx-next repo (https://github.com/vuejs/jsx-next)
  3. Vue 3 组合式 api (https://v3.cn.vuejs.org/guide/composition-api-introduction.html)
退出移动版