第七期:前端九条启发分享
一、 ts数组类型推导莫名被干扰
事情是这样的, 周五的时候需要做一个小优化, 某个文件里面原本导出3个 "json对象
", 我需要改成导出三个function
方便根据传参改变导出内容, 这时就发生了奇怪的类型报错, 假设当前有如下的代码:
下面代码不会报错, 可以正常运行
export interface Dog {
name: string;
age?: 1 | 2 | 3;
}
export const a = (): Dog => ({
name: "金毛",
});
export const b = () => ({
name: "比熊",
age: 2,
});
export const c = () => ({
name: "拉布拉多",
});
const cc: Dog[] = [a(), b(), c()];
但是奇怪的是, 下面这段代码就会报错
export interface Dog {
name: string;
age?: 1 | 2 | 3;
}
export const a = (): Dog => ({
name: "金毛",
});
export const b = () => ({
name: "比熊",
age: 2,
});
export const c = (): Dog => ({
name: "拉布拉多",
});
const cc: Dog[] = [a(), b(), c()];
我只是为第三个方法指定了返回值, 第二个方法居然报错了, 我不为第三个方法指定返回值反而第三个方法不会报错, 接下来还有奇怪的现象, 但我为第三个方法指定为返回any类型的时候, 居然第二个方法也不报错了:
经过研究发现, 当数组内某个值被指定为any
的时候, 数组类型会变成any[]
, 所以才有了c()
被指定为返回any不报错。
当a与c
两个方法都被指定为Dog
类型时, ts
发现b方法
的返回值并不一定符合Dog
的推演, 因为Dog
类型里面的age
只能含有1|2|3
所以报错, 但是如果你完全不给a b c
指定返回值类型也不会报错, ts会自动推导出2在范围内, 真神奇!
二、 如何设计 undefined | bl 这种结构的组件
举个例子假设tip提示框
组件, 他有一个是否显示的参数showTip
为布尔值, 当这个值为true
的时候就算用户没有用鼠标悬停这个弹框也会出现, 为false
就是怎么也无法让它显示。
当前有一个需求要求进入页面后这个框主动弹出停留3s
后消失, 那其实我们很容易就想到将showTip
赋予为true
3秒然后变为undefined
即可, 因为只存在两种情况, 要么true要么undefined, 就好比如下的代码编写方式:
const obj = {};
if(isFirstOpenPage){
obj.showTip = true;
}
return <tip {...obj}>
但是当我使用某个组件库的时候出现了只要定义过showTip
属性就无法置为undefined
的情况, 导致我弹出tip
之后就无法恢复到让用户去控制显隐了。
这其实就是代码设计时候要考虑的问题了, 当我们设计一个值为布尔
的属性时, 应该同时也考虑到undefined
也可能是一种传值情况。
三、 react如何刷新组件自身
我遇到的情况是, 有a, b, c
三个组件, 这三个组件分别对应三种不同的特殊情况, 每个情况都有可能出现, 但这三个组件同一时间只能出现一个, 比如当前出现了a
组件, 用户点击了关闭才会出现b
组件, 这就导致每次当用户点击关闭时我要判断是否另外两个组件需要被渲染出来。
问题点就在于比如移除a组件
, 组件库的做法是直接把a的dom结构
移除了, 导致react监控不到。
const [_, forceUpdate] = useReducer(x => x + 1, 0);
if (showA) {
return <A forceUpdate={forceUpdate} />;
} else if (showB) {
return <B forceUpdate={forceUpdate} />;
} else if (showC) {
return <C forceUpdate={forceUpdate} />;
} else {
return null;
}
每个组件里面
onClose={() => {
if (forceUpdate) {
forceUpdate();
}
}}
上述方法的关键就是useReducer
的变化可以引起组件的重新渲染。
四、 为字符串插入jsx元素
事情是这样的, 比如当前我们有个i18n文案
是highest price {max} - lowest price {min}
, 现有方法可以通过传入两个字符串替换掉{max}
与{min}
, 但是只能用字符串替换, 无法插入dom结构, 比如我想用蓝色的字来替换。
假设intl
方法可以把字符串里面的{max}替换掉, 我们对intl
进行扩充变成intlPlus
, 下面是平时使用的方式。
intl.format(
"highest price {max} - lowest price {min}"
[
"99", "33"
]
)
intlPlus 的编写
import intl from './intl';
const pix = '-------------';
export default function intlPlus(i18n: string, arg: any[]) {
const len = arg.length;
const box = Array(len).fill(pix);
const text = intl.format(i18n, box);
const textArr = text.split(pix);
const res: any[] = [];
for (let i = 0; i < textArr.length; i++) {
res.push(textArr[i]);
if (arg[i] !== undefined) {
res.push(arg[i]);
}
}
return res;
}
上面的原理就是使用'-------------';
作为站位符号, 然后再按顺序对占位符号进行切割, 然后按一个文案一个插值的形式循环插入, 并以数组的形式输出出去, 使用方法如下:
intl.format(
"highest price {max} - lowest price {min}"
[
"99", <span style={{color: red}}> 33 </span>
]
)
五、 如何防止测试环境地址泄露
比如我们平时 测试环境
的地址不要让外界看到, 比如如下的代码就是会泄露测试域名的代码:
const pathObj = {
prod: 'www.xxxxxx.com',
dev: 'www.xxxxxxx-dev.com',
test: 'www.xxxxxx-test.com'
}
// ....
if(location.host === 'localhost:3000'){
window.open(pathObj.dev)
}
插件 webpack.DefinePlugin
闪亮登场✨, 这个插件可以让我们设置一些"全局"
(带引号的)的变量, 这些变量可以在正式 打包之前
使用:
plugins: [
new webpack.DefinePlugin({
isDev:
process.env.NODE_ENV === "production"
? JSON.stringify("false")
: JSON.stringify("true")
}),
]
上面很奇怪的使用了JSON.stringify
, 因为只有这样webpack
才能把它当做字符串来处理, 否则会被当做语句来处理, 接下来我们举个使用时的例子:
我们观察打包文件:
观察可知'oooooooo'已经不存在于打包文件里面了。
它的原理就是在全局进行替换, 比如上面的代码:
// 打包前:
if (isDev) {
console.log("tttttttttt");
} else {
console.log("ooooooooooo");
}
// 打包时:
if (false) {
console.log("tttttttttt");
} else {
console.log("ooooooooooo");
}
很明显了, tree-shaking
不会放过这种代码, 所以下面的'ooooooo'逻辑就会被删掉。
这个插件有点难理解的点就是, 它并不是运行时执行的, 而是tree-shaking
前进行了一次全局的替换。
之所以它可以直接写在全局isDev
, 但是不可以写window.isDev
就是因为它并没有挂载在widnow对象上, 这类变量并不会在用户端运行。
六、写成{}导出后i18n翻译不实时生效
有很多需要实时变化的配置最好不要设计为json的形式, 比如项目里的翻译, 当用户切换语言英语为中文的时候, 发现某些地方翻译没有变化依然还是英语, 这些地方的特征就是全是导出的json类似下面这种:
export UserName = {
title: <span> {i18n(USER_NAME_TITLE)} </span>
}
使用方式
import {UserName} from './userName'
// ...
<Table
columns={[UserName]}
>
上述写法的表格表头
并不会随着语言的切换而改变文案。
七、 item2的分割很好用
命令行工具可以很大的缓解我们多项目启动的问题, 如果你是在vscode
提供的shell
上启动多个项目, 就会感觉到来回切换有点吃力了, 更糟的情况是你同时开发两个以上微前端
项目, 所以这个item2
必须好好安利一下, 因为这个实在是太好用了, 还没这样玩的朋友可以快快玩起来:
每次点击都会增加一个小窗口, 让我们看看把控制台分成'6份':
为了防止混乱, 我们可以为每个窗口命名:
设置过程如下:
还可以设置自己喜欢的背景图案:
设置过程如下:
八、 tip提示离奇被隐藏
我在做的一个弹出框里面有个table表格
, 这个table
其中一列的表头默认是弹出状态, 但是遇到的bug是这个弹出状态某些时候会一闪而过, 重新变回未弹出的状态, 看过组件库的源码之后确定不是组件本身的问题。
经过多次测试发现, 这个bug与点击弹出弹框的按钮的位置有关, 这才让我想明白bug的原因, 因为这个弹出框有一个弹出动画, 从左边由小变大的出现, 这个变大的过程中实际已经预渲染了这个弹框里面的组件, 导致弹框出现的过程中会经过鼠标, 这就导致组件认为用户悬停过, 导致tip隐藏...
解决方式是将这个弹框出现时的300毫秒的鼠标事件禁止掉。
九、 借助 Whistle 设置线上域名开发本地代码, 防止被csrf防御
由于server
端经常会加一些安全策略, 比如只能某个referer
的网站才能调用api
, 其他网址过来的都会报403
, 这个时候我们前端可怜的http://localhost:3000
就遭殃了, 每次都要与相关人员沟通本地调试的问题。
索性我们直接使用测试环境的域名进行开发就得了, 不使用http://localhost:
(类似于windows修改本地的host文件)总算行了吧...
第一步: 安装Whistle
npm install -g whistle
w2 start -p 8899 // 启动服务
直接打开域名即可看到操作界面: http://localhost:8899
第二步: 安装proxy
由于我们需要把浏览器的请求全部代理到Whistle, 所以需要用到proxy这个谷歌插件,
搜索 : Proxy SwitchyOmega
这一步是代理浏览器的请求到这个地址
第三步: 配置Whistle
前面的是被代理的地址, 空格后面是目标地址
当前有这样一个页面
代理之后
第六步: Whistle配置秘钥证书
由于Whistle要帮助我们处理所有的请求, 这其中也必然会有https请求, 所以一定要导入Whistle的证书:
大功告成, 开开心心的使用测试环境域名
进行本地开发
吧。
end
这次就是这样, 希望与你一起进步。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。