若是有任何错误,请务必指出。
参考连接:https://github.com/typescript...react
react
:import * as React from 'react'
为每一个文件建立一个类组件。在一个文件内请尽可能只写一个类组件,其余的请使用函数组件git
// ReactTs.tsx class TsExample extends React.Component<any, any> {}
使用 extends
来继承 React.component
接口github
React.Component
后面带了 <any, any>
这个奇怪的字符串。这是 React.Component
接口的类型参数。第一个是 props
的类型,第二个是 state
的类型。为了给 props
和 state
定义类型,咱们可使用 interface
关键字。咱们能够将类型定义写在当前的组件文件,可是因为在当前文件中具备了 import
关键字,当前文件会成为一个模块,即没法被全局引用。因此建议新建一个文件 index.d.ts
来储存 interface\type
等typescript
// index.d.ts interface ITsExampleProps { name: string }
注:export
和 import
会将一个文件变成模块,即里面的变量不会被暴露到全局数组
接下来咱们为这个组件初始化 state
app
// ReactTs.tsx class TsExample extends React.Component<ITsExampleProps, ITsExampleState> { public state = { count: 0 } }
此处建议不要 public state = {}
和 constructor
两种方法混用函数
为组件添加 UI:每一个类组件都应该有一个 render
方法来进行 UI 渲染。render
方法中必须返回一个 ReactNode
,ReactNode
能够是一个HTMLElement
或者一个字符串,数字工具
// ReactTs.tsx class TsExample extends React.Component<ITsExampleProps, ITsExampleState> { public state = { count: 0 } public render() { let { count } = this.state return <div styleName="wrap">这里是 ui</div> } }
为组件添加并调用方法,使用 state
ui
// ReactTs.tsx class TsExample extends React.Component<IExampleProps, IExampleState> { public state = { count: 0, currentCount: 0 } public plusCount = () => { this.setState({ count: ++this.state.count }) } public render() { let { count } = this.state return ( <div styleName=""> {/* 调用state */} <div>{count}</div> {/* 调用方法 */} <Button onClick={this.plusCount}>增长</Button> <div> <Button onClick={() => { this.showCurrentCount(this.state.count) }} > 当前数量 </Button> </div> </div> ) } }
8) 接下来咱们将该组件做为一个子组件放到另外一个组件中this
// example.tsx class Example extends React.Component<IExampleProps, IExampleState> { public render() { return ( <div styleName="example"> <ReactTs /> </div> ) } }
为子组件传入一个参数
// example.tsx class Example extends React.Component<IExampleProps, IExampleState> { public render() { return ( <div styleName='example'> <ReactTs name="React-Ts" /> </div> ) } }
10) 在子组件中使用参数:this.props.name
// ReactTs.tsx public render() { let {count} = this.state return ( <div styleName='React-Ts'> <div>名字:{this.props.name}</div> <div>增长数量:{count}</div> <div>当前数量:{this.state.currentCount}</div> <div><Button onClick={this.plusCount}>增长</Button></div> <Button onClick={() => { this.showCurrentCount(this.state.count) }}> 当前数量 </Button> </div> ) }
函数式组件类型可使用React.FC
,使用这个类型有个好处就是,提醒你必须返回一个ReactNode
const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => { return <div></div> }
3) 在函数式组件中,咱们没法像在类组件中同样使用state
和生命钩子函数,但React
提供了HOOK
4) 使用 useState
来存储函数组件的状态。在下面的例子中,咱们使用了React.useState
,传入color
的初始值,返回了一个对象,咱们使用解构,得到了color, setColor
。其中 color
至关于 this.state.color
,setColor('green')
至关于 this.setState({color: 'green'})
。
const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => { const [color, setColor] = React.useState('blue') const changeColor = () => { setColor('green') } return ( <div> <div style={{ color }}>函数式组件</div> <Button type="primary" className="example-button" onClick={changeColor}> 点击换色{' '} </Button> </div> ) }
假如你想像在类组件中使用 componentWillUnmount
,componentDidMount
,componentDidUpdate
,你可使用useEffect
。useEffect
接受一个回调函数,这个回调函数内代码会在 componentDidMount
,componentDidUpdate
时执行。回调函数的返回值应该是一个函数,这个函数会在 componentWillUnmount
被执行。
const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => { const [color, setColor] = React.useState('blue') const [fontSize, setFontsize] = React.useState('16px') const changeColor = () => { setColor('green') } React.useEffect(() => { let timer = setInterval(() => { setFontsize('100px') }, 10000) return () => { clearInterval(timer) } return ( <div> <div style={{color,fontSize}}>函数式组件</div> <Button type='primary' className='example-button' onClick={changeColor}>点击换色 </Button> </div> ) }
假如咱们须要操做 DOM
,咱们能够经过 useRef
来获取。text.current
就是被咱们绑定的元素。注意:不容许直接操做 DOM,除非无可奈何
const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => { const [color, setColor] = React.useState('blue') const [fontSize, setFontsize] = React.useState('16px') const text = React.useRef<HTMLDivElement>(null) const changeColor = () => { setColor('green') } const changeBgC = () => { (text.current as HTMLDivElement).style.backgroundColor = '#e9e9e9' } React.useEffect(() => { let timer = setInterval(() => { setFontsize('100px') }, 10000) return () => { clearInterval(timer) } return ( <div> <div style={{color,fontSize}}>函数式组件</div> <Button type='primary' className='example-button' onClick={changeColor}>点击换色 </Button> <Button type='primary' className='example-button' onClick={changeBgC}>点击换背景色</Button> </div> ) }
defaultProps
,这个默认参数是从多个组件的 props
中提取出来的。这时可使用 交叉类型 &
,注意,请不要在交叉类型中使用相同的属性,就算使用了,也请不要为两个同名属性定义不一样的基础类型,这样将会形成这个属性须要同时知足两种基础类型。type propsType = typeof defaultProps & { count: number } const defaultProps = { name: 'world' } const DefaultPrppsExample = (props: propsType) => { return ( <div> {props.name} {props.count} </div> ) } // 在另外一个组件中使用 ;<DefaultPrppsExample count={1} name={'默认参数示例'} />
createRef
和 forwardRef
在函数式组件中,咱们可使用 useRef
来获取到 React
元素。在类组件中,咱们可使用 createRef
来获取 React
元素。当咱们须要获取某个组件中的元素时,使用 forwardRef
来获取这个组件内部的元素。仍是不建议直接操纵 DOM
元素
const RefExample = React.forwardRef((props: React.CSSProperties, ref: React.Ref<HTMLInputElement>) => { return <input style={{ ...props}} ref={ref} /> }) class TsExample extends React.Component<ITsExampleProps, ITsExampleState> { ... public state = { inputRef: React.createRef() } public getFocus = () => { if (this.state.inputRef) { // 注意:这里断言的类型应该是使用 RefObject,由于其里面的具备 current: T | null, // 若是使用的是 ReactRef ,这个类型是一个联合类型,里面有多是 null ((this.state.inputRef as React.RefObject<HTMLInputElement>).current as HTMLInputElement).focus() } } ... <div styleName="example-item"> <h3>使用 createRef 和 forwardRef</h3> <RefExample color='red' ref={this.state.inputRef} /> <Button onClick={this.getFocus}>获取焦点</Button> </div> ... }
3) React
默认会把咱们的子组件挂载到父组件上。当咱们想本身指定挂载的元素时,就须要用到 createPortal
了。
这里写了一个简陋的蒙层组件(样式未彻底实现),即便咱们是TsExample
中使用了这个组件,但这个组件仍是会被挂载到一个插入 body
的元素上
注意:这里咱们在父组件上绑定了一个点击事件,PortalExample
被点击时依然会冒泡进而触发父组件的点击事件
const PortalExample = (props: IPortarExampleProps) => { const [modelDOM, setModelDOM] = React.useState(document.createElement('div')) modelDOM.classList.add('modal') if (props.visible) { document.body.append(modelDOM) return ReactDOM.createPortal( <div styleName='modal-inner'> 蒙层 </div>, modelDOM) }else { if (modelDOM.parentNode) { modelDOM.parentNode.removeChild(modelDOM) } return null } } class TsExample extends React.Component<ITsExampleProps, ITsExampleState> { ... <div styleName="example-item" onClick={this.handleModalClick}> <h1>使用 createPortal 将子组件挂载到父组件之外的元素内</h1> <PortalExample visible={this.state.modalVisible} /> <Button onClick={this.toggleModal}>切换modal</Button> </div> ... }
TS
类型基本使用咱们在使用类型注解的时候每每会使用如下类型
js
的七种基本类型 string, number, boolean, undefined, null, symbol
,还有 void, never
type, interface
来声明变量的形状使用 |
来创造一个联合类型。例如:type pet = 'cat' | 'dog'
当咱们须要规定一个函数参数的类型时,又恰巧有一个已经有一个接口已经知足了,咱们可使用 keyof
来快速生成一个联合类型
当咱们在使用联合类型时,假如两个子类型 A 和 B 都是接口或类等时,咱们预想的是要么是 A,要么是 B。但实际上,A&B
也是符合 A|B
的。
这个时候咱们须要先用 in
关键字来判断是符合哪一个类型再进行操做
interface IMan { handsNum: number } interface IDog { tailNum: number } public typeGuarding = (obj: IMan | IDog) => { if ('handsNum' in obj) { ... } else if ('tailNum' in obj) { ... } }
使用 &
来创造一个联合类型,以前已经有示例了
可选类型其实不是一个类型,但在接口和函数参数中都有可选这个概念。
interface IPet { name: string age?: number } interface IMan { ... } const feedPet (pet: IPet, man?: IMan) => { ... }
注意:函数的可选参数必须写在最后面
在函数声明中,直接为参数赋予一个值,能够起到默认参数的做用
interface IPetDefault { name: string age = 1 } const feedPet (pet: IPet, man={name: '我'}) => { ... }
当咱们须要使用枚举类型时,就须要用到enum
关键字。默认,第一个的值为 0,而后递增,能够手动指定每个的值,以后的值也会递增。咱们能够经过值查key
,也能够经过key
查值
enum animal { 乌龟, 鳄鱼, 麻雀, 河马 } animal['乌龟'] // 0 animal[0] // 乌龟
当咱们给一个函数传入参数时,咱们知道这个参数时符合被规定的参数类型的,但编译器是不知道的。为了让编译器能知道类型符合,咱们就须要使用类型断言,关键字:as
type animalName = '乌龟' | '鳄鱼' | '麻雀' | '河马' animal[this.state.animalName as animalName] // 0
类型断言里还有另外一个类型:在值的后面使用 !
,表示这个值不为 null
type animalNameWithNull = '乌龟' | '鳄鱼' | '麻雀' | '河马' | null animal[(this.state.animalName as animalName)!] // 0
注:类型扩展是直译的结果,若是你知道这种用法的名字,请修正它
TS
在检查一个类型是否符合是,并非查找类型的名字,而是比较类型的结构。也就是说,当你为两种类型userId
和 orderId
都定义了同样的结构:{name: string}
。这时候,TS
会断定它们是同一种类型。请看下面的例子:
interface IOrderId { name: string } interface IUserId { name: string } let userId: IUserId = { name: 'userId' } let orderId: IOrderId = userId // ok
这里你是否会疑惑呢?两种不一样的类型却能够相互赋值。这就是 结构类型检查 了。假如名字不一样,类型就不一样的就是 名称类型检查
那咱们如何来避免呢?第一种方法是多加一个字段
interface MyUserId { name: string type: 'user' } interface MyOrderId { name: string type: 'order' } let myUserId: MyUserId = { name: 'user', type: 'user' } let myOrderId: MyOrderId = myUserId // error
还有另外一种方法,那就是利用 unique symbol
和 交叉类型
type UserIdString = string & { readonly brand: unique symbol } type OrderIdString = string & { readonly brand: unique symbol } const getUniqueUserId = (id: string) => { return id as UserIdString } const getUniqueOrderId = (id: string) => { return id as OrderIdString } let uniqueUserId: UserIdString = getUniqueUserId('1') let uniqueOrderId: OrderIdString = uniqueUserId // error
unique symbol
是 symbol
的子类型。咱们在上面用交叉类型将 {readonly brand: unique symbol}
也加入到类型中去,这样,两种类型就算结构同样,它们也是不相同的
函数重载用于咱们的函数拥有不一样的输入或输出时
第一个函数声明应该是最精确的,由于编译器是从前日后开始匹配的。函数实现应该是兼容各个函数声明的。
这里函数重载的意义在于,若是们直接使用第三种声明,那么传入 3 个参数也是能够接受的,可是据咱们所知, padding 是不接受 3 个参数的
// 重载 function padding(all: number) function padding(topAndBottom: number, leftAndRight: number) function padding(top: number, right: number, bottom: number, left: number) // Actual implementation that is a true representation of all the cases the function body needs to handle function padding(a: number, b?: number, c?: number, d?: number) { if (b === undefined && c === undefined && d === undefined) { b = c = d = a } else if (c === undefined && d === undefined) { c = a d = b } return { top: a, right: b, bottom: c, left: d } }
typeof
关键字快速定义一个类型有时候,咱们在前面定义了一个变量,并使用类型推导(也就是说,这时候咱们并无明确地给它声明类型)。而后咱们想要使用这个变量的类型了,怎么办?回去定义一下类型吗?使用 typeof 变量名
就能够获取到这个变量的类型了
TS
中内置了一些语法糖给咱们使用,例如 Partial、Omit、Exclude
等
这里举例 partial
的用法:Patial
关键字可让将一个接口内部的属性变成可选
interface dog { name: string bark: boolean } type littleDog = Partial<dog> let dog1: littleDog = { name: 'dog1' } let dog2: littleDog = { bark: false }
当咱们须要引入某些模块时,这些模块又没有被声明过,这个时候就会报错了。咱们只须要使用
decleare module 模块名
就可解决
decleare module "*.png" import * as logo from 'logo.png'