使用React+TypeScript构建自己的组件库
TypeScript基础
数据类型
ECMAScript标准定义了8种数据类型
- Boolean
- Null
- Undefined
- Number
- BigInt
- String
- Symbol
- Object
interface接口
interface的主要作用如下:
- 对对象的形状(
shape
)进行描述 - 对类(
class
)进行抽象 Duck Typing
(鸭子类型)
interface Person {
readonly id: number;
name: string;
age?: number;
}
函数表达式
const add: (x: number, y: number, z: number) => number = function (x, y, z): number {
return x + y + z;
}
类
面向对象的三大特性:封装,继承,多态
- 封装:当我们使用类中的某个方法时,我们无需知道类中的具体实现细节
- 继承:子类可以继承父类,让子类具有父类的属性以及方法
- 多态:子类可以覆盖父类中的方法,从而让子类和父类的实例表现出不同的特性
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
run() {
console.log(`${this.name} is running`)
}
}
class Cat extends Animal {
constructor(name: string) {
super(name);
// 重写父类的构造方法,首先需要使用super调用父类的构造函数
console.log(this.name);
}
run () {
console.log('miao~');
// 当在子类中需要调用父类的方法时,也可以使用super来调用
super.run();
}
}
类中属性或者方法的访问修饰符
- private: 仅仅能够在当前类的内部访问,在子类或者实例中都无法访问
- protected: 可以在当前类内部和子类中访问,无法在实例中访问
- public: 默认的访问修饰符,能够在类,子类,实例中都可以访问到
- readonly: 用来修饰类中不可变的属性
- static: 可以直接用类名来访问其修饰的属性和方法
接口interface
当多个class需要都需要实现某些相同的方法时,我们可以使用interface来实现
interface Radio {
switchRadio() :void;
}
class Car implements Radio {
switchRadio() {}
}
class Phone implements Radio {
switchRadio() {}
}
常量枚举
const enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
泛型约束
可以使用extends关键字来对泛型参数进行限制
interface IWithLengh {
length: number;
}
function echoWithLength<T extends IWithLengh>(arg: T): T {
console.log(arg.length);
return arg;
}
const str = echoWithLength('abc');
const obj = echoWithLength({ length: 1 });
const arr = echoWithLength([1, 2])
类也可以使用泛型来约束
class Queue<T> {
private data: T[] = [];
push(item: T): void {
this.data.push(item);
}
pop(): T {
return this.data.pop();
}
}
const queue = new Queue<number>();
queue.push(1);
console.log(queue.pop().toFixed(2));
const queue2 = new Queue<string>()
接口也可以使用泛型来约束
interface KeyPair<T, U> {
key: T;
value: U;
}
使用泛型来约束函数
interface IPlus<T> {
(a: T, b: T): T;
}
function plus(a: number, b: number): number {
return a + b;
}
const a: IPlus<number> = plus
类型别名
类型别名多用于联合类型中
type NameResolver = () => string;
type NameOrResolver = string | NameResolver;
function getName(n: NameOrResolver): string {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
类型断言
function getLength(input: string | number): number {
if ((<string>input).length) {
return (<string>input).length;
} else {
return input.toString().length;
}
}
React基础
使用脚手架工具搭建项目
首先使用create-react-app搭建项目
npx create-react-app ts-with-react --typescript
简单的函数式组件
interface IHelloProps {
message: string;
}
const Hello: React.FC<IHelloProps> = (props) => {
return <h2>{ props.message }</h2>
}
Hello.defaultProps = {
message: 'Hello World'
}
React Hooks解决的问题
- 组件很难复用状态逻辑
- 复杂组件难以理解,尤其是生命周期函数
useState
该hook相当于组件内的状态
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
const [status, setStatus] = useState(true)
return (
<>
<button onClick={() => { setLike(like + 1)}}>
{like}
</button>
<button onClick={() => { setStatus(!status) }}>
{String(status)}
</button>
</>
)
}
useEffect
该hook默认会在第一次渲染完成和每次界面更新时执行,相当于class组件中的componentDidMount
和componentDidUpdate
useEffect(() => {
document.title = `点击了${like}次`
})
使用useEffect的返回值清除副作用
const LikeButton: React.FC = () => {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const updatePostion = (e: MouseEvent) => {
setPosition({
x: e.clientX,
y: e.clientY
})
}
document.addEventListener('click', updatePostion)
return () => {
// 会在下一次更新界面之后,重新添加此effect之前执行
document.removeEventListener('click', updatePostion)
}
})
return <p>X: {position.x}, Y: {[position.y]}</p>
}
控制useEffect执行时机,这里需要使用到useEffect的第二个参数。第二个参数是一个数组,可以填入依赖项,当这些依赖项发生变化时,才会去执行useEffect。当第二个参数为空数组,显然这种情况是没有依赖可以变化的,因此这种情况的useEffect仅仅会在组件加载和卸载时执行一次。
useEffect(() => {
document.title = `点击了${like}次`
}, [like])
自定义hook
自定义hook需要以use开头
const useMousePosition = () => {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const updatePostion = (e: MouseEvent) => {
setPosition({
x: e.clientX,
y: e.clientY
})
}
document.addEventListener('click', updatePostion)
return () => {
document.removeEventListener('click', updatePostion)
}
}, [])
return position
}
使用一个自定义hook
const position = useMousePosition()
使用自定义hooks封装一个请求公共hooks
import { useState, useEffect } from 'react'
import axios from 'axios'
const useURLLoader = (url: string, deps: any[] = []) => {
const [data, setData] = useState<any>(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
axios.get(url).then(result => {
setData(result.data)
setLoading(false)
})
}, deps)
return data
}
export default useURLLoader
完成组件库
完成一个组件库需要考虑的问题
- 代码结构
- 样式解决方案
- 组件需求分析和编码
- 组件测试用例分析和编码
- 代码打包输出和发布
- CI/CD,文档生成等
CSS解决方案
- inline css
- css in js
- styled component
- sass/less
未完待续....2020年06月15日19:18:23