业务逻辑里,如何干掉if else&switch case渲染组件并且对ts友好的语句呢,让我们来一起探讨吧,看看是否你也曾经是这样处理过。
处理多种渲染结果
考虑我们的代码里有以下3钟完全不一样的组件Foo,Bar、Baz,为了可以贴合真实情况的复杂度,我们的演示的组件不仅渲染结果不一样,输入的参数props也是不一样的.
interface IPropsFoo {
id: string;
}
const Foo = React.memo((props: IPropsFoo) => {
return <h1>{props.id}</h1>
})
interface IPropsBar {
name: string;
}
const Bar = React.memo((props: IPropsBar) => {
return <h1>{props.name}</h1>
})
interface IPropsBaz {
age: number;
}
const Baz = React.memo((props: IPropsBaz) => {
return <h1>{props.age}</h1>
})
在处理一些单选按钮组RadioGroup或者可切换页卡Tab组件时,通常会根据不同的选中key渲染不同的结果,偶们大多数时候大笔一挥会洒脱写下以下代码。
function Demo1() {
let initialKey: 1 | 2 | 3 = 1;
const [key] = React.useState(initialKey);
const [state] = React.useState({ id: '', name: '', age: 1 });
let ui: React.ReactNode = '';
if (key === 1) {
ui = <Foo id={state.id} />;
} else if (key === 2) {
ui = <Bar name={state.name} />;
} else if (key === 3) {
ui = <Baz age={state.age} />;
}
return <h1>
{ui}
</h1>;
}
分析行为,创建策略池
显而易见这里的if else分支语句会和key的枚举数量呈现正相关增长关系(10个单选项孵化出10个 if else分支语句),函数的圈复杂度也就很容易随之而上升了,这时候策略模式就排上用场了,我们分析下此场景,可以把key当前行为,渲染实例当做输出结果,那么就很容易写个策略池来描述不同的key应该命中的输出结果啦。
const compUis = {
1: <Foo id={state.id} />,
2: <Bar name={state.name} />,
3: <Baz age={state.age} />,
};
return <h1>
{compUis[key]}
</h1>;
更高效的运行效率
或许大多数时候,我们写出上面的代码就认为已经可以交差了,但我们如果追求更高效的运行效率,是不应该把所有组件都提前实例化好,然后在根据key去具体命中的,而是惰性的在命中那一刻才实例化,所以我们可以继续优化为
const compUis = {
1: () => <Foo id={state.id} />,
2: () => <Bar name={state.name} />,
3: () => <Baz age={state.age} />,
};
return <h1>
{compUis[key]()}
</h1>;
react/no-unstable-nested-components
上面代码看起来很完美,利用箭头函数包装一下实例,延迟了实例化时机,但是经过eslint校验会给出react/no-unstable-nested-components,告诉我们不允许在组件内部临时申明不稳定的组件,这可能会引起一些莫名的bug(通常由闭包引起),具体说明可见链接
这时候,我们仔细回想一下jsx语法,<Some />语法糖经过babel编译后其实是React.createElement(Some)语句,所以我们变通下,把组件的元数据提取出来描述清楚即可。
const compMetas = {
1: { Comp: Foo, props: { id } },
2: { Comp: Bar, props: { name } },
3: { Comp: Baz, props: { age } },
};
const meta = compMetas[key];
return <h1>
{<meta.Comp {...meta.props} />}
</h1>;
丢失的类型校验
到此为止我们解决掉了react/no-unstable-nested-components问题,可是组件的类型校验却完全丢失了,Foo组件的props声明我们可以随便写错,ts也不会帮我们发现错误,所以呢我们还需要补齐类型声明,让不同的组件Comp类型与props类型关联起来
const compMetas: {
1: { Comp: typeof Foo, props: Parameters<typeof Foo>[0] },
2: { Comp: typeof Bar, props: Parameters<typeof Bar>[0] },
3: { Comp: typeof Baz, props: Parameters<typeof Baz>[0] },
} = {
1: { Comp: Foo, props: { id } },
2: { Comp: Bar, props: { name } },
3: { Comp: Baz, props: { age } },
};
const meta = compMetas[key];
return <h1>
{<meta.Comp {...meta.props} />}
</h1>;
更精简的类型推导
经过以上改造,ts不会让我们随便写错props值了,可以我们发现这样的类型推导好冗余,可以稍稍再改造下,让它变得更精简吧^_^
type CompMeta<C extends
(React.NamedExoticComponent<any> & { readonly type: (props: any) => JSX.Element; })
> = { Comp: C, props: Parameters<C>[0] };
const compMetas: {
1: CompMeta<typeof Foo>,
2: CompMeta<typeof Bar>,
3: CompMeta<typeof Baz>,
} = {
1: { Comp: Foo, props: { id } },
2: { Comp: Bar, props: { name } },
3: { Comp: Baz, props: { age } },
};
const meta = compMetas[key];
结语
经过以上操练,你学会如何在ts里结合策略模式书写react组件了吗^_^
one more thing, 如果你想在react里尝试类vue书写体验,composition api 等有趣特性,欢迎❤ star concent^_^