- JavaScript that scales
- 静态类型风格的类型系统
- 从 es6 到 es10 甚至是 esnext 的语法支持
- 兼容各种浏览器,各种系统,各种服务器,完全开源
- 程序更容易理解
- 问题:函数或者方法输入输出的参数类型,外部条件等
- 动态语言的约束:需要手动调试等过程
- 效率更高
- 在不通的代码块和定义中进行跳转
- 代码自动补全
- 丰富的接口提示
- 更少的错误
- 非常好的包容性
- 完全兼容 JavaScript
- 第三方库可以单独编写类型文件
- 流行的项目都支持 typescript
- 一点小缺点
- 对 对象的形状 (shape) 进行描述
- 对类 (class) 进行抽象
- Duck Typing (鸭子类型)
- 如果它像鸭子那样的叫、走路,那就可以看作是鸭子
- 接口更关注对象如何被使用,而不是对象的类型是什么
- 类 (Class):定义了一切事物的抽象特点
- 对象 (Object):类的实例
- 面向对象 (OOP) 三大特性:封装、继承、多态
function echo<T>(arg: T): T {
return arg;
}
const result = echo(true);
function swap<T, U>(tuple: [T: U]): [U: T] {
return [tuple[1], tuple[0]];
}
const result2= swap(['str', 123])
- 泛型的约束
- 有这么一个需求:
- 传入的参数必须得有 length 这个属性
interface IWithLength {
length: number
}
function echowithLenght<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item);
}
pop(): T {
return this.data.shift();
}
}
const queue = new Queue<number>();
queue.push(1);
// queue.push('str');
console.log(queue.pop().toFixed());
// --------
interface KeyPair<T, U> {
key: T;
value: U;
}
let key1: KeyPair<number, string> = {key: 1, value: "str"}
interface IPlus<T> {
(a: T, b: T): T;
}
function plus(a: number, b: number): number {
return a + b;
}
function connect(a: string, b: string): string {
return a + b;
}
const a: IPlus<number> = plus;
const b: IPlus<String> = connect;
- 系统色板- 基础色板 + 中性色板
- 产品色板 - 品牌色 + 功能色板
- 基础色彩系统
- 字体系统
- 表单
- 按钮
- 边框和阴影
- 可选配置开关
.btn {
position: relative;
display: inline-block;
font-weight: $btn-font-weight;
line-height: $btn-line-height;
color: $body-color;
white-space: nowrap;
text-align: center;
vertical-align: middle;
background-image: none;
border: $btn-border-width solid transparent;
@include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $border-radius);
box-shadow: $btn-box-shadow;
cursor: pointer;
transition: $btn-transition;
&.disabled,
&[disabled] {
cursor: not-allowed;
opacity: $btn-disabled-opacity;
box-shadow: none;
> * {
pointer-events: none;
}
}
}
- 支持原生 DOM 属性
- 拿到所有原生 button 属性
React.ButtonHTMLAttributes<HTMLElement>
- 如果合并两个类型的属性呢?
Intersection Types
交叉类型,用 &
表示
- 联合类型
|
,仅能返回 a
或 b
其中一个
Partial<T & U>
所有属性都变为可选属性
type NativeButtonProps = React.ButtonHTMLAttributes<HTMLElement>;
type AnchorButtonProps = BaseButtonProps & React.AnchorHTMLAttributes<HTMLElement>;
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>;
// 最简单的测试
test('first react test case', () => {
const wrapper = render(<Button>OK</Button>);
const element = wrapper.queryByText('OK');
expect(element).toBeTruthy();
});
type SelectCallback = (selectedIndex: number) => void;
- 解决 “爷父子孙” 嵌套组件状态、属性、回调传递和共享
interface IMenuContent {
index: number;
onSelect?: SelectCallback;
}
export const MenuContext = createContext < IMenuContent > { index: 0 };
const Menu: React.FC<MenuProps> = (props) => {
const passedContext: IMenuContent = {
index: currentIndex ? currentIndex : 0,
onSelect: handleClick,
};
return (
<ul className={classes} style={style}>
<MenuContext.Provider value={passedContext}>{children}</MenuContext.Provider>
</ul>
);
};
// ---- 曾孙组件 ----
import { MenuContext } from './menu';
const MenuItem: React.FC<MenuItemProps> = (props) => {
const context = useContext(MenuContext);
};
React.Children.map
和 React.cloneElement
的应用
const renderChildren = () => {
return React.Children.map(children, (child, index) => {
const childElement = child as React.FunctionComponentElement<MenuItemProps>;
const { displayName } = childElement.type;
if (displayName === 'MenuItem') {
return React.cloneElement(childElement, { index });
} else {
console.error('Warning: Menu has a child which is not a MenuItem component');
}
});
};
- CSS 选择器
:scope
匹配当前元素所在容器
- 精髓
const SubMenu: React.FC<SubMenuProps> = ({ index, title, children, className }) => {
const context = useContext(MenuContext);
const openedSubMenus = context.defaultOpenSubMenus as Array<string>;
const isOpened = (index && context.mode === 'vertical') ? openedSubMenus.includes(index): false;
const [menuOpen, setOpen] = useState(isOpened);
const classes = classNames('menu-item submenu-item', className, {
'is-active': context.index === index,
});
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
setOpen(!menuOpen);
}
let timer: any;
const handleMouse = (e: React.MouseEvent, toggle: boolean) => {
clearTimeout(timer);
e.preventDefault();
timer = setTimeout(() => {
setOpen(toggle);
}, 300);
}
const clickEvents = context.mode === 'vertical' ? {
onClick: handleClick
} : {}
const hoverEvents = context.mode !== 'vertical' ? {
onMouseEnter: (e: React.MouseEvent) => { handleMouse(e, true)},
onMouseLeave: (e: React.MouseEvent) => { handleMouse(e, false)}
} : {}
const renderChildren = () => {
const subMenuClasses = classNames('menu-submenu', {
'menu-opened': menuOpen
})
const childComponent = React.Children.map(children, (child, i) => {
const childElement = child as FunctionComponentElement<MenuItemProps>;
if (childElement.type.displayName === 'MenuItem') {
return React.cloneElement(childElement, {
index: `${index}-${i}`
});
} else {
console.error('Warning: Menu has a child which is not a MenuItem component');
}
});
return <ul className={subMenuClasses}>{childComponent}</ul>;
};
return (
<li key={index} className={classes} {...hoverEvents}>
<div className="submenu-title" {...clickEvents}>{title}</div>
{renderChildren()}
</li>
);
};
// -------- _mixin.scss
$theme-colors:
(
"primary": $primary,
"secondary": $secondary,
"success": $success,
"info": $info,
"warning": $warning,
"danger": $danger,
"light": $light,
"dark": $dark
);
@each $key, $val in $theme-colors {
.icon-#{$key} {
color: $val;
}
}
- 使用mixin封装css
@mixin zoom-animation(
$direction: 'top',
$scaleStart: scaleY(0),
$scaleEnd: scaleY(1),
$origin: center top,
) {
.zoom-in-#{$direction}-enter {
opacity: 0;
transform: $scaleStart;
}
.zoom-in-#{$direction}-enter-active {
opacity: 1;
transform: $scaleEnd;
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;
transform-origin: $origin
}
.zoom-in-#{$direction}-exit {
opacity: 1;
}
.zoom-in-#{$direction}-exit-active {
opacity: 0;
transform: $scaleStart;
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;
transform-origin: $origin;
}
}
@mixin border-right-radius($raduis) {
border-top-right-radius: $raduis;
border-bottom-right-radius: $raduis;
}
@mixin border-left-radius($raduis) {
border-top-left-radius: $raduis;
border-bottom-left-radius: $raduis;
}
// -------- _animation.scss
@include zoom-animation('top', scaleY(0), scaleY(1), center top);
@include zoom-animation('left', scale(0.45, 0.45), scale(1, 1), top left);
@include zoom-animation('right', scale(0.45, 0.45), scale(1, 1), top right);
@include zoom-animation('top', scaleY(0), scaleY(1), center bottom);