根据 Child 中的状态对 Parent 组件的 Child 组件进行反应排序

React Sort Child Components of Parent Component based on state in Child

我正在为 运行 纸笔活动开发一个工具。在这个工具中,我希望能够在彼此之下显示任意数量的生物实体。它们应该按特定顺序显示。

该顺序应该是根据生物 "ini" 的值得出的(即具有最高值的生物显示在顶部)。该秩序将始终得到维持。因此,当值发生变化时(因为它包含在输入字段中),应该再次对生物组件进行排序。

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

我试过使用 React.Children.toArray(...) 但这似乎不对。我还想尝试使用 refs 来保存 child 组件。但我并不真正了解如何使用它们,也不知道如何处理。除此之外,我一无所知。

这是我的 parent 组件:

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>
        )
    }
}

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

一如既往,你问了之后很快就会自己找到答案:

我每次渲染生物组件时都使用唯一键解决了这个问题。由于 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 but I didn't feel fully comfortable with that yet. I am using the uuidv4 Function from this thread 解决(来自 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})
    }

Creatures 对象被放入一个数组中,然后该数组根据当前的主动值进行排序,然后再应用于状态,从而触发渲染。

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

    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 属性在每个渲染中都是唯一的,因此强制 react 重新渲染整个事物并因此保持排序!