使用React Hooks和Typescript更新嵌套状态-性能与清晰度

时间:2019-12-20 06:53:28

标签: reactjs typescript frontend react-hooks

我有一些深度嵌套的react组件。让我们将它们限制为3个嵌套级别,并将它们称为“父母”,“子代”和“孙子代”。整个应用程序的状态存储在Parent组件的单个对象中,并使用useState挂钩进行管理。

export interface Parent {
    children: Child[];
}

export interface Child {
    weight: number;
    grandchildren: Grandchild[];
}

export interface Grandchild {
    age: number;
}

我有两种替代的方式来更新状态:通过在每个级别上增量更新状态并冒泡到根,或通过增量构建包含目标节点路径的数组并在根级别上只更新一次状态

方法1:增量更新

export interface ContainerProps<T> {
    index: number;
    value: T;
    updateHandler: any;
}

const ParentContainer: React.FC = () => {
    const [value, setValue] = useState<Parent>(); // source of truth

    const updateChild = (childIndex: number, child: Child) => {
        value.children[childIndex] = child;
        setValue({...value});
    };

    return (
        <div className="Parent">
            {value.children.map((child, index) => (
                <ChildContainer key={index} index={index} value={child} updateHandler={updateChild} />
            ))}
        </div>
    );
}

const ChildContainer: React.FC<ContainerProps<Child>> = ({index, value, updateHandler}) => {
    const updateGrandchild = (grandchildIndex: number, grandchild: Grandchild) => {
        value.grandchildren[grandchildIndex] = grandchild;
        updateHandler(value);
    };

    const increaseWeight = () => {
        updateHandler(index, {
            weight: value.weight + 1
        })
    }

    return (
        <div className="Child">
            <button onClick={event => increaseWeight()}>
                Increase weight
            </button>

            {value.grandchildren.map((grandchild, index) => (
                <ChildContainer key={index} index={index} value={grandchild} updateHandler={updateGrandchild} />
            ))}
        </div>
    );
}

const GrandchildContainer: React.FC<ContainerProps<Grandchild>> = ({index, value, updateHandler}) => {
    const increaseAge = () => {
        updateHandler(index, {
            age: value.age + 1
        })
    }

    return (
        <div className="Grandchild">
            <button onClick={event => increaseAge()}>
                Increase age
            </button>
        </div>
    );
}

方法2:路径数组

export type PathElement = string | number;

const ParentContainer: React.FC = () => {
    const [value, setValue] = useState<Parent>(); // source of truth

    // e.g. to set a grandchild's age to 2,
    // path = [0, 'grandchildren', 2, 'age'], item = 2
    const update = (path: PathElement[], item: any) => {
        let node = value['children'];
        for (let i = 0; i < path.length; i++) {
            if (i === path.length - 1) {
                node[path[i]] = item;
            }
            else {
                node = node[path[i]];
            }
        }

        setValue({...value});
    };

    return (
        <div className="Parent">
            {value.children.map((child, index) => (
                <ChildContainer key={index} index={index} value={child} updateHandler={updateChild} />
            ))}
        </div>
    );
}

const ChildContainer: React.FC<ContainerProps<Child>> = ({index, value, updateHandler}) => {
    const updateGrandchild = (path: PathElement, item: any) => {
        updateHandler([index, 'grandchildren'].concat(path), item);
    };

    const increaseWeight = () => {
        updateHandler([index, 'weight'], value.weight + 1);
    }

    return (
        <div className="Child">
            <button onClick={event => increaseWeight()}>
                Increase weight
            </button>

            {value.grandchildren.map((grandchild, index) => (
                <ChildContainer key={index} index={index} value={grandchild} updateHandler={updateGrandchild} />
            ))}
        </div>
    );
}

const GrandchildContainer: React.FC<ContainerProps<Grandchild>> = ({index, value, updateHandler}) => {
    const increaseAge = () => {
        updateHandler([index, 'age'], value.age + 1);
    }

    return (
        <div className="Grandchild">
            <button onClick={event => increaseAge()}>
                Increase age
            </button>
        </div>
    );
}

我的想法:

  • 方法1:
    • ✔️看起来更简单
    • ✔️在每个级别都有类型检查,确保从有罪的组件中抛出错误消息
    • ❌需要许多对象分配,每个对象分配的大小逐渐增加,从而减少了时间和内存性能
  • 方法2
    • ✔️根据目标节点的深度,分配数量是线性的,因此具有更好的时间和内存性能
    • ❌代码更复杂
    • ❌类型检查较差,这意味着直到到达根组件,错误才被发现

我知道,对于前端代码,可维护性优先于较小的性能问题。但是,在一种用例中,方法2更适合:假设父组件具有变量totalAge,该变量在孙子的年龄更新时都会更新。使用方法1,如果不传递额外的年龄处理程序或向现有的更新处理程序添加额外的参数,就不可能知道哪个元素已更新。使用方法2,您可以简单地检查路径的最后一个元素是否为“年龄”。

有更好的方法吗?

0 个答案:

没有答案