为什么setState在循环中插入重复值

时间:2019-09-01 10:41:54

标签: javascript html reactjs

我有两个逻辑相关的HTML选择组件。

第一个代表地区列表,第二个代表相应的街道。

选择地区后,应更改“分区”选项数组,以代表所选地区的分区。

以下是它们在组件渲染方法中的表示方式:

  <div style={{display: "inline-block", marginRight: "20px"}}>
      <h style={{fontSize: "20px"}}>District</h>
      <br/>
      <select id="districts-select-list" onChange={this.updateDistrictData}>
          {this.state.districtsSelectOptionsArrayState}
      </select>
  </div>

  <div style={{display: "inline-block"}}>
      <h style={{fontSize: "20px"}}>Subdistrict</h>
      <br/>
      <select id="subdistricts-select-list">
          {this.state.subdistrictsSelectOptionsArrayState}
      </select>
      {this.state.subdistrictsSelectOptionsArrayState}
  </div>

如您所见,选项取决于状态。

以下是更新数据的方式:

updateDistrictData(e) {
    this.setState({subdistrictsSelectOptionsArrayState : []});

    var categoryList = document.getElementById("districts-select-list");
    var selectedDistrictId = categoryList.options[categoryList.selectedIndex].value;

    if(selectedDistrictId == undefined) {
        return;
    }

    var currentSubdistrictList = this.subdistrictDataArray[selectedDistrictId];

    if(currentSubdistrictList != undefined) {
        var currentSubdistrictListLength = currentSubdistrictList.length;

        if(
            currentSubdistrictListLength == undefined ||
            currentSubdistrictListLength == 0
        ) {
            return;
        }

        for(var index = 0; index < currentSubdistrictListLength; index++) {
            var currentDistrictObject = currentSubdistrictList[index];

            if(currentDistrictObject != undefined) {
                var currentSubdistrictId = currentDistrictObject["id"];
                var currentSubdistrictName = currentDistrictObject["name"];

                console.log("SUBDISTRICT NAME IS : " + currentSubdistrictName);

                var currentSubdistrictOption = (
                    <option value={currentSubdistrictId}>
                        {currentSubdistrictName}
                    </option>
                );

                this.setState(prevState => ({
                    subdistrictsSelectOptionsArrayState:[
                        ...prevState.subdistrictsSelectOptionsArrayState, 
                        (
                            <option value={currentSubdistrictId}>
                                {currentSubdistrictName}
                            </option>
                        )
                    ]
                }));

            }

        }

    }

}

从服务器中检索分区并在“分区”中选择组件的onChange方法后,我调用updateDistrictData方法。

首次加载页面时,区域和相应的街道区域会正确更改。

但是当我之后使用“区域选择”组件本身更改区域时,将在子区域选择组件中填充重复的子区域选项,该选项的重复次数是当前区域中子区域的数量。

我在做什么错了?

2 个答案:

答案 0 :(得分:2)

此问题是由于在闭包(setState回调)中使用var(currentSubdistrictId和currentSubdistrictName)引起的。

=>由于使用var声明,所有选项都使用currentSubdistrictId和currentSubdistrictName的最后一个值。

var s(某种全局范围)一起使用时,在js中关闭确实很棘手。

由于使用的是es6,因此应正确使用let(在for循环中的类似索引的块内修改)和const(在块中声明时仅设置一次)变量声明和永远不要使用var(一种全局范围)。

class App extends React.Component {
    districtDataArray = [
        { name: 'A', id: 0 },
        { name: 'B', id: 1 },
        { name: 'C', id: 2 },
    ]
    subdistrictDataArray = [
        [
            { name: 'AA', id: 0 },
            { name: 'AB', id: 1 },
            { name: 'AC', id: 2 },
        ],
        [
            { name: 'BA', id: 0 },
            { name: 'BB', id: 1 },
            { name: 'BC', id: 2 },
        ],
        [
            { name: 'CA', id: 0 },
            { name: 'CB', id: 1 },
            { name: 'CC', id: 2 },
        ],
    ]

    state = {
        districtsSelectOptionsArrayState: this.districtDataArray.map(d => (
            <option value={d.id}>
                {d.name}
            </option>
        )),
        subdistrictsSelectOptionsArrayState: [],
    }

    constructor(props) {
        super(props);
        this.updateDistrictData = this.updateDistrictData.bind(this);
    }

    updateDistrictData(e) {
        this.setState({subdistrictsSelectOptionsArrayState : []});

        const categoryList = document.getElementById("districts-select-list");
        const selectedDistrictId = categoryList.options[categoryList.selectedIndex].value;

        if(!selectedDistrictId) {
            return;
        }

        const currentSubdistrictList = this.subdistrictDataArray[selectedDistrictId];

        if(currentSubdistrictList) {
            const currentSubdistrictListLength = currentSubdistrictList.length;

            if(!currentSubdistrictListLength) {
                return;
            }

            for(let index = 0; index < currentSubdistrictListLength; index++) {
                // use const for block level constant variables
                const currentDistrictObject = currentSubdistrictList[index];

                if(currentDistrictObject) {
                    // use const for block level constant variables
                    const currentSubdistrictId = currentDistrictObject["id"];
                    const currentSubdistrictName = currentDistrictObject["name"];

                    console.log("SUBDISTRICT NAME IS : " + currentSubdistrictName);

                    const currentSubdistrictOption = (
                        <option value={currentSubdistrictId}>
                            {currentSubdistrictName}
                        </option>
                    );

                    this.setState(prevState => ({
                        subdistrictsSelectOptionsArrayState:[
                            ...prevState.subdistrictsSelectOptionsArrayState, 
                            (
                                <option value={currentSubdistrictId}>
                                    {currentSubdistrictName}
                                </option>
                            )
                        ]
                    }));

                }

            }

        }

    }
    render() {
    
        return (
            <div>
                <div style={{display: "inline-block", marginRight: "20px"}}>
                    <h style={{fontSize: "20px"}}>District</h>
                    <br/>
                    <select id="districts-select-list" onChange={this.updateDistrictData}>
                        {this.state.districtsSelectOptionsArrayState}
                    </select>
                </div>

                <div style={{display: "inline-block"}}>
                    <h style={{fontSize: "20px"}}>Subdistrict</h>
                    <br/>
                    <select id="subdistricts-select-list">
                        {this.state.subdistrictsSelectOptionsArrayState}
                    </select>
                    {this.state.subdistrictsSelectOptionsArrayState}
                </div>
            </div>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

此外,在updateDistrictData中更新状态的方式效率很低(n + 1 setStates,n个循环,n是分区数)。

您应该在变量中计算最终状态,并在计算完成后立即设置所有状态。

虽然我的实现在不对代码进行过多修改的情况下解释了您的代码出了什么问题,但是下面的Jared回答是一个很好的示例,说明了如何更清洁地完成代码。

答案 1 :(得分:2)

这应该可以解决原始问题,但是希望可以解决很多 other 问题...

您的显示代码应如下所示:

0

您的更新代码现在如下所示:

  <div style={{ display: "inline-block", marginRight: "20px" }}>
    <h style={{ fontSize: "20px" }}>District</h>
    <br />
    <select id="districts-select-list" onChange={this.updateDistrictData}>
      {this.state.districts.map(({ name, id }) => (
        <option value={id} key={id}>{name}</option>
      ))}
    </select>
  </div>

  <div style={{ display: "inline-block" }}>
    <h style={{ fontSize: "20px" }}>Subdistrict</h>
    <br />
    <select id="subdistricts-select-list">
      {
        this.state
          .subdistricts
          .filter(({ districtId }) => districtId === this.state.selectedDistrictId)
          .map(({ id, name }) => <option value={id} key={id}>{name}</option>)
      }
    </select>
  </div>

只要您为updateDistrictData (e) { this.setState({ selectedDistrictId: e.target.value }); } 属性使用唯一ID,React便不会进行不必要的重新渲染。将所有这些JSX react节点都存储在状态中是没有意义的。

我还建议您将列表完全移出状态,并将它们作为有状态父组件的支持传递。通常,最好让向用户显示信息的组件是完全无状态的,并在链的更高端对状态进行操作。