簡體   English   中英

避免遞歸函數中的狀態變異(Redux)

[英]Avoiding state mutation in a recursive function (Redux)

我正在使用React和Redux構建一個Web應用程序,我遇到了有關狀態不變性的問題。

我的州看起來與此類似: -

{
    tasks: [
        {
            id: 't1',
            text: 'Task 1',
            times: {
                min: 0,
                max: 0
            },
            children: [
                { id: 't1c1', text: 'Task 1 child 1', times: { min: 2, max: 3 }, children: [] },
                { id: 't1c2', text: 'Task 1 child 2', times: { min: 3, max: 4 }, children: [] }
                ...
            ]
        }
        ...
    ]
}

這定義了“任務”對象的層次結構,每個任務在“children”數組中具有零個或多個子任務。 每個任務還​​有一個“時間”對象,它記錄此任務將采用的最小(“最小”)和最大(“最大”)時間(以小時為單位)。

我創建了一個簡單的遞歸函數,用於遍歷任務層次結構,並計算和設置具有一個或多個子項的所有任務的總“時間”值(最小值和最大值)。 因此,在上面的示例中,任務“t1”在計算后應具有5(2 + 3)的最小時間和7(3 + 4)的最大時間。 當然,這個函數本質上需要遞歸,因為每個任務都可以有任意數量的祖先。

(以下內容是在未經測試的情況下從內存中編寫的,請忽略/原諒任何錯別字)

function taskTimes(tasks)
{
    let result = { min: 0, max: 0 };

    if (tasks.length === 0)
        return result;

    tasks.forEach(task => {
        if (task.children.length > 0)
        {
            let times = taskTimes(task.children);
            task.times.min = times.min; // MUTATING!
            task.times.max = times.max; // MUTATING!
            result.min += times.min;
            result.max += times.max;
        }
        else
        {
            result.min += task.times.min;
            result.max += task.times.max;
        }
    });

    return result;
}

我創建的函數正常工作但我在寫之前就知道我會遇到一個問題:在遍歷任務層次結構時,各個任務對象(特別是“時間”對象)會發生變異。 這是一個問題,因為我想在reducer中運行它,因此我想從這個動作創建一個新狀態而不是改變它。

因此,我的問題是,修改這個系統以避免改變狀態的最佳方法是什么?

我可以嘗試使用Immutable.js在整個層次結構中強制實現不變性,嘗試這可能是我的下一步。 另一個選擇是根本不存儲這些數據,而是在必要時在每個組件中計算它。 雖然后者可能是一個更清潔的選擇,但我仍然想知道如何最好地解決這個問題,因為我可以預見在其他項目中必須做類似的事情,為了安心,我現在想解決問題。

對於該領域的最佳實踐的任何建議將不勝感激。 另外,如果我忽略了一些非常明顯的事情,請善待! 提前致謝。

如果你的意圖不是操縱狀態,那么創建一個副本將是一個快速而骯臟的解決方法,如果你在計算結果時操作它並不重要。 如果您打算使用該函數更新子任務的所有時間,則一種簡單的方法是創建狀態的(深層)副本,操作並返回它。

例如,reducer的片段:

...
switch(action.type) {
  case actions.RECALCULATE_TASK_COUNT:
    var nextState = $.extend({}, state);
    taskTimes(nextState);
    return nextState;
  ...

}
...

function taskTimes(tasks)
{
    let result = { min: 0, max: 0 };

    if (tasks.length === 0)
        return result;

    tasks.forEach(task => {
        if (task.children.length > 0)
        {
            let times = taskTimes(task.children);
            task.times.min = times.min; // MUTATING!
            task.times.max = times.max; // MUTATING!
            result.min += times.min;
            result.max += times.max;
        }
        else
        {
            result.min += task.times.min;
            result.max += task.times.max;
        }
    });

    return result;
}

你的第二個假設是對的。 您應該盡可能少地存儲商店中的數據。 可以在運行中計算可以計算的數據。 如果你正在進行一些繁重的計算(如上面的遞歸計算),你應該看看redux-reselect ,一個很小的庫來創建memoized選擇器來計算你商店的派生數據。

首先,感謝Pcriulan提供了解決這個問題的答案,並感謝Deton為理論問題提供了可能的解決方案。

我以為我會分享我創建的最終解決方案。 正如Pcriulan指出的那樣,這可能不是在現實世界中應用的最好方法,但是我想在這里展示這個解決方案,因為它解決了理論問題。

因此,我的解決方案涉及修改遞歸函數,使其以與reducer本身類似的方式運行,特別是它將根據其屬性返回新的或未修改的任務對象的tasks數組(和子數組)的副本。被修改與否。 因此,它僅提供已修改對象的深層副本,因此它應該相對有效。 我的單元測試還表明狀態現在沒有被函數變異,因此解決了初始問題。

更新的功能是: -

function getTaskTime(tasks)
{
    let result = { min: 0, max: 0, tasks: [] };

    if (tasks.length === 0)
        return result;

    // Build a new tasks array
    result.tasks = tasks.map(task => {

        if (task.children.length > 0)
        {
            // Get times and new task array for task.children
            const child_result = getTaskTime(task.children);

            // Add child times to current totals
            result.min += child_result.min;
            result.max += child_result.max;

            // Return new task object with new child array and modified times
            return Object.assign({}, task, {
                time: { min: child_result.min, max: child_result.max },
                children: child_result.tasks
            });
        }
        else
        {
            // If task has no children, simply add times to current totals and return
            // the unmodified task object
            result.min += task.time.min;
            result.max += task.time.max;
            return task;
        }

    });

    return result;
}

希望這將有助於其他人面臨類似的情況。 如果有任何改進或我錯過的問題,請隨時發表評論。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM