一周收获和总结(前端)

136 阅读5分钟

这周感觉过去的很快,周一到周五,咻的一下就没了。明天就是国庆节了,祝大家节日快乐✿✿ヽ(°▽°)ノ✿。(因为疫情,想去的地方又不能去了,属实有点难受。)

封装 atnd 的 select 组件

这个是之前做的一个需求,需要一个有表头和表尾的下拉框,类似于这样

当时我的想法是利用 SelectdropdownRender 属性,添加一个 table,简简单单就解决了,可是我花了几个小时实现了之后,才发现table 想要单独选中某一行的值,必须是 radio 或者 checkbox,效果出来就有点难受,大家可以自行看着 Table 脑补一下,今天抽了点时间去实现了之前没有做出来的东西。感兴趣的掘友可以自己粘贴代码运行一下。

image.png

先说说怎么使用吧,fieldProps 这个对象我们可以传递一些自己想要的属性,比如 placeholder、style、onChange等,options是数据源,labels是表头,你可以自己定义,也可以不传,组件内部会根据你传入的options获取表头需要的内容,onChange可以让我们获取到选择的值。

再来说说组件,主要分为两部分,一部分是表头部分,也就是代码的这一块;另一部分是下拉框的内容区域。 表头这里会获取输入的 lables,如果 lables 不存在的话,就去查询数据中的key,通过 reduce 高阶函数进行 整合和去重。dropdownRender 中的 menu 是 表的内容区域,取决于我们设置的 Option 组件的内容。

CustomSelect.tsx

import * as React from "react";
import { Select } from "antd";

const { Option } = Select;

interface IOption {
  key?: string | number;
  disabled?: boolean;
  title?: string;
}

interface ILabels {
  value: string;
  style?: Record<string, any>;
}

interface IProps {
  fieldProps?: Record<string, any>;
}

const CustomSelect = (props: IProps) => {
  const { options, labels, ...restProps } = props?.fieldProps || {};
  const [_key, ...allLabels] = options?.reduce(
    (pre: string[], cur: Record<string, any>) => {
      return new Set([...pre, ...Object.keys(cur)]);
    },
    []
  );
  const headers = (labels || allLabels)?.map((item: string) => {
    return {
      value: item,
    };
  });
  const width = restProps.style.width || 320;
  const labelLength = headers.length;

  const dropdownRender = (menu: React.ReactNode) => (
    <>
      <div
        style={{
          display: "flex",
          flexDirection: "row",
          width: width - 20,
          margin: "0 10px 0",
        }}
      >
        {headers?.map((label: ILabels, index: number) => {
          const style: Record<string, any> =
            index !== labelLength - 1
              ? {
                  flex: 1,
                  textAlign: "center",
                  borderRight: "1px solid #bfbfbf",
                  backgroundColor: "#fafafa",
                }
              : {
                  flex: 1,
                  textAlign: "center",
                  backgroundColor: "#fafafa",
                };

          return (
            <div key={label.value} style={label.style || style}>
              {label.value}
            </div>
          );
        })}
      </div>
      {menu}
    </>
  );

  return (
    <>
      <Select {...restProps} dropdownRender={dropdownRender}>
        {options?.map((item: IOption) => {
          const { key, title, disabled, ...rest } = item || {};
          const keys: string[] = Object.keys(rest) || [];
          return (
            <Option
              key={key || JSON.stringify(item)}
              value={JSON.stringify(rest)}
              title={title || JSON.stringify(rest)}
              disabled={disabled || false}
            >
              <div
                style={{
                  display: "flex",
                  flexDirection: "row",
                }}
              >
                {keys.map((key: string) => {
                  return (
                    <div
                      key={key}
                      style={{
                        flex: 1,
                        textAlign: "center",
                        width: Math.floor((width - 20) / labelLength),
                        overflow: "hidden",
                        whiteSpace: "nowrap",
                        textOverflow: "ellipsis",
                      }}
                    >
                      {item[key]}
                    </div>
                  );
                })}
              </div>
            </Option>
          );
        })}
      </Select>
    </>
  );
};

export default CustomSelect;

App.tsx

import "./App.css";
import { Space, Button } from "antd";
import { Routes, Route } from "react-router-dom";
import Home from "./views/Home";
import About from "./views/About";
import Team from "./views/Team";
import CustomSelect from "./components/CustomSelect";
import React from "react";

interface valueType {
  key: string | number;
  name: string;
  // age: number;
  sex: string;
  teacher: string;
}

interface IOption extends valueType {
  disabled?: boolean;
  title?: string;
}
const App = () => {
  const options: IOption[] = [
    { key: 1, name: "jack", sex: "males", teacher: "benbnegou" },
    { key: 2, name: "licu", sex: "males", teacher: "benbnegou" },
    {
      key: 3,
      name: "tom",
      sex: "malesssssdvsebetnnem",
      teacher: "benbengou",
    },
  ];

  const fieldProps = {
    style: { width: 320 },
    placeholder: "please select one person",
    options,
    onChange: (e: string) => {
      console.log(e);
    },
  };

  return (
    <>
      <Space>
        <CustomSelect fieldProps={fieldProps} />
      </Space>
    </>
  );
};

export default App;

Typescript

有关typescript,这周和之前做项目的时候,学到了一些typescript的 utility types,但是之前写博客没有想起来写,就在这期总结一下吧。

Record

Record 是TypeScript的一种工具类,表示严格的键值对。 更具体地说, Record<K,V> 表示对象只接受类型 K ,并且对应于这些键的值应该是类型 V 。 Record<K,V> 的键将产生 K 作为类型,而 Record<K,V> [K] 等价于 V。举个例子

image.png

我们在编写typescript代码的过程中,可能会遇到类似的情况,需要声明一个对象,然后这个对象的参数暂时是不知道的,原来我就是第一种写法,可是它飘红确实好难受,后来就改成了 any,可是再想想 我要写 typescript 而不是 anyscript。最后这种写法,真的不错!

如果我需要一个对象,有 name、age和sex 三个属性,属性的值必须是数字或者字符串,那么就可以这么写:

type key = 'name' | 'age' | 'sex'

const c: Record<key, string | number> = {
   name: 'tom',
   age: 20,
   sex:'male',
}

console.log(c)

//{ "name": "tom", "age": 20, "sex": "male" }

也可以通过 keyof 来获取现有类型的所有属性,比如:

interface Staff {
  name:string,
  salary:string,
}
  
type StaffJson = Record<keyof Staff, string>

const product: StaffJson = {
  name: 'John',
  salary:'3000'
}

typeof、keyof 和 valueof(自定义)

在 TypeScript 中,typeof 操作符用来获取一个变量或对象的类型;keyof 操作符可以用于获取某种类型的所有键,其返回类型是联合类型

type ValueOf<T> = T[keyof T];

interface IStudent {
    name: string
    age: number
    friends: string[]
    teacherInfo: {
        name: string
    }
}

type StatusVal = ValueOf<IStudent>

type person = Record<keyof IStudent, StatusVal>

const p: person = {
    name: 'tom',
    age: 12,
    friends: ['kang'],
    teacherInfo: {
        name:'zhudi'
    }
}

type personCopy = typeof p

其中,typeof 返回对象 p 的 类型,keyof 返回 接口 IStudent 的 键的联合类型,

image.png

valueof 返回值的 联合类型

image.png

omit、pick、Partial、Required

假如我们现在有一个student的接口,

interface IStudent {
    name: string
    age: number
    friends: string[]
    teacherInfo: {
        name: string
    }
}

Omit去除类型中某些项

现在需要定义新的数据类型,去除 student 中的 老师信息

type person = Omit<IStudent, 'teacherInfo'>

function test(value: person): void {
    console.log(value)
}

const p1: person = {
    name: 'tom',
    age: 12,
    friends: ['michael']
}

test(p1)

image.png

源码

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Omit 构造一个除类型K外具有T性质的类型 参数为<type,string>;第一个为继承的type类型,第二个为想要的key的字符串,多个字符串用 | 分开

Pick选取类型中指定类型

现在需要定义新的数据类型,只要 student 中的 姓名和年龄

type person = Pick<IStudent, 'name' | 'age'>

function test(value: person): void {
    console.log(value)
}

const p1: person = {
    name: 'tom',
    age: 12,
}

test(p1)

image.png

源码

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

Partial将类型中所有选项变为可选,即加上?

现在需要定义新的数据类型,将 student 中的 老师信息 变成 可选

type person = Partial<IStudent>

function test(value: person): void {
    console.log(value)
}

const p1: person = {
    name: 'tom',
    age: 12,
}

test(p1)

image.png

源码

/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

Required将类型中所有选项变为必选,去除所有?

type person = Partial<IStudent>

function test(value: person): void {
    console.log(value)
}

const p1: person = {
    name: 'tom',
    age: 12,
}

type person2 = Required<person>

test(p1)

image.png

源码

/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

React hooks

这些 hook 都是从公司学习来的,后续也会陆续更新。

useBoolean

该 hook 主要用来管理 Boolean 状态。体验demo

import * as React from "react";

type ReturnValue = [boolean, () => void, () => void, () => void];

const useBoolean = (defaultValue = false): ReturnValue => {
  const [flag, setFlag] = React.useState<boolean>(defaultValue);

  const setTrue = React.useCallback(() => {
    setFlag(true);
  }, [setFlag]);

  const setFalse = React.useCallback(() => {
    setFlag(false);
  }, [setFlag]);

  const toggle = React.useCallback(() => {
    setFlag((bool) => !bool);
  }, [setFlag]);

  return [flag, setTrue, setFalse, toggle];
};

export default useBoolean;

useQuery

该 hook 主要用来获取 url 携带的参数。

import { useLocation } from "react-router-dom";

const useQuery = () => {
  const { search } = useLocation();

  const params = new URLSearchParams(search);
  const query: Record<string, any> = {};

  params?.forEach((param, key) => {
    if (typeof query[key] === "undefined") {
      query[key] = param;
    } else {
      const oldValues =
        typeof query[key] === "string" ? [query[key]] : query[key];

      query[key] = [...oldValues, param];
    }
  });

  return [query];
};

export default useQuery;

image.png

参考资料

如有疑问或者错误,欢迎留言。