杂谈 - panchaow/blog GitHub Wiki
在使用 emotion 作为组件库的 CSS 解决方案时,可能会遇到一个问题。emotion 生成的 classname 的形式是css-[hash]
,比如css-2nrfbr
。如果指定了 label,生成的则是css-[hash]-[label]
,如css-jlndvo-Logo
。如果是用于写 app 的样式,这个特性能尽量避免全局 classname 重复,是非常有用的。但是如果是用于组件库的样式,这样的 classname 会导致用户难以通过 class selector 选中元素进行样式覆盖。Material UI 库是通过增加额外的 classname 来实现允许用户使用 CSS 进行覆盖样式的功能的。
如图所示,Material UI 为允许用户自定义的元素上增加了一个冗余的 classname。
假如有如下函数:
const prop =
<O, K extends keyof O>(key: K) =>
(o: O) =>
o[key];
这里的 Generic 实际上是不正确的。比如prop("name")
中,由于此时不能确定O
,于是K
只能被判断为never
。那么,"name"
作为一个string
自然无法赋值给never
。所以,TypeScript 会报错。可惜的是,这样的函数参数顺序在函数式编程是非常常见的。目前还没有得到解决的办法。
虽然本质上 Class 确实是一个函数,但是 Class 和普通的函数还是有一个明显的区别:Class 只能通过new
来调用。那么,如何判断一个函数是不是 Class 呢?首先,利用Object.prototype.toString
,我们可以排除一些函数类型:
Object.prototype.toString.call(class {}); // [object Function]
Object.prototype.toString.call(function () {}); // [object Function]
Object.prototype.toString.call(function* () {}); // [object GeneratorFunction]
Object.prototype.toString.call(async function () {}); // [object AsyncFunction]
Object.prototype.toString.call(() => {}); // [object Function]
可以看到,有三种类型函数,Object.prototype.toString
都返回了"[object Function]"
。可惜的是,目前没有很好的办法可以区别这三种类型。但有一个比较 dirty 的方法可以勉强完成任务:
// non-strict
Object.getOwnPropertyNames(function () {}); // [ 'length', 'name', 'arguments', 'caller', 'prototype' ]
Object.getOwnPropertyNames(class {}); // [ 'length', 'prototype', 'name' ]
Object.getOwnPropertyNames(() => {}); // [ 'length', 'name' ]
观察三条语句输出的内容,可以发现不包含 name 为prototype
的属性的函数是箭头函数,剩下的不包含 name 为arguments
/caller
的属性的函数是 Class。只是箭头函数完全可能被定义一个 name 为prototype
的属性,Class 对象也有可能被定义一个 name 为arguments
或者caller
的属性,而且在 strict 模式下,function() {}
本身也不再包含 name 为arguments
/caller
的属性,因此以上的判断非常不可靠。
在使用 css preprosessor,比如 Less、Sass 作为组件库的 CSS 解决方案时,往往会使用到这些 preprosessor 的 variable 功能。这样一方面可以使代码更加 DRY,一方面也方便了用户自定义组件的样式。在使用 CSS-in-JS,比如 Styled components、Emotion 作为样式解决方案时,可以通过 Theme 实现类似的效果。
export type PropsOf<
C extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>
> = JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>;
经常可以看到定义如下的DistributiveOmit
:
type DistributiveOmit<T, K extends keyof T> = T extends unknown
? Omit<T, K>
: never;
因为T extends unknown
这个条件一定是满足的,所以这段代码会让人第一眼看着感觉有点奇怪。其实,这个是利用了 TypeScript 的 Conditional Types 的distributive特性:
When conditional types act on a generic type, they become distributive when given a union type.
官方文档中的 Example:
type ToArray<Type> = Type extends unkown ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
最为基础的一个例子:
import React, { forwardRef } from "react";
import hoistStatics from "hoist-non-react-statics";
interface Theme {
color: string;
}
interface InjectedProps {
theme?: Theme;
}
export default function withTheme(theme: Theme) {
return function <T extends InjectedProps = InjectedProps>(
WrappedComponent: React.ComponentType<T>
) {
// Try to create a nice displayName for React Dev Tools.
const displayName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
// Creating the inner component. The calculated Props type here is the where the magic happens.
const ComponentWithTheme = forwardRef(
(props: Omit<T, keyof InjectedProps>, ref) => {
// Fetch the props you want to inject. This could be done with context instead.
const injectedProps = {
theme,
};
// props comes afterwards so the can override the default ones.
return (
<WrappedComponent {...injectedProps} {...(props as T)} ref={ref} />
);
}
);
ComponentWithTheme.displayName = `withTheme(${displayName})`;
return hoistStatics(ComponentWithTheme, WrappedComponent);
};
}
Vue 和 React 都有 lifecycle 的概念,只是它们采用的术语可能有区别。在 Vue 的官方文档中,这个概念被称为 Lifecycle Hooks,React 社区内则更多使用 Lifecyecle Methods。除了名字的差异,相比起 React,Vue 提供的 Lifecycles 也更加丰富。其中的active
和deactived
可能对于只使用 React 的开发者来说就不太熟悉了。它们是用来配合keep-alive
组件使用的。简单来说,被keep-alive
组件包裹的组件实例被卸载时,会被“缓存”起来。vue 在需要重新加载那个组件的实例时,复用被缓存的实例。deactived
和active
也就在被缓存和被复用时候触发。keep-alive
常被用于根据条件在组件实例之间切换的情景。而对于 React 来说,这样的组件就很实现。因为 React 支持函数式组件,并且函数式组件的使用也越来越成为主流。所谓函数式组件只是一个返回ReactElement
的普通函数。对其而言,不存在实例,甚至没有 lifecycle 的概念。React 团队也没有打算提供类似功能的想法。但是其实,这个功能可能还挺有用的,缓存卸载的组件除了带来运行效率上的收益,还可以保证实例的State不会丢失。如果要在React中实现类似的功能可能就要借助另外的数据存储的方式,比如redux了。总的来说,Vue 给我的印象是非常 Practical,对于开发者非常友好,可能这也是 Vue 在国内大范围流行的一个原因。