TypeScript 在 Vue2 中使用
vue2对typescript非常不友好, 是基于类的注解装饰器进行开发,一般用 vue-property-decorator
基本写法
<scriptlang='ts'>import {Component, Vue} from vue-property-decorator;
@ComponentexportdefaultclassAppextendsVue {
};
</script>
lang="ts":script声明当前语言是ts
@Component:注明此类为一个vue组件
export default class Test extends Vue: export当前组件类是继承vue的
data中定义数据
data中的数据由原来的data()方法改成直接在对象中定义,data内的属性直接作为实例属性书写,默认都是public公有属性
exportdefaultclassAppextendsVue {
private msg1 : string = '123';
private msg2 : number = 1;
}
生命周期钩子函数
exportdefaultclassAppextendsVue {
privatecreated() {
this.init();
}
privatemounted() {
console.log("mounted");
}
}
注意:写ts版的vue2不要在生命周期中使用其他组件的变量。
方法
exportdefaultclassAppextendsVue {
privateinit(name: string): string {
return name
console.log('init');
}
}
async await
asyncfunctionhello(): Promise<void>{
awaitsay()
}
@Component()
<scriptlang="ts">import { Component, Vue } from"vue-property-decorator";
importHelloWorldfrom"@/components/HelloWorld.vue";
@Component({
components: {
HelloWorld, // 声明子组件的引用
}
})
exportdefaultclassAppextendsVue {}
</script>
@Prop
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
可以传如下参数
Constructor 例如String, Number, Boolean
Constructor[], 构造函数的队列, 类型在这队列中即可
PropOptions
type 类型不对会报错 Invalid prop: type check failed for prop “xxx”. Expected Function, got String with value “xxx”.
default 如果父组件没有传的话为该值, 注意只能用这一种形式来表示默认值, 不能@Prop() name = 1来表示默认值 1, 虽然看起来一样, 但是会在 console 里报错, 不允许修改 props 中的值
required 没有会报错 [Vue warn]: Missing required prop: “xxx”
validator 为一个函数, 参数为传入的值, 比如(value) => value > 100
@Prop()
propA: string@Prop()
propB: number@Prop({
type: Number,
validator: (value) => {
return value > 100;
},
required: true
}) private propC!: string// !表示有值, 否则 ts 会告警未初始化@Prop({
default: 'this is title',
required: true
}) propD!: string; // !表示有值, 否则 ts 会告警未初始化@Prop({
default: true,
required: true
}) propE: boolean | undefined;
@PropSync()
与 Prop 的区别是子组件可以对 props 进行更改, 并同步给父组件
子组件
<template><div><p>{{count}}</p><button @click="innerCount += 1">increment</button></div></template><scriptlang="ts">
@ComponentexportdefaultclassPropSyncComponentextendsVue {
@PropSync('count') private innerCount!: number // 注意@PropSync 里的参数不能与定义的实例属性同名, 因为还是那个原理, props 是只读的.
}
</script>
父组件:注意父组件里绑定 props 时需要加修饰符 .sync
<template><PropSyncComponent:count.sync="count"/></template><scriptlang="ts">
@Component({
components: PropSyncComponent
})
exportdefaultclassPropSyncComponentextendsVue {
@PropSync('count') private innerCount!: number // 注意@PropSync 里的参数不能与定义的实例属性同名, 因为还是那个原理, props 是只读的.
}
</script>
@Emit()
定义emit事件,参数字符串表示分发的事件名,如果没有,则使用方法名作为分发事件名,会自动转连字符写法:
@Emit()不传参数,那么它触发的事件名就是它所修饰的函数名.
@Emit(name: string),里面传递一个字符串,该字符串为要触发的事件名.
js写法
exportdefault {
data() {
return {
count: 0
}
},
methods: {
addToCount(n) {
this.count += n
this.$emit('add-to-count', n)
},
resetCount() {
this.count = 0this.$emit('reset')
},
returnValue() {
this.$emit('return-value', 10)
},
promise() {
const promise = newPromise(resolve => {
setTimeout(() => {
resolve(20)
}, 0)
})
promise.then(value => {
this.$emit('promise', value)
})
}
}
}
ts写法
import { Vue, Component, Emit } from'vue-property-decorator'@ComponentexportdefaultclassYourComponentextendsVue {
count = 0@Emit()
addToCount(n: number) {
this.count += n
}
@Emit('reset')
resetCount() {
this.count = 0
}
@Emit()
returnValue() {
return10
}
@Emit()
promise() {
returnnewPromise(resolve => {
setTimeout(() => {
resolve(20)
}, 0)
})
}
}
计算属性
对于Vue中的计算属性,我们只需要将该计算属性名定义为一个函数,,在函数前加上get关键字即可,原本Vue中的computed里的每个计算属性都变成了在前缀添加get的函数。
publicgetcomputedMsg(){
return'这里是计算属性' + this.message;
}
publicsetcomputedMsg(message: string){
}
@Watch()
监听属性发生更改时被触发. 可接受配置参数 options
immediate: boolean 是否在侦听开始之后立即调用该函数
deep: boolean 是否深度监听.
import { Vue, Component, Watch } from vue-property-decorator;
exportdefaultclassAppextendsVue {
@Watch('child')
onChangeChildValue(newValue: string, oldValue: string){
......todo
}
@Watch('parent', {immediate: true, deep: false})
privateonChangeParentValue(newValue: string, oldValue: string){
......todo
}
}
mixin
ts中写mixin,mixin本身是个ts文件,这点需要注意 但是Mixin 在方法里找不到组件中的变量和方法。
// PeMixin.ts 注意这里的文件命名import { Component, Vue } from'vue-property-decorator';
@ComponentexportdefaultclassPeMixinextendsVue {
// 组件逻辑
}
使用mixin文件
// 使用mixin的文件import {
Component, Mixins,
} from'vue-property-decorator';
importPeMixinfrom'./PeMixin.ts'// 两个的话,Mixins里面继续添加即可@ComponentexportdefaultclassPeextendsMixins(PeMixin) {
// 组件逻辑
}
$refs
constlngLat:any = e.lngLat;
(this.$refs.coordinatesasany).style.display = 'block';
要写成(this.$refs.coordinates as any) 且前面一行代码要加 ;
vuex 在 @Component 使用
@Component ({
computed:{
//获取store的state
...mapState(['name']),
//获取store中的getters
...mapGetters(["tack/getParams"]),
name() {
returnthis.$store.state.name
},
validList(){
returnthis.$store.getters.validList
}
},
methods:{
//获取store的mutations
...mapMutations['setCount'],
//获取store中的actions
...mapactions(["tack/getParams"]),
setCount(data) {
this.$store.commit('setCount',5)
},
asyncSetCount(data) {
this.$store.dispatch("asyncSetCount",data)
}
}
})
vuex 在 类组件中使用
import {Component,Vue} from'vue-property-decorator'import {namespace,State,Getter,Mutation,Action} from 'vuex-class'
// 1.namespace的使用
const userModule = namespace('user')
@Component
export default class StateComp extends Vue {
@userModule.State(state => state.username) username?:string@userModule.State('username') username?:string@userModule.Getter('username') username?:string@userModule.Mutation('setUsername') setUsername!:(username:string)=>string@userModule.Action('setUsername') setUsername!:(username:string)=>string// 2.@State @Getter @Mutation @Action@State('username',{namespace:'user'})
username!:string@Getter('username',{namespace:'user'})
username!:string@Mutation('setUsername',{namespace:'user'})
setUsername!:any@Action('setUsername',{namespace:'user'})
setUsername!:anymounted() {
// 3.直接调用 this.$store.state.namethis.$store.getters.validListthis.$store.commit('setCount',5)
this.$store.dispatch("asyncSetCount",data)
}
}
TypeScript 在 Vue3 中使用
为组件的 props 标注类型#
使用 <script setup>#
当使用 <script setup> 时,defineProps() 宏函数支持从它的参数中推导类型:
<scriptsetuplang="ts">const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
props.foo// string
props.bar// number | undefined</script>
这被称之为“运行时声明”,因为传递给 defineProps() 的参数会作为运行时的 props 选项使用。
然而,通过泛型参数来定义 props 的类型通常更直接:
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
这被称之为“基于类型的声明”。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。在这种场景下,我们第二个例子中编译出的运行时选项和第一个是完全一致的。
基于类型的声明或者运行时声明可以择一使用,但是不能同时使用。
我们也可以将 props 的类型移入一个单独的接口中:
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
语法限制#
为了生成正确的运行时代码,传给 defineProps() 的泛型参数必须是以下之一:
一个类型字面量:
defineProps<{ /*... */ }>()
对同一个文件中的一个接口或对象类型字面量的引用:
interfaceProps {/* ... */}
defineProps<Props>()
接口或对象字面类型可以包含从其他文件导入的类型引用,但是,传递给 defineProps 的泛型参数本身不能是一个导入的类型:
import { Props } from'./other-file'// 不支持!
defineProps<Props>()
这是因为 Vue 组件是单独编译的,编译器目前不会抓取导入的文件以分析源类型。我们计划在未来的版本中解决这个限制。
Props 解构默认值#
当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults 编译器宏解决:
exportinterfaceProps {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
这将被编译为等效的运行时 props default 选项。此外,withDefaults 帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。
或者,你可以使用目前为实验性的响应性语法糖:
<script setup lang="ts">
interfaceProps {
name: string
count?: number
}
// 对 defineProps() 的响应性解构// 默认值会被编译为等价的运行时选项const { name, count = 100 } = defineProps<Props>()
</script>
这个行为目前需要显式地选择开启。
非 <script setup> 场景下#
如果没有使用 <script setup>,那么为了开启 props 的类型推导,必须使用 defineComponent()。传入 setup() 的 props 对象类型是从 props 选项中推导而来。
import { defineComponent } from'vue'exportdefaultdefineComponent({
props: {
message: String
},
setup(props) {
props.message// <-- 类型:string
}
})
为组件的 emits 标注类型
在 <script setup> 中,emit 函数的类型标注也可以通过运行时声明或是类型声明进行:
<scriptsetuplang="ts">// 运行时const emit = defineEmits(['change', 'update'])
// 基于类型const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
这个类型参数应该是一个带调用签名的类型字面量。这个类型字面量的类型就是返回的 emit 函数的类型。我们可以看到,基于类型的声明使我们可以对所触发事件的类型进行更细粒度的控制。
若没有使用 <script setup>,defineComponent() 也可以根据 emits 选项推导暴露在 setup 上下文中的 emit 函数的类型:
import { defineComponent } from'vue'exportdefaultdefineComponent({
emits: ['change'],
setup(props, { emit }) {
emit('change') // <-- 类型检查 / 自动补全
}
})
为 ref() 标注类型
ref 会根据初始化时的值推导其类型:
import { ref } from'vue'// 推导出的类型:Ref<number>const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
有时我们可能想为 ref 内的值指定一个更复杂的类型,可以通过使用 Ref 这个类型:
import { ref } from'vue'importtype { Ref } from'vue'constyear: Ref<string | number> = ref('2020')
year.value = 2020// 成功!
或者,在调用 ref() 时传入一个泛型参数,来覆盖默认的推导行为:
// 得到的类型:Ref<string | number>const year = ref<string | number>('2020')
year.value = 2020// 成功!
如果你指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含 undefined 的联合类型:
// 推导得到的类型:Ref<number | undefined>const n = ref<number>()
为 reactive() 标注类型
reactive() 也会隐式地从它的参数中推导类型:
import { reactive } from'vue'// 推导得到的类型:{ title: string }const book = reactive({ title: 'Vue 3 指引' })
要显式地标注一个 reactive 变量的类型,我们可以使用接口:
import { reactive } from'vue'interfaceBook {
title: string
year?: number
}
constbook: Book = reactive({ title: 'Vue 3 指引' })
不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。
为 computed() 标注类型
computed() 会自动从其计算函数的返回值上推导出类型:
import { ref, computed } from'vue'const count = ref(0)
// 推导得到的类型:ComputedRef<number>constdouble = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'const result = double.value.split('')
你还可以通过泛型参数显式指定类型:
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})
为事件处理函数标注类型
在处理原生 DOM 事件时,应该为我们传递给事件处理函数的参数正确地标注类型。让我们看一下这个例子:
<scriptsetuplang="ts">functionhandleChange(event) {
// `event` 隐式地标注为 `any` 类型console.log(event.target.value)
}
</script><template><inputtype="text" @change="handleChange" /></template>
没有类型标注时,这个 event 参数会隐式地标注为 any 类型。这也会在 tsconfig.json 中配置了 "strict": true 或 "noImplicitAny": true 时报出一个 TS 错误。因此,建议显式地为事件处理函数的参数标注类型。此外,你可能需要显式地强制转换 event 上的属性:
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
为 provide / inject 标注类型
provide 和 inject 通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型:
import { provide, inject } from'vue'importtype { InjectionKey } from'vue'const key = Symbol() asInjectionKey<string>
provide(key, 'foo') // 若提供的是非字符串值会导致错误const foo = inject(key) // foo 的类型:string | undefined
建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入。
当使用字符串注入 key 时,注入值的类型是 unknown,需要通过泛型参数显式声明:
const foo = inject<string>('foo') // 类型:string | undefined
注意注入的值仍然可以是 undefined,因为无法保证提供者一定会在运行时 provide 这个值。
当提供了一个默认值后,这个 undefined 类型就可以被移除:
const foo = inject<string>('foo', 'bar') // 类型:string
如果你确定该值将始终被提供,则还可以强制转换该值:
const foo = inject('foo') asstring
为模板引用标注类型
模板引用需要通过一个显式指定的泛型参数和一个初始值 null 来创建:
<scriptsetuplang="ts">import { ref, onMounted } from'vue'const el = ref<HTMLInputElement | null>(null)
onMounted(() => {
el.value?.focus()
})
</script><template><inputref="el" /></template>
注意为了严格的类型安全,有必要在访问 el.value 时使用可选链或类型守卫。这是因为直到组件被挂载前,这个 ref 的值都是初始的 null,并且在由于 v-if 的行为将引用的元素卸载时也可以被设置为 null。
为组件模板引用标注类型
有时,你可能需要为一个子组件添加一个模板引用,以便调用它公开的方法。举例来说,我们有一个 MyModal 子组件,它有一个打开模态框的方法:
<!-- MyModal.vue --><scriptsetuplang="ts">import { ref } from'vue'const isContentShown = ref(false)
constopen = () => (isContentShown.value = true)
defineExpose({
open
})
</script>
为了获取 MyModal 的类型,我们首先需要通过 typeof 得到其类型,再使用 TypeScript 内置的 InstanceType 工具类型来获取其实例类型:
<!-- App.vue --><scriptsetuplang="ts">importMyModalfrom'./MyModal.vue'const modal = ref<InstanceType<typeofMyModal> | null>(null)
constopenModal = () => {
modal.value?.open()
}
</script>
注意,如果你想在 TypeScript 文件而不是在 Vue SFC 中使用这种技巧,需要开启 Volar 的 Takeover 模式。
TypeScript 在 React 中使用
引入 React
推荐使用
import * asReactfrom'react'import * asReactDOMfrom'react-dom'
需要添加额外的配置"allowSyntheticDefaultImports": true
importReactfrom'react'importReactDOMfrom'react-dom'
函数式组件声明方式
推荐 React.FunctionComponent === React.FC(我们项目中也是这么用的)
// GreattypeAppProps = {
message: string
}
constApp: React.FC<AppProps> = ({ message, children }) => (
<div>
{message}
{children}
</div>
)
使用用 React.FC 声明函数组件和普通声明以及 PropsWithChildren 的区别是:
React.FC 显式地定义了返回类型,其他方式是隐式推导的
React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全
React.FC 为 children 提供了隐式的类型(ReactElement | null)
比如以下用法 React.FC 会报类型错误:
const App: React.FC = props => props.children
const App: React.FC = () => [1, 2, 3]
const App: React.FC = () => 'hello'
解决方法:
constApp: React.FC<{}> = props => props.childrenasanyconstApp: React.FC<{}> = () => [1, 2, 3] asanyconstApp: React.FC<{}> = () =>'hello'asany// 或者constApp: React.FC<{}> = props => (props.childrenasunknown) asJSX.ElementconstApp: React.FC<{}> = () => ([1, 2, 3] asunknown) asJSX.ElementconstApp: React.FC<{}> = () => ('hello'asunknown) asJSX.Element
如果出现类型不兼容问题,建议使用以下两种方式:
PropsWithChildren
这种方式可以为你省去频繁定义 children 的类型,自动设置 children 类型为 ReactNode
typeAppProps = React.PropsWithChildren<{ message: string }>
constApp = ({ message, children }: AppProps) => (
<div>
{message}
{children}
</div>
)
直接声明:
typeAppProps = {
message: string
children?: React.ReactNode
}
constApp = ({ message, children }: AppProps) => (
<div>
{message}
{children}
</div>
)
Hook
useState
默认情况下会为你根据你设置的初始值自动进行推导,但是如果初始值为 null 需要显示的声明类型
typeUser = {
name: stringage: number
}
const [user, setUser] = React.useState<User | null>(null)
useRef
当初始值为 null 时,有两种创建方式:
const ref1 = React.useRef<HTMLInputElement>(null)
const ref2 = React.useRef<HTMLInputElement | null>(null)
这两种的区别在于:
第一种方式的 ref1.current 是只读的(read-only),并且可以传递给内置的 ref 属性,绑定 DOM 元素 ;
第二种方式的 ref2.current 是可变的(类似于声明类的成员变量)
这两种方式在使用时,都需要对类型进行检查:
const onButtonClick = () => {
ref1.current?.focus()
ref2.current?.focus()
}
在某种情况下,可以省去类型检查,通过添加 ! 断言,不推荐:
// BadfunctionMyComponent() {
const ref1 = React.useRef<HTMLDivElement>(null!)
React.useEffect(() => {
// 不需要做类型检查,需要人为保证ref1.current.focus一定存在doSomethingWith(ref1.current.focus())
})
return<divref={ref1}> etc </div>
}
useEffect
返回值只能是函数或者是 undefined
useMemo / useCallback
useMemo 和 useCallback 都可以直接从它们返回的值中推断出它们的类型
useCallback 的参数必须制定类型,否则 ts 不会报错,默认指定为 any
const value = 10// 自动推断返回值为 numberconst result = React.useMemo(() => value * 2, [value])
// 自动推断 (value: number) => numberconst multiply = React.useCallback((value: number) => value * multiplier, [
multiplier,
])
同时也支持传入泛型, useMemo 的泛型指定了返回值类型,useCallback 的泛型指定了参数类型
// 也可以显式的指定返回值类型,返回值不一致会报错const result = React.useMemo<string>(() =>2, [])
// 类型“() => number”的参数不能赋给类型“() => string”的参数。const handleChange = React.useCallback<
React.ChangeEventHandler<HTMLInputElement>
>(evt => {
console.log(evt.target.value)
}, [])
自定义 Hook
需要注意,自定义 Hook 的返回值如果是数组类型,TS 会自动推导为 Union 类型,而我们实际需要的是数组里里每一项的具体类型,需要手动添加 const 断言 进行处理
functionuseLoading() {
const [isLoading, setState] = React.useState(false)
constload = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() =>setState(false))
}
// 实际需要: [boolean, typeof load] 类型// 而不是自动推导的:(boolean | typeof load)[]return [isLoading, load] asconst
}
如果使用 const 断言遇到问题,也可以直接定义返回类型:
exportfunctionuseLoading(): [
boolean,
(aPromise: Promise<any>) =>Promise<any>
] {
const [isLoading, setState] = React.useState(false)
constload = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() =>setState(false))
}
return [isLoading, load]
}
如果有大量的自定义 Hook 需要处理,这里有一个方便的工具方法可以处理 tuple 返回值:
function tuplify<T extendsany[]>(...elements: T) {
return elements
}
functionuseLoading() {
const [isLoading, setState] = React.useState(false)
constload = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() =>setState(false))
}
// (boolean | typeof load)[]return [isLoading, load]
}
functionuseTupleLoading() {
const [isLoading, setState] = React.useState(false)
constload = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() =>setState(false))
}
// [boolean, typeof load]returntuplify(isLoading, load)
}
默认属性 defaultProps
在我们进行迁移的时候,我们可以通过自定义组件 props 类型的必选或者可选来规定参数是否必须传,而对于 angular 中有初始值的@Input 属性,我们可以使用 React 组件的默认值来为其初始化
但是大部分文章都不推荐使用 defaultProps 来进行初始化,推荐使用默认参数值来代替默认属性:
但是这种方式对于属性特别多的时候又很鸡肋
typeGreetProps = { age?: number }
constGreet = ({ age = 21 }: GreetProps) => {
/* ... */
}
Types or Interfaces
implements 与 extends 静态操作,不允许存在一种或另一种实现的情况,所以不支持使用联合类型:
使用 Type 还是 Interface?
有几种常用规则:
在定义公共 API 时(比如编辑一个库)使用 interface,这样可以方便使用者继承接口
在定义组件属性(Props)和状态(State)时,建议使用 type,因为 type 的约束性更强
interface 和 type 在 ts 中是两个不同的概念,但在 React 大部分使用的 case 中,interface 和 type 可以达到相同的功能效果,type 和 interface 最大的区别是:
type 类型不能二次编辑,而 interface 可以随时扩展
interface Animal {
name: string
}
// 可以继续在原有属性基础上,添加新属性:colorinterface Animal {
color: string
}
/********************************/type Animal = {
name: string
}
// type类型不支持属性扩展// Error: Duplicate identifier 'Animal'type Animal = {
color: string
}
获取未导出的 Type
某些场景下我们在引入第三方的库时会发现想要使用的组件并没有导出我们需要的组件参数类型或者返回值类型,这时候我们可以通过 ComponentProps/ ReturnType 来获取到想要的类型。
// 获取参数类型import { Button } from'library'// 但是未导出props typetypeButtonProps = React.ComponentProps<typeofButton> // 获取propstypeAlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClickconstAlertButton: React.FC<AlertButtonProps> = props => (
<ButtononClick={() => alert('hello')} {...props} />
)
// 获取返回值类型functionfoo() {
return { baz: 1 }
}
typeFooReturn = ReturnType<typeof foo> // { baz: number }
Props
通常我们使用 type 来定义 Props,为了提高可维护性和代码可读性,在日常的开发过程中我们希望可以添加清晰的注释。
现在有这样一个 type
type OtherProps = {
name: string
color: string
}
增加相对详细的注释,使用时会更清晰,需要注意,注释需要使用 /**/ , // 无法被 vscode 识别
// Great/**
* @param color color
* @param children children
* @param onClick onClick
*/typeProps = {
/** color */
color?: string/** children */children: React.ReactNode/** onClick */onClick: () =>void
}
// type Props// @param color — color// @param children — children// @param onClick — onClickconstButton: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {
return (
<buttonstyle={{backgroundColor:color }} onClick={onClick}>
{children}
</button>
)
}
常用的 React 属性类型
exportdeclareinterfaceAppBetterProps {
children: React.ReactNode// 一般情况下推荐使用,支持所有类型 GreatfunctionChildren: (name: string) =>React.ReactNode
style?: React.CSSProperties// 传递style对象
onChange?: React.FormEventHandler<HTMLInputElement>
}
Forms and Events
onChange
change 事件,有两个定义参数类型的方法。
第一种方法使用推断的方法签名(例如:React.FormEvent :void)
import * asReactfrom'react'type changeFn = (e: React.FormEvent<HTMLInputElement>) =>voidconstApp: React.FC = () => {
const [state, setState] = React.useState('')
constonChange: changeFn = e => {
setState(e.currentTarget.value)
}
return (
<div><inputtype="text"value={state}onChange={onChange} /></div>
)
}
第二种方法强制使用 @types / react 提供的委托类型,两种方法均可。
import * asReactfrom'react'constApp: React.FC = () => {
const [state, setState] = React.useState('')
constonChange: React.ChangeEventHandler<HTMLInputElement> = e => {
setState(e.currentTarget.value)
}
return (
<div><inputtype="text"value={state}onChange={onChange} /></div>
)
}
onSubmit
如果不太关心事件的类型,可以直接使用 React.SyntheticEvent,如果目标表单有想要访问的自定义命名输入,可以使用类型扩展
import * asReactfrom'react'constApp: React.FC = () => {
constonSubmit = (e: React.SyntheticEvent) => {
e.preventDefault()
const target = e.targetastypeof e.target & {
password: { value: string }
} // 类型扩展const password = target.password.value
}
return (
<formonSubmit={onSubmit}><div><label>
Password:
<inputtype="password"name="password" /></label></div><div><inputtype="submit"value="Log in" /></div></form>
)
}
不要在 type 或 interface 中使用函数声明
保持一致性,类型/接口的所有成员都通过相同的语法定义。
--strictFunctionTypes 在比较函数类型时强制执行更严格的类型检查
事件处理
我们在进行事件注册时经常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时我们通过 clientX、clientY 去获取指针的坐标。
大家可能会想到直接把 event 设置为 any 类型,但是这样就失去了我们对代码进行静态检查的意义。
幸运的是 React 的声明文件提供了 Event 对象的类型声明。
Event 事件对象类型
ClipboardEvent<T = Element> 剪切板事件对象
DragEvent<T =Element> 拖拽事件对象
ChangeEvent<T = Element> Change 事件对象
KeyboardEvent<T = Element> 键盘事件对象
MouseEvent<T = Element> 鼠标事件对象
TouchEvent<T = Element> 触摸事件对象
WheelEvent<T = Element> 滚轮时间对象
AnimationEvent<T = Element> 动画事件对象
TransitionEvent<T = Element> 过渡事件对象
事件处理函数类型
当我们定义事件处理函数时有没有更方便定义其函数类型的方式呢?答案是使用 React 声明文件所提供的 EventHandler 类型别名,通过不同事件的 EventHandler 的类型别名来定义事件处理函数的类型
type EventHandler<E extends React.SyntheticEvent<any>> = {
bivarianceHack(event: E): void
}['bivarianceHack']
type ReactEventHandler<T = Element> =EventHandler<React.SyntheticEvent<T>>
type ClipboardEventHandler<T = Element> =EventHandler<React.ClipboardEvent<T>>
type DragEventHandler<T = Element> =EventHandler<React.DragEvent<T>>
type FocusEventHandler<T = Element> =EventHandler<React.FocusEvent<T>>
type FormEventHandler<T = Element> =EventHandler<React.FormEvent<T>>
type ChangeEventHandler<T = Element> =EventHandler<React.ChangeEvent<T>>
type KeyboardEventHandler<T = Element> =EventHandler<React.KeyboardEvent<T>>
type MouseEventHandler<T = Element> =EventHandler<React.MouseEvent<T>>
type TouchEventHandler<T = Element> =EventHandler<React.TouchEvent<T>>
type PointerEventHandler<T = Element> =EventHandler<React.PointerEvent<T>>
type UIEventHandler<T = Element> =EventHandler<React.UIEvent<T>>
type WheelEventHandler<T = Element> =EventHandler<React.WheelEvent<T>>
type AnimationEventHandler<T = Element> =EventHandler<React.AnimationEvent<T>>
type TransitionEventHandler<T = Element> =EventHandler<
React.TransitionEvent<T>
>
Promise 类型
在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。Promise 是一个泛型类型,T 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型。
泛型参数的组件
typeProps<T> = {
name: T
name2?: T
}
constTestC: <T>(props: Props<T>) =>React.ReactElement = ({ name, name2 }) => {
return (
<divclassName="test-b">
TestB--{name}
{name2}
</div>
)
}
constTestD = () => {
return (
<div>
<TestC<string> name="123" />
</div>
)
}
什么时候使用泛型
需要作用到很多类型的时候
需要被用到很多地方的时候,比如常用的工具泛型 Partial
如果需要深 Partial 我们可以通过泛型递归来实现
type DeepPartial<T>=T extends Function
?T:T extends object
?{[P in keyof T]?: DeepPartial<T[P]>}:T
type PartialedWindow = DeepPartial<Window>
react篇转载自:http://events.jianshu.io/p/793b1e4207a4