乐趣区

关于前端:第五期前端九条bug分享-ts相关知识较多

本期

     最近换了新工作终于凑齐一些乏味的 bug 与问题了, 比方在 ts 方面做了深刻的钻研, 国际化开发方面有了一些思考等等, 总之新的工作刚刚开始就播种满满, 有对 ’ 字节跳动 ’ 国际化业务感兴趣的同学能够把简历砸过来, 未来一起挑战各种乏味的难题。(当然除了国际化其它岗位我也能够内推的! 🙋🏻‍)

     小声说: 最近切实是好多事要忙, 像 3d 地球那个系列只能过段时间更新了 …😭

1: url 的编码操作

     当咱们通过 url 来传递一些信息的时候, 可能会呈现一些读取的问题, 咱们罕用 encodeURIencodeURIComponent进行编码后再进行传递, 然而我发现我的项目中所有中央都用encodeURIComponent, 为什么会这样这两种有什么区别?

     因为 encodeURI 并不会对 ;/?:@&=+$,# 之类的字符进行转移, 这就会导致某些非凡状况下解析 uri 呈现问题 (后端应用的语言不同导致解析形式不同), encodeURIComponent 会本义 URI 各个局部的标点符号比方罕用的连接符 &?, 也就是说它本义的更彻底安全性更高, 所以倡议尽可能应用 encodeURIComponent 来解决。

2: 国际化我的项目左右翻转(前端 RTL 适配)

     来到国际化前端团队才学习到, 从左往右写的为 ”LTR”, 从右往左写的为 ”RTL”, 比方 ’ 希伯来语 ’、’ 阿拉伯语 ’ 等, 如果你的公司要开发一款 app 提供给多个国家应用, 那就要思考到有的国家书写文字是从右往左的, 并且很多图片也要从右往左展现, 比方把返回按钮放在右上方!

     咱们要做的就是文字书写的翻转、输入框的翻转、图标的本身翻转以及地位的镜像、然而某些图标不必反转比方 “ 时钟 ” 亦或者 30%不必反转为%03, 当然这些头疼的问题也是有成熟的解决方案的。

第一种: dir="rtl"属性设置

     为 body 元素加上属性dir="rtl", 浏览器就能够主动翻转了, 没试过的快试试很好玩的。

     毛病也很显著, 就比方咱们的 css 属性margin: left; 依然是作用于右边。

第二种: rtlcss

     rtlcss的官网, 他的实现思路就是配合 rtl 属性应用, 将页面上的 left 相干属性都转为 right 属性, 核心思想就是某些属性的全局替换。

3: 后端 int64 类型出错

     公司外部有一个库能够把后端的 rpc 接口标准间接转成 ts 标准供前端应用, 然而忽然有一天呈现了类型谬误, 比方后端规定返回参数为 code 数字类型, msg为字符串类型, 那么就会生成如下文件:

export type XxxxApi = {
   code: number;
   msg: string;
}

     然而一天夜里后端返回的 code 对应的类型居然变成了 string, 我和共事查看了后端同学的代码, 定义的也确实是int 类型, 但不过不是 int32 而是 int64, 原来是因为js 的数字的极限是 2 的 53 次方:

     所以才采纳 string 的形式来表白 int64 这个数据类型, 后端同学将类型改为 int32 就没问题了, 当然啦后端同学擅自改类型不告诉咱们要提出批评😁。

4: 开发时候款式好好的, 打包后就出问题了

* bug 场景
     明明开发时候好好的, 但为什么打包之后就会呈现各种谬误, 比方款式失落, 这里说下起因之一:

     很久之前的某天我把开发好的前端我的项目代码公布到了服务器上, 在我本地拜访时款式很完满, 然而当我通过测试环境 url 关上这个我的项目的时候, 居然表格款式有些崩坏宽高与我本地的不一样, 然而我没有想明确 bug 的起因, 就去与 ‘ 同学 a ’ 交换为什么呈现这种景象。

     ‘ 同学 a ’ 说是因为用户的浏览器和我不一样导致的, 可是我就是用户, 开发就在我的浏览器上也是我用浏览器拜访的测试环境, 然而是同一个浏览器, 但 ’a 同学 ’ 保持说不可能产生这种情况, 我就给他演示了一遍从开发到公布到测试环境的全流程, 看到 bug 的确再次出现的他说是我 ’ 人品 ’ 的问题 …. (起初是通过改了一些 css 的写法解决的)

     我对这个事件印象还是比拟粗浅的, 但在往年的某一天, 我在配置 webpack 的时候忽然发现了一个问题点, 比方 postcss 在配置的时候会有一个设置, 在 developmentproduction两种模式下别离兼容到支流浏览器什么版本, 那这里其实就很可能是问题所在, 因为针对开发与打包进行了不同的翻译, 这就会导致无奈预期的谬误产生, 尽管曾经不在那家公司了当年的代码曾经找不到了, 但想到这点还是会很强烈的感觉到之前毫无脉络的问题有了一个解决方向!

5: pc 端唤起 WhatsApp & Email 为何生效

URL Schema

     要想学习唤起 app 就要先晓得 Schema 是什么, 我艰深点讲一下, 就是你下载到零碎里的每个 app 其实都能够注册一个属于它的 url 地址, 这个地址你能够了解为就是Schema

     而咱们唤起某个 app 就能够利用这个 ”Schema 地址 ”。

调起 WhatsApp

假如联系人的电话号码为 18200000000, 并且为中国的号码区号为(86)

- window.open("https://wa.me/8618200000000")  会闪屏
- 或
- window.open("https://api.whatsapp.com/send/?phone=8618200000000")

不倡议用 window.location.href 的形式跳转, 他会导致闪屏。

pc 端为何无奈通过给定的 WhatsApp 号码唤起 WhatsApp?

  1. 因为 WhatsApp 属于国内软件它要兼容辨别各个国家, 所以要加上国家的区号。
  2. 当然这个网址有时候不稳固也会导致加载不进去。
什么是 mailto

     mailto 是一种相似 http 的 url 协定, 但它属于本地协定(本地协定比拟典型的还有 file), 也就是不须要连贯网络就能够解析的协定, mailto 的性能是唤起默认邮箱。

唤起 Email

  • 假如联系人的邮箱号码为 xxx@qqqqq.com
指定收件人
- window.location.href="mailto: xxx@qqqqq.com"

如果为多集体发邮件则 ',' 宰割
- window.location.href="mailto: xxx1@qqqqq.com, xxx2@qqqqq.com"

如果要增加主题, 减少 subject 参数
- window.location.href=`mailto: xxx@qqqqq.com?subject=${encodeURIComponent("我是主题 xxx")}`

如果要增加主题, 减少 body 参数
- window.location.href=`mailto: xxx@qqqqq.com?subject=${encodeURIComponent("我是主题 xxx")}&body=${encodeURIComponent('我是内容 xxx')}`

6: React.FC

     经常出现 React.Fc 这个函数, 比方我不应用 React.Fc 来解决组件的函数, 则在组件外面应用 props.children 会报错, 咱们一起进入源码剖析一下。

    type FC<P = {}> = FunctionComponent<P>;

    interface FunctionComponent<P = {}> {
        // 第一句
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;

        propTypes?: WeakValidationMap<P> | undefined;
        contextTypes?: ValidationMap<any> | undefined;
        defaultProps?: Partial<P> | undefined;
        displayName?: string | undefined;
    }
  1. FC这个 type 接管一个参数P, 默认值为空对象, 而这个P
  2. FunctionComponent就是个适度的名称而已, 能够认为 FC 就是FunctionComponent
  3. 第一句意义为第一个参数为 PropsWithChildren<P> 类型, 第二个参数可有可无, 有则为任意类型, 返回 React 的 dom 或者返回null
  4. 前面四个参数不是必填, 咱们次要钻研第一句。

咱们追究一下PropsWithChildren

    type PropsWithChildren<P> = P & {children?: ReactNode | undefined};

     只是将传入的 P 的类型与 {children?: ReactNode | undefined} 合并而已, 看到这里咱们就明确了, 其实用 React.FC 包裹一下是能够帮忙 ts 推导出 props 身上可能有 children 属性。

7: RematchRootState

rematch 官网;

     rematch是对 Redux 的二次封装, 而 RematchRootStaterematch导出的一个 ts 推导函数, RematchRootState到底做了什么令程序员脱发的操作 …

     我的项目里应用了这个 RematchRootState 之后, 发现某些类型推导进去 never 类型如图所示:

一起摸索源码里的奥义
export type RematchRootState<M> = ExtractRematchStateFromModels<M>

export type ExtractRematchStateFromModels<M> = {[modelKey in keyof M]: M[modelKey] extends ModelConfig ? M[modelKey]['state'] : never
}
  1. 在应用 RematchRootState 时咱们应用 typeof 的模式导出了一个 ts 类型, typeof 在 ts 外面应用就不是 js 外面的意义了:

    Xxx<typeof {name:'金毛', age:9}>
    
    // 此处就相当于:
    
    Xxx<typeof {name:string, age:number}>
  2. [modelKey in keyof M]循环 M 对象外面所有的 key 值, 每次循环时命名为 modelKey。
  3. M[modelKey]就是取出对应的值, 这里特指 ts 外面的类型值。
  4. M[modelKey] extends ModelConfig ? M[modelKey]['state'] : never, 也就是每次取出 ’ 值 ’, 并且此 ’ 值 ’ 合乎 ModelConfig 类型 的话则返回 M[modelKey]['state'] 的类型, 否则返回 never, 这里的extends 你能够了解为 is 用来判断某个值是不是符合规范的, 当前文章还会波及 extends 的其余用法。

     至此咱们曾经明确了, 问题肯定出在 M[modelKey] 的类型未合乎 ModelConfig的类型标准导致的返回的 never, 那ModelConfig 又是什么标准那?

export interface ModelConfig<S = any, SS = S, K extends string = string> {
    name?: string
    state: S
    baseReducer?: (state: SS, action: Action) => SS
    reducers?: ModelReducers<S>

    effects?:
        | ModelEffects<any>
        | (<M extends Models<K> | void = void>(dispatch: RematchDispatch<M>) => ModelEffects<any>)
}
  1. 因为调用 ModelConfig 时什么参数都没传, 则应用默认值。
  2. name 属性咱们赋予了 number 类型时会导致谬误。
  3. state 对应的 S 类型, 也就是默认的 any 任何类型都能够。
  4. baseReducer的参数不符合规范, 或是返回值不符合规范时。
effects 要独自拿出来讲

第一个: effects = ModelEffects<any>

type ModelEffects<S> = {[key: string]: (this: { [key: string]: (payload?: any, meta?: any) => Action<any, any> },payload: any,rootState: S) => void
}

export type Action<P = any, M = any> = {
    type: string,
    payload?: P,
    meta?: M,
}
  1. [key: string]这种写法意思就是取出外面所有的项进行循环。
  2. ModelEffects每一项都为函数, 并且没有返回值。
  3. ModelEffects对象的每个函数的第一个参数为一个对象, 这个对象外面值都为函数, 并且返回值为Action<any, any>
  4. ModelEffects对象的每个函数的第二个参数为任意类型。
  5. ModelEffects对象的每个函数的第三个参数为rootState: S S 类型, S 则是咱们上一步传入进来的, 也就是any

第二个:

effects = (<M extends Models<K> | void = void>(dispatch: RematchDispatch<M>) 
=> ModelEffects<any>)
  1. M是新定义的一个泛型, 它合乎 Models<K> 的标准, 如果不合乎的话就为 void 类型。
  2. 这个 K 就是下面默认的string (写到这里我都感觉好麻烦😭)。

这里是 Models 的类型:

export type Models<K extends string = string> = {[key in K]: ModelConfig
}

每项都是 ModelConfig, 而ModelConfig 咱们下面曾经讲过啦。

  1. 这个函数接管的第一个参数 dispatch 要合乎类型RematchDispatch<M>, 这里就不再扩大了, 往下还有特地深:
export type RematchDispatch<M = void> =

  ExtractRematchDispatchersFromModels<M> &

    (RematchDispatcher | RematchDispatcherAsync) &

    (Redux.Dispatch<any>)

能够看进去, 这个参数的类型咱们用 Redux.Dispatch<any> 来定义就没问题的。

  1. 返回值必须为: ModelEffects<any>这个咱们方才也讲过了。

正确的用法能够是如下的样子:

  effects: (dispatch: Redux.Dispatch) => ({async FnXxx(_: any, state: RootState) {console.log(state.xxx.xxxList)
    },
  }),

总结一句话就是 ” 读这代码善意累 ”!

8: ts 批改函数参数

实现: 减少函数一个参数

假如以后我有这样一个type:

  type Obj = {getX: (a: string, c: boolean) => void;
    getN: (a: number) => void;
  };

而我心愿将这个 type 解决成上面这个样子:

  type Obj = {getX: (s: string[], a: string, c: boolean) => void;
    getN: (s: string[], a: number) => void;
  };

这里的关键点就是取到函数返回值的类型, 以及函数参数的类型汇合, 实现代码如下:

  type Obj2<T> = {[Key in keyof T]: T[Key] extends (...arg: any) => any
      ?(s: string[], ...arg: Parameters<T[Key]>) => ReturnType<T[Key]>
      : T[Key];
  };
  1. 循环泛型 T 外面所有的值。
  2. 如果 T[Key] 不满足 (...arg: any) => any 则不解决, 因为 T[Key] 可能不是函数类型。
  3. 反之 T[Key] 为函数类型, 则第一个参数为s: string[]
  4. ...arg为后续参数类型, Parameters<>为自带办法, 能够推导出函数的所有参数组成的数组的类型。
  5. ReturnType<>为自带办法, 能够推导出函数的返回值的类型。

应用办法就是:

type NewObj = Obj2<Obj1>
实现: 去掉函数第一个参数

假如以后我有这样一个type:

  type Obj = {getX: (a: string, c: boolean) => void;
    getN: (a: number) => void;
  };

而我心愿将这个 type 解决成上面这个样子:

  type Obj = {getX: (c: boolean) => void;
    getN: () => void;};

这里的关键点就是, 在 ts 里如何剔除数组的第一个元素, 并应用剩下的元素组成数组返回进去:

  type Obj2<T> = {[Key in keyof T]: T[Key] extends (s: any, ...arg: infer Arg) => any
      ? (...arg: Arg) => ReturnType<T[Key]>
      : T[Key];
  };
  1. 这里整体的逻辑是不变的, 与下面一个原理。
  2. (s: any, ...arg: infer Arg) => any, 这里是外围, 将函数解决第一个参数以外的参数独自拿进去命名为 Arg, 而后应用Arg 来定义函数的参数。
  3. infer是 ts 内置的关键字, 有点相似 js 中的var, 他能够定义一个变量。

应用办法就是:

type NewObj = Obj2<Obj1>

9: gzip压缩能够用什么代替

     之前我始终认为 gzip 压缩是以后最好的前端压缩计划, 然而其压缩计划并不惟一并且有着很多分类, 压缩形式被 ” 无状态压缩 ”, “ 有状态压缩 ”。

     无状态意味着它看到的任何大块数据,它都会压缩,而不依赖于以前的输出。速度更快但通常压缩水平更低; 有状态压缩查看以前的数据来决定如何压缩以后数据,但速度较慢但压缩好得多。

     比方 zstd 压缩属于有状态压缩, 会依据压缩过程中遇到的反复代码块生成字典, 再遇到雷同的代码用字典里对应的 key 来标识即可。

end

     这次就是这样, 心愿与你一起提高。

退出移动版