根据子级中的状态对父级组件的排序子级组件进行反应

时间:2019-11-13 18:39:14

标签: reactjs typescript sorting react-component

我正在开发一种用于运行“纸笔运动”的工具。在这个工具中,我希望能够显示任意数量的生物实体。应该以一定的顺序显示它们。

该顺序应从生物的“ ini”值得出(即,具有最高值的生物显示在顶部)。该顺序始终保持不变。因此,当值更改时(因为它包含在输入字段中),应该对生物组成部分进行重新排序。

我已经制作了必要的生物组件,并制作了父组件来显示它们。但是我现在不知道如何对其进行排序。我已经将一个函数从父级传递给子级,一旦值更改,该函数就会被触发。但是我不知道如何访问子项并根据其状态值对子项进行排序。我对React还是很陌生,所以如果这是一件小事,请原谅。我只是在任何地方都找不到答案!

我尝试使用React.Children.toArray(...),但这似乎不正确。我还想尝试使用refs保存子组件。但是我并不真正了解如何使用它们,也不知道该如何处理。除此之外,我还很笨。

这是我的父组件:

export class Encounter extends React.Component<IEncounterProps,IEncounterState> {

    sortByIni(event) {
        //somehow trigger something that sorts the creature components
    }

    addCreature() {

        let creature =
            <Creature
                name={'Ancient Bugbear'}
                //I removed other props for the sake of reducing verbosity... 
                ini={12}
            />;
        return creature
    }


    render(): any {
        return (
            <div>
                {this.addCreature()}
                {this.addCreature()}
                {this.addCreature()}
            </div>

        )
    }

}

这是生物的组成部分:

export interface ICreatureProps {
    name: string,
    ini: number,
    //Removing non relevant props for verbosity reducing...
}

export interface ICreatureState {
    hitpoints: number
    armorclass: number
    ini: number
}

export class Creature extends React.Component<ICreatureProps, ICreatureState> {

    constructor(props) {
        super(props);
        this.state= {
            hitpoints: this.props.hitpoints || 0,
            armorclass: this.props.armorclass || 0,
            ini: this.props.ini || 0
        };
        this.handleIniChange = this.handleIniChange.bind(this);
        this.handleACChange = this.handleACChange.bind(this);
        this.handleHPChange = this.handleHPChange.bind(this)
    }

    handleIniChange(event) {
        this.setState({ini: event.target.value})
    }

    handleACChange(event) {
        this.setState({armorclass: event.target.value})
    }

    handleHPChange(event) {
        this.setState({hitpoints: event.target.value})
    }

    render(): any {
        return (
            <div>
                <div className={style.creatureContainer}>
                    <div className={style.nextToTitleContainer}>
                        <p className={style.statDisplay}>TP: <input type="number" className={style.inputField} value={this.state.hitpoints} onChange={this.handleHPChange}/></p>
                        <p className={style.statDisplay}>RK: <input type="number" className={style.inputField} value={this.state.armorclass} onChange={this.handleACChange}/></p>
                        //This is the field that is supposed to trigger sorting when it changes value
                        <p className={style.statDisplay}>INI:<input type="number" className={style.inputField} value={this.state.ini} onChange={e=>{this.handleIniChange(e); this.props.sortByIni(e)}}/></p>
                    </div>
                </div>
            </div>
        )
    }
}

这是预期的结果。正如人们所看到的,具有最高价值的生物在最上面。当然,只要值改变,它就应该求助于自己。

Desired Outcome

1 个答案:

答案 0 :(得分:0)

一如既往,您问了一下,便找到了答案:

我每次渲染生物组件时都使用唯一的键来解决它。由于react通过键标识组件,因此由于ini值的更改,每次渲染时它们都必须是新的。如果没有,react不会改变dom树的顺序,因为具有该键的组件仍然存在。

我将此功能添加到Encounter中,以基于单击按钮生成生物:

addCreature() {
        let creatureMap = this.state.creatureMap;
        let creatureEntry =
            {
                id: this.uuidv4(),
                name: Math.random().toString(36).substring(7),
                ini: 2,
                currentIni: Math.floor(Math.random() * 20) + 2,

            };
        creatureMap.push(creatureEntry);
        let creatureMapSorted = creatureMap.sort((creatureA, creatureB) => {
            if (creatureA.currentIni < creatureB.currentIni) return 1;
            if (creatureA.currentIni > creatureB.currentIni) return -1;
            return 0;
        });
        this.setState({
            creatureMap: creatureMapSorted
        });
    }

如您所见,我给每个生物一个唯一的ID。这很可能也可以通过Refs来解决,但是我对此还不太满意。我正在使用this thread中的uuidv4函数(来自Briguy37的回答)。当输入字段中的ini值更改时,生物将给出其ID,以便可以在生物图谱中对其进行更改:

<p className={style.statDisplay}>INI:<input type="number" className={style.inputField} defaultValue={this.state.currentIni} onBlur={e=>{this.handleIniChange(e); this.props.sortByIni(e,this.props.id)}}/></p>

然后在Encounter

中触发此功能
    sortByIni(event,id) {
        let creatureMap = this.state.creatureMap;
        creatureMap.filter(creature=> {
            return creature.id == id
        })[0].currentIni = parseInt(event.target.value);
        let creatureMapSorted = creatureMap.sort((creatureA,creatureB) => {
            if (creatureA.currentIni < creatureB.currentIni) return 1;
            if (creatureA.currentIni > creatureB.currentIni) return -1;
            return 0;
        });
        this.setState({creatureMap : creatureMapSorted})
    }

将生物对象放入一个数组中,然后将该数组根据当前初始值进行排序,然后将其应用于状态,从而触发渲染。

如您所见,只有带有自定义比较器的简单排序函数。没有什么花哨。现在,正如我所说,通常反应不会在意此列表是否已排序,因为组件的键仍然相同。因此它不会重新渲染它们。我这样解决了这个问题:

    render(): any {

        return (
            <div>
                <button type="button" onClick={this.addCreature}>Add Creature</button>
                {this.state.creatureMap.map((creature, i) => {
                    return (
                        <Creature
                            id={creature.id}
                            name={creature.name}
                            hitpoints={creature.hitpoints}
                            key={this.uuidv4()}
                            */Shortened...*/
                        />
                    )
                })}
            </div>

像这样,key属性在每个渲染中都是唯一的,因此迫使做出反应以重新渲染整个事物,并因此保持其排序!