这是一个公司的面试小项目,根据数据生成多选框,要有一个全选的选项目,这是我的代码,请问还有哪些需要改进的地方和不足?因为对方不回复,只能来这里求教。
多选框组件 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 11:50:26 +08:00 via iPhone
交完后,你不适合我们公司 doge 。白嫖一波
|
3
mascteen OP @tesguest123 对呀,所以来这里问问
|
4
aaronlam 2020-12-16 14:51:16 +08:00
这是个现场上机题?
|
5
Chrisssss 2020-12-16 17:54:06 +08:00
比如 `_getColumns` 不关乎业务的东西可以提到 FC 外面去,部分 any 定义使用泛型,有一些代码风格不太好,比如 `selectAll` 函数的 c 参数命名,其实可以用 `checked`,然后里面直接`{
label: "Select All", value: "Select All", checked }` 就好。`‘Select All’` 可以定义一个常量。这些就是我作为面试官的话看了一眼会给你扣分的地方🐶 |
6
Chrisssss 2020-12-16 17:54:38 +08:00
其实还有很多问题😢
|
7
buhi 2020-12-16 17:59:26 +08:00
好好的 Array#map 不用, 造了个 any=>any=>(any[])=>any[], 一通下来全给你整 any 了
这就是你认为的函数式编程吗? 遇见我同事这么做直接打死了 |
12
yzbythesea 2020-12-16 19:09:59 +08:00
逻辑挺简单的,但是我就是读不懂你的代码。。。
为什么不用 array 存 option ?单独造一个 selectAll 的类干什么?为什么不把 selectAll 当作一个 option 加进去呢? 然后 checkbox 的逻辑都可以但写出 function,再套进里面,你这么写看着好晕。。。 |