
这是一个公司的面试小项目,根据数据生成多选框,要有一个全选的选项目,这是我的代码,请问还有哪些需要改进的地方和不足?因为对方不回复,只能来这里求教。
多选框组件 MultiCheck.tsx
import React, { useState, useEffect, FunctionComponent } from "react"; import { fpMap, pipe } from "../utils"; import "./MultiCheck.css"; export type Option = { label: string; value: string; checked: boolean; }; /** * Notice: * 1. There should be a special `Select All` option with checkbox to control all passing options * 2. If columns > 1, the options should be placed from top to bottom in each column * * @param {string} label - the label text of this component * @param {Option[]} options - options * @param {number} columns - default value is 1 * @param {Function} onChange - when checked options are changed, * they should be passed to outside */ type Props = { label?: string; options: Option[]; columns?: number; onChange?: (options: Option[]) => void; }; const selectAll = (c = false) => ({ label: "Select All", value: "Select All", checked: c }); const MultiCheck: FunctionComponent<Props> = (props): JSX.Element => { const { label = "", columns = 1, OnChange= () => {} } = props; const [value, setValue] = useState({}); // for call props.onChange const [options, setOptions] = useState<Option[]>([ /* if original options all checked */ selectAll(!hasUnchecked(props.options)), ...props.options ]); function hasUnchecked(o: Option[]) { return o.filter(o => o.value !== selectAll().value).some(o => !o.checked); } function _handleClick(o: Option): void { /* user toggle click checkbox */ const cOnvertCheck= fpMap(i => { if (i.value === o.value) { return { ...i, checked: !o.checked }; } return i; }); /* user toggle click SelectAll option */ const cOnvertSelectAll= fpMap(i => { if (selectAll().value === o.value) { return { ...i, checked: !o.checked }; } return i; }); /* if all other option checked than SelectAll option checked else unchecked */ const isAllChecked = (o: Option[]) => { return fpMap(i => { if (i.value === selectAll().value) { return { ...i, checked: !hasUnchecked(o) }; } return i; })(o); }; setOptions(prev => pipe(convertCheck, convertSelectAll, isAllChecked)(prev) ); setValue(o); } function _getColumns(n: number): number { return n > 0 ? Math.ceil(options.length / n) : 1; } useEffect(() => { /* selectAll option can not be pass outside */ onChange(options.filter(i => i.value !== selectAll().value)); }, [value]); return ( <div className="multi-check-container"> <div className="multi-check"> <div className="multi-check-label"> <label>{label}</label> </div> <div className="multi-check-items" style={{ gridTemplateRows: `repeat(${_getColumns(columns)},auto)` }} > {fpMap(o => ( <label className="multi-check-item" key={o.value}> <input type="checkbox" value={o.value} checked={o.checked} OnChange={() => _handleClick(o)} /> <span></span> <div>{o.label}</div> </label> ))(options)} </div> </div> </div> ); }; export default MultiCheck; 测试用例
import "@testing-library/jest-dom"; import React from "react"; import renderer from "react-test-renderer"; import { fireEvent, render, screen } from "@testing-library/react"; import { fpMap, pipe } from "../utils"; import MultiCheck from "./MultiCheck"; describe("MultiCheck", () => { describe("initialize", () => { it("renders correctly", () => { const tree = renderer.create(<MultiCheck optiOns={[]} />).toJSON(); expect(tree).toMatchSnapshot(); }); it("renders the label if label provided", () => { const label = "jest test"; render(<MultiCheck optiOns={[]} label={label} />); expect(screen.queryByLabelText(label)).toBeDefined(); }); it("render and click checkbox", () => { const optiOns= [{ label: "test", value: "test", checked: false }]; render(<MultiCheck optiOns={options} />); expect(screen.queryByLabelText(/test/i)).not.toBeChecked(); // simulate user click checkbox fireEvent.click(screen.getByLabelText(/test/i)); expect(screen.queryByLabelText(/test/i)).toBeChecked(); }); }); describe("utils", () => { it("fpMap", () => { let arr = [1, 2, 3]; let add1 = (o: number) => o + 1; expect(fpMap(add1)(arr)).toEqual([2, 3, 4]); }); it("pipe", () => { let arr = [1, 2, 3]; let add1Each = (i: []) => i.map(o => o + 1); let prod2Each = (i: []) => i.map(o => o * 2); expect(pipe(add1Each, prod2Each)(arr)).toEqual([4, 6, 8]); }); }); }); 工具代码 utils.ts
type fn = (arg: any) => any; // functional programming for map iterator export function fpMap(func: fn): fn { return function(arr: unknown[]): unknown[] { let length: number = arr.length || 0; let i: number = 0; let result: unknown[] = []; while (i < length) { result.push(func(arr[i])); i++; } return result; }; } // functional programming pipe export function pipe(...fns: fn[]): fn { return function(x: any) { return fns.reduce((v: any, f: fn) => f(v), x); }; } 1 mascteen OP 很完美么? |
2 tesguest123 2020 年 12 月 16 日 via iPhone 交完后,你不适合我们公司 doge 。白嫖一波 |
3 mascteen OP @tesguest123 对呀,所以来这里问问 |
4 aaronlam 2020 年 12 月 16 日 这是个现场上机题? |
5 Chrisssss 2020 年 12 月 16 日 比如 `_getColumns` 不关乎业务的东西可以提到 FC 外面去,部分 any 定义使用泛型,有一些代码风格不太好,比如 `selectAll` 函数的 c 参数命名,其实可以用 `checked`,然后里面直接`{ label: "Select All", value: "Select All", checked }` 就好。`‘Select All’` 可以定义一个常量。这些就是我作为面试官的话看了一眼会给你扣分的地方 |
6 Chrisssss 2020 年 12 月 16 日 其实还有很多问题 |
7 buhi 2020 年 12 月 16 日 好好的 Array#map 不用, 造了个 any=>any=>(any[])=>any[], 一通下来全给你整 any 了 这就是你认为的函数式编程吗? 遇见我同事这么做直接打死了 |